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);