Set a minimum number of chaos protection in new protector

This simplifies testing whether chaos protection was performed since now we're guaranteed to get extra frames.

This CL also adds a new e2e test for chaos protection v2, which test ensures that multi-packet chaos protection works as expected with kyber enabled. In particular, it ensures that retransmissions behave correctly.

This change is only made in the new protector, which is behind a flag that is still enabled_blocked_by.

Protected by FLAGS_quic_reloadable_flag_quic_enable_new_chaos_protector.

PiperOrigin-RevId: 703637630
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 208fe89..a2dd544 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -6,6 +6,7 @@
 #include <array>
 #include <cstddef>
 #include <cstdint>
+#include <limits>
 #include <list>
 #include <memory>
 #include <optional>
@@ -23,6 +24,7 @@
 #include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/crypto/quic_client_session_cache.h"
 #include "quiche/quic/core/frames/quic_blocked_frame.h"
+#include "quiche/quic/core/frames/quic_crypto_frame.h"
 #include "quiche/quic/core/http/http_constants.h"
 #include "quiche/quic/core/http/quic_spdy_client_stream.h"
 #include "quiche/quic/core/http/quic_spdy_session.h"
@@ -37,7 +39,10 @@
 #include "quiche/quic/core/quic_dispatcher.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_interval_set.h"
 #include "quiche/quic/core/quic_packet_creator.h"
+#include "quiche/quic/core/quic_packet_number.h"
 #include "quiche/quic/core/quic_packet_writer.h"
 #include "quiche/quic/core/quic_packet_writer_wrapper.h"
 #include "quiche/quic/core/quic_packets.h"
@@ -48,6 +53,7 @@
 #include "quiche/quic/core/tls_client_handshaker.h"
 #include "quiche/quic/platform/api/quic_expect_bug.h"
 #include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_ip_address.h"
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/quic/platform/api/quic_socket_address.h"
 #include "quiche/quic/platform/api/quic_test.h"
@@ -76,6 +82,7 @@
 #include "quiche/quic/test_tools/quic_test_server.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
 #include "quiche/quic/test_tools/server_thread.h"
+#include "quiche/quic/test_tools/simple_quic_framer.h"
 #include "quiche/quic/test_tools/web_transport_test_tools.h"
 #include "quiche/quic/tools/quic_backend_response.h"
 #include "quiche/quic/tools/quic_memory_cache_backend.h"
@@ -289,6 +296,11 @@
         std::make_unique<QuicClientSessionCache>(),
         GetParam().event_loop->Create(QuicDefaultClock::Get()));
     client->SetUserAgentID(kTestUserAgentId);
+    if (enable_kyber_in_client_) {
+      std::vector<uint16_t> client_supported_groups = {
+          SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519};
+      client->SetPreferredGroups(client_supported_groups);
+    }
     client->UseWriter(writer);
     if (!pre_shared_key_client_.empty()) {
       client->client()->SetPreSharedKey(pre_shared_key_client_);
@@ -990,6 +1002,9 @@
     CreateClientWithWriter();
   }
 
+  void TestMultiPacketChaosProtection(int num_packets, bool drop_first_packet,
+                                      bool kyber = false);
+
   quiche::test::ScopedEnvironmentForThreads environment_;
   bool initialized_;
   // If true, the Initialize() function will create |client_| and starts to
@@ -1022,6 +1037,7 @@
   int override_client_connection_id_length_ = -1;
   uint8_t expected_server_connection_id_length_;
   bool enable_web_transport_ = false;
+  bool enable_kyber_in_client_ = false;
   std::vector<std::string> received_webtransport_unidirectional_streams_;
   bool use_preferred_address_ = false;
   QuicSocketAddress server_preferred_address_;
@@ -6557,35 +6573,233 @@
   server_thread_->Resume();
 }
 
-// Testing packet writer that makes a copy of the first sent packets before
-// sending them. Useful for tests that need access to sent packets.
-class CopyingPacketWriter : public PacketDroppingTestWriter {
+// Testing packet writer that parses initial packets and saves information
+// relevant to chaos protection.
+class ChaosPacketWriter : public PacketDroppingTestWriter {
  public:
-  explicit CopyingPacketWriter(int num_packets_to_copy)
-      : num_packets_to_copy_(num_packets_to_copy) {}
+  explicit ChaosPacketWriter(const ParsedQuicVersion& version,
+                             bool drop_first_initial_packet)
+      : framer_({version}),
+        drop_next_initial_packet_(drop_first_initial_packet) {
+    framer_.framer()->SetInitialObfuscators(TestConnectionId());
+  }
+
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
                           PerPacketOptions* options,
                           const QuicPacketWriterParams& params) override {
-    if (num_packets_to_copy_ > 0) {
-      num_packets_to_copy_--;
-      packets_.push_back(
-          QuicEncryptedPacket(buffer, buf_len, /*owns_buffer=*/false).Clone());
+    bool drop_packet = false;
+    QuicEncryptedPacket packet(buffer, buf_len);
+    if (framer_.ProcessPacket(packet)) {
+      if (framer_.header().form == IETF_QUIC_LONG_HEADER_PACKET &&
+          framer_.header().long_packet_type == INITIAL) {
+        auto initial_packet = std::make_unique<InitialPacketContents>();
+        for (const auto& frame : framer_.crypto_frames()) {
+          QuicInterval<QuicStreamOffset> interval(
+              frame->offset, frame->offset + frame->data_length);
+          initial_packet->crypto_data_intervals.Add(interval);
+          initial_packet->total_crypto_data_length += frame->data_length;
+        }
+        initial_packet->packet_number =
+            framer_.header().packet_number.ToUint64();
+        initial_packet->num_crypto_frames = framer_.crypto_frames().size();
+        initial_packet->num_padding_frames = framer_.padding_frames().size();
+        initial_packet->num_ping_frames = framer_.ping_frames().size();
+        if (drop_next_initial_packet_) {
+          drop_packet = true;
+          drop_next_initial_packet_ = false;
+          initial_packet->was_dropped = true;
+        }
+        initial_packets_.push_back(std::move(initial_packet));
+      }
+    }
+    if (drop_packet) {
+      return WriteResult(WRITE_STATUS_OK, buf_len);
     }
     return PacketDroppingTestWriter::WritePacket(buffer, buf_len, self_address,
                                                  peer_address, options, params);
   }
 
-  std::vector<std::unique_ptr<QuicEncryptedPacket>>& packets() {
-    return packets_;
+  struct InitialPacketContents {
+    uint64_t packet_number = std::numeric_limits<uint64_t>::max();
+    int num_crypto_frames = 0;
+    int num_padding_frames = 0;
+    int num_ping_frames = 0;
+    bool was_dropped = false;
+    QuicByteCount total_crypto_data_length = 0;
+    QuicIntervalSet<QuicStreamOffset> crypto_data_intervals;
+    QuicByteCount min_crypto_offset() const {
+      return crypto_data_intervals.SpanningInterval().min();
+    }
+    QuicByteCount max_crypto_data() const {
+      return crypto_data_intervals.SpanningInterval().max();
+    }
+  };
+
+  const std::vector<std::unique_ptr<InitialPacketContents>>& initial_packets() {
+    return initial_packets_;
   }
 
  private:
-  int num_packets_to_copy_;
-  std::vector<std::unique_ptr<QuicEncryptedPacket>> packets_;
+  SimpleQuicFramer framer_;
+  std::vector<std::unique_ptr<InitialPacketContents>> initial_packets_;
+  bool drop_next_initial_packet_;
 };
 
+TEST_P(EndToEndTest, KyberChaosProtection) {
+  TestMultiPacketChaosProtection(/*num_packets=*/2,
+                                 /*drop_first_packet=*/false,
+                                 /*kyber=*/true);
+}
+
+TEST_P(EndToEndTest, KyberChaosProtectionWithRetransmission) {
+  TestMultiPacketChaosProtection(/*num_packets=*/2,
+                                 /*drop_first_packet=*/true,
+                                 /*kyber=*/true);
+}
+
+TEST_P(EndToEndTest, TwoPacketChaosProtection) {
+  TestMultiPacketChaosProtection(/*num_packets=*/2,
+                                 /*drop_first_packet=*/false);
+}
+
+TEST_P(EndToEndTest, TwoPacketChaosProtectionWithRetransmission) {
+  TestMultiPacketChaosProtection(/*num_packets=*/2,
+                                 /*drop_first_packet=*/true);
+}
+
+TEST_P(EndToEndTest, ThreePacketChaosProtection) {
+  TestMultiPacketChaosProtection(/*num_packets=*/3,
+                                 /*drop_first_packet=*/false);
+}
+
+TEST_P(EndToEndTest, ThreePacketChaosProtectionWithRetransmission) {
+  TestMultiPacketChaosProtection(/*num_packets=*/3,
+                                 /*drop_first_packet=*/true);
+}
+
+void EndToEndTest::TestMultiPacketChaosProtection(int num_packets,
+                                                  bool drop_first_packet,
+                                                  bool kyber) {
+  if (!version_.HasIetfQuicFrames()) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  SetQuicReloadableFlag(quic_enable_new_chaos_protector, true);
+  // Setup test harness with a custom client writer.
+  connect_to_server_on_initialize_ = false;
+  int discard_length;
+  if (kyber) {
+    discard_length = 1216;
+    enable_kyber_in_client_ = true;
+  } else {
+    discard_length = 1000 * num_packets;
+    client_config_.SetDiscardLengthToSend(discard_length);
+  }
+  ASSERT_TRUE(Initialize());
+  auto copying_writer = new ChaosPacketWriter(version_, drop_first_packet);
+  delete client_writer_;
+  client_writer_ = copying_writer;
+  client_.reset(CreateQuicClient(client_writer_, /*connect=*/false));
+  client_->UseConnectionId(TestConnectionId());
+  client_->Connect();
+  MockableQuicClient* client = client_->client();
+  QuicConnection* client_connection = GetClientConnection();
+  client_writer_->Initialize(
+      QuicConnectionPeer::GetHelper(client_connection),
+      QuicConnectionPeer::GetAlarmFactory(client_connection),
+      std::make_unique<ClientDelegate>(client));
+  EXPECT_TRUE(client->connected());
+  // Make sure application data can be sent.
+  EXPECT_TRUE(SendSynchronousFooRequestAndCheckResponse());
+
+  // Make sure the first flight contains the entire client hello.
+  QuicIntervalSet<QuicStreamOffset> crypto_data_intervals;
+  int num_first_flight_packets = 0;
+  for (size_t i = 0; i < copying_writer->initial_packets().size(); ++i) {
+    if (copying_writer->initial_packets()[i]->crypto_data_intervals.Empty()) {
+      continue;
+    }
+    bool found = false;
+    for (const auto& interval :
+         copying_writer->initial_packets()[i]->crypto_data_intervals) {
+      if (!crypto_data_intervals.IsDisjoint(interval)) {
+        found = true;
+      }
+      crypto_data_intervals.Add(interval);
+    }
+    if (found) {
+      break;
+    }
+    num_first_flight_packets++;
+  }
+  EXPECT_EQ(num_first_flight_packets, num_packets);
+  EXPECT_EQ(crypto_data_intervals.Size(), 1u);
+  EXPECT_EQ(crypto_data_intervals.SpanningInterval().min(), 0u);
+  EXPECT_GT(crypto_data_intervals.SpanningInterval().max(), discard_length);
+
+  ASSERT_GE(copying_writer->initial_packets().size(), 2u);
+  // First packet contains the start and end of the client hello.
+  auto& packet1 = copying_writer->initial_packets()[0];
+  EXPECT_EQ(packet1->was_dropped, drop_first_packet);
+  EXPECT_EQ(packet1->packet_number, 1u);
+  EXPECT_GE(packet1->num_crypto_frames, 3u);
+  EXPECT_GE(packet1->num_ping_frames, 2u);
+  EXPECT_GE(packet1->num_padding_frames, 1u);
+  EXPECT_EQ(packet1->min_crypto_offset(), 0u);
+  EXPECT_GE(packet1->max_crypto_data(), discard_length);
+  EXPECT_GE(packet1->total_crypto_data_length, 500u);
+  // Subsequent packets contain the middle of the client hello.
+  auto& packet2 = copying_writer->initial_packets()[1];
+  EXPECT_FALSE(packet2->was_dropped);
+  EXPECT_EQ(packet2->packet_number, 2u);
+  if (num_packets == 2) {
+    EXPECT_GE(packet2->num_crypto_frames, 3u);
+    EXPECT_GE(packet2->num_ping_frames, 2u);
+  } else {
+    EXPECT_GE(packet2->num_crypto_frames, 1u);
+  }
+  EXPECT_GT(packet2->min_crypto_offset(), 0u);
+  EXPECT_LT(packet2->max_crypto_data(), discard_length);
+  EXPECT_GE(packet2->total_crypto_data_length, 500u);
+  if (num_packets >= 3) {
+    ASSERT_GE(copying_writer->initial_packets().size(), 3u);
+    auto& packet3 = copying_writer->initial_packets()[2];
+    EXPECT_FALSE(packet3->was_dropped);
+    EXPECT_EQ(packet3->packet_number, 3u);
+    EXPECT_GE(packet3->num_crypto_frames, 3u);
+    EXPECT_GE(packet3->num_ping_frames, 2u);
+    EXPECT_GE(packet3->num_padding_frames, 1u);
+    EXPECT_GT(packet3->min_crypto_offset(), 0u);
+    EXPECT_LT(packet3->max_crypto_data(), discard_length);
+    EXPECT_GE(packet3->total_crypto_data_length, 500u);
+  }
+  if (!drop_first_packet) {
+    return;
+  }
+  // Retransmission of the first packet contains the start and end of the client
+  // hello. This validates that the multiple crypto frames are retransmitted in
+  // the same packet, without the packet creator flushing between them.
+  bool found_retransmission = false;
+  for (size_t i = num_packets; i < copying_writer->initial_packets().size();
+       ++i) {
+    // Iterate on subsequent packets until we find the one that contains the
+    // retransmission of the crypto frame that contains the start of the client
+    // hello.
+    auto& packet = copying_writer->initial_packets()[i];
+    if (packet->num_crypto_frames == 0 || packet->min_crypto_offset() != 0) {
+      continue;
+    }
+    found_retransmission = true;
+    EXPECT_FALSE(packet->was_dropped);
+    EXPECT_GE(packet->num_crypto_frames, 2u);
+    EXPECT_GE(packet->max_crypto_data(), discard_length);
+    EXPECT_GE(packet->total_crypto_data_length, 500u);
+  }
+  EXPECT_TRUE(found_retransmission);
+}
+
 TEST_P(EndToEndTest, KeyUpdateInitiatedByClient) {
   if (!version_.UsesTls()) {
     // Key Update is only supported in TLS handshake.
diff --git a/quiche/quic/core/quic_chaos_protector.cc b/quiche/quic/core/quic_chaos_protector.cc
index 0a1d3d1..8295730 100644
--- a/quiche/quic/core/quic_chaos_protector.cc
+++ b/quiche/quic/core/quic_chaos_protector.cc
@@ -379,9 +379,12 @@
       static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
           crypto_buffer_offset_ + crypto_data_length_, crypto_data_length_));
   // Pick a random number of CRYPTO frames to add.
+  constexpr uint64_t kMinAddedCryptoFrames = 2;
   constexpr uint64_t kMaxAddedCryptoFrames = 10;
   const uint64_t num_added_crypto_frames =
-      random_->InsecureRandUint64() % (kMaxAddedCryptoFrames + 1);
+      kMinAddedCryptoFrames +
+      random_->InsecureRandUint64() %
+          (kMaxAddedCryptoFrames + 1 - kMinAddedCryptoFrames);
   for (uint64_t i = 0; i < num_added_crypto_frames; i++) {
     if (remaining_padding_bytes_ < max_overhead_of_adding_a_crypto_frame) {
       break;
@@ -430,10 +433,12 @@
   if (remaining_padding_bytes_ == 0) {
     return;
   }
+  constexpr uint64_t kMinAddedPingFrames = 2;
   constexpr uint64_t kMaxAddedPingFrames = 10;
-  const uint64_t num_ping_frames =
-      random_->InsecureRandUint64() %
-      std::min<uint64_t>(kMaxAddedPingFrames, remaining_padding_bytes_);
+  const uint64_t num_ping_frames = std::min<uint64_t>(
+      kMinAddedPingFrames + random_->InsecureRandUint64() %
+                                (kMaxAddedPingFrames + 1 - kMinAddedPingFrames),
+      remaining_padding_bytes_);
   for (uint64_t i = 0; i < num_ping_frames; i++) {
     frames_.push_back(QuicFrame(QuicPingFrame()));
   }
diff --git a/quiche/quic/core/quic_chaos_protector_test.cc b/quiche/quic/core/quic_chaos_protector_test.cc
index fb87aa1..b090916 100644
--- a/quiche/quic/core/quic_chaos_protector_test.cc
+++ b/quiche/quic/core/quic_chaos_protector_test.cc
@@ -434,41 +434,43 @@
 
 TEST_P(QuicChaosProtectorTest, Main) {
   BuildEncryptAndParse();
-  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 6u);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, 0u);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length, 1u);
-  ASSERT_EQ(validation_framer_.ping_frames().size(), 3u);
-  ASSERT_EQ(validation_framer_.padding_frames().size(), 7u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 5u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 9u);
   EXPECT_EQ(validation_framer_.padding_frames()[0].num_padding_bytes, 3);
 }
 
 TEST_P(QuicChaosProtectorTest, DifferentRandom) {
   random_.ResetBase(4);
   BuildEncryptAndParse();
-  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
-  ASSERT_EQ(validation_framer_.ping_frames().size(), 4u);
-  ASSERT_EQ(validation_framer_.padding_frames().size(), 8u);
+  EXPECT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 6u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 8u);
 }
 
 TEST_P(QuicChaosProtectorTest, RandomnessZero) {
   random_.ResetBase(0);
   BuildEncryptAndParse();
-  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
-  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, 1);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length,
-            crypto_data_length_);
-  ASSERT_EQ(validation_framer_.ping_frames().size(), 0u);
-  ASSERT_EQ(validation_framer_.padding_frames().size(), 1u);
+            crypto_data_length_ - 1);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->data_length, 1);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 1u);
 }
 
 TEST_P(QuicChaosProtectorTest, Offset) {
   ResetOffset(123);
   BuildEncryptAndParse();
-  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 6u);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length, 1u);
-  ASSERT_EQ(validation_framer_.ping_frames().size(), 3u);
-  ASSERT_EQ(validation_framer_.padding_frames().size(), 7u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 5u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 8u);
   EXPECT_EQ(validation_framer_.padding_frames()[0].num_padding_bytes, 3);
 }
 
@@ -476,12 +478,14 @@
   ResetOffset(123);
   random_.ResetBase(0);
   BuildEncryptAndParse();
-  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
-  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_ + 1);
   EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length,
-            crypto_data_length_);
-  ASSERT_EQ(validation_framer_.ping_frames().size(), 0u);
-  ASSERT_EQ(validation_framer_.padding_frames().size(), 1u);
+            crypto_data_length_ - 1);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->data_length, 1);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 1u);
 }
 
 TEST_P(QuicChaosProtectorTest, ZeroRemainingBytesAfterSplit) {
@@ -505,17 +509,17 @@
   random_.ResetBase(38);
   BuildEncryptAndParse();
   EXPECT_EQ(validation_framer_.crypto_frames().size(), 6u);
-  EXPECT_EQ(validation_framer_.ping_frames().size(), 8u);
-  EXPECT_EQ(validation_framer_.padding_frames().size(), 3u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 4u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 4u);
 }
 
 TEST_P(QuicChaosProtectorTest, ReorderedCryptoCryptoAndPadding) {
   input_frames_pattern_ = InputFramesPattern::kReorderedCryptoCryptoAndPadding;
   random_.ResetBase(38);
   BuildEncryptAndParse();
-  EXPECT_EQ(validation_framer_.crypto_frames().size(), 7u);
-  EXPECT_EQ(validation_framer_.ping_frames().size(), 8u);
-  EXPECT_EQ(validation_framer_.padding_frames().size(), 3u);
+  EXPECT_EQ(validation_framer_.crypto_frames().size(), 6u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 4u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 4u);
 }
 
 TEST_P(QuicChaosProtectorTest, AckCryptoAndPadding) {
@@ -523,8 +527,8 @@
   random_.ResetBase(37);
   BuildEncryptAndParse();
   EXPECT_EQ(validation_framer_.crypto_frames().size(), 3u);
-  EXPECT_EQ(validation_framer_.ping_frames().size(), 7u);
-  EXPECT_EQ(validation_framer_.padding_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 3u);
+  EXPECT_EQ(validation_framer_.padding_frames().size(), 4u);
   ASSERT_EQ(validation_framer_.ack_frames().size(), 1u);
   // Chaos protector does not insert padding before ACK, or recorder ACK frames.
   EXPECT_EQ(validation_framer_.frame_types()[0], ACK_FRAME);
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index da3e87d..c9bc761 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -10243,7 +10243,7 @@
   EXPECT_EQ(connection_.max_packet_length(), writer_->last_packet_size());
 
   // Verify packet process.
-  EXPECT_EQ(1u, writer_->crypto_frames().size());
+  EXPECT_LE(1u, writer_->crypto_frames().size());
   EXPECT_EQ(0u, writer_->stream_frames().size());
   // Verify there is coalesced packet.
   EXPECT_NE(nullptr, writer_->coalesced_packet());
@@ -10706,7 +10706,7 @@
   // RETRY.
   if (GetParam().ack_response == AckResponse::kImmediate) {
     EXPECT_EQ(2u, writer_->packets_write_attempts());
-    EXPECT_EQ(1u, writer_->framer()->crypto_frames().size());
+    EXPECT_LE(1u, writer_->framer()->crypto_frames().size());
   }
 }
 
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc
index f4e32e9..94baf89 100644
--- a/quiche/quic/core/quic_packet_creator_test.cc
+++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -1412,9 +1412,9 @@
   EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_, _));
   EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
   if (enabled) {
-    EXPECT_CALL(framer_visitor_, OnCryptoFrame(_)).Times(AtLeast(2));
+    EXPECT_CALL(framer_visitor_, OnCryptoFrame(_)).Times(AtLeast(3));
     EXPECT_CALL(framer_visitor_, OnPaddingFrame(_)).Times(AtLeast(2));
-    EXPECT_CALL(framer_visitor_, OnPingFrame(_)).Times(AtLeast(1));
+    EXPECT_CALL(framer_visitor_, OnPingFrame(_)).Times(AtLeast(2));
   } else {
     EXPECT_CALL(framer_visitor_, OnCryptoFrame(_)).Times(1);
     EXPECT_CALL(framer_visitor_, OnPaddingFrame(_)).Times(1);
diff --git a/quiche/quic/test_tools/quic_test_client.cc b/quiche/quic/test_tools/quic_test_client.cc
index 1513064..4c011a6 100644
--- a/quiche/quic/test_tools/quic_test_client.cc
+++ b/quiche/quic/test_tools/quic_test_client.cc
@@ -377,6 +377,11 @@
   client_->SetUserAgentID(user_agent_id);
 }
 
+void QuicTestClient::SetPreferredGroups(
+    const std::vector<uint16_t>& preferred_groups) {
+  client_->SetPreferredGroups(preferred_groups);
+}
+
 int64_t QuicTestClient::SendRequest(const std::string& uri) {
   quiche::HttpHeaderBlock headers;
   if (!PopulateHeaderBlockFromUrl(uri, &headers)) {
diff --git a/quiche/quic/test_tools/quic_test_client.h b/quiche/quic/test_tools/quic_test_client.h
index 5091e23..44c5210 100644
--- a/quiche/quic/test_tools/quic_test_client.h
+++ b/quiche/quic/test_tools/quic_test_client.h
@@ -149,6 +149,9 @@
   // Sets the |user_agent_id| of the |client_|.
   void SetUserAgentID(const std::string& user_agent_id);
 
+  // Sets the preferred TLS key exchange groups of the |client_|.
+  void SetPreferredGroups(const std::vector<uint16_t>& preferred_groups);
+
   // Wraps data in a quic packet and sends it.
   int64_t SendData(const std::string& data, bool last_data);
   // As above, but |delegate| will be notified when |data| is ACKed.
diff --git a/quiche/quic/tools/quic_client_base.h b/quiche/quic/tools/quic_client_base.h
index c707176..550e546 100644
--- a/quiche/quic/tools/quic_client_base.h
+++ b/quiche/quic/tools/quic_client_base.h
@@ -190,6 +190,10 @@
     crypto_config_.set_user_agent_id(user_agent_id);
   }
 
+  void SetPreferredGroups(const std::vector<uint16_t>& preferred_groups) {
+    crypto_config_.set_preferred_groups(preferred_groups);
+  }
+
   void SetTlsSignatureAlgorithms(std::string signature_algorithms) {
     crypto_config_.set_tls_signature_algorithms(
         std::move(signature_algorithms));