Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
new file mode 100644
index 0000000..e4a42a4
--- /dev/null
+++ b/quic/core/quic_dispatcher_test.cc
@@ -0,0 +1,2753 @@
+// 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 "net/third_party/quiche/src/quic/core/quic_dispatcher.h"
+
+#include <memory>
+#include <ostream>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/chlo_extractor.h"
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h"
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_writer_wrapper.h"
+#include "net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/stateless_rejector.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/fake_proof_source.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_time_wait_list_manager.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_buffered_packet_store_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_crypto_server_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_dispatcher_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_time_wait_list_manager_peer.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h"
+
+using testing::_;
+using testing::InSequence;
+using testing::Invoke;
+using testing::NiceMock;
+using testing::Return;
+using testing::WithArg;
+using testing::WithoutArgs;
+
+static const size_t kDefaultMaxConnectionsInStore = 100;
+static const size_t kMaxConnectionsWithoutCHLO =
+ kDefaultMaxConnectionsInStore / 2;
+static const int16_t kMaxNumSessionsToCreate = 16;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestQuicSpdyServerSession : public QuicServerSessionBase {
+ public:
+ TestQuicSpdyServerSession(const QuicConfig& config,
+ QuicConnection* connection,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache)
+ : QuicServerSessionBase(config,
+ CurrentSupportedVersions(),
+ connection,
+ nullptr,
+ nullptr,
+ crypto_config,
+ compressed_certs_cache),
+ crypto_stream_(QuicServerSessionBase::GetMutableCryptoStream()) {}
+ TestQuicSpdyServerSession(const TestQuicSpdyServerSession&) = delete;
+ TestQuicSpdyServerSession& operator=(const TestQuicSpdyServerSession&) =
+ delete;
+
+ ~TestQuicSpdyServerSession() override { delete connection(); }
+
+ MOCK_METHOD3(OnConnectionClosed,
+ void(QuicErrorCode error,
+ const QuicString& error_details,
+ ConnectionCloseSource source));
+ MOCK_METHOD1(CreateIncomingStream, QuicSpdyStream*(QuicStreamId id));
+ MOCK_METHOD1(CreateIncomingStream, QuicSpdyStream*(PendingStream pending));
+ MOCK_METHOD0(CreateOutgoingBidirectionalStream, QuicSpdyStream*());
+ MOCK_METHOD0(CreateOutgoingUnidirectionalStream, QuicSpdyStream*());
+
+ QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache) override {
+ return new QuicCryptoServerStream(
+ crypto_config, compressed_certs_cache,
+ GetQuicReloadableFlag(enable_quic_stateless_reject_support), this,
+ stream_helper());
+ }
+
+ void SetCryptoStream(QuicCryptoServerStream* crypto_stream) {
+ crypto_stream_ = crypto_stream;
+ }
+
+ QuicCryptoServerStreamBase* GetMutableCryptoStream() override {
+ return crypto_stream_;
+ }
+
+ const QuicCryptoServerStreamBase* GetCryptoStream() const override {
+ return crypto_stream_;
+ }
+
+ QuicCryptoServerStream::Helper* stream_helper() {
+ return QuicServerSessionBase::stream_helper();
+ }
+
+ private:
+ QuicCryptoServerStreamBase* crypto_stream_;
+};
+
+class TestDispatcher : public QuicDispatcher {
+ public:
+ TestDispatcher(const QuicConfig* config,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicVersionManager* version_manager)
+ : QuicDispatcher(config,
+ crypto_config,
+ version_manager,
+ QuicMakeUnique<MockQuicConnectionHelper>(),
+ std::unique_ptr<QuicCryptoServerStream::Helper>(
+ new QuicSimpleCryptoServerStreamHelper(
+ QuicRandom::GetInstance())),
+ QuicMakeUnique<MockAlarmFactory>(),
+ kQuicDefaultConnectionIdLength) {}
+
+ MOCK_METHOD4(CreateQuicSession,
+ QuicServerSessionBase*(QuicConnectionId connection_id,
+ const QuicSocketAddress& peer_address,
+ QuicStringPiece alpn,
+ const quic::ParsedQuicVersion& version));
+
+ MOCK_METHOD2(ShouldCreateOrBufferPacketForConnection,
+ bool(QuicConnectionId connection_id, bool ietf_quic));
+
+ struct TestQuicPerPacketContext : public QuicPerPacketContext {
+ QuicString custom_packet_context;
+ };
+
+ std::unique_ptr<QuicPerPacketContext> GetPerPacketContext() const override {
+ auto test_context = QuicMakeUnique<TestQuicPerPacketContext>();
+ test_context->custom_packet_context = custom_packet_context_;
+ return std::move(test_context);
+ }
+
+ void RestorePerPacketContext(
+ std::unique_ptr<QuicPerPacketContext> context) override {
+ TestQuicPerPacketContext* test_context =
+ static_cast<TestQuicPerPacketContext*>(context.get());
+ custom_packet_context_ = test_context->custom_packet_context;
+ }
+
+ QuicString custom_packet_context_;
+
+ using QuicDispatcher::current_client_address;
+ using QuicDispatcher::current_peer_address;
+ using QuicDispatcher::current_self_address;
+ using QuicDispatcher::writer;
+};
+
+// A Connection class which unregisters the session from the dispatcher when
+// sending connection close.
+// It'd be slightly more realistic to do this from the Session but it would
+// involve a lot more mocking.
+class MockServerConnection : public MockQuicConnection {
+ public:
+ MockServerConnection(QuicConnectionId connection_id,
+ MockQuicConnectionHelper* helper,
+ MockAlarmFactory* alarm_factory,
+ QuicDispatcher* dispatcher)
+ : MockQuicConnection(connection_id,
+ helper,
+ alarm_factory,
+ Perspective::IS_SERVER),
+ dispatcher_(dispatcher) {}
+
+ void UnregisterOnConnectionClosed() {
+ QUIC_LOG(ERROR) << "Unregistering " << connection_id();
+ dispatcher_->OnConnectionClosed(connection_id(), QUIC_NO_ERROR,
+ "Unregistering.",
+ ConnectionCloseSource::FROM_SELF);
+ }
+
+ private:
+ QuicDispatcher* dispatcher_;
+};
+
+class QuicDispatcherTest : public QuicTest {
+ public:
+ QuicDispatcherTest()
+ : QuicDispatcherTest(crypto_test_utils::ProofSourceForTesting()) {}
+
+ ParsedQuicVersionVector AllSupportedVersionsIncludingTls() {
+ SetQuicFlag(&FLAGS_quic_supports_tls_handshake, true);
+ return AllSupportedVersions();
+ }
+
+ explicit QuicDispatcherTest(std::unique_ptr<ProofSource> proof_source)
+ :
+
+ version_manager_(AllSupportedVersionsIncludingTls()),
+ crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance(),
+ std::move(proof_source),
+ KeyExchangeSource::Default(),
+ TlsServerHandshaker::CreateSslCtx()),
+ server_address_(QuicIpAddress::Any4(), 5),
+ dispatcher_(new NiceMock<TestDispatcher>(&config_,
+ &crypto_config_,
+ &version_manager_)),
+ time_wait_list_manager_(nullptr),
+ session1_(nullptr),
+ session2_(nullptr),
+ store_(nullptr) {}
+
+ void SetUp() override {
+ dispatcher_->InitializeWithWriter(new MockPacketWriter());
+ // Set the counter to some value to start with.
+ QuicDispatcherPeer::set_new_sessions_allowed_per_event_loop(
+ dispatcher_.get(), kMaxNumSessionsToCreate);
+ ON_CALL(*dispatcher_, ShouldCreateOrBufferPacketForConnection(_, _))
+ .WillByDefault(Return(true));
+ }
+
+ MockQuicConnection* connection1() {
+ if (session1_ == nullptr) {
+ return nullptr;
+ }
+ return reinterpret_cast<MockQuicConnection*>(session1_->connection());
+ }
+
+ MockQuicConnection* connection2() {
+ if (session2_ == nullptr) {
+ return nullptr;
+ }
+ return reinterpret_cast<MockQuicConnection*>(session2_->connection());
+ }
+
+ // Process a packet with an 8 byte connection id,
+ // 6 byte packet number, default path id, and packet number 1,
+ // using the first supported version.
+ void ProcessPacket(QuicSocketAddress peer_address,
+ QuicConnectionId connection_id,
+ bool has_version_flag,
+ const QuicString& data) {
+ ProcessPacket(peer_address, connection_id, has_version_flag, data,
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER);
+ }
+
+ // Process a packet with a default path id, and packet number 1,
+ // using the first supported version.
+ void ProcessPacket(QuicSocketAddress peer_address,
+ QuicConnectionId connection_id,
+ bool has_version_flag,
+ const QuicString& data,
+ QuicConnectionIdIncluded connection_id_included,
+ QuicPacketNumberLength packet_number_length) {
+ ProcessPacket(peer_address, connection_id, has_version_flag, data,
+ connection_id_included, packet_number_length, 1);
+ }
+
+ // Process a packet using the first supported version.
+ void ProcessPacket(QuicSocketAddress peer_address,
+ QuicConnectionId connection_id,
+ bool has_version_flag,
+ const QuicString& data,
+ QuicConnectionIdIncluded connection_id_included,
+ QuicPacketNumberLength packet_number_length,
+ uint64_t packet_number) {
+ ProcessPacket(peer_address, connection_id, has_version_flag,
+ CurrentSupportedVersions().front(), data,
+ connection_id_included, packet_number_length, packet_number);
+ }
+
+ // Processes a packet.
+ void ProcessPacket(QuicSocketAddress peer_address,
+ QuicConnectionId connection_id,
+ bool has_version_flag,
+ ParsedQuicVersion version,
+ const QuicString& data,
+ QuicConnectionIdIncluded connection_id_included,
+ QuicPacketNumberLength packet_number_length,
+ uint64_t packet_number) {
+ ParsedQuicVersionVector versions(SupportedVersions(version));
+ std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+ connection_id, EmptyQuicConnectionId(), has_version_flag, false,
+ packet_number, data, connection_id_included, CONNECTION_ID_ABSENT,
+ packet_number_length, &versions));
+ std::unique_ptr<QuicReceivedPacket> received_packet(
+ ConstructReceivedPacket(*packet, mock_helper_.GetClock()->Now()));
+
+ if (ChloExtractor::Extract(*packet, versions, {}, nullptr,
+ connection_id.length())) {
+ // Add CHLO packet to the beginning to be verified first, because it is
+ // also processed first by new session.
+ data_connection_map_[connection_id].push_front(
+ QuicString(packet->data(), packet->length()));
+ } else {
+ // For non-CHLO, always append to last.
+ data_connection_map_[connection_id].push_back(
+ QuicString(packet->data(), packet->length()));
+ }
+ dispatcher_->ProcessPacket(server_address_, peer_address, *received_packet);
+ }
+
+ void ValidatePacket(QuicConnectionId conn_id,
+ const QuicEncryptedPacket& packet) {
+ EXPECT_EQ(data_connection_map_[conn_id].front().length(),
+ packet.AsStringPiece().length());
+ EXPECT_EQ(data_connection_map_[conn_id].front(), packet.AsStringPiece());
+ data_connection_map_[conn_id].pop_front();
+ }
+
+ QuicServerSessionBase* CreateSession(
+ TestDispatcher* dispatcher,
+ const QuicConfig& config,
+ QuicConnectionId connection_id,
+ const QuicSocketAddress& peer_address,
+ MockQuicConnectionHelper* helper,
+ MockAlarmFactory* alarm_factory,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ TestQuicSpdyServerSession** session) {
+ MockServerConnection* connection = new MockServerConnection(
+ connection_id, helper, alarm_factory, dispatcher);
+ connection->SetQuicPacketWriter(dispatcher->writer(),
+ /*owns_writer=*/false);
+ *session = new TestQuicSpdyServerSession(config, connection, crypto_config,
+ compressed_certs_cache);
+ connection->set_visitor(*session);
+ ON_CALL(*connection, CloseConnection(_, _, _))
+ .WillByDefault(WithoutArgs(Invoke(
+ connection, &MockServerConnection::UnregisterOnConnectionClosed)));
+ return *session;
+ }
+
+ void CreateTimeWaitListManager() {
+ time_wait_list_manager_ = new MockTimeWaitListManager(
+ QuicDispatcherPeer::GetWriter(dispatcher_.get()), dispatcher_.get(),
+ mock_helper_.GetClock(), &mock_alarm_factory_);
+ // dispatcher_ takes the ownership of time_wait_list_manager_.
+ QuicDispatcherPeer::SetTimeWaitListManager(dispatcher_.get(),
+ time_wait_list_manager_);
+ }
+
+ QuicString SerializeCHLO() {
+ CryptoHandshakeMessage client_hello;
+ client_hello.set_tag(kCHLO);
+ client_hello.SetStringPiece(kALPN, "hq");
+ return QuicString(client_hello.GetSerialized().AsStringPiece());
+ }
+
+ QuicString SerializeTlsClientHello() { return ""; }
+
+ void MarkSession1Deleted() { session1_ = nullptr; }
+
+ MockQuicConnectionHelper mock_helper_;
+ MockAlarmFactory mock_alarm_factory_;
+ QuicConfig config_;
+ QuicVersionManager version_manager_;
+ QuicCryptoServerConfig crypto_config_;
+ QuicSocketAddress server_address_;
+ std::unique_ptr<NiceMock<TestDispatcher>> dispatcher_;
+ MockTimeWaitListManager* time_wait_list_manager_;
+ TestQuicSpdyServerSession* session1_;
+ TestQuicSpdyServerSession* session2_;
+ std::map<QuicConnectionId, std::list<QuicString>> data_connection_map_;
+ QuicBufferedPacketStore* store_;
+};
+
+TEST_F(QuicDispatcherTest, TlsClientHelloCreatesSession) {
+ if (CurrentSupportedVersions().front().transport_version < QUIC_VERSION_47) {
+ // TLS is only supported in versions 47 and greater.
+ return;
+ }
+ FLAGS_quic_supports_tls_handshake = true;
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), client_address,
+ QuicStringPiece(""), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(1), _));
+ ProcessPacket(
+ client_address, TestConnectionId(1), true,
+ ParsedQuicVersion(PROTOCOL_TLS1_3,
+ CurrentSupportedVersions().front().transport_version),
+ SerializeCHLO(), CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ EXPECT_EQ(client_address, dispatcher_->current_peer_address());
+ EXPECT_EQ(server_address_, dispatcher_->current_self_address());
+}
+
+TEST_F(QuicDispatcherTest, ProcessPackets) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(1), _));
+ ProcessPacket(client_address, TestConnectionId(1), true, SerializeCHLO());
+ EXPECT_EQ(client_address, dispatcher_->current_peer_address());
+ EXPECT_EQ(server_address_, dispatcher_->current_self_address());
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(2), client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(2), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session2_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session2_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(2), packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(2), _));
+ ProcessPacket(client_address, TestConnectionId(2), true, SerializeCHLO());
+
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(1)
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ ProcessPacket(client_address, TestConnectionId(1), false, "data");
+}
+
+// Regression test of b/93325907.
+TEST_F(QuicDispatcherTest, DispatcherDoesNotRejectPacketNumberZero) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ // Verify both packets 1 and 2 are processed by connection 1.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(2)
+ .WillRepeatedly(
+ WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(1), _));
+ ProcessPacket(
+ client_address, TestConnectionId(1), true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO,
+ CurrentSupportedVersions().front().transport_version),
+ SerializeCHLO(), CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ // Packet number 256 with packet number length 1 would be considered as 0 in
+ // dispatcher.
+ ProcessPacket(
+ client_address, TestConnectionId(1), false,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO,
+ CurrentSupportedVersions().front().transport_version),
+ "", CONNECTION_ID_PRESENT, PACKET_1BYTE_PACKET_NUMBER, 256);
+ EXPECT_EQ(client_address, dispatcher_->current_peer_address());
+ EXPECT_EQ(server_address_, dispatcher_->current_self_address());
+}
+
+TEST_F(QuicDispatcherTest, StatelessVersionNegotiation) {
+ CreateTimeWaitListManager();
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ SendVersionNegotiationPacket(_, _, _, _, _, _))
+ .Times(1);
+ QuicTransportVersion version =
+ static_cast<QuicTransportVersion>(QuicTransportVersionMin() - 1);
+ ParsedQuicVersion parsed_version(PROTOCOL_QUIC_CRYPTO, version);
+ // Pad the CHLO message with enough data to make the packet large enough
+ // to trigger version negotiation.
+ QuicString chlo = SerializeCHLO() + QuicString(1200, 'a');
+ DCHECK_LE(1200u, chlo.length());
+ ProcessPacket(client_address, TestConnectionId(1), true, parsed_version, chlo,
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+}
+
+TEST_F(QuicDispatcherTest, NoVersionNegotiationWithSmallPacket) {
+ CreateTimeWaitListManager();
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ SendVersionNegotiationPacket(_, _, _, _, _, _))
+ .Times(0);
+ QuicTransportVersion version =
+ static_cast<QuicTransportVersion>(QuicTransportVersionMin() - 1);
+ ParsedQuicVersion parsed_version(PROTOCOL_QUIC_CRYPTO, version);
+ QuicString chlo = SerializeCHLO() + QuicString(1200, 'a');
+ // Truncate to 1100 bytes of payload which results in a packet just
+ // under 1200 bytes after framing, packet, and encryption overhead.
+ DCHECK_LE(1200u, chlo.length());
+ QuicString truncated_chlo = chlo.substr(0, 1100);
+ DCHECK_EQ(1100u, truncated_chlo.length());
+ ProcessPacket(client_address, TestConnectionId(1), true, parsed_version,
+ truncated_chlo, CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+}
+
+// Disabling CHLO size validation allows the dispatcher to send version
+// negotiation packets in response to a CHLO that is otherwise too small.
+TEST_F(QuicDispatcherTest, VersionNegotiationWithoutChloSizeValidation) {
+ crypto_config_.set_validate_chlo_size(false);
+
+ CreateTimeWaitListManager();
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ SendVersionNegotiationPacket(_, _, _, _, _, _))
+ .Times(1);
+ QuicTransportVersion version =
+ static_cast<QuicTransportVersion>(QuicTransportVersionMin() - 1);
+ ParsedQuicVersion parsed_version(PROTOCOL_QUIC_CRYPTO, version);
+ QuicString chlo = SerializeCHLO() + QuicString(1200, 'a');
+ // Truncate to 1100 bytes of payload which results in a packet just
+ // under 1200 bytes after framing, packet, and encryption overhead.
+ DCHECK_LE(1200u, chlo.length());
+ QuicString truncated_chlo = chlo.substr(0, 1100);
+ DCHECK_EQ(1100u, truncated_chlo.length());
+ ProcessPacket(client_address, TestConnectionId(1), true, parsed_version,
+ truncated_chlo, CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+}
+
+TEST_F(QuicDispatcherTest, Shutdown) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(_, client_address, QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(1), _));
+ ProcessPacket(client_address, TestConnectionId(1), true, SerializeCHLO());
+
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ CloseConnection(QUIC_PEER_GOING_AWAY, _, _));
+
+ dispatcher_->Shutdown();
+}
+
+TEST_F(QuicDispatcherTest, TimeWaitListManager) {
+ CreateTimeWaitListManager();
+
+ // Create a new session.
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(TestConnectionId(1), _));
+ ProcessPacket(client_address, connection_id, true, SerializeCHLO());
+
+ // Now close the connection, which should add it to the time wait list.
+ session1_->connection()->CloseConnection(
+ QUIC_INVALID_VERSION,
+ "Server: Packet 2 without version flag before version negotiated.",
+ ConnectionCloseBehavior::SILENT_CLOSE);
+ EXPECT_TRUE(time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id));
+
+ // Dispatcher forwards subsequent packets for this connection_id to the time
+ // wait list manager.
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, connection_id, _, _))
+ .Times(1);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true, "data");
+}
+
+TEST_F(QuicDispatcherTest, NoVersionPacketToTimeWaitListManager) {
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ // Dispatcher forwards all packets for this connection_id to the time wait
+ // list manager.
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, QuicStringPiece("hq"), _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, connection_id, _, _))
+ .Times(1);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(1);
+ ProcessPacket(client_address, connection_id, false, SerializeCHLO());
+}
+
+TEST_F(QuicDispatcherTest, ProcessPacketWithZeroPort) {
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 0);
+
+ // dispatcher_ should drop this packet.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(0);
+ ProcessPacket(client_address, TestConnectionId(1), true, SerializeCHLO());
+}
+
+TEST_F(QuicDispatcherTest, OKSeqNoPacketProcessed) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+
+ // A packet whose packet number is the largest that is allowed to start a
+ // connection.
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true, SerializeCHLO(),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ QuicDispatcher::kMaxReasonableInitialPacketNumber);
+ EXPECT_EQ(client_address, dispatcher_->current_peer_address());
+ EXPECT_EQ(server_address_, dispatcher_->current_self_address());
+}
+
+TEST_F(QuicDispatcherTest, TooBigSeqNoPacketToTimeWaitListManager) {
+ CreateTimeWaitListManager();
+ SetQuicRestartFlag(quic_enable_accept_random_ipn, false);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+
+ // Dispatcher forwards this packet for this connection_id to the time wait
+ // list manager.
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, QuicStringPiece("hq"), _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, TestConnectionId(1), _, _))
+ .Times(1);
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, TestConnectionId(2), _, _))
+ .Times(1);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(2);
+ // A packet whose packet number is one to large to be allowed to start a
+ // connection.
+ ProcessPacket(client_address, connection_id, true, SerializeCHLO(),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ QuicDispatcher::kMaxReasonableInitialPacketNumber + 1);
+ connection_id = TestConnectionId(2);
+ SetQuicRestartFlag(quic_enable_accept_random_ipn, true);
+ ProcessPacket(client_address, connection_id, true, SerializeCHLO(),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ MaxRandomInitialPacketNumber().ToUint64() +
+ QuicDispatcher::kMaxReasonableInitialPacketNumber + 1);
+}
+
+TEST_F(QuicDispatcherTest, SupportedTransportVersionsChangeInFlight) {
+ static_assert(QUIC_ARRAYSIZE(kSupportedTransportVersions) == 6u,
+ "Supported versions out of sync");
+ SetQuicReloadableFlag(quic_disable_version_39, false);
+ SetQuicReloadableFlag(quic_enable_version_43, true);
+ SetQuicReloadableFlag(quic_enable_version_44, true);
+ SetQuicReloadableFlag(quic_enable_version_46, true);
+ SetQuicReloadableFlag(quic_enable_version_47, true);
+ SetQuicReloadableFlag(quic_enable_version_99, true);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ uint64_t conn_id = 1;
+ QuicConnectionId connection_id = TestConnectionId(conn_id);
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ParsedQuicVersion version(
+ PROTOCOL_QUIC_CRYPTO,
+ static_cast<QuicTransportVersion>(QuicTransportVersionMin() - 1));
+ ProcessPacket(client_address, connection_id, true, version, SerializeCHLO(),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO,
+ QuicVersionMin().transport_version),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true, QuicVersionMax(),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn off version 47.
+ SetQuicReloadableFlag(quic_enable_version_47, false);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_47),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn on version 47.
+ SetQuicReloadableFlag(quic_enable_version_47, true);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_47),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn off version 46.
+ SetQuicReloadableFlag(quic_enable_version_46, false);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn on version 46.
+ SetQuicReloadableFlag(quic_enable_version_46, true);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn off version 44.
+ SetQuicReloadableFlag(quic_enable_version_44, false);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_44),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn on version 44.
+ SetQuicReloadableFlag(quic_enable_version_44, true);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_44),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn off version 43.
+ SetQuicReloadableFlag(quic_enable_version_43, false);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn on version 43.
+ SetQuicReloadableFlag(quic_enable_version_43, true);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn off version 39.
+ SetQuicReloadableFlag(quic_disable_version_39, true);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_39),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Turn on version 39.
+ SetQuicReloadableFlag(quic_disable_version_39, false);
+ connection_id = TestConnectionId(++conn_id);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _));
+ ProcessPacket(client_address, connection_id, true,
+ ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_39),
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+}
+
+// Enables mocking of the handshake-confirmation for stateless rejects.
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+ MockQuicCryptoServerStream(const QuicCryptoServerConfig& crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicServerSessionBase* session,
+ QuicCryptoServerStream::Helper* helper)
+ : QuicCryptoServerStream(
+ &crypto_config,
+ compressed_certs_cache,
+ GetQuicReloadableFlag(enable_quic_stateless_reject_support),
+ session,
+ helper),
+ handshake_confirmed_(false) {}
+ MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+ MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+ delete;
+
+ void set_handshake_confirmed_for_testing(bool handshake_confirmed) {
+ handshake_confirmed_ = handshake_confirmed;
+ }
+
+ bool handshake_confirmed() const override { return handshake_confirmed_; }
+
+ private:
+ bool handshake_confirmed_;
+};
+
+struct StatelessRejectTestParams {
+ StatelessRejectTestParams(bool enable_stateless_rejects_via_flag,
+ bool client_supports_statelesss_rejects,
+ bool crypto_handshake_successful)
+ : enable_stateless_rejects_via_flag(enable_stateless_rejects_via_flag),
+ client_supports_statelesss_rejects(client_supports_statelesss_rejects),
+ crypto_handshake_successful(crypto_handshake_successful) {}
+
+ friend std::ostream& operator<<(std::ostream& os,
+ const StatelessRejectTestParams& p) {
+ os << "{ enable_stateless_rejects_via_flag: "
+ << p.enable_stateless_rejects_via_flag << std::endl;
+ os << " client_supports_statelesss_rejects: "
+ << p.client_supports_statelesss_rejects << std::endl;
+ os << " crypto_handshake_successful: " << p.crypto_handshake_successful
+ << " }";
+ return os;
+ }
+
+ // This only enables the stateless reject feature via the feature-flag.
+ // This should be a no-op if the peer does not support them.
+ bool enable_stateless_rejects_via_flag;
+ // Whether or not the client supports stateless rejects.
+ bool client_supports_statelesss_rejects;
+ // Should the initial crypto handshake succeed or not.
+ bool crypto_handshake_successful;
+};
+
+// Constructs various test permutations for stateless rejects.
+std::vector<StatelessRejectTestParams> GetStatelessRejectTestParams() {
+ std::vector<StatelessRejectTestParams> params;
+ for (bool enable_stateless_rejects_via_flag : {true, false}) {
+ for (bool client_supports_statelesss_rejects : {true, false}) {
+ for (bool crypto_handshake_successful : {true, false}) {
+ params.push_back(StatelessRejectTestParams(
+ enable_stateless_rejects_via_flag,
+ client_supports_statelesss_rejects, crypto_handshake_successful));
+ }
+ }
+ }
+ return params;
+}
+
+class QuicDispatcherStatelessRejectTest
+ : public QuicDispatcherTest,
+ public testing::WithParamInterface<StatelessRejectTestParams> {
+ public:
+ QuicDispatcherStatelessRejectTest()
+ : QuicDispatcherTest(), crypto_stream1_(nullptr) {}
+
+ ~QuicDispatcherStatelessRejectTest() override {
+ if (crypto_stream1_) {
+ delete crypto_stream1_;
+ }
+ }
+
+ // This test setup assumes that all testing will be done using
+ // crypto_stream1_.
+ void SetUp() override {
+ QuicDispatcherTest::SetUp();
+ SetQuicReloadableFlag(enable_quic_stateless_reject_support,
+ GetParam().enable_stateless_rejects_via_flag);
+ }
+
+ // Returns true or false, depending on whether the server will emit
+ // a stateless reject, depending upon the parameters of the test.
+ bool ExpectStatelessReject() {
+ return GetParam().enable_stateless_rejects_via_flag &&
+ !GetParam().crypto_handshake_successful &&
+ GetParam().client_supports_statelesss_rejects;
+ }
+
+ // Sets up dispatcher_, session1_, and crypto_stream1_ based on
+ // the test parameters.
+ QuicServerSessionBase* CreateSessionBasedOnTestParams(
+ QuicConnectionId connection_id,
+ const QuicSocketAddress& client_address) {
+ CreateSession(dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_);
+
+ crypto_stream1_ = new MockQuicCryptoServerStream(
+ crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ session1_, session1_->stream_helper());
+ session1_->SetCryptoStream(crypto_stream1_);
+ crypto_stream1_->set_handshake_confirmed_for_testing(
+ GetParam().crypto_handshake_successful);
+ crypto_stream1_->SetPeerSupportsStatelessRejects(
+ GetParam().client_supports_statelesss_rejects);
+ return session1_;
+ }
+
+ MockQuicCryptoServerStream* crypto_stream1_;
+};
+
+// Parameterized test for stateless rejects. Should test all
+// combinations of enabling/disabling, reject/no-reject for stateless
+// rejects.
+INSTANTIATE_TEST_SUITE_P(QuicDispatcherStatelessRejectTests,
+ QuicDispatcherStatelessRejectTest,
+ ::testing::ValuesIn(GetStatelessRejectTestParams()));
+
+TEST_P(QuicDispatcherStatelessRejectTest, ParameterizedBasicTest) {
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(
+ CreateSessionBasedOnTestParams(connection_id, client_address)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _))
+ .Times(1);
+
+ // Process the first packet for the connection.
+ ProcessPacket(client_address, connection_id, true, SerializeCHLO());
+ if (ExpectStatelessReject()) {
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ CloseConnection(QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT, _, _));
+ // If this is a stateless reject, the crypto stream will close the
+ // connection.
+ session1_->connection()->CloseConnection(
+ QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT, "stateless reject",
+ ConnectionCloseBehavior::SILENT_CLOSE);
+ }
+
+ // Send a second packet and check the results. If this is a stateless reject,
+ // the existing connection_id will go on the time-wait list.
+ EXPECT_EQ(ExpectStatelessReject(),
+ time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id));
+ if (ExpectStatelessReject()) {
+ // The second packet will be processed on the time-wait list.
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, connection_id, _, _))
+ .Times(1);
+ } else {
+ // The second packet will trigger a packet-validation
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(1)
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ }
+ ProcessPacket(client_address, connection_id, true, "data");
+}
+
+TEST_P(QuicDispatcherStatelessRejectTest, CheapRejects) {
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects, true);
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ if (GetParam().enable_stateless_rejects_via_flag) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(connection_id, client_address, _, _))
+ .Times(0);
+ } else {
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("h2"), _))
+ .WillOnce(testing::Return(
+ CreateSessionBasedOnTestParams(connection_id, client_address)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ }
+
+ QUIC_LOG(INFO) << "ExpectStatelessReject: " << ExpectStatelessReject();
+ QUIC_LOG(INFO) << "Params: " << GetParam();
+ // Process the first packet for the connection.
+ CryptoHandshakeMessage client_hello =
+ crypto_test_utils::CreateCHLO({{"AEAD", "AESG"},
+ {"KEXS", "C255"},
+ {"COPT", "SREJ"},
+ {"NONC", "1234567890123456789012"},
+ {"ALPN", "h2"},
+ {"VER\0", "Q025"}},
+ kClientHelloMinimumSize);
+
+ if (GetParam().enable_stateless_rejects_via_flag) {
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, _, connection_id, _, _))
+ .Times(1);
+ } else {
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _))
+ .Times(1);
+ }
+ ProcessPacket(client_address, connection_id, true,
+ QuicString(client_hello.GetSerialized().AsStringPiece()));
+
+ if (GetParam().enable_stateless_rejects_via_flag) {
+ EXPECT_EQ(true,
+ time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id));
+ }
+}
+
+TEST_P(QuicDispatcherStatelessRejectTest, BufferNonChlo) {
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects, true);
+ CreateTimeWaitListManager();
+
+ const QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ const QuicConnectionId connection_id = TestConnectionId(1);
+
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(connection_id, _))
+ .Times(1);
+ ProcessPacket(client_address, connection_id, true, "NOT DATA FOR A CHLO");
+
+ // Process the first packet for the connection.
+ CryptoHandshakeMessage client_hello =
+ crypto_test_utils::CreateCHLO({{"AEAD", "AESG"},
+ {"KEXS", "C255"},
+ {"NONC", "1234567890123456789012"},
+ {"ALPN", "h3"},
+ {"VER\0", "Q025"}},
+ kClientHelloMinimumSize);
+
+ // If stateless rejects are enabled then a connection will be created now
+ // and the buffered packet will be processed
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(connection_id, client_address,
+ QuicStringPiece("h3"), _))
+ .WillOnce(testing::Return(
+ CreateSessionBasedOnTestParams(connection_id, client_address)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, client_address, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })));
+ // Expect both packets to be passed to ProcessUdpPacket(). And one of them
+ // is already expected in CreateSessionBasedOnTestParams().
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, client_address, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(connection_id, packet);
+ })))
+ .RetiresOnSaturation();
+ ProcessPacket(client_address, connection_id, true,
+ QuicString(client_hello.GetSerialized().AsStringPiece()));
+ EXPECT_FALSE(
+ time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id));
+}
+
+// Verify the stopgap test: Packets with truncated connection IDs should be
+// dropped.
+class QuicDispatcherTestStrayPacketConnectionId : public QuicDispatcherTest {};
+
+// Packets with truncated connection IDs should be dropped.
+TEST_F(QuicDispatcherTestStrayPacketConnectionId,
+ StrayPacketTruncatedConnectionId) {
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId connection_id = TestConnectionId(1);
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, QuicStringPiece("hq"), _))
+ .Times(0);
+ if (CurrentSupportedVersions()[0].transport_version > QUIC_VERSION_43) {
+ // This IETF packet has invalid connection ID length.
+ EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(0);
+ } else {
+ // This GQUIC packet is considered as IETF QUIC packet with short header
+ // with unacceptable packet number.
+ EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _))
+ .Times(1);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(_, _, _, _, _))
+ .Times(1);
+ }
+ ProcessPacket(client_address, connection_id, true, "data",
+ CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER);
+}
+
+class BlockingWriter : public QuicPacketWriterWrapper {
+ public:
+ BlockingWriter() : write_blocked_(false) {}
+
+ bool IsWriteBlocked() const override { return write_blocked_; }
+ void SetWritable() override { write_blocked_ = false; }
+
+ WriteResult WritePacket(const char* buffer,
+ size_t buf_len,
+ const QuicIpAddress& self_client_address,
+ const QuicSocketAddress& peer_client_address,
+ PerPacketOptions* options) override {
+ // It would be quite possible to actually implement this method here with
+ // the fake blocked status, but it would be significantly more work in
+ // Chromium, and since it's not called anyway, don't bother.
+ QUIC_LOG(DFATAL) << "Not supported";
+ return WriteResult();
+ }
+
+ bool write_blocked_;
+};
+
+class QuicDispatcherWriteBlockedListTest : public QuicDispatcherTest {
+ public:
+ void SetUp() override {
+ QuicDispatcherTest::SetUp();
+ writer_ = new BlockingWriter;
+ QuicDispatcherPeer::UseWriter(dispatcher_.get(), writer_);
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(_, client_address, QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &helper_, &alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ EXPECT_CALL(*dispatcher_, ShouldCreateOrBufferPacketForConnection(
+ TestConnectionId(1), _));
+ ProcessPacket(client_address, TestConnectionId(1), true, SerializeCHLO());
+
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(_, client_address, QuicStringPiece("hq"), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(2), client_address,
+ &helper_, &alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session2_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session2_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(2), packet);
+ })));
+ EXPECT_CALL(*dispatcher_, ShouldCreateOrBufferPacketForConnection(
+ TestConnectionId(2), _));
+ ProcessPacket(client_address, TestConnectionId(2), true, SerializeCHLO());
+
+ blocked_list_ = QuicDispatcherPeer::GetWriteBlockedList(dispatcher_.get());
+ }
+
+ void TearDown() override {
+ if (connection1() != nullptr) {
+ EXPECT_CALL(*connection1(), CloseConnection(QUIC_PEER_GOING_AWAY, _, _));
+ }
+
+ if (connection2() != nullptr) {
+ EXPECT_CALL(*connection2(), CloseConnection(QUIC_PEER_GOING_AWAY, _, _));
+ }
+ dispatcher_->Shutdown();
+ }
+
+ // Set the dispatcher's writer to be blocked. By default, all connections use
+ // the same writer as the dispatcher in this test.
+ void SetBlocked() {
+ QUIC_LOG(INFO) << "set writer " << writer_ << " to blocked";
+ writer_->write_blocked_ = true;
+ }
+
+ // Simulate what happens when connection1 gets blocked when writing.
+ void BlockConnection1() {
+ Connection1Writer()->write_blocked_ = true;
+ dispatcher_->OnWriteBlocked(connection1());
+ }
+
+ BlockingWriter* Connection1Writer() {
+ return static_cast<BlockingWriter*>(connection1()->writer());
+ }
+
+ // Simulate what happens when connection2 gets blocked when writing.
+ void BlockConnection2() {
+ Connection2Writer()->write_blocked_ = true;
+ dispatcher_->OnWriteBlocked(connection2());
+ }
+
+ BlockingWriter* Connection2Writer() {
+ return static_cast<BlockingWriter*>(connection2()->writer());
+ }
+
+ protected:
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ BlockingWriter* writer_;
+ QuicDispatcher::WriteBlockedList* blocked_list_;
+};
+
+TEST_F(QuicDispatcherWriteBlockedListTest, BasicOnCanWrite) {
+ // No OnCanWrite calls because no connections are blocked.
+ dispatcher_->OnCanWrite();
+
+ // Register connection 1 for events, and make sure it's notified.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+
+ // It should get only one notification.
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ dispatcher_->OnCanWrite();
+ EXPECT_FALSE(dispatcher_->HasPendingWrites());
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, OnCanWriteOrder) {
+ // Make sure we handle events in order.
+ InSequence s;
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+
+ // Check the other ordering.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection2());
+ dispatcher_->OnWriteBlocked(connection1());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, OnCanWriteRemove) {
+ // Add and remove one connction.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ blocked_list_->erase(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ dispatcher_->OnCanWrite();
+
+ // Add and remove one connction and make sure it doesn't affect others.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection2());
+ blocked_list_->erase(connection1());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+
+ // Add it, remove it, and add it back and make sure things are OK.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ blocked_list_->erase(connection1());
+ dispatcher_->OnWriteBlocked(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(1);
+ dispatcher_->OnCanWrite();
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, DoubleAdd) {
+ // Make sure a double add does not necessitate a double remove.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection1());
+ blocked_list_->erase(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ dispatcher_->OnCanWrite();
+
+ // Make sure a double add does not result in two OnCanWrite calls.
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(1);
+ dispatcher_->OnCanWrite();
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, OnCanWriteHandleBlockConnection1) {
+ // If the 1st blocked writer gets blocked in OnCanWrite, it will be added back
+ // into the write blocked list.
+ InSequence s;
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite())
+ .WillOnce(
+ Invoke(this, &QuicDispatcherWriteBlockedListTest::BlockConnection1));
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+
+ // connection1 should be still in the write blocked list.
+ EXPECT_TRUE(dispatcher_->HasPendingWrites());
+
+ // Now call OnCanWrite again, connection1 should get its second chance.
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ EXPECT_CALL(*connection2(), OnCanWrite()).Times(0);
+ dispatcher_->OnCanWrite();
+ EXPECT_FALSE(dispatcher_->HasPendingWrites());
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, OnCanWriteHandleBlockConnection2) {
+ // If the 2nd blocked writer gets blocked in OnCanWrite, it will be added back
+ // into the write blocked list.
+ InSequence s;
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ EXPECT_CALL(*connection2(), OnCanWrite())
+ .WillOnce(
+ Invoke(this, &QuicDispatcherWriteBlockedListTest::BlockConnection2));
+ dispatcher_->OnCanWrite();
+
+ // connection2 should be still in the write blocked list.
+ EXPECT_TRUE(dispatcher_->HasPendingWrites());
+
+ // Now call OnCanWrite again, connection2 should get its second chance.
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+ EXPECT_FALSE(dispatcher_->HasPendingWrites());
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest,
+ OnCanWriteHandleBlockBothConnections) {
+ // Both connections get blocked in OnCanWrite, and added back into the write
+ // blocked list.
+ InSequence s;
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ dispatcher_->OnWriteBlocked(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite())
+ .WillOnce(
+ Invoke(this, &QuicDispatcherWriteBlockedListTest::BlockConnection1));
+ EXPECT_CALL(*connection2(), OnCanWrite())
+ .WillOnce(
+ Invoke(this, &QuicDispatcherWriteBlockedListTest::BlockConnection2));
+ dispatcher_->OnCanWrite();
+
+ // Both connections should be still in the write blocked list.
+ EXPECT_TRUE(dispatcher_->HasPendingWrites());
+
+ // Now call OnCanWrite again, both connections should get its second chance.
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+ EXPECT_FALSE(dispatcher_->HasPendingWrites());
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest, PerConnectionWriterBlocked) {
+ // By default, all connections share the same packet writer with the
+ // dispatcher.
+ EXPECT_EQ(dispatcher_->writer(), connection1()->writer());
+ EXPECT_EQ(dispatcher_->writer(), connection2()->writer());
+
+ // Test the case where connection1 shares the same packet writer as the
+ // dispatcher, whereas connection2 owns it's packet writer.
+ // Change connection2's writer.
+ connection2()->SetQuicPacketWriter(new BlockingWriter, /*owns_writer=*/true);
+ EXPECT_NE(dispatcher_->writer(), connection2()->writer());
+
+ BlockConnection2();
+ EXPECT_TRUE(dispatcher_->HasPendingWrites());
+
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_->OnCanWrite();
+ EXPECT_FALSE(dispatcher_->HasPendingWrites());
+}
+
+TEST_F(QuicDispatcherWriteBlockedListTest,
+ RemoveConnectionFromWriteBlockedListWhenDeletingSessions) {
+ if (!GetQuicReloadableFlag(
+ quic_connection_do_not_add_to_write_blocked_list_if_disconnected)) {
+ return;
+ }
+
+ dispatcher_->OnConnectionClosed(connection1()->connection_id(),
+ QUIC_PACKET_WRITE_ERROR, "Closed by test.",
+ ConnectionCloseSource::FROM_SELF);
+
+ SetBlocked();
+
+ ASSERT_FALSE(dispatcher_->HasPendingWrites());
+ SetBlocked();
+ dispatcher_->OnWriteBlocked(connection1());
+ ASSERT_TRUE(dispatcher_->HasPendingWrites());
+
+ EXPECT_QUIC_BUG(dispatcher_->DeleteSessions(),
+ "QuicConnection was in WriteBlockedList before destruction");
+ MarkSession1Deleted();
+}
+
+// Tests that bufferring packets works in stateful reject, expensive stateless
+// reject and cheap stateless reject.
+struct BufferedPacketStoreTestParams {
+ BufferedPacketStoreTestParams(bool enable_stateless_rejects_via_flag,
+ bool support_cheap_stateless_reject)
+ : enable_stateless_rejects_via_flag(enable_stateless_rejects_via_flag),
+ support_cheap_stateless_reject(support_cheap_stateless_reject) {}
+
+ friend std::ostream& operator<<(std::ostream& os,
+ const BufferedPacketStoreTestParams& p) {
+ os << "{ enable_stateless_rejects_via_flag: "
+ << p.enable_stateless_rejects_via_flag << std::endl;
+ os << " support_cheap_stateless_reject: "
+ << p.support_cheap_stateless_reject << " }";
+ return os;
+ }
+
+ // This only enables the stateless reject feature via the feature-flag.
+ // This should be a no-op if the peer does not support them.
+ bool enable_stateless_rejects_via_flag;
+ // Whether to do cheap stateless or not.
+ bool support_cheap_stateless_reject;
+};
+
+std::vector<BufferedPacketStoreTestParams> GetBufferedPacketStoreTestParams() {
+ std::vector<BufferedPacketStoreTestParams> params;
+ for (bool enable_stateless_rejects_via_flag : {true, false}) {
+ for (bool support_cheap_stateless_reject : {true, false}) {
+ params.push_back(BufferedPacketStoreTestParams(
+ enable_stateless_rejects_via_flag, support_cheap_stateless_reject));
+ }
+ }
+ return params;
+}
+
+// A dispatcher whose stateless rejector will always ACCEPTs CHLO.
+class BufferedPacketStoreTest
+ : public QuicDispatcherTest,
+ public testing::WithParamInterface<BufferedPacketStoreTestParams> {
+ public:
+ BufferedPacketStoreTest()
+ : QuicDispatcherTest(),
+ server_addr_(QuicSocketAddress(QuicIpAddress::Any4(), 5)),
+ client_addr_(QuicIpAddress::Loopback4(), 1234),
+ signed_config_(new QuicSignedServerConfig) {
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects,
+ GetParam().support_cheap_stateless_reject);
+ SetQuicReloadableFlag(enable_quic_stateless_reject_support,
+ GetParam().enable_stateless_rejects_via_flag);
+ }
+
+ void SetUp() override {
+ QuicDispatcherTest::SetUp();
+ clock_ = QuicDispatcherPeer::GetHelper(dispatcher_.get())->GetClock();
+
+ QuicTransportVersion version = AllSupportedTransportVersions().front();
+ CryptoHandshakeMessage chlo =
+ crypto_test_utils::GenerateDefaultInchoateCHLO(clock_, version,
+ &crypto_config_);
+ chlo.SetVector(kCOPT, QuicTagVector{kSREJ});
+ // Pass an inchoate CHLO.
+ crypto_test_utils::GenerateFullCHLO(
+ chlo, &crypto_config_, server_addr_, client_addr_, version, clock_,
+ signed_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &full_chlo_);
+ }
+
+ QuicString SerializeFullCHLO() {
+ return QuicString(full_chlo_.GetSerialized().AsStringPiece());
+ }
+
+ protected:
+ QuicSocketAddress server_addr_;
+ QuicSocketAddress client_addr_;
+ QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
+ const QuicClock* clock_;
+ CryptoHandshakeMessage full_chlo_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ BufferedPacketStoreTests,
+ BufferedPacketStoreTest,
+ ::testing::ValuesIn(GetBufferedPacketStoreTestParams()));
+
+TEST_P(BufferedPacketStoreTest, ProcessNonChloPacketsUptoLimitAndProcessChlo) {
+ InSequence s;
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId conn_id = TestConnectionId(1);
+ // A bunch of non-CHLO should be buffered upon arrival, and the first one
+ // should trigger ShouldCreateOrBufferPacketForConnection().
+ EXPECT_CALL(*dispatcher_, ShouldCreateOrBufferPacketForConnection(conn_id, _))
+ .Times(1);
+ for (size_t i = 1; i <= kDefaultMaxUndecryptablePackets + 1; ++i) {
+ ProcessPacket(client_address, conn_id, true,
+ QuicStrCat("data packet ", i + 1), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, /*packet_number=*/i + 1);
+ }
+ EXPECT_EQ(0u, dispatcher_->session_map().size())
+ << "No session should be created before CHLO arrives.";
+
+ // Pop out the last packet as it is also be dropped by the store.
+ data_connection_map_[conn_id].pop_back();
+ // When CHLO arrives, a new session should be created, and all packets
+ // buffered should be delivered to the session.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(conn_id, client_address, QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, conn_id, client_address, &mock_helper_,
+ &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+
+ // Only |kDefaultMaxUndecryptablePackets| packets were buffered, and they
+ // should be delivered in arrival order.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(kDefaultMaxUndecryptablePackets + 1) // + 1 for CHLO.
+ .WillRepeatedly(
+ WithArg<2>(Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+}
+
+TEST_P(BufferedPacketStoreTest,
+ ProcessNonChloPacketsForDifferentConnectionsUptoLimit) {
+ InSequence s;
+ // A bunch of non-CHLO should be buffered upon arrival.
+ size_t kNumConnections = kMaxConnectionsWithoutCHLO + 1;
+ for (size_t i = 1; i <= kNumConnections; ++i) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), i);
+ QuicConnectionId conn_id = TestConnectionId(i);
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+ ProcessPacket(client_address, conn_id, true,
+ QuicStrCat("data packet on connection ", i),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ /*packet_number=*/2);
+ }
+
+ // Pop out the packet on last connection as it shouldn't be enqueued in store
+ // as well.
+ data_connection_map_[TestConnectionId(kNumConnections)].pop_front();
+
+ // Reset session creation counter to ensure processing CHLO can always
+ // create session.
+ QuicDispatcherPeer::set_new_sessions_allowed_per_event_loop(dispatcher_.get(),
+ kNumConnections);
+ // Process CHLOs to create session for these connections.
+ for (size_t i = 1; i <= kNumConnections; ++i) {
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), i);
+ QuicConnectionId conn_id = TestConnectionId(i);
+ if (i == kNumConnections) {
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+ }
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id, client_address,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, conn_id, client_address, &mock_helper_,
+ &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ // First |kNumConnections| - 1 connections should have buffered
+ // a packet in store. The rest should have been dropped.
+ size_t num_packet_to_process = i <= kMaxConnectionsWithoutCHLO ? 2u : 1u;
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, client_address, _))
+ .Times(num_packet_to_process)
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+ }
+}
+
+// Tests that store delivers empty packet list if CHLO arrives firstly.
+TEST_P(BufferedPacketStoreTest, DeliverEmptyPackets) {
+ QuicConnectionId conn_id = TestConnectionId(1);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(conn_id, client_address, QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, conn_id, client_address, &mock_helper_,
+ &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, client_address, _));
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+}
+
+// Tests that a retransmitted CHLO arrives after a connection for the
+// CHLO has been created.
+TEST_P(BufferedPacketStoreTest, ReceiveRetransmittedCHLO) {
+ InSequence s;
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId conn_id = TestConnectionId(1);
+ ProcessPacket(client_address, conn_id, true, QuicStrCat("data packet ", 2),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ /*packet_number=*/2);
+
+ // When CHLO arrives, a new session should be created, and all packets
+ // buffered should be delivered to the session.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(conn_id, client_address, QuicStringPiece(), _))
+ .Times(1) // Only triggered by 1st CHLO.
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, conn_id, client_address, &mock_helper_,
+ &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(3) // Triggered by 1 data packet and 2 CHLOs.
+ .WillRepeatedly(
+ WithArg<2>(Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+}
+
+// Tests that expiration of a connection add connection id to time wait list.
+TEST_P(BufferedPacketStoreTest, ReceiveCHLOAfterExpiration) {
+ InSequence s;
+ CreateTimeWaitListManager();
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+ QuicBufferedPacketStorePeer::set_clock(store, mock_helper_.GetClock());
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId conn_id = TestConnectionId(1);
+ ProcessPacket(client_address, conn_id, true, QuicStrCat("data packet ", 2),
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ /*packet_number=*/2);
+
+ mock_helper_.AdvanceTime(
+ QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs));
+ QuicAlarm* alarm = QuicBufferedPacketStorePeer::expiration_alarm(store);
+ // Cancel alarm as if it had been fired.
+ alarm->Cancel();
+ store->OnExpirationTimeout();
+ // New arrived CHLO will be dropped because this connection is in time wait
+ // list.
+ ASSERT_TRUE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+ EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, conn_id, _, _));
+ ProcessPacket(client_address, conn_id, true, SerializeFullCHLO());
+}
+
+TEST_P(BufferedPacketStoreTest, ProcessCHLOsUptoLimitAndBufferTheRest) {
+ // Process more than (|kMaxNumSessionsToCreate| +
+ // |kDefaultMaxConnectionsInStore|) CHLOs,
+ // the first |kMaxNumSessionsToCreate| should create connections immediately,
+ // the next |kDefaultMaxConnectionsInStore| should be buffered,
+ // the rest should be dropped.
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+ const size_t kNumCHLOs =
+ kMaxNumSessionsToCreate + kDefaultMaxConnectionsInStore + 1;
+ for (uint64_t conn_id = 1; conn_id <= kNumCHLOs; ++conn_id) {
+ EXPECT_CALL(*dispatcher_, ShouldCreateOrBufferPacketForConnection(
+ TestConnectionId(conn_id), _));
+ if (conn_id <= kMaxNumSessionsToCreate) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id),
+ client_addr_, &mock_helper_, &mock_alarm_factory_,
+ &crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &session1_)));
+ EXPECT_CALL(
+ *reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true,
+ SerializeFullCHLO());
+ if (conn_id <= kMaxNumSessionsToCreate + kDefaultMaxConnectionsInStore &&
+ conn_id > kMaxNumSessionsToCreate) {
+ EXPECT_TRUE(store->HasChloForConnection(TestConnectionId(conn_id)));
+ } else {
+ // First |kMaxNumSessionsToCreate| CHLOs should be passed to new
+ // connections immediately, and the last CHLO should be dropped as the
+ // store is full.
+ EXPECT_FALSE(store->HasChloForConnection(TestConnectionId(conn_id)));
+ }
+ }
+
+ // Graduately consume buffered CHLOs. The buffered connections should be
+ // created but the dropped one shouldn't.
+ for (uint64_t conn_id = kMaxNumSessionsToCreate + 1;
+ conn_id <= kMaxNumSessionsToCreate + kDefaultMaxConnectionsInStore;
+ ++conn_id) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id), client_addr_,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(kNumCHLOs), client_addr_,
+ QuicStringPiece(), _))
+ .Times(0);
+
+ while (store->HasChlosBuffered()) {
+ dispatcher_->ProcessBufferedChlos(kMaxNumSessionsToCreate);
+ }
+
+ EXPECT_EQ(TestConnectionId(static_cast<size_t>(kMaxNumSessionsToCreate) +
+ kDefaultMaxConnectionsInStore),
+ session1_->connection_id());
+}
+
+// Duplicated CHLO shouldn't be buffered.
+TEST_P(BufferedPacketStoreTest, BufferDuplicatedCHLO) {
+ for (uint64_t conn_id = 1; conn_id <= kMaxNumSessionsToCreate + 1;
+ ++conn_id) {
+ // Last CHLO will be buffered. Others will create connection right away.
+ if (conn_id <= kMaxNumSessionsToCreate) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id),
+ client_addr_, &mock_helper_, &mock_alarm_factory_,
+ &crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &session1_)));
+ EXPECT_CALL(
+ *reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true,
+ SerializeFullCHLO());
+ }
+ // Retransmit CHLO on last connection should be dropped.
+ QuicConnectionId last_connection =
+ TestConnectionId(kMaxNumSessionsToCreate + 1);
+ ProcessPacket(client_addr_, last_connection, true, SerializeFullCHLO());
+
+ size_t packets_buffered = 2;
+
+ // Reset counter and process buffered CHLO.
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(last_connection, client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, last_connection, client_addr_,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ // Only one packet(CHLO) should be process.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(packets_buffered)
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, last_connection](const QuicEncryptedPacket& packet) {
+ ValidatePacket(last_connection, packet);
+ })));
+ dispatcher_->ProcessBufferedChlos(kMaxNumSessionsToCreate);
+}
+
+TEST_P(BufferedPacketStoreTest, BufferNonChloPacketsUptoLimitWithChloBuffered) {
+ uint64_t last_conn_id = kMaxNumSessionsToCreate + 1;
+ QuicConnectionId last_connection_id = TestConnectionId(last_conn_id);
+ for (uint64_t conn_id = 1; conn_id <= last_conn_id; ++conn_id) {
+ // Last CHLO will be buffered. Others will create connection right away.
+ if (conn_id <= kMaxNumSessionsToCreate) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id),
+ client_addr_, &mock_helper_, &mock_alarm_factory_,
+ &crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &session1_)));
+ EXPECT_CALL(
+ *reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true,
+ SerializeFullCHLO());
+ }
+
+ // Process another |kDefaultMaxUndecryptablePackets| + 1 data packets. The
+ // last one should be dropped.
+ for (uint64_t packet_number = 2;
+ packet_number <= kDefaultMaxUndecryptablePackets + 2; ++packet_number) {
+ ProcessPacket(client_addr_, last_connection_id, true, "data packet");
+ }
+
+ // Reset counter and process buffered CHLO.
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(last_connection_id, client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, last_connection_id, client_addr_,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ // Only CHLO and following |kDefaultMaxUndecryptablePackets| data packets
+ // should be process.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(kDefaultMaxUndecryptablePackets + 1)
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, last_connection_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(last_connection_id, packet);
+ })));
+ dispatcher_->ProcessBufferedChlos(kMaxNumSessionsToCreate);
+}
+
+// Tests that when dispatcher's packet buffer is full, a CHLO on connection
+// which doesn't have buffered CHLO should be buffered.
+TEST_P(BufferedPacketStoreTest, ReceiveCHLOForBufferedConnection) {
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+
+ uint64_t conn_id = 1;
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true, "data packet",
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ /*packet_number=*/1);
+ // Fill packet buffer to full with CHLOs on other connections. Need to feed
+ // extra CHLOs because the first |kMaxNumSessionsToCreate| are going to create
+ // session directly.
+ for (conn_id = 2;
+ conn_id <= kDefaultMaxConnectionsInStore + kMaxNumSessionsToCreate;
+ ++conn_id) {
+ if (conn_id <= kMaxNumSessionsToCreate + 1) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), _))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id),
+ client_addr_, &mock_helper_, &mock_alarm_factory_,
+ &crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &session1_)));
+ EXPECT_CALL(
+ *reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true,
+ SerializeFullCHLO());
+ }
+ EXPECT_FALSE(store->HasChloForConnection(
+ /*connection_id=*/TestConnectionId(1)));
+
+ // CHLO on connection 1 should still be buffered.
+ ProcessPacket(client_addr_, /*connection_id=*/TestConnectionId(1), true,
+ SerializeFullCHLO());
+ EXPECT_TRUE(store->HasChloForConnection(
+ /*connection_id=*/TestConnectionId(1)));
+}
+
+// Regression test for b/117874922.
+TEST_P(BufferedPacketStoreTest, ProcessBufferedChloWithDifferentVersion) {
+ // Turn off version 99, such that the preferred version is not supported by
+ // the server.
+ SetQuicReloadableFlag(quic_enable_version_99, false);
+ uint64_t last_connection_id = kMaxNumSessionsToCreate + 5;
+ ParsedQuicVersionVector supported_versions = CurrentSupportedVersions();
+ for (uint64_t conn_id = 1; conn_id <= last_connection_id; ++conn_id) {
+ // Last 5 CHLOs will be buffered. Others will create connection right away.
+ ParsedQuicVersion version =
+ supported_versions[(conn_id - 1) % supported_versions.size()];
+ if (conn_id <= kMaxNumSessionsToCreate) {
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), version))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id),
+ client_addr_, &mock_helper_, &mock_alarm_factory_,
+ &crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &session1_)));
+ EXPECT_CALL(
+ *reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ ProcessPacket(client_addr_, TestConnectionId(conn_id), true, version,
+ SerializeFullCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+ }
+
+ // Process buffered CHLOs. Verify the version is correct.
+ for (uint64_t conn_id = kMaxNumSessionsToCreate + 1;
+ conn_id <= last_connection_id; ++conn_id) {
+ ParsedQuicVersion version =
+ supported_versions[(conn_id - 1) % supported_versions.size()];
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(conn_id), client_addr_,
+ QuicStringPiece(), version))
+ .WillOnce(testing::Return(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(conn_id), client_addr_,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(conn_id), packet);
+ })));
+ }
+ dispatcher_->ProcessBufferedChlos(kMaxNumSessionsToCreate);
+}
+
+// Test which exercises the async GetProof codepaths, especially in the context
+// of stateless rejection.
+class AsyncGetProofTest : public QuicDispatcherTest {
+ public:
+ AsyncGetProofTest()
+ : QuicDispatcherTest(
+ std::unique_ptr<FakeProofSource>(new FakeProofSource())),
+ client_addr_(QuicIpAddress::Loopback4(), 1234),
+ client_addr_2_(QuicIpAddress::Loopback4(), 1357),
+ crypto_config_peer_(&crypto_config_),
+ server_addr_(QuicIpAddress::Any4(), 5),
+ signed_config_(new QuicSignedServerConfig) {
+ SetQuicReloadableFlag(enable_quic_stateless_reject_support, true);
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects, true);
+ }
+
+ void SetUp() override {
+ QuicDispatcherTest::SetUp();
+
+ clock_ = QuicDispatcherPeer::GetHelper(dispatcher_.get())->GetClock();
+ QuicTransportVersion version = AllSupportedTransportVersions().front();
+ chlo_ = crypto_test_utils::GenerateDefaultInchoateCHLO(clock_, version,
+ &crypto_config_);
+ chlo_.SetVector(kCOPT, QuicTagVector{kSREJ});
+ chlo_.SetStringPiece(kALPN, "HTTP/1");
+ // Pass an inchoate CHLO.
+ crypto_test_utils::GenerateFullCHLO(
+ chlo_, &crypto_config_, server_addr_, client_addr_, version, clock_,
+ signed_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &full_chlo_);
+
+ crypto_test_utils::GenerateFullCHLO(
+ chlo_, &crypto_config_, server_addr_, client_addr_2_, version, clock_,
+ signed_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ &full_chlo_2_);
+
+ GetFakeProofSource()->Activate();
+ }
+
+ FakeProofSource* GetFakeProofSource() const {
+ return static_cast<FakeProofSource*>(crypto_config_peer_.GetProofSource());
+ }
+
+ QuicString SerializeFullCHLO() {
+ return QuicString(full_chlo_.GetSerialized().AsStringPiece());
+ }
+
+ QuicString SerializeFullCHLOForClient2() {
+ return QuicString(full_chlo_2_.GetSerialized().AsStringPiece());
+ }
+
+ QuicString SerializeCHLO() {
+ return QuicString(chlo_.GetSerialized().AsStringPiece());
+ }
+
+ // Sets up a session, and crypto stream based on the test parameters.
+ QuicServerSessionBase* GetSession(QuicConnectionId connection_id,
+ QuicSocketAddress client_address) {
+ auto it = sessions_.find(connection_id);
+ if (it != sessions_.end()) {
+ return it->second.session;
+ }
+
+ TestQuicSpdyServerSession* session;
+ CreateSession(dispatcher_.get(), config_, connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session);
+
+ std::unique_ptr<MockQuicCryptoServerStream> crypto_stream(
+ new MockQuicCryptoServerStream(
+ crypto_config_, QuicDispatcherPeer::GetCache(dispatcher_.get()),
+ session, session->stream_helper()));
+ session->SetCryptoStream(crypto_stream.get());
+ crypto_stream->SetPeerSupportsStatelessRejects(true);
+ const bool ok =
+ sessions_
+ .insert(std::make_pair(
+ connection_id, SessionInfo{session, std::move(crypto_stream)}))
+ .second;
+ CHECK(ok);
+ return session;
+ }
+
+ protected:
+ const QuicSocketAddress client_addr_;
+ const QuicSocketAddress client_addr_2_;
+ CryptoHandshakeMessage chlo_;
+
+ private:
+ QuicCryptoServerConfigPeer crypto_config_peer_;
+ QuicSocketAddress server_addr_;
+ QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_;
+ const QuicClock* clock_;
+ CryptoHandshakeMessage full_chlo_; // CHLO for client_addr_
+ CryptoHandshakeMessage full_chlo_2_; // CHLO for client_addr_2_
+
+ struct SessionInfo {
+ TestQuicSpdyServerSession* session;
+ std::unique_ptr<MockQuicCryptoServerStream> crypto_stream;
+ };
+ std::map<QuicConnectionId, SessionInfo> sessions_;
+};
+
+// Test a simple situation of connections which the StatelessRejector will
+// accept.
+TEST_F(AsyncGetProofTest, BasicAccept) {
+ QuicConnectionId conn_id = TestConnectionId(1);
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id, client_addr_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id, client_addr_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+ }
+
+ // Send a CHLO that the StatelessRejector will accept.
+ ProcessPacket(client_addr_, conn_id, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ check.Call(1);
+ // Complete the ProofSource::GetProof call and verify that a session is
+ // created.
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+
+ check.Call(2);
+ // Verify that a data packet gets processed immediately.
+ ProcessPacket(client_addr_, conn_id, true, "My name is Data");
+}
+
+TEST_F(AsyncGetProofTest, RestorePacketContext) {
+ QuicConnectionId conn_id_1 = TestConnectionId(1);
+ QuicConnectionId conn_id_2 = TestConnectionId(2);
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_1, _));
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_1, client_addr_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id_1, client_addr_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id_1, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id_1](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id_1, packet);
+ })));
+
+ EXPECT_CALL(check, Call(2));
+
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_2, _));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_2, client_addr_2_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id_2, client_addr_2_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id_2, client_addr_2_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id_2](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id_2, packet);
+ })));
+ }
+
+ // Send a CHLO that the StatelessRejector will accept.
+ dispatcher_->custom_packet_context_ = "connection 1";
+ ProcessPacket(client_addr_, conn_id_1, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Send another CHLO that the StatelessRejector will accept.
+ dispatcher_->custom_packet_context_ = "connection 2";
+ ProcessPacket(client_addr_2_, conn_id_2, true, SerializeFullCHLOForClient2());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 2);
+
+ // Complete the first ProofSource::GetProof call and verify that a session is
+ // created.
+ check.Call(1);
+
+ EXPECT_EQ(client_addr_2_, dispatcher_->current_client_address());
+ EXPECT_EQ(client_addr_2_, dispatcher_->current_peer_address());
+ EXPECT_EQ("connection 2", dispatcher_->custom_packet_context_);
+
+ // Runs the async proof callback for conn_id_1 from client_addr_.
+ GetFakeProofSource()->InvokePendingCallback(0);
+
+ EXPECT_EQ(client_addr_, dispatcher_->current_client_address());
+ EXPECT_EQ(client_addr_, dispatcher_->current_peer_address());
+ EXPECT_EQ("connection 1", dispatcher_->custom_packet_context_);
+
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Complete the second ProofSource::GetProof call and verify that a session is
+ // created.
+ check.Call(2);
+
+ EXPECT_EQ(client_addr_, dispatcher_->current_client_address());
+ EXPECT_EQ(client_addr_, dispatcher_->current_peer_address());
+ EXPECT_EQ("connection 1", dispatcher_->custom_packet_context_);
+
+ // Runs the async proof callback for conn_id_2 from client_addr_2_.
+ GetFakeProofSource()->InvokePendingCallback(0);
+
+ EXPECT_EQ(client_addr_2_, dispatcher_->current_client_address());
+ EXPECT_EQ(client_addr_2_, dispatcher_->current_peer_address());
+ EXPECT_EQ("connection 2", dispatcher_->custom_packet_context_);
+
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+}
+
+// Test a simple situation of connections which the StatelessRejector will
+// reject.
+TEST_F(AsyncGetProofTest, BasicReject) {
+ CreateTimeWaitListManager();
+
+ QuicConnectionId conn_id = TestConnectionId(1);
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(conn_id, _, _, _, _));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id, _, _));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id, client_addr_,
+ QuicStringPiece("hq"), _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id, _, _));
+ }
+
+ // Send a CHLO that the StatelessRejector will reject.
+ ProcessPacket(client_addr_, conn_id, true, SerializeCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Complete the ProofSource::GetProof call and verify that the connection and
+ // packet are processed by the time wait list manager.
+ check.Call(1);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+
+ // Verify that a data packet is passed to the time wait list manager.
+ check.Call(2);
+ ProcessPacket(client_addr_, conn_id, true, "My name is Data");
+}
+
+// Test a situation with multiple interleaved connections which the
+// StatelessRejector will accept.
+TEST_F(AsyncGetProofTest, MultipleAccept) {
+ QuicConnectionId conn_id_1 = TestConnectionId(1);
+ QuicConnectionId conn_id_2 = TestConnectionId(2);
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_2, _));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_2, client_addr_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id_2, client_addr_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id_2, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id_2](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id_2, packet);
+ })));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id_2, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id_2](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id_2, packet);
+ })));
+
+ EXPECT_CALL(check, Call(3));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_1, _));
+
+ EXPECT_CALL(check, Call(4));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_1, client_addr_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id_1, client_addr_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id_1, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillRepeatedly(WithArg<2>(
+ Invoke([this, conn_id_1](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id_1, packet);
+ })));
+ }
+
+ // Send a CHLO that the StatelessRejector will accept.
+ ProcessPacket(client_addr_, conn_id_1, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Send another CHLO that the StatelessRejector will accept.
+ ProcessPacket(client_addr_, conn_id_2, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 2);
+
+ // Complete the second ProofSource::GetProof call and verify that a session is
+ // created.
+ check.Call(1);
+ GetFakeProofSource()->InvokePendingCallback(1);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Verify that a data packet on that connection gets processed immediately.
+ check.Call(2);
+ ProcessPacket(client_addr_, conn_id_2, true, "My name is Data");
+
+ // Verify that a data packet on the other connection does not get processed
+ // yet.
+ check.Call(3);
+ ProcessPacket(client_addr_, conn_id_1, true, "My name is Data");
+ EXPECT_TRUE(store->HasBufferedPackets(conn_id_1));
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_2));
+
+ // Complete the first ProofSource::GetProof call and verify that a session is
+ // created and the buffered packet is processed.
+ check.Call(4);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+}
+
+// Test a situation with multiple interleaved connections which the
+// StatelessRejector will reject.
+TEST_F(AsyncGetProofTest, MultipleReject) {
+ CreateTimeWaitListManager();
+
+ QuicConnectionId conn_id_1 = TestConnectionId(1);
+ QuicConnectionId conn_id_2 = TestConnectionId(2);
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_2, client_addr_, _, _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(conn_id_2, _, _, _, _));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id_2, _, _));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id_2, _, _));
+
+ EXPECT_CALL(check, Call(3));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_1, _));
+
+ EXPECT_CALL(check, Call(4));
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(conn_id_1, _, _, _, _));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id_1, _, _));
+ }
+
+ // Send a CHLO that the StatelessRejector will reject.
+ ProcessPacket(client_addr_, conn_id_1, true, SerializeCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Send another CHLO that the StatelessRejector will reject.
+ ProcessPacket(client_addr_, conn_id_2, true, SerializeCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 2);
+
+ // Complete the second ProofSource::GetProof call and verify that the
+ // connection and packet are processed by the time wait manager.
+ check.Call(1);
+ GetFakeProofSource()->InvokePendingCallback(1);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Verify that a data packet on that connection gets processed immediately by
+ // the time wait manager.
+ check.Call(2);
+ ProcessPacket(client_addr_, conn_id_2, true, "My name is Data");
+
+ // Verify that a data packet on the first connection gets buffered.
+ check.Call(3);
+ ProcessPacket(client_addr_, conn_id_1, true, "My name is Data");
+ EXPECT_TRUE(store->HasBufferedPackets(conn_id_1));
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_2));
+
+ // Complete the first ProofSource::GetProof call and verify that the CHLO is
+ // processed by the time wait manager and the remaining packets are discarded.
+ check.Call(4);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_1));
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_2));
+}
+
+// Test a situation with multiple identical CHLOs which the StatelessRejector
+// will reject.
+TEST_F(AsyncGetProofTest, MultipleIdenticalReject) {
+ CreateTimeWaitListManager();
+
+ QuicConnectionId conn_id_1 = TestConnectionId(1);
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id_1, _));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id_1, client_addr_,
+ QuicStringPiece(), _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_,
+ AddConnectionIdToTimeWait(conn_id_1, _, _, _, _));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id_1, _, _));
+ }
+
+ // Send a CHLO that the StatelessRejector will reject.
+ ProcessPacket(client_addr_, conn_id_1, true, SerializeCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_1));
+
+ // Send an identical CHLO which should get buffered.
+ check.Call(1);
+ ProcessPacket(client_addr_, conn_id_1, true, SerializeCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+ EXPECT_TRUE(store->HasBufferedPackets(conn_id_1));
+
+ // Complete the ProofSource::GetProof call and verify that the CHLO is
+ // rejected and the copy is discarded.
+ check.Call(2);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id_1));
+}
+
+// Test dispatcher behavior when packets time out of the buffer while CHLO
+// validation is still pending.
+TEST_F(AsyncGetProofTest, BufferTimeout) {
+ CreateTimeWaitListManager();
+
+ QuicConnectionId conn_id = TestConnectionId(1);
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+ QuicBufferedPacketStorePeer::set_clock(store, mock_helper_.GetClock());
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*time_wait_list_manager_,
+ ProcessPacket(_, client_addr_, conn_id, _, _));
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(conn_id, client_addr_, QuicStringPiece(), _))
+ .Times(0);
+ }
+
+ // Send a CHLO that the StatelessRejector will accept.
+ ProcessPacket(client_addr_, conn_id, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+
+ // Send a data packet that will get buffered
+ check.Call(1);
+ ProcessPacket(client_addr_, conn_id, true, "My name is Data");
+ EXPECT_TRUE(store->HasBufferedPackets(conn_id));
+
+ // Pretend that enough time has gone by for the packets to get expired out of
+ // the buffer
+ mock_helper_.AdvanceTime(
+ QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs));
+ QuicBufferedPacketStorePeer::expiration_alarm(store)->Cancel();
+ store->OnExpirationTimeout();
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+ EXPECT_TRUE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+
+ // Now allow the CHLO validation to complete, and verify that no connection
+ // gets created.
+ check.Call(2);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+ EXPECT_TRUE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+}
+
+// Test behavior when packets time out of the buffer *and* the connection times
+// out of the time wait manager while CHLO validation is still pending. This
+// *should* be impossible, but anything can happen with timing conditions.
+TEST_F(AsyncGetProofTest, TimeWaitTimeout) {
+ QuicConnectionId conn_id = TestConnectionId(1);
+ QuicBufferedPacketStore* store =
+ QuicDispatcherPeer::GetBufferedPackets(dispatcher_.get());
+ QuicBufferedPacketStorePeer::set_clock(store, mock_helper_.GetClock());
+ CreateTimeWaitListManager();
+ QuicTimeWaitListManagerPeer::set_clock(time_wait_list_manager_,
+ mock_helper_.GetClock());
+
+ testing::MockFunction<void(int check_point)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(conn_id, _));
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(conn_id, client_addr_,
+ QuicStringPiece("HTTP/1"), _))
+ .WillOnce(testing::Return(GetSession(conn_id, client_addr_)));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(
+ GetSession(conn_id, client_addr_)->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(
+ Invoke([this, conn_id](const QuicEncryptedPacket& packet) {
+ ValidatePacket(conn_id, packet);
+ })));
+ }
+
+ // Send a CHLO that the StatelessRejector will accept.
+ ProcessPacket(client_addr_, conn_id, true, SerializeFullCHLO());
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+
+ // Send a data packet that will get buffered
+ check.Call(1);
+ ProcessPacket(client_addr_, conn_id, true, "My name is Data");
+ EXPECT_TRUE(store->HasBufferedPackets(conn_id));
+
+ // Pretend that enough time has gone by for the packets to get expired out of
+ // the buffer
+ mock_helper_.AdvanceTime(
+ QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs));
+ QuicBufferedPacketStorePeer::expiration_alarm(store)->Cancel();
+ store->OnExpirationTimeout();
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+ EXPECT_TRUE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+
+ // Pretend that enough time has gone by for the connection ID to be removed
+ // from the time wait manager
+ mock_helper_.AdvanceTime(
+ QuicTimeWaitListManagerPeer::time_wait_period(time_wait_list_manager_));
+ QuicTimeWaitListManagerPeer::expiration_alarm(time_wait_list_manager_)
+ ->Cancel();
+ time_wait_list_manager_->CleanUpOldConnectionIds();
+ EXPECT_FALSE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+
+ // Now allow the CHLO validation to complete. Expect that a connection is
+ // indeed created, since QUIC has forgotten that this connection ever existed.
+ // This is a miniscule corner case which should never happen in the wild, so
+ // really we are just verifying that the dispatcher does not explode in this
+ // situation.
+ check.Call(2);
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+ EXPECT_FALSE(store->HasBufferedPackets(conn_id));
+ EXPECT_FALSE(time_wait_list_manager_->IsConnectionIdInTimeWait(conn_id));
+}
+
+// Regression test for
+// https://bugs.chromium.org/p/chromium/issues/detail?id=748289
+TEST_F(AsyncGetProofTest, DispatcherFailedToPickUpVersionForAsyncProof) {
+ // This test mimics the scenario that dispatcher's framer can have different
+ // version when async proof returns.
+ // When dispatcher sends SREJ, the SREJ frame can be serialized in
+ // different endianness which causes the client to close the connection
+ // because of QUIC_INVALID_STREAM_DATA.
+
+ SetQuicReloadableFlag(quic_disable_version_39, false);
+ SetQuicReloadableFlag(quic_enable_version_43, true);
+ ParsedQuicVersion chlo_version(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43);
+ chlo_.SetVersion(kVER, chlo_version);
+ // Send a CHLO with v43. Dispatcher framer's version is set to v43.
+ ProcessPacket(client_addr_, TestConnectionId(1), true, chlo_version,
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Send another CHLO with v39. Dispatcher framer's version is set to v39.
+ chlo_version.transport_version = QUIC_VERSION_39;
+ chlo_.SetVersion(kVER, chlo_version);
+ // Invalidate the cached serialized form.
+ chlo_.MarkDirty();
+ ProcessPacket(client_addr_, TestConnectionId(2), true, chlo_version,
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 2);
+
+ // Complete the ProofSource::GetProof call for v43. This would cause the
+ // version mismatch between the CHLO packet and the dispatcher.
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+}
+
+// Regression test for b/116200989.
+TEST_F(AsyncGetProofTest, DispatcherHasWrongLastPacketIsIetfQuic) {
+ // Process a packet of v44.
+ ParsedQuicVersion chlo_version(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_44);
+ chlo_.SetVersion(kVER, chlo_version);
+ ProcessPacket(client_addr_, TestConnectionId(1), true, chlo_version,
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+
+ // Process another packet of v43.
+ chlo_version.transport_version = QUIC_VERSION_43;
+ chlo_.SetVersion(kVER, chlo_version);
+ // Invalidate the cached serialized form.
+ chlo_.MarkDirty();
+ ProcessPacket(client_addr_, TestConnectionId(2), true, chlo_version,
+ SerializeCHLO(), CONNECTION_ID_PRESENT,
+ PACKET_4BYTE_PACKET_NUMBER, 1);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 2);
+
+ // Complete the ProofSource::GetProof call for v44.
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Complete the ProofSource::GetProof call for v43.
+ GetFakeProofSource()->InvokePendingCallback(0);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 0);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic