Refactor QuicChaosProtector

This CL refactors the chaos protector to have it ingest an array of QuicFrames instead of a single QuicCryptoFrame and padding. This CL does not change behavior for existing code, it prepares it for a future CL that will add multi-packet chaos protection.

Portions of this change were written by wub@, in particular:
- Added code to prevent reordering of ACK frames, to minimize impact on congestion control.
- Added unit tests for the cases when input frames are `[crypto, crypto, padding]` or `[ack, crypto, padding]`.

Protected by FLAGS_quic_reloadable_flag_quic_enable_new_chaos_protector.

PiperOrigin-RevId: 696443185
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 09bf653..3217e69 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -33,6 +33,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_ecn_in_first_ack, false, false, "When true, reports ECN in counts in the ACK of the a client initial that goes in the buffered packet store.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_disable_resumption, true, true, "If true, disable resumption when receiving NRES connection option.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_mtu_discovery_at_server, false, false, "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_new_chaos_protector, false, false, "If true, use new refactored QuicChaosProtector implementation.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_server_on_wire_ping, true, true, "If true, enable server retransmittable on wire PING.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_version_rfcv2, false, false, "When true, support RFC9369.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_fix_timeouts, true, true, "If true, postpone setting handshake timeout to infinite to handshake complete.")
diff --git a/quiche/quic/core/quic_chaos_protector.cc b/quiche/quic/core/quic_chaos_protector.cc
index 6760df4..f69c4ef 100644
--- a/quiche/quic/core/quic_chaos_protector.cc
+++ b/quiche/quic/core/quic_chaos_protector.cc
@@ -22,8 +22,10 @@
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream_frame_data_producer.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/simple_buffer_allocator.h"
 
 namespace quic {
 
@@ -222,4 +224,275 @@
   return length;
 }
 
+// End of old code, start of new code.
+
+QuicChaosProtectorNew::QuicChaosProtectorNew(size_t packet_size,
+                                             EncryptionLevel level,
+                                             QuicFramer* framer,
+                                             QuicRandom* random)
+    : packet_size_(packet_size),
+      level_(level),
+      framer_(framer),
+      random_(random) {
+  QUICHE_DCHECK_NE(framer_, nullptr);
+  QUICHE_DCHECK_NE(framer_->data_producer(), nullptr);
+  QUICHE_DCHECK_NE(random_, nullptr);
+}
+
+bool QuicChaosProtectorNew::IngestFrames(const QuicFrames& frames) {
+  bool has_crypto_frame = false;
+  bool has_padding_frame = false;
+  QuicByteCount max_crypto_data;
+  for (const QuicFrame& frame : frames) {
+    if (frame.type == CRYPTO_FRAME) {
+      if (level_ != frame.crypto_frame->level) {
+        QUIC_BUG(chaos encryption level)
+            << level_ << " != " << frame.crypto_frame->level;
+        return false;
+      }
+      if (!has_crypto_frame) {
+        crypto_data_length_ = frame.crypto_frame->data_length;
+        crypto_buffer_offset_ = frame.crypto_frame->offset;
+        max_crypto_data = crypto_buffer_offset_ + crypto_data_length_;
+        QUIC_LOG(ERROR) << "ds33 first max_crypto_data = " << max_crypto_data
+                        << " offset = " << crypto_buffer_offset_
+                        << " data_length = " << crypto_data_length_;
+      } else {
+        crypto_buffer_offset_ =
+            std::min(crypto_buffer_offset_, frame.crypto_frame->offset);
+        max_crypto_data =
+            std::max(max_crypto_data, frame.crypto_frame->offset +
+                                          frame.crypto_frame->data_length);
+        crypto_data_length_ = max_crypto_data - crypto_buffer_offset_;
+        QUIC_LOG(ERROR) << "ds33 subsequent max_crypto_data = "
+                        << max_crypto_data
+                        << " offset = " << crypto_buffer_offset_
+                        << " data_length = " << crypto_data_length_;
+      }
+      has_crypto_frame = true;
+      frames_.push_back(QuicFrame(new QuicCryptoFrame(*frame.crypto_frame)));
+      continue;
+    }
+    if (frame.type == PADDING_FRAME) {
+      if (has_padding_frame) {
+        return false;
+      }
+      has_padding_frame = true;
+      remaining_padding_bytes_ = frame.padding_frame.num_padding_bytes;
+      if (remaining_padding_bytes_ <= 0) {
+        // Do not perform chaos protection if we do not have a known number of
+        // padding bytes to work with.
+        return false;
+      }
+      continue;
+    }
+    // Copy any other frames unmodified. Note that the buffer allocator is
+    // only used for DATAGRAM frames, and those aren't used here, so the
+    // buffer allocator is never actually used.
+    frames_.push_back(
+        CopyQuicFrame(quiche::SimpleBufferAllocator::Get(), frame));
+  }
+  return has_crypto_frame && has_padding_frame;
+}
+
+QuicChaosProtectorNew::~QuicChaosProtectorNew() { DeleteFrames(&frames_); }
+
+std::optional<size_t> QuicChaosProtectorNew::BuildDataPacket(
+    const QuicPacketHeader& header, const QuicFrames& frames, char* buffer) {
+  if (!IngestFrames(frames)) {
+    QUIC_DVLOG(1) << "Failed to ingest frames";
+    return std::nullopt;
+  }
+  if (!CopyCryptoDataToLocalBuffer()) {
+    QUIC_DVLOG(1) << "Failed to copy crypto data to local buffer";
+    return std::nullopt;
+  }
+  SplitCryptoFrame();
+  AddPingFrames();
+  SpreadPadding();
+  ReorderFrames();
+  return BuildPacket(header, buffer);
+}
+
+WriteStreamDataResult QuicChaosProtectorNew::WriteStreamData(
+    QuicStreamId id, QuicStreamOffset offset, QuicByteCount data_length,
+    QuicDataWriter* /*writer*/) {
+  QUIC_BUG(chaos stream) << "This should never be called; id " << id
+                         << " offset " << offset << " data_length "
+                         << data_length;
+  return STREAM_MISSING;
+}
+
+bool QuicChaosProtectorNew::WriteCryptoData(EncryptionLevel level,
+                                            QuicStreamOffset offset,
+                                            QuicByteCount data_length,
+                                            QuicDataWriter* writer) {
+  if (level != level_) {
+    QUIC_BUG(chaos bad level) << "Unexpected " << level << " != " << level_;
+    return false;
+  }
+  // This is `offset + data_length > buffer_offset_ + buffer_length_`
+  // but with integer overflow protection.
+  if (offset < crypto_buffer_offset_ || data_length > crypto_data_length_ ||
+      offset - crypto_buffer_offset_ > crypto_data_length_ - data_length) {
+    QUIC_BUG(chaos bad lengths)
+        << "Unexpected buffer_offset_ " << crypto_buffer_offset_ << " offset "
+        << offset << " buffer_length_ " << crypto_data_length_
+        << " data_length " << data_length;
+    return false;
+  }
+  writer->WriteBytes(&crypto_data_buffer_[offset - crypto_buffer_offset_],
+                     data_length);
+  return true;
+}
+
+bool QuicChaosProtectorNew::CopyCryptoDataToLocalBuffer() {
+  size_t frame_size = QuicDataWriter::GetVarInt62Len(crypto_buffer_offset_) +
+                      QuicDataWriter::GetVarInt62Len(crypto_data_length_) +
+                      crypto_data_length_;
+  crypto_frame_buffer_ = std::make_unique<char[]>(frame_size);
+  // We use |framer_| to serialize the CRYPTO frame in order to extract its
+  // data from the crypto data producer. This ensures that we reuse the
+  // usual serialization code path, but has the downside that we then need to
+  // parse the offset and length in order to skip over those fields.
+  QuicDataWriter writer(frame_size, crypto_frame_buffer_.get());
+  QuicCryptoFrame crypto_frame(level_, crypto_buffer_offset_,
+                               crypto_data_length_);
+  if (!framer_->AppendCryptoFrame(crypto_frame, &writer)) {
+    QUIC_BUG(chaos write crypto data);
+    return false;
+  }
+  QuicDataReader reader(crypto_frame_buffer_.get(), writer.length());
+  uint64_t parsed_offset, parsed_length;
+  if (!reader.ReadVarInt62(&parsed_offset) ||
+      !reader.ReadVarInt62(&parsed_length)) {
+    QUIC_BUG(chaos parse crypto frame);
+    return false;
+  }
+
+  absl::string_view crypto_data = reader.ReadRemainingPayload();
+  crypto_data_buffer_ = crypto_data.data();
+
+  QUICHE_DCHECK_EQ(parsed_offset, crypto_buffer_offset_);
+  QUICHE_DCHECK_EQ(parsed_length, crypto_data_length_);
+  QUICHE_DCHECK_EQ(parsed_length, crypto_data.length());
+
+  return true;
+}
+
+void QuicChaosProtectorNew::SplitCryptoFrame() {
+  const int max_overhead_of_adding_a_crypto_frame =
+      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 kMaxAddedCryptoFrames = 10;
+  const uint64_t num_added_crypto_frames =
+      random_->InsecureRandUint64() % (kMaxAddedCryptoFrames + 1);
+  for (uint64_t i = 0; i < num_added_crypto_frames; i++) {
+    if (remaining_padding_bytes_ < max_overhead_of_adding_a_crypto_frame) {
+      break;
+    }
+    // Pick a random frame and split it by shrinking the picked frame and
+    // moving the second half of its data to a new frame that is then appended
+    // to |frames|.
+    size_t frame_to_split_index =
+        random_->InsecureRandUint64() % frames_.size();
+    // Only split CRYPTO frames.
+    if (frames_[frame_to_split_index].type != CRYPTO_FRAME) {
+      continue;
+    }
+    QuicCryptoFrame* frame_to_split =
+        frames_[frame_to_split_index].crypto_frame;
+    if (frame_to_split->data_length <= 1) {
+      continue;
+    }
+    const int frame_to_split_old_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            frame_to_split->offset, frame_to_split->data_length));
+    const QuicPacketLength frame_to_split_new_data_length =
+        1 + (random_->InsecureRandUint64() % (frame_to_split->data_length - 1));
+    const QuicPacketLength new_frame_data_length =
+        frame_to_split->data_length - frame_to_split_new_data_length;
+    const QuicStreamOffset new_frame_offset =
+        frame_to_split->offset + frame_to_split_new_data_length;
+    frame_to_split->data_length -= new_frame_data_length;
+    frames_.push_back(QuicFrame(
+        new QuicCryptoFrame(level_, new_frame_offset, new_frame_data_length)));
+    const int frame_to_split_new_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            frame_to_split->offset, frame_to_split->data_length));
+    const int new_frame_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            new_frame_offset, new_frame_data_length));
+    QUICHE_DCHECK_LE(frame_to_split_new_overhead, frame_to_split_old_overhead);
+    // Readjust padding based on increased overhead.
+    remaining_padding_bytes_ -= new_frame_overhead;
+    remaining_padding_bytes_ -= frame_to_split_new_overhead;
+    remaining_padding_bytes_ += frame_to_split_old_overhead;
+  }
+}
+
+void QuicChaosProtectorNew::AddPingFrames() {
+  if (remaining_padding_bytes_ == 0) {
+    return;
+  }
+  constexpr uint64_t kMaxAddedPingFrames = 10;
+  const uint64_t num_ping_frames =
+      random_->InsecureRandUint64() %
+      std::min<uint64_t>(kMaxAddedPingFrames, remaining_padding_bytes_);
+  for (uint64_t i = 0; i < num_ping_frames; i++) {
+    frames_.push_back(QuicFrame(QuicPingFrame()));
+  }
+  remaining_padding_bytes_ -= static_cast<int>(num_ping_frames);
+}
+
+void QuicChaosProtectorNew::ReorderFrames() {
+  // Walk the array backwards and swap each frame with a random earlier one.
+  for (size_t i = frames_.size() - 1; i > 0; i--) {
+    quic::QuicFrame& lhs = frames_[i];
+    quic::QuicFrame& rhs = frames_[random_->InsecureRandUint64() % (i + 1)];
+    // Do not swap ACK frames to minimize impact on congestion control.
+    if (lhs.type != ACK_FRAME && rhs.type != ACK_FRAME) {
+      std::swap(lhs, rhs);
+    }
+  }
+}
+
+void QuicChaosProtectorNew::SpreadPadding() {
+  for (auto it = frames_.begin(); it != frames_.end(); ++it) {
+    const int padding_bytes_in_this_frame =
+        random_->InsecureRandUint64() % (remaining_padding_bytes_ + 1);
+    if (padding_bytes_in_this_frame <= 0) {
+      continue;
+    }
+    // Do not add PADDING before ACK to minimize impact on congestion control.
+    if (it->type == ACK_FRAME) {
+      continue;
+    }
+    it = frames_.insert(
+        it, QuicFrame(QuicPaddingFrame(padding_bytes_in_this_frame)));
+    ++it;  // Skip over the padding frame we just added.
+    remaining_padding_bytes_ -= padding_bytes_in_this_frame;
+  }
+  if (remaining_padding_bytes_ > 0) {
+    frames_.push_back(QuicFrame(QuicPaddingFrame(remaining_padding_bytes_)));
+  }
+}
+
+std::optional<size_t> QuicChaosProtectorNew::BuildPacket(
+    const QuicPacketHeader& header, char* buffer) {
+  QuicStreamFrameDataProducer* original_data_producer =
+      framer_->data_producer();
+  framer_->set_data_producer(this);
+
+  size_t length =
+      framer_->BuildDataPacket(header, frames_, buffer, packet_size_, level_);
+
+  framer_->set_data_producer(original_data_producer);
+  if (length == 0) {
+    return std::nullopt;
+  }
+  return length;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_chaos_protector.h b/quiche/quic/core/quic_chaos_protector.h
index f053f89..2456b42 100644
--- a/quiche/quic/core/quic_chaos_protector.h
+++ b/quiche/quic/core/quic_chaos_protector.h
@@ -90,6 +90,81 @@
   QuicRandom* random_;  // Unowned.
 };
 
+// End of old code, start of new code.
+
+namespace test {
+class QuicChaosProtectorNewTest;
+}
+
+// QuicChaosProtectorNew will take a crypto frame and an amount of padding and
+// build a data packet that will parse to something equivalent.
+class QUICHE_EXPORT QuicChaosProtectorNew : public QuicStreamFrameDataProducer {
+ public:
+  // |framer| and |random| must be valid for the lifetime of
+  // QuicChaosProtectorNew.
+  explicit QuicChaosProtectorNew(size_t packet_size, EncryptionLevel level,
+                                 QuicFramer* framer, QuicRandom* random);
+
+  ~QuicChaosProtectorNew() override;
+
+  QuicChaosProtectorNew(const QuicChaosProtectorNew&) = delete;
+  QuicChaosProtectorNew(QuicChaosProtectorNew&&) = delete;
+  QuicChaosProtectorNew& operator=(const QuicChaosProtectorNew&) = delete;
+  QuicChaosProtectorNew& operator=(QuicChaosProtectorNew&&) = delete;
+
+  // Attempts to build a data packet with chaos protection. If an error occurs,
+  // then std::nullopt is returned. Otherwise returns the serialized length.
+  std::optional<size_t> BuildDataPacket(const QuicPacketHeader& header,
+                                        const QuicFrames& frames, char* buffer);
+
+  // From QuicStreamFrameDataProducer.
+  WriteStreamDataResult WriteStreamData(QuicStreamId id,
+                                        QuicStreamOffset offset,
+                                        QuicByteCount data_length,
+                                        QuicDataWriter* /*writer*/) override;
+  bool WriteCryptoData(EncryptionLevel level, QuicStreamOffset offset,
+                       QuicByteCount data_length,
+                       QuicDataWriter* writer) override;
+
+ private:
+  friend class test::QuicChaosProtectorNewTest;
+
+  // Ingest the frames to be chaos protected.
+  bool IngestFrames(const QuicFrames& frames);
+
+  // Allocate the crypto data buffer, create the CRYPTO frame and write the
+  // crypto data to our buffer.
+  bool CopyCryptoDataToLocalBuffer();
+
+  // Split the CRYPTO frame in |frames_| into one or more CRYPTO frames that
+  // collectively represent the same data. Adjusts padding to compensate.
+  void SplitCryptoFrame();
+
+  // Add a random number of PING frames to |frames_| and adjust padding.
+  void AddPingFrames();
+
+  // Randomly reorder |frames_|.
+  void ReorderFrames();
+
+  // Add PADDING frames randomly between all other frames.
+  void SpreadPadding();
+
+  // Serialize |frames_| using |framer_|.
+  std::optional<size_t> BuildPacket(const QuicPacketHeader& header,
+                                    char* buffer);
+
+  size_t packet_size_;
+  std::unique_ptr<char[]> crypto_frame_buffer_;
+  const char* crypto_data_buffer_ = nullptr;
+  QuicByteCount crypto_data_length_ = 0;
+  QuicStreamOffset crypto_buffer_offset_ = 0;
+  EncryptionLevel level_;
+  int remaining_padding_bytes_ = 0;
+  QuicFrames frames_;   // Inner frames owned, will be deleted by destructor.
+  QuicFramer* framer_;  // Unowned.
+  QuicRandom* random_;  // Unowned.
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_CHAOS_PROTECTOR_H_
diff --git a/quiche/quic/core/quic_chaos_protector_test.cc b/quiche/quic/core/quic_chaos_protector_test.cc
index 2131cb4..fe82c47 100644
--- a/quiche/quic/core/quic_chaos_protector_test.cc
+++ b/quiche/quic/core/quic_chaos_protector_test.cc
@@ -12,6 +12,7 @@
 #include "quiche/quic/core/frames/quic_crypto_frame.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_interval_set.h"
 #include "quiche/quic/core/quic_packet_number.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_stream_frame_data_producer.h"
@@ -130,7 +131,7 @@
   QuicPacketHeader header_;
   QuicFramer framer_;
   SimpleQuicFramer validation_framer_;
-  MockRandom random_;
+  ::testing::NiceMock<MockRandom> random_;
   EncryptionLevel level_;
   QuicStreamOffset crypto_offset_;
   QuicByteCount crypto_data_length_;
@@ -226,5 +227,309 @@
 }
 
 }  // namespace
+
+// End of old code, start of new code.
+
+// Sequence of frames to be chaos protected.
+enum class InputFramesPattern {
+  kCryptoAndPadding,
+  kCryptoCryptoAndPadding,
+  kReorderedCryptoCryptoAndPadding,
+  kAckCryptoAndPadding,
+};
+
+class QuicChaosProtectorNewTest : public QuicTestWithParam<ParsedQuicVersion>,
+                                  public QuicStreamFrameDataProducer {
+ public:
+  QuicChaosProtectorNewTest()
+      : version_(GetParam()),
+        framer_({version_}, QuicTime::Zero(), Perspective::IS_CLIENT,
+                kQuicDefaultConnectionIdLength),
+        validation_framer_({version_}),
+        random_(/*base=*/3),
+        level_(ENCRYPTION_INITIAL),
+        crypto_offset_(0),
+        crypto_data_length_(100),
+        num_padding_bytes_(50),
+        packet_size_(1000),
+        packet_buffer_(std::make_unique<char[]>(packet_size_)) {
+    ReCreateChaosProtector();
+  }
+
+  void TearDown() override {
+    // Verify that the output crypto frames are disjoint and when concatenated,
+    // the crypto data covers the range
+    // [crypto_offset_, crypto_offset_+crypto_data_length_).
+    QuicIntervalSet<QuicStreamOffset> crypto_data_intervals;
+    for (size_t i = 0; i < validation_framer_.crypto_frames().size(); ++i) {
+      const QuicCryptoFrame& frame = *validation_framer_.crypto_frames()[i];
+      QuicInterval<QuicStreamOffset> interval(frame.offset,
+                                              frame.offset + frame.data_length);
+      ASSERT_TRUE(crypto_data_intervals.IsDisjoint(interval));
+      crypto_data_intervals.Add(interval);
+      for (QuicStreamOffset j = 0; j < frame.data_length; ++j) {
+        EXPECT_EQ(frame.data_buffer[j],
+                  static_cast<char>((frame.offset + j) & 0xFF))
+            << "i = " << i << ", j = " << j << ", offset = " << frame.offset
+            << ", data_length = " << frame.data_length;
+      }
+    }
+    EXPECT_EQ(crypto_data_intervals.Size(), 1u);
+    EXPECT_EQ(*crypto_data_intervals.begin(),
+              QuicInterval<QuicStreamOffset>(
+                  crypto_offset_, crypto_offset_ + crypto_data_length_));
+  }
+
+  void ReCreateChaosProtector() {
+    chaos_protector_ = std::make_unique<QuicChaosProtectorNew>(
+        packet_size_, level_, SetupHeaderAndFramers(), &random_);
+  }
+
+  // From QuicStreamFrameDataProducer.
+  WriteStreamDataResult WriteStreamData(QuicStreamId /*id*/,
+                                        QuicStreamOffset /*offset*/,
+                                        QuicByteCount /*data_length*/,
+                                        QuicDataWriter* /*writer*/) override {
+    ADD_FAILURE() << "This should never be called";
+    return STREAM_MISSING;
+  }
+
+  // From QuicStreamFrameDataProducer.
+  bool WriteCryptoData(EncryptionLevel level, QuicStreamOffset offset,
+                       QuicByteCount data_length,
+                       QuicDataWriter* writer) override {
+    EXPECT_EQ(level, level);
+    EXPECT_EQ(offset, crypto_offset_);
+    EXPECT_EQ(data_length, crypto_data_length_);
+    for (QuicByteCount i = 0; i < data_length; i++) {
+      EXPECT_TRUE(writer->WriteUInt8(static_cast<uint8_t>((offset + i) & 0xFF)))
+          << i;
+    }
+    return true;
+  }
+
+ protected:
+  QuicFramer* SetupHeaderAndFramers() {
+    // Setup header.
+    header_.destination_connection_id = TestConnectionId();
+    header_.destination_connection_id_included = CONNECTION_ID_PRESENT;
+    header_.source_connection_id = EmptyQuicConnectionId();
+    header_.source_connection_id_included = CONNECTION_ID_PRESENT;
+    header_.reset_flag = false;
+    header_.version_flag = true;
+    header_.has_possible_stateless_reset_token = false;
+    header_.packet_number_length = PACKET_4BYTE_PACKET_NUMBER;
+    header_.version = version_;
+    header_.packet_number = QuicPacketNumber(1);
+    header_.form = IETF_QUIC_LONG_HEADER_PACKET;
+    header_.long_packet_type = INITIAL;
+    header_.retry_token_length_length =
+        quiche::VARIABLE_LENGTH_INTEGER_LENGTH_1;
+    header_.length_length = quiche::kQuicheDefaultLongHeaderLengthLength;
+    // Setup validation framer.
+    validation_framer_.framer()->SetInitialObfuscators(
+        header_.destination_connection_id);
+    // Setup framer.
+    framer_.SetInitialObfuscators(header_.destination_connection_id);
+    framer_.set_data_producer(this);
+    return &framer_;
+  }
+
+  void BuildEncryptAndParse() {
+    QuicFrames frames;
+    switch (input_frames_pattern_) {
+      case InputFramesPattern::kCryptoAndPadding: {
+        frames.push_back(QuicFrame(
+            new QuicCryptoFrame(level_, crypto_offset_, crypto_data_length_)));
+        frames.push_back(QuicFrame(QuicPaddingFrame(num_padding_bytes_)));
+        break;
+      }
+      case InputFramesPattern::kCryptoCryptoAndPadding: {
+        const QuicByteCount first_crypto_frame_length = crypto_data_length_ / 4;
+        frames.push_back(QuicFrame(new QuicCryptoFrame(
+            level_, crypto_offset_, first_crypto_frame_length)));
+        frames.push_back(QuicFrame(new QuicCryptoFrame(
+            level_, crypto_offset_ + first_crypto_frame_length,
+            crypto_data_length_ - first_crypto_frame_length)));
+        frames.push_back(QuicFrame(QuicPaddingFrame(num_padding_bytes_)));
+        break;
+      }
+      case InputFramesPattern::kReorderedCryptoCryptoAndPadding: {
+        const QuicByteCount first_crypto_frame_length = crypto_data_length_ / 4;
+        frames.push_back(QuicFrame(new QuicCryptoFrame(
+            level_, crypto_offset_ + first_crypto_frame_length,
+            crypto_data_length_ - first_crypto_frame_length)));
+        frames.push_back(QuicFrame(new QuicCryptoFrame(
+            level_, crypto_offset_, first_crypto_frame_length)));
+        frames.push_back(QuicFrame(QuicPaddingFrame(num_padding_bytes_)));
+        break;
+      }
+      case InputFramesPattern::kAckCryptoAndPadding: {
+        QuicAckFrame* ack_frame = new QuicAckFrame();
+        ack_frame->largest_acked = QuicPacketNumber(1);
+        ack_frame->packets.Add(ack_frame->largest_acked);
+        frames.push_back(QuicFrame(ack_frame));
+        frames.push_back(QuicFrame(
+            new QuicCryptoFrame(level_, crypto_offset_, crypto_data_length_)));
+        frames.push_back(QuicFrame(QuicPaddingFrame(num_padding_bytes_)));
+        break;
+      }
+    }
+
+    std::optional<size_t> length = chaos_protector_->BuildDataPacket(
+        header_, frames, packet_buffer_.get());
+    DeleteFrames(&frames);
+    ASSERT_TRUE(length.has_value());
+    ASSERT_GT(length.value(), 0u);
+    size_t encrypted_length = framer_.EncryptInPlace(
+        level_, header_.packet_number,
+        GetStartOfEncryptedData(framer_.transport_version(), header_),
+        length.value(), packet_size_, packet_buffer_.get());
+    ASSERT_GT(encrypted_length, 0u);
+    ASSERT_TRUE(validation_framer_.ProcessPacket(QuicEncryptedPacket(
+        absl::string_view(packet_buffer_.get(), encrypted_length))));
+  }
+
+  void ResetOffset(QuicStreamOffset offset) {
+    crypto_offset_ = offset;
+    ReCreateChaosProtector();
+  }
+
+  void ResetLength(QuicByteCount length) {
+    crypto_data_length_ = length;
+    ReCreateChaosProtector();
+  }
+
+  ParsedQuicVersion version_;
+  QuicPacketHeader header_;
+  QuicFramer framer_;
+  SimpleQuicFramer validation_framer_;
+  ::testing::NiceMock<MockRandom> random_;
+  EncryptionLevel level_;
+  InputFramesPattern input_frames_pattern_ =
+      InputFramesPattern::kCryptoAndPadding;
+  QuicStreamOffset crypto_offset_;
+  QuicByteCount crypto_data_length_;
+  int num_padding_bytes_;
+  size_t packet_size_;
+  std::unique_ptr<char[]> packet_buffer_;
+  std::unique_ptr<QuicChaosProtectorNew> chaos_protector_;
+};
+
+namespace {
+
+ParsedQuicVersionVector TestVersionsNew() {
+  ParsedQuicVersionVector versions;
+  for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+    if (version.UsesCryptoFrames()) {
+      versions.push_back(version);
+    }
+  }
+  return versions;
+}
+
+INSTANTIATE_TEST_SUITE_P(QuicChaosProtectorNewTests, QuicChaosProtectorNewTest,
+                         ::testing::ValuesIn(TestVersionsNew()),
+                         ::testing::PrintToStringParamName());
+
+TEST_P(QuicChaosProtectorNewTest, Main) {
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  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_.padding_frames()[0].num_padding_bytes, 3);
+}
+
+TEST_P(QuicChaosProtectorNewTest, 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);
+}
+
+TEST_P(QuicChaosProtectorNewTest, RandomnessZero) {
+  random_.ResetBase(0);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  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);
+}
+
+TEST_P(QuicChaosProtectorNewTest, Offset) {
+  ResetOffset(123);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  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_.padding_frames()[0].num_padding_bytes, 3);
+}
+
+TEST_P(QuicChaosProtectorNewTest, OffsetAndRandomnessZero) {
+  ResetOffset(123);
+  random_.ResetBase(0);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  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);
+}
+
+TEST_P(QuicChaosProtectorNewTest, ZeroRemainingBytesAfterSplit) {
+  QuicPacketLength new_length = 63;
+  num_padding_bytes_ = QuicFramer::GetMinCryptoFrameSize(
+      crypto_offset_ + new_length, new_length);
+  ResetLength(new_length);
+  BuildEncryptAndParse();
+
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 2u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length, 4);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->offset, crypto_offset_ + 4);
+  EXPECT_EQ(validation_framer_.crypto_frames()[1]->data_length,
+            crypto_data_length_ - 4);
+  EXPECT_EQ(validation_framer_.ping_frames().size(), 0u);
+}
+
+TEST_P(QuicChaosProtectorNewTest, CryptoCryptoAndPadding) {
+  input_frames_pattern_ = InputFramesPattern::kCryptoCryptoAndPadding;
+  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);
+}
+
+TEST_P(QuicChaosProtectorNewTest, 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);
+}
+
+TEST_P(QuicChaosProtectorNewTest, AckCryptoAndPadding) {
+  input_frames_pattern_ = InputFramesPattern::kAckCryptoAndPadding;
+  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);
+  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);
+}
+
+}  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc
index f17a733..6f7187e 100644
--- a/quiche/quic/core/quic_packet_creator.cc
+++ b/quiche/quic/core/quic_packet_creator.cc
@@ -783,16 +783,22 @@
       framer_->data_producer() == nullptr) {
     return std::nullopt;
   }
-  const QuicCryptoFrame& crypto_frame = *queued_frames_[0].crypto_frame;
-  if (packet_.encryption_level != crypto_frame.level) {
-    QUIC_BUG(chaos frame level)
-        << ENDPOINT << packet_.encryption_level << " != " << crypto_frame.level;
-    return std::nullopt;
+  if (!GetQuicReloadableFlag(quic_enable_new_chaos_protector)) {
+    const QuicCryptoFrame& crypto_frame = *queued_frames_[0].crypto_frame;
+    if (packet_.encryption_level != crypto_frame.level) {
+      QUIC_BUG(chaos frame level) << ENDPOINT << packet_.encryption_level
+                                  << " != " << crypto_frame.level;
+      return std::nullopt;
+    }
+    QuicChaosProtector chaos_protector(
+        crypto_frame, queued_frames_[1].padding_frame.num_padding_bytes,
+        packet_size_, framer_, random_);
+    return chaos_protector.BuildDataPacket(header, buffer);
   }
-  QuicChaosProtector chaos_protector(
-      crypto_frame, queued_frames_[1].padding_frame.num_padding_bytes,
-      packet_size_, framer_, random_);
-  return chaos_protector.BuildDataPacket(header, buffer);
+  QUIC_RELOADABLE_FLAG_COUNT(quic_enable_new_chaos_protector);
+  QuicChaosProtectorNew chaos_protector(packet_size_, packet_.encryption_level,
+                                        framer_, random_);
+  return chaos_protector.BuildDataPacket(header, queued_frames_, buffer);
 }
 
 bool QuicPacketCreator::SerializePacket(QuicOwnedPacketBuffer encrypted_buffer,
diff --git a/quiche/quic/test_tools/simple_quic_framer.cc b/quiche/quic/test_tools/simple_quic_framer.cc
index 6d177d1..13b21f6 100644
--- a/quiche/quic/test_tools/simple_quic_framer.cc
+++ b/quiche/quic/test_tools/simple_quic_framer.cc
@@ -71,6 +71,10 @@
                              EncryptionLevel /*decryption_level*/,
                              bool /*has_decryption_key*/) override {}
 
+  // Returns the types of the frames in the packet so far, in the order they
+  // were received.
+  const std::vector<QuicFrameType>& frame_types() const { return frame_types_; }
+
   bool OnStreamFrame(const QuicStreamFrame& frame) override {
     // Save a copy of the data so it is valid after the packet is processed.
     std::string* string_data =
@@ -80,6 +84,7 @@
     stream_frames_.push_back(std::make_unique<QuicStreamFrame>(
         frame.stream_id, frame.fin, frame.offset,
         absl::string_view(*string_data)));
+    frame_types_.push_back(STREAM_FRAME);
     return true;
   }
 
@@ -90,6 +95,7 @@
     crypto_data_.push_back(absl::WrapUnique(string_data));
     crypto_frames_.push_back(std::make_unique<QuicCryptoFrame>(
         frame.level, frame.offset, absl::string_view(*string_data)));
+    frame_types_.push_back(CRYPTO_FRAME);
     return true;
   }
 
@@ -99,6 +105,7 @@
     ack_frame.largest_acked = largest_acked;
     ack_frame.ack_delay_time = ack_delay_time;
     ack_frames_.push_back(ack_frame);
+    frame_types_.push_back(ACK_FRAME);
     return true;
   }
 
@@ -121,101 +128,121 @@
 
   bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) override {
     stop_waiting_frames_.push_back(frame);
+    frame_types_.push_back(STOP_WAITING_FRAME);
     return true;
   }
 
   bool OnPaddingFrame(const QuicPaddingFrame& frame) override {
     padding_frames_.push_back(frame);
+    frame_types_.push_back(PADDING_FRAME);
     return true;
   }
 
   bool OnPingFrame(const QuicPingFrame& frame) override {
     ping_frames_.push_back(frame);
+    frame_types_.push_back(PING_FRAME);
     return true;
   }
 
   bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override {
     rst_stream_frames_.push_back(frame);
+    frame_types_.push_back(RST_STREAM_FRAME);
     return true;
   }
 
   bool OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
     connection_close_frames_.push_back(frame);
+    frame_types_.push_back(CONNECTION_CLOSE_FRAME);
     return true;
   }
 
   bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override {
     new_connection_id_frames_.push_back(frame);
+    frame_types_.push_back(NEW_CONNECTION_ID_FRAME);
     return true;
   }
 
   bool OnRetireConnectionIdFrame(
       const QuicRetireConnectionIdFrame& frame) override {
     retire_connection_id_frames_.push_back(frame);
+    frame_types_.push_back(RETIRE_CONNECTION_ID_FRAME);
     return true;
   }
 
   bool OnNewTokenFrame(const QuicNewTokenFrame& frame) override {
     new_token_frames_.push_back(frame);
+    frame_types_.push_back(NEW_TOKEN_FRAME);
     return true;
   }
 
   bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override {
     stop_sending_frames_.push_back(frame);
+    frame_types_.push_back(STOP_SENDING_FRAME);
     return true;
   }
 
   bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override {
     path_challenge_frames_.push_back(frame);
+    frame_types_.push_back(PATH_CHALLENGE_FRAME);
     return true;
   }
 
   bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override {
     path_response_frames_.push_back(frame);
+    frame_types_.push_back(PATH_RESPONSE_FRAME);
     return true;
   }
 
   bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override {
     goaway_frames_.push_back(frame);
+    frame_types_.push_back(GOAWAY_FRAME);
     return true;
   }
   bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
     max_streams_frames_.push_back(frame);
+    frame_types_.push_back(MAX_STREAMS_FRAME);
     return true;
   }
 
   bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
     streams_blocked_frames_.push_back(frame);
+    frame_types_.push_back(STREAMS_BLOCKED_FRAME);
     return true;
   }
 
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override {
     window_update_frames_.push_back(frame);
+    frame_types_.push_back(WINDOW_UPDATE_FRAME);
     return true;
   }
 
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override {
     blocked_frames_.push_back(frame);
+    frame_types_.push_back(BLOCKED_FRAME);
     return true;
   }
 
   bool OnMessageFrame(const QuicMessageFrame& frame) override {
     message_frames_.emplace_back(frame.data, frame.message_length);
+    frame_types_.push_back(MESSAGE_FRAME);
     return true;
   }
 
   bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override {
     handshake_done_frames_.push_back(frame);
+    frame_types_.push_back(HANDSHAKE_DONE_FRAME);
     return true;
   }
 
   bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override {
     ack_frequency_frames_.push_back(frame);
+    frame_types_.push_back(ACK_FREQUENCY_FRAME);
     return true;
   }
 
   bool OnResetStreamAtFrame(const QuicResetStreamAtFrame& frame) override {
     reset_stream_at_frames_.push_back(frame);
+    frame_types_.push_back(RESET_STREAM_AT_FRAME);
     return true;
   }
 
@@ -299,6 +326,7 @@
   QuicPacketHeader header_;
   std::unique_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
   std::unique_ptr<QuicIetfStatelessResetPacket> stateless_reset_packet_;
+  std::vector<QuicFrameType> frame_types_;
   std::vector<QuicAckFrame> ack_frames_;
   std::vector<QuicStopWaitingFrame> stop_waiting_frames_;
   std::vector<QuicPaddingFrame> padding_frames_;
@@ -378,6 +406,10 @@
          crypto_frames().size();
 }
 
+const std::vector<QuicFrameType>& SimpleQuicFramer::frame_types() const {
+  return visitor_->frame_types();
+}
+
 const std::vector<QuicAckFrame>& SimpleQuicFramer::ack_frames() const {
   return visitor_->ack_frames();
 }
diff --git a/quiche/quic/test_tools/simple_quic_framer.h b/quiche/quic/test_tools/simple_quic_framer.h
index a748f4c..53f883e 100644
--- a/quiche/quic/test_tools/simple_quic_framer.h
+++ b/quiche/quic/test_tools/simple_quic_framer.h
@@ -35,6 +35,7 @@
 
   const QuicPacketHeader& header() const;
   size_t num_frames() const;
+  const std::vector<QuicFrameType>& frame_types() const;
   const std::vector<QuicAckFrame>& ack_frames() const;
   const std::vector<QuicConnectionCloseFrame>& connection_close_frames() const;
   const std::vector<QuicStopWaitingFrame>& stop_waiting_frames() const;