Optimize client handling of server preferred address:
1) Start validating server preferred address on handshake complete (rather than confirmed)
2) While validation is pending, duplicate coalesced packets (up to 5) on both paths.
This is behind client_connection_option SPA2.

PiperOrigin-RevId: 498291601
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 314f491..874eeb1 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1463,7 +1463,7 @@
   }
 
   // Receive server preferred address.
-  void ServerPreferredAddressInit() {
+  void ServerPreferredAddressInit(QuicConfig& config) {
     ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective());
     ASSERT_TRUE(version().HasIetfQuicFrames());
     ASSERT_TRUE(connection_.self_address().host().IsIPv6());
@@ -1485,10 +1485,9 @@
     ProcessFramePacketAtLevel(1, QuicFrame(&frame), ENCRYPTION_INITIAL);
     // Discard INITIAL key.
     connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
 
-    QuicConfig config;
     config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
-    config.SetClientConnectionOptions(QuicTagVector{kSPAD});
     QuicConfigPeer::SetReceivedStatelessResetToken(&config,
                                                    kTestStatelessResetToken);
     QuicConfigPeer::SetReceivedAlternateServerAddress(&config,
@@ -15987,7 +15986,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16065,7 +16066,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16129,7 +16132,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16191,6 +16196,128 @@
       connection_.GetStats().failed_to_validate_server_preferred_address);
 }
 
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress) {
+  if (!connection_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  QuicIpAddress host;
+  host.FromString("2604:31c0::");
+  const QuicSocketAddress kServerPreferredAddress(host, 443);
+  const QuicSocketAddress kNewSelfAddress =
+      QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+  TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+                            kServerPreferredAddress))
+      .WillOnce(Return(
+          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+  // Verify the packet is sent on both paths.
+  EXPECT_FALSE(writer_->stream_frames().empty());
+  EXPECT_FALSE(new_writer.stream_frames().empty());
+
+  // Verify packet duplication stops on handshake confirmed.
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  SendPing();
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress2) {
+  if (!connection_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  QuicIpAddress host;
+  host.FromString("2604:31c0::");
+  const QuicSocketAddress kServerPreferredAddress(host, 443);
+  const QuicSocketAddress kNewSelfAddress =
+      QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+  TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+                            kServerPreferredAddress))
+      .WillOnce(Return(
+          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+  // Verify the packet is sent on both paths.
+  EXPECT_FALSE(writer_->stream_frames().empty());
+  EXPECT_FALSE(new_writer.stream_frames().empty());
+
+  // Simluate path validation times out.
+  for (size_t i = 0; i < QuicPathValidator::kMaxRetryTimes + 1; ++i) {
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
+    static_cast<TestAlarmFactory::TestAlarm*>(
+        QuicPathValidatorPeer::retry_timer(
+            QuicConnectionPeer::path_validator(&connection_)))
+        ->Fire();
+  }
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  // Verify packet duplication stops if there is no pending validation.
+  SendPing();
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, MaxDuplicatedPacketsSentToServerPreferredAddress) {
+  if (!connection_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  QuicIpAddress host;
+  host.FromString("2604:31c0::");
+  const QuicSocketAddress kServerPreferredAddress(host, 443);
+  const QuicSocketAddress kNewSelfAddress =
+      QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+  TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+                            kServerPreferredAddress))
+      .WillOnce(Return(
+          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  size_t write_limit = writer_->packets_write_attempts();
+  size_t new_write_limit = new_writer.packets_write_attempts();
+  for (size_t i = 0; i < kMaxDuplicatedPacketsSentToServerPreferredAddress;
+       ++i) {
+    connection_.SendStreamDataWithString(3, "foo", i * 3, NO_FIN);
+    // Verify the packet is sent on both paths.
+    ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+    ASSERT_EQ(new_write_limit + 1, new_writer.packets_write_attempts());
+    ++write_limit;
+    ++new_write_limit;
+    EXPECT_FALSE(writer_->stream_frames().empty());
+    EXPECT_FALSE(new_writer.stream_frames().empty());
+  }
+
+  // Verify packet duplication stops if duplication limit is hit.
+  SendPing();
+  ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+  ASSERT_EQ(new_write_limit, new_writer.packets_write_attempts());
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic