Allow client to buffer Handshake packets when the server Initial is delayed, even if the connection ID changes.

PiperOrigin-RevId: 478100528
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index bf50a86..0e71a7f 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -214,6 +214,20 @@
           header.long_packet_type == RETRY);
 }
 
+// Due to a lost Initial packet, a Handshake packet might use a new connection
+// ID we haven't seen before. We shouldn't update the connection ID based on
+// this, but should buffer the packet in case it works out.
+bool NewServerConnectionIdMightBeValid(const QuicPacketHeader& header,
+                                       Perspective perspective,
+                                       bool connection_id_already_replaced) {
+  return perspective == Perspective::IS_CLIENT &&
+         header.form == IETF_QUIC_LONG_HEADER_PACKET &&
+         header.version.IsKnown() &&
+         header.version.AllowsVariableLengthConnectionIds() &&
+         header.long_packet_type == HANDSHAKE &&
+         !connection_id_already_replaced;
+}
+
 CongestionControlType GetDefaultCongestionControlType() {
   if (GetQuicReloadableFlag(quic_default_to_bbr_v2)) {
     return kBBRv2;
@@ -1015,6 +1029,11 @@
     return true;
   }
 
+  if (NewServerConnectionIdMightBeValid(
+          header, perspective_, server_connection_id_replaced_by_initial_)) {
+    return true;
+  }
+
   return false;
 }
 
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 8b96894..4d9c91d 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -15670,6 +15670,85 @@
   }
 }
 
+ACTION_P(InstallHandshakeKeys, conn) {
+  conn->SetEncrypter(ENCRYPTION_HANDSHAKE,
+                     std::make_unique<TaggingEncrypter>(0x02));
+  conn->InstallDecrypter(ENCRYPTION_HANDSHAKE,
+                         std::make_unique<StrictTaggingDecrypter>(0x02));
+  conn->SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+}
+
+TEST_P(QuicConnectionTest, ServerConnectionIdChangeWithLostInitial) {
+  if (!connection_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  // Call SetFromConfig so that the undecrypted packet buffer size is
+  // initialized above zero.
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(1);
+  QuicConfig config;
+  connection_.SetFromConfig(config);
+
+  // Send Client Initial.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+  connection_.SendCryptoStreamData();
+
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  // Server Handshake Packet Arrives with new connection ID.
+  QuicConnectionId old_id = connection_id_;
+  connection_id_ = TestConnectionId(2);
+  peer_creator_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                             std::make_unique<TaggingEncrypter>(0x02));
+  ProcessCryptoPacketAtLevel(0, ENCRYPTION_HANDSHAKE);
+  // Packet is buffered.
+  EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 1u);
+  EXPECT_EQ(connection_.connection_id(), old_id);
+
+  // Pretend the server Initial packet will yield the Handshake keys.
+  EXPECT_CALL(visitor_, OnCryptoFrame(_))
+      .Times(2)
+      .WillOnce(InstallHandshakeKeys(&connection_))
+      .WillOnce(Return());
+  ProcessCryptoPacketAtLevel(0, ENCRYPTION_INITIAL);
+  // Two packets processed, connection ID changed.
+  EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 0u);
+  EXPECT_EQ(connection_.connection_id(), connection_id_);
+}
+
+TEST_P(QuicConnectionTest, ServerConnectionIdChangeTwiceWithLostInitial) {
+  if (!connection_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  // Call SetFromConfig so that the undecrypted packet buffer size is
+  // initialized above zero.
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(1);
+  QuicConfig config;
+  connection_.SetFromConfig(config);
+
+  // Send Client Initial.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+  connection_.SendCryptoStreamData();
+
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  // Server Handshake Packet Arrives with new connection ID.
+  QuicConnectionId old_id = connection_id_;
+  connection_id_ = TestConnectionId(2);
+  peer_creator_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                             std::make_unique<TaggingEncrypter>(0x02));
+  ProcessCryptoPacketAtLevel(0, ENCRYPTION_HANDSHAKE);
+  // Packet is buffered.
+  EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 1u);
+  EXPECT_EQ(connection_.connection_id(), old_id);
+
+  // Pretend the server Initial packet will yield the Handshake keys.
+  EXPECT_CALL(visitor_, OnCryptoFrame(_))
+      .WillOnce(InstallHandshakeKeys(&connection_));
+  connection_id_ = TestConnectionId(1);
+  ProcessCryptoPacketAtLevel(0, ENCRYPTION_INITIAL);
+  // Handshake packet discarded because there's a different connection ID.
+  EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 0u);
+  EXPECT_EQ(connection_.connection_id(), connection_id_);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic