Adopt TlsChloExtractor from QuicDispatcher
This CL allows the dispatcher to parse TLS
ClientHellos like it handles QUIC_CRYPTO CHLOs.
gfe-relnote: parse TLS CHLO, protected by TLS flags
PiperOrigin-RevId: 308293400
Change-Id: Ia72f4a7dd5d96b994479e312183fa6bb6763fb32
diff --git a/quic/core/quic_buffered_packet_store.cc b/quic/core/quic_buffered_packet_store.cc
index 24ca6ef..cafe871 100644
--- a/quic/core/quic_buffered_packet_store.cc
+++ b/quic/core/quic_buffered_packet_store.cc
@@ -86,23 +86,25 @@
QuicSocketAddress self_address,
QuicSocketAddress peer_address,
bool is_chlo,
- const std::string& alpn,
+ const std::vector<std::string>& alpns,
const ParsedQuicVersion& version) {
QUIC_BUG_IF(!GetQuicFlag(FLAGS_quic_allow_chlo_buffering))
<< "Shouldn't buffer packets if disabled via flag.";
QUIC_BUG_IF(is_chlo && QuicContainsKey(connections_with_chlo_, connection_id))
<< "Shouldn't buffer duplicated CHLO on connection " << connection_id;
- QUIC_BUG_IF(!is_chlo && !alpn.empty())
+ QUIC_BUG_IF(!is_chlo && !alpns.empty())
<< "Shouldn't have an ALPN defined for a non-CHLO packet.";
QUIC_BUG_IF(is_chlo && version.transport_version == QUIC_VERSION_UNSUPPORTED)
<< "Should have version for CHLO packet.";
- if (!QuicContainsKey(undecryptable_packets_, connection_id) &&
- ShouldBufferPacket(is_chlo)) {
- // Drop the packet if the upper limit of undecryptable packets has been
- // reached or the whole capacity of the store has been reached.
- return TOO_MANY_CONNECTIONS;
- } else if (!QuicContainsKey(undecryptable_packets_, connection_id)) {
+ const bool is_first_packet =
+ !QuicContainsKey(undecryptable_packets_, connection_id);
+ if (is_first_packet) {
+ if (ShouldBufferPacket(is_chlo)) {
+ // Drop the packet if the upper limit of undecryptable packets has been
+ // reached or the whole capacity of the store has been reached.
+ return TOO_MANY_CONNECTIONS;
+ }
undecryptable_packets_.emplace(
std::make_pair(connection_id, BufferedPacketList()));
undecryptable_packets_.back().second.ietf_quic = ietf_quic;
@@ -138,14 +140,25 @@
// Add CHLO to the beginning of buffered packets so that it can be delivered
// first later.
queue.buffered_packets.push_front(std::move(new_entry));
- queue.alpns = {alpn};
+ queue.alpns = alpns;
connections_with_chlo_[connection_id] = false; // Dummy value.
// Set the version of buffered packets of this connection on CHLO.
queue.version = version;
} else {
// Buffer non-CHLO packets in arrival order.
queue.buffered_packets.push_back(std::move(new_entry));
+
+ // Attempt to parse multi-packet TLS CHLOs.
+ if (is_first_packet) {
+ queue.tls_chlo_extractor.IngestPacket(version, packet);
+ // Since this is the first packet and it's not a CHLO, the
+ // TlsChloExtractor should not have the entire CHLO.
+ QUIC_BUG_IF(queue.tls_chlo_extractor.HasParsedFullChlo())
+ << "First packet in list should not contain full CHLO";
+ }
+ // TODO(b/154857081) Reorder CHLO packets ahead of other ones.
}
+
MaybeSetExpirationAlarm();
return SUCCESS;
}
@@ -240,4 +253,25 @@
return QuicContainsKey(connections_with_chlo_, connection_id);
}
+bool QuicBufferedPacketStore::IngestPacketForTlsChloExtraction(
+ const QuicConnectionId& connection_id,
+ const ParsedQuicVersion& version,
+ const QuicReceivedPacket& packet,
+ std::vector<std::string>* out_alpns) {
+ DCHECK_NE(out_alpns, nullptr);
+ DCHECK_EQ(version.handshake_protocol, PROTOCOL_TLS1_3);
+ auto it = undecryptable_packets_.find(connection_id);
+ if (it == undecryptable_packets_.end()) {
+ QUIC_BUG << "Cannot ingest packet for unknown connection ID "
+ << connection_id;
+ return false;
+ }
+ it->second.tls_chlo_extractor.IngestPacket(version, packet);
+ if (!it->second.tls_chlo_extractor.HasParsedFullChlo()) {
+ return false;
+ }
+ *out_alpns = it->second.tls_chlo_extractor.alpns();
+ return true;
+}
+
} // namespace quic
diff --git a/quic/core/quic_buffered_packet_store.h b/quic/core/quic_buffered_packet_store.h
index 3d9290e..0cc3708 100644
--- a/quic/core/quic_buffered_packet_store.h
+++ b/quic/core/quic_buffered_packet_store.h
@@ -13,6 +13,7 @@
#include "net/third_party/quiche/src/quic/core/quic_clock.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
@@ -73,6 +74,7 @@
// If buffered_packets contains the CHLO, it is the version of the CHLO.
// Otherwise, it is the version of the first packet in |buffered_packets|.
ParsedQuicVersion version;
+ TlsChloExtractor tls_chlo_extractor;
};
typedef QuicLinkedHashMap<QuicConnectionId,
@@ -108,12 +110,22 @@
QuicSocketAddress self_address,
QuicSocketAddress peer_address,
bool is_chlo,
- const std::string& alpn,
+ const std::vector<std::string>& alpns,
const ParsedQuicVersion& version);
// Returns true if there are any packets buffered for |connection_id|.
bool HasBufferedPackets(QuicConnectionId connection_id) const;
+ // Ingests this packet into the corresponding TlsChloExtractor. This should
+ // only be called when HasBufferedPackets(connection_id) is true.
+ // Returns whether we've now parsed a full multi-packet TLS CHLO.
+ // When this returns true, |out_alpns| is populated with the list of ALPNs
+ // extracted from the CHLO.
+ bool IngestPacketForTlsChloExtraction(const QuicConnectionId& connection_id,
+ const ParsedQuicVersion& version,
+ const QuicReceivedPacket& packet,
+ std::vector<std::string>* out_alpns);
+
// Returns the list of buffered packets for |connection_id| and removes them
// from the store. Returns an empty list if no early arrived packets for this
// connection are present.
diff --git a/quic/core/quic_buffered_packet_store_test.cc b/quic/core/quic_buffered_packet_store_test.cc
index 1b1544b..d99f4e0 100644
--- a/quic/core/quic_buffered_packet_store_test.cc
+++ b/quic/core/quic_buffered_packet_store_test.cc
@@ -74,7 +74,7 @@
TEST_F(QuicBufferedPacketStoreTest, SimpleEnqueueAndDeliverPacket) {
QuicConnectionId connection_id = TestConnectionId(1);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
EXPECT_TRUE(store_.HasBufferedPackets(connection_id));
auto packets = store_.DeliverPackets(connection_id);
const std::list<BufferedPacket>& queue = packets.buffered_packets;
@@ -97,9 +97,9 @@
QuicSocketAddress addr_with_new_port(QuicIpAddress::Any4(), 256);
QuicConnectionId connection_id = TestConnectionId(1);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- addr_with_new_port, false, "", invalid_version_);
+ addr_with_new_port, false, {}, invalid_version_);
std::list<BufferedPacket> queue =
store_.DeliverPackets(connection_id).buffered_packets;
ASSERT_EQ(2u, queue.size());
@@ -114,9 +114,9 @@
for (uint64_t conn_id = 1; conn_id <= num_connections; ++conn_id) {
QuicConnectionId connection_id = TestConnectionId(conn_id);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
}
// Deliver packets in reversed order.
@@ -138,12 +138,12 @@
// keep.
EXPECT_EQ(QuicBufferedPacketStore::SUCCESS,
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, true, "", valid_version_));
+ peer_address_, true, {}, valid_version_));
for (size_t i = 1; i <= num_packets; ++i) {
// Only first |kDefaultMaxUndecryptablePackets packets| will be buffered.
EnqueuePacketResult result =
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
if (i <= kDefaultMaxUndecryptablePackets) {
EXPECT_EQ(EnqueuePacketResult::SUCCESS, result);
} else {
@@ -165,7 +165,7 @@
QuicConnectionId connection_id = TestConnectionId(conn_id);
EnqueuePacketResult result =
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
if (conn_id <= kMaxConnectionsWithoutCHLO) {
EXPECT_EQ(EnqueuePacketResult::SUCCESS, result);
} else {
@@ -194,7 +194,7 @@
for (uint64_t conn_id = 1; conn_id <= num_chlos; ++conn_id) {
EXPECT_EQ(EnqueuePacketResult::SUCCESS,
store_.EnqueuePacket(TestConnectionId(conn_id), false, packet_,
- self_address_, peer_address_, true, "",
+ self_address_, peer_address_, true, {},
valid_version_));
}
@@ -205,7 +205,7 @@
QuicConnectionId connection_id = TestConnectionId(conn_id);
EnqueuePacketResult result =
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, true, "", valid_version_);
+ peer_address_, true, {}, valid_version_);
if (conn_id <= kDefaultMaxConnectionsInStore) {
EXPECT_EQ(EnqueuePacketResult::SUCCESS, result);
} else {
@@ -220,7 +220,7 @@
QuicConnectionId connection_id = TestConnectionId(conn_id);
EXPECT_EQ(EnqueuePacketResult::SUCCESS,
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_));
+ peer_address_, false, {}, invalid_version_));
}
// Buffer CHLOs on other connections till store is full.
@@ -229,7 +229,7 @@
QuicConnectionId connection_id = TestConnectionId(i);
EnqueuePacketResult rs =
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, true, "", valid_version_);
+ peer_address_, true, {}, valid_version_);
if (i <= kDefaultMaxConnectionsInStore) {
EXPECT_EQ(EnqueuePacketResult::SUCCESS, rs);
EXPECT_TRUE(store_.HasChloForConnection(connection_id));
@@ -246,7 +246,7 @@
EXPECT_EQ(EnqueuePacketResult::SUCCESS,
store_.EnqueuePacket(
/*connection_id=*/TestConnectionId(1), false, packet_,
- self_address_, peer_address_, true, "", valid_version_));
+ self_address_, peer_address_, true, {}, valid_version_));
EXPECT_TRUE(store_.HasChloForConnection(
/*connection_id=*/TestConnectionId(1)));
@@ -274,14 +274,14 @@
TEST_F(QuicBufferedPacketStoreTest, PacketQueueExpiredBeforeDelivery) {
QuicConnectionId connection_id = TestConnectionId(1);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
EXPECT_EQ(EnqueuePacketResult::SUCCESS,
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, true, "", valid_version_));
+ peer_address_, true, {}, valid_version_));
QuicConnectionId connection_id2 = TestConnectionId(2);
EXPECT_EQ(EnqueuePacketResult::SUCCESS,
store_.EnqueuePacket(connection_id2, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_));
+ peer_address_, false, {}, invalid_version_));
// CHLO on connection 3 arrives 1ms later.
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
@@ -290,7 +290,7 @@
// connections.
QuicSocketAddress another_client_address(QuicIpAddress::Any4(), 255);
store_.EnqueuePacket(connection_id3, false, packet_, self_address_,
- another_client_address, true, "", valid_version_);
+ another_client_address, true, {}, valid_version_);
// Advance clock to the time when connection 1 and 2 expires.
clock_.AdvanceTime(
@@ -322,9 +322,9 @@
// for them to expire.
QuicConnectionId connection_id4 = TestConnectionId(4);
store_.EnqueuePacket(connection_id4, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id4, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
clock_.AdvanceTime(
QuicBufferedPacketStorePeer::expiration_alarm(&store_)->deadline() -
clock_.ApproximateNow());
@@ -339,9 +339,9 @@
// Enqueue some packets
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
EXPECT_TRUE(store_.HasBufferedPackets(connection_id));
EXPECT_FALSE(store_.HasChlosBuffered());
@@ -365,11 +365,11 @@
// Enqueue some packets, which include a CHLO
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, true, "", valid_version_);
+ peer_address_, true, {}, valid_version_);
store_.EnqueuePacket(connection_id, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
EXPECT_TRUE(store_.HasBufferedPackets(connection_id));
EXPECT_TRUE(store_.HasChlosBuffered());
@@ -394,11 +394,11 @@
// Enqueue some packets for two connection IDs
store_.EnqueuePacket(connection_id_1, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id_1, false, packet_, self_address_,
- peer_address_, false, "", invalid_version_);
+ peer_address_, false, {}, invalid_version_);
store_.EnqueuePacket(connection_id_2, false, packet_, self_address_,
- peer_address_, true, "h3", valid_version_);
+ peer_address_, true, {"h3"}, valid_version_);
EXPECT_TRUE(store_.HasBufferedPackets(connection_id_1));
EXPECT_TRUE(store_.HasBufferedPackets(connection_id_2));
EXPECT_TRUE(store_.HasChlosBuffered());
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index 18b6e56..e9bd2f0 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -15,6 +15,7 @@
#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/quic_versions.h"
+#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
@@ -481,9 +482,37 @@
switch (fate) {
case kFateProcess: {
if (packet_info->version.handshake_protocol == PROTOCOL_TLS1_3) {
- // TODO(nharper): Support buffering non-ClientHello packets when using
- // TLS.
- ProcessChlo(/*alpn=*/"", packet_info);
+ bool has_full_tls_chlo = false;
+ std::vector<std::string> alpns;
+ if (buffered_packets_.HasBufferedPackets(
+ packet_info->destination_connection_id)) {
+ // If we already have buffered packets for this connection ID,
+ // use the associated TlsChloExtractor to parse this packet.
+ has_full_tls_chlo =
+ buffered_packets_.IngestPacketForTlsChloExtraction(
+ packet_info->destination_connection_id, packet_info->version,
+ packet_info->packet, &alpns);
+ } else {
+ // If we do not have a BufferedPacketList for this connection ID,
+ // create a single-use one to check whether this packet contains a
+ // full single-packet CHLO.
+ TlsChloExtractor tls_chlo_extractor;
+ tls_chlo_extractor.IngestPacket(packet_info->version,
+ packet_info->packet);
+ if (tls_chlo_extractor.HasParsedFullChlo()) {
+ // This packet contains a full single-packet CHLO.
+ has_full_tls_chlo = true;
+ alpns = tls_chlo_extractor.alpns();
+ }
+ }
+ if (has_full_tls_chlo) {
+ ProcessChlo(alpns, packet_info);
+ } else {
+ // This packet does not contain a full CHLO. It could be a 0-RTT
+ // packet that arrived before the CHLO (due to loss or reordering),
+ // or it could be a fragment of a multi-packet CHLO.
+ BufferEarlyPacket(*packet_info);
+ }
break;
}
if (GetQuicFlag(FLAGS_quic_allow_chlo_buffering) &&
@@ -495,7 +524,7 @@
BufferEarlyPacket(*packet_info);
break;
}
- ProcessChlo(alpn_extractor.ConsumeAlpn(), packet_info);
+ ProcessChlo({alpn_extractor.ConsumeAlpn()}, packet_info);
} break;
case kFateTimeWait:
// Add this connection_id to the time-wait state, to safely reject
@@ -911,13 +940,13 @@
packet_info.destination_connection_id,
packet_info.form != GOOGLE_QUIC_PACKET, packet_info.packet,
packet_info.self_address, packet_info.peer_address, /*is_chlo=*/false,
- /*alpn=*/"", packet_info.version);
+ /*alpns=*/{}, packet_info.version);
if (rs != EnqueuePacketResult::SUCCESS) {
OnBufferPacketFailure(rs, packet_info.destination_connection_id);
}
}
-void QuicDispatcher::ProcessChlo(const std::string& alpn,
+void QuicDispatcher::ProcessChlo(const std::vector<std::string>& alpns,
ReceivedPacketInfo* packet_info) {
if (!buffered_packets_.HasBufferedPackets(
packet_info->destination_connection_id) &&
@@ -933,7 +962,7 @@
packet_info->destination_connection_id,
packet_info->form != GOOGLE_QUIC_PACKET, packet_info->packet,
packet_info->self_address, packet_info->peer_address,
- /*is_chlo=*/true, alpn, packet_info->version);
+ /*is_chlo=*/true, alpns, packet_info->version);
if (rs != EnqueuePacketResult::SUCCESS) {
OnBufferPacketFailure(rs, packet_info->destination_connection_id);
}
@@ -945,6 +974,7 @@
packet_info->destination_connection_id = MaybeReplaceServerConnectionId(
original_connection_id, packet_info->version);
// Creates a new session and process all buffered packets for this connection.
+ std::string alpn = SelectAlpn(alpns);
std::unique_ptr<QuicSession> session =
CreateQuicSession(packet_info->destination_connection_id,
packet_info->peer_address, alpn, packet_info->version);
diff --git a/quic/core/quic_dispatcher.h b/quic/core/quic_dispatcher.h
index b85f667..cd58130 100644
--- a/quic/core/quic_dispatcher.h
+++ b/quic/core/quic_dispatcher.h
@@ -196,7 +196,8 @@
// Called when |packet_info| is a CHLO packet. Creates a new connection and
// delivers any buffered packets for that connection id.
- void ProcessChlo(const std::string& alpn, ReceivedPacketInfo* packet_info);
+ void ProcessChlo(const std::vector<std::string>& alpns,
+ ReceivedPacketInfo* packet_info);
// Return true if dispatcher wants to destroy session outside of
// OnConnectionClosed() call stack.
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index fe5adb0..62e1e7b 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -14,6 +14,7 @@
#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_config.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"
@@ -304,7 +305,8 @@
const QuicSocketAddress& peer_address,
const ParsedQuicVersion& version,
const QuicConnectionId& server_connection_id) {
- if (ChloExtractor::Extract(*received_packet, version, {}, nullptr,
+ if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO &&
+ ChloExtractor::Extract(*received_packet, version, {}, nullptr,
server_connection_id.length())) {
// Add CHLO packet to the beginning to be verified first, because it is
// also processed first by new session.
@@ -392,10 +394,6 @@
}
std::string ExpectedAlpnForVersion(ParsedQuicVersion version) {
- if (version.handshake_protocol == PROTOCOL_TLS1_3) {
- // TODO(b/149597791) Remove this once we can parse ALPN with TLS.
- return "";
- }
return AlpnForVersion(version);
}
@@ -434,6 +432,8 @@
ProcessFirstFlight(version, client_address, connection_id);
}
+ void TestTlsMultiPacketClientHello(bool add_reordering);
+
ParsedQuicVersion version_;
MockQuicConnectionHelper mock_helper_;
MockAlarmFactory mock_alarm_factory_;
@@ -488,6 +488,62 @@
ProcessFirstFlight(client_address, TestConnectionId(1));
}
+void QuicDispatcherTestBase::TestTlsMultiPacketClientHello(
+ bool add_reordering) {
+ if (version_.handshake_protocol != PROTOCOL_TLS1_3) {
+ return;
+ }
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId server_connection_id = TestConnectionId();
+ QuicConfig client_config = DefaultQuicConfig();
+ // Add a 2000-byte custom parameter to increase the length of the CHLO.
+ constexpr auto kCustomParameterId =
+ static_cast<TransportParameters::TransportParameterId>(0xff33);
+ std::string kCustomParameterValue(2000, '-');
+ client_config.custom_transport_parameters_to_send()[kCustomParameterId] =
+ kCustomParameterValue;
+ std::vector<std::unique_ptr<QuicReceivedPacket>> packets =
+ GetFirstFlightOfPackets(version_, client_config, server_connection_id);
+ ASSERT_EQ(packets.size(), 2u);
+ if (add_reordering) {
+ std::swap(packets[0], packets[1]);
+ }
+
+ // Processing the first packet should not create a new session.
+ EXPECT_CALL(*dispatcher_,
+ ShouldCreateOrBufferPacketForConnection(
+ ReceivedPacketInfoConnectionIdEquals(server_connection_id)));
+ ProcessReceivedPacket(std::move(packets[0]), client_address, version_,
+ server_connection_id);
+
+ EXPECT_EQ(dispatcher_->session_map().size(), 0u)
+ << "No session should be created before the rest of the CHLO arrives.";
+
+ // Processing the second packet should create the new session.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(server_connection_id, client_address,
+ Eq(ExpectedAlpn()), _))
+ .WillOnce(Return(ByMove(CreateSession(
+ dispatcher_.get(), config_, server_connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_))));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(2);
+
+ ProcessReceivedPacket(std::move(packets[1]), client_address, version_,
+ server_connection_id);
+ EXPECT_EQ(dispatcher_->session_map().size(), 1u);
+}
+
+TEST_P(QuicDispatcherTestAllVersions, TlsMultiPacketClientHello) {
+ TestTlsMultiPacketClientHello(/*add_reordering=*/false);
+}
+
+TEST_P(QuicDispatcherTestAllVersions, TlsMultiPacketClientHelloWithReordering) {
+ TestTlsMultiPacketClientHello(/*add_reordering=*/true);
+}
+
TEST_P(QuicDispatcherTestAllVersions, ProcessPackets) {
QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);