Chaos Protection v2
This CL adds support for multi-packet chaos protection. While the first version of Chaos Protection would scramble the ClientHello if it fit in a CRYPTO frame in a single packet, the emergence of post-quantum cryptography has now caused the ClientHello to span multiple packets. Chaos Protection v2 now scrambles the ClientHello across packets, such that it is now required to parse all of them in order to extract information such as the SNI.
This CL also removes automatic flushing of initial crypto packets in the packet creator. This is safe because all uses of such functions are guaranteed to use a scope packed flusher anyway.
Note that --quic_enable_new_chaos_protector is still marked as enabled_blocked_by so it is safe to add new functionality behind it.
Protected by FLAGS_quic_enable_new_chaos_protector.
PiperOrigin-RevId: 702456667
diff --git a/quiche/quic/core/quic_chaos_protector.cc b/quiche/quic/core/quic_chaos_protector.cc
index 8fc8d90..0a1d3d1 100644
--- a/quiche/quic/core/quic_chaos_protector.cc
+++ b/quiche/quic/core/quic_chaos_protector.cc
@@ -291,11 +291,14 @@
std::optional<size_t> QuicChaosProtector::BuildDataPacket(
const QuicPacketHeader& header, const QuicFrames& frames, char* buffer) {
if (!IngestFrames(frames)) {
- QUIC_DVLOG(1) << "Failed to ingest frames";
+ QUIC_DVLOG(1) << "Failed to ingest frames for initial packet number "
+ << header.packet_number;
return std::nullopt;
}
if (!CopyCryptoDataToLocalBuffer()) {
- QUIC_DVLOG(1) << "Failed to copy crypto data to local buffer";
+ QUIC_DVLOG(1) << "Failed to copy crypto data to local buffer for initial "
+ "packet number "
+ << header.packet_number;
return std::nullopt;
}
SplitCryptoFrame();
@@ -481,8 +484,12 @@
framer_->set_data_producer(original_data_producer);
if (length == 0) {
+ QUIC_DVLOG(1) << "Failed to build data packet for initial packet number "
+ << header.packet_number;
return std::nullopt;
}
+ QUIC_DVLOG(1) << "Performed chaos protection on initial packet number "
+ << header.packet_number << " with length " << length;
return length;
}
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 68f8594..da3e87d 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -341,18 +341,23 @@
// split needlessly across packet boundaries). As a result, we have separate
// tests for some cases for this stream.
QuicConsumedData SendCryptoStreamData() {
+ return SendCryptoStreamDataAtLevel(ENCRYPTION_INITIAL);
+ }
+
+ QuicConsumedData SendCryptoStreamDataAtLevel(
+ EncryptionLevel encryption_level) {
QuicStreamOffset offset = 0;
absl::string_view data("chlo");
if (!QuicVersionUsesCryptoFrames(transport_version())) {
return SendCryptoDataWithString(data, offset);
}
- producer_.SaveCryptoData(ENCRYPTION_INITIAL, offset, data);
+ producer_.SaveCryptoData(encryption_level, offset, data);
size_t bytes_written;
if (notifier_) {
bytes_written =
- notifier_->WriteCryptoData(ENCRYPTION_INITIAL, data.length(), offset);
+ notifier_->WriteCryptoData(encryption_level, data.length(), offset);
} else {
- bytes_written = QuicConnection::SendCryptoData(ENCRYPTION_INITIAL,
+ bytes_written = QuicConnection::SendCryptoData(encryption_level,
data.length(), offset);
}
return QuicConsumedData(bytes_written, /*fin_consumed*/ false);
@@ -4009,7 +4014,7 @@
connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
QuicConnection::ScopedPacketFlusher flusher(&connection_);
- connection_.SendCryptoStreamData();
+ connection_.SendCryptoStreamDataAtLevel(ENCRYPTION_FORWARD_SECURE);
connection_.SendStreamData3();
}
EXPECT_EQ(0u, connection_.NumQueuedPackets());
@@ -17751,7 +17756,6 @@
}
}
-
TEST_P(QuicConnectionTest, EcnValidationDisabled) {
QuicConnectionPeer::DisableEcnCodepointValidation(&connection_);
for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc
index 5e5aa9c..f982ea8 100644
--- a/quiche/quic/core/quic_packet_creator.cc
+++ b/quiche/quic/core/quic_packet_creator.cc
@@ -282,6 +282,10 @@
<< " packet_number:" << packet_.packet_number;
return;
}
+ QUIC_DVLOG(1) << ENDPOINT << "Skipping " << count
+ << " packet numbers, least_packet_awaited_by_peer: "
+ << least_packet_awaited_by_peer
+ << " packet_number: " << packet_.packet_number;
packet_.packet_number += count;
// Packet number changes, update packet number length if necessary.
UpdatePacketNumberLength(least_packet_awaited_by_peer, max_packets_in_flight);
@@ -797,7 +801,7 @@
packet_size_, framer_, random_);
return chaos_protector.BuildDataPacket(header, buffer);
}
- QUIC_RELOADABLE_FLAG_COUNT(quic_enable_new_chaos_protector);
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_new_chaos_protector, 1, 2);
QuicChaosProtector chaos_protector(packet_size_, packet_.encryption_level,
framer_, random_);
return chaos_protector.BuildDataPacket(header, queued_frames_, buffer);
@@ -1561,11 +1565,134 @@
return 0;
}
total_bytes_consumed += frame.crypto_frame->data_length;
- FlushCurrentPacket();
+ if (!GetQuicReloadableFlag(quic_enable_new_chaos_protector) ||
+ level != ENCRYPTION_INITIAL ||
+ frame.crypto_frame->data_length < write_length) {
+ FlushCurrentPacket();
+ }
}
return total_bytes_consumed;
}
+size_t QuicPacketCreator::MultiPacketChaosProtect(EncryptionLevel level,
+ size_t write_length,
+ QuicStreamOffset offset) {
+ if (!GetQuicFlag(quic_enable_chaos_protection) ||
+ !GetQuicReloadableFlag(quic_enable_new_chaos_protector) ||
+ framer_->perspective() != Perspective::IS_CLIENT ||
+ level != ENCRYPTION_INITIAL || !framer_->version().UsesCryptoFrames() ||
+ framer_->data_producer() == nullptr ||
+ !fully_pad_crypto_handshake_packets_ || offset != 0 ||
+ !delegate_->ShouldGeneratePacket(HAS_RETRANSMITTABLE_DATA,
+ IS_HANDSHAKE)) {
+ return 0;
+ }
+ size_t min_crypto_frame_size = QuicFramer::GetMinCryptoFrameSize(
+ offset + write_length - 1, write_length);
+ if (BytesFree() < 2 * min_crypto_frame_size) {
+ // First packet is too full to fit two crypto frames, do not attempt chaos
+ // protection.
+ return 0;
+ }
+ size_t max_crypto_data_length_first_packet =
+ BytesFree() - 2 * min_crypto_frame_size;
+ if (max_plaintext_size_ < PacketHeaderSize() + min_crypto_frame_size) {
+ QUIC_BUG(multi_packet_chaos_protection_packets_too_small);
+ return 0;
+ }
+ size_t max_crypto_data_length_other_packets =
+ max_plaintext_size_ - PacketHeaderSize() - min_crypto_frame_size;
+
+ if (write_length <= max_crypto_data_length_first_packet) {
+ // We can fit the entire crypto data in a single packet, no need for
+ // multi-packet chaos protection.
+ return 0;
+ }
+ if (max_crypto_data_length_other_packets <
+ max_crypto_data_length_first_packet) {
+ QUIC_BUG(multi_packet_chaos_protection_unexpected_math);
+ return 0;
+ }
+ size_t first_packet_occupied_space = max_crypto_data_length_other_packets -
+ max_crypto_data_length_first_packet;
+ size_t num_packets = (write_length + first_packet_occupied_space +
+ max_crypto_data_length_other_packets - 1) /
+ max_crypto_data_length_other_packets;
+ size_t data_in_other_packets =
+ (write_length + first_packet_occupied_space + num_packets - 1) /
+ num_packets;
+ if (data_in_other_packets < first_packet_occupied_space) {
+ // First packet is too full, do not attempt chaos protection.
+ return 0;
+ }
+ size_t data_in_first_packet =
+ data_in_other_packets - first_packet_occupied_space;
+ // The TLS client hello starts with a mostly fixed header followed by
+ // type-length-value extensions. The simplest way to force receivers to parse
+ // multiple packets is therefore to ensure that one packet has the header and
+ // the length of the first extension, and then put the subsequent extensions
+ // into the second packet. When there are 3 cipher suites enabled, this all
+ // adds up to 55 bytes. We also add some randomness for good measure. After
+ // this split, the first packet contains the first ~55 bytes and the end of
+ // the client hello. The middle of the client hello is then split between
+ // subsequent packets.
+ static constexpr size_t kMinFirstFrameLength = 55;
+ static constexpr size_t kFirstFrameLengthRandom = 32;
+ size_t first_frame_length =
+ kMinFirstFrameLength +
+ (random_->InsecureRandUint64() % kFirstFrameLengthRandom);
+ if (write_length <= first_frame_length) {
+ QUIC_BUG(multi_packet_chaos_protection_short_first_frame);
+ return 0;
+ }
+ if (data_in_first_packet <= first_frame_length) {
+ // Return early if we can't fit the first crypto frame in the first packet.
+ return 0;
+ }
+ size_t last_frame_length = data_in_first_packet - first_frame_length;
+ size_t total_bytes_consumed = 0;
+ QuicFrame first_frame;
+ if (!CreateCryptoFrame(level, first_frame_length, offset, &first_frame)) {
+ QUIC_BUG(multi_packet_chaos_protection_failed_to_create_first_frame);
+ return 0;
+ }
+ total_bytes_consumed += first_frame_length;
+ QuicFrame last_frame;
+ if (!CreateCryptoFrame(level, last_frame_length,
+ offset + write_length - last_frame_length,
+ &last_frame)) {
+ QUIC_BUG(multi_packet_chaos_protection_failed_to_create_last_frame);
+ DeleteFrame(&first_frame);
+ return 0;
+ }
+ if (!AddFrame(first_frame, next_transmission_type_)) {
+ QUIC_BUG(multi_packet_chaos_protection_failed_to_add_first_frame);
+ DeleteFrame(&first_frame);
+ DeleteFrame(&last_frame);
+ return 0;
+ }
+ if (!AddFrame(last_frame, next_transmission_type_)) {
+ QUIC_BUG(multi_packet_chaos_protection_failed_to_add_last_frame);
+ DeleteFrame(&last_frame);
+ return 0;
+ }
+ needs_full_padding_ = true;
+ FlushCurrentPacket();
+ RemoveSoftMaxPacketLength();
+
+ size_t remaining_bytes_consumed = GenerateRemainingCryptoFrames(
+ level, write_length - last_frame_length, offset, total_bytes_consumed);
+ if (remaining_bytes_consumed == 0) {
+ QUIC_BUG(multi_packet_chaos_protection_failed_to_generate_remaining_frames);
+ return 0;
+ }
+ QUIC_DVLOG(1) << "Performed multi-packet Chaos Protection for "
+ << write_length << " bytes accross " << num_packets
+ << " packets (first=" << first_frame_length
+ << ", last=" << last_frame_length << ")";
+ return remaining_bytes_consumed + last_frame_length;
+}
+
size_t QuicPacketCreator::ConsumeCryptoData(EncryptionLevel level,
size_t write_length,
QuicStreamOffset offset) {
@@ -1581,15 +1708,27 @@
// TODO(nharper): Once we have separate packet number spaces, everything
// should be driven by encryption level, and we should stop flushing in this
// spot.
- if (HasPendingRetransmittableFrames()) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_new_chaos_protector, 2, 2);
+ if (HasPendingRetransmittableFrames() &&
+ (!GetQuicReloadableFlag(quic_enable_new_chaos_protector) ||
+ level != ENCRYPTION_INITIAL)) {
FlushCurrentPacket();
}
- size_t total_bytes_consumed = GenerateRemainingCryptoFrames(
- level, write_length, offset, /*total_bytes_consumed=*/0);
+ size_t total_bytes_consumed =
+ MultiPacketChaosProtect(level, write_length, offset);
+ if (total_bytes_consumed == 0) {
+ // Multi-packet chaos protection was not applied, generate the crypto frames
+ // here instead.
+ total_bytes_consumed = GenerateRemainingCryptoFrames(
+ level, write_length, offset, total_bytes_consumed);
+ }
// Don't allow the handshake to be bundled with other retransmittable frames.
- FlushCurrentPacket();
+ if (!GetQuicReloadableFlag(quic_enable_new_chaos_protector) ||
+ level != ENCRYPTION_INITIAL) {
+ FlushCurrentPacket();
+ }
return total_bytes_consumed;
}
diff --git a/quiche/quic/core/quic_packet_creator.h b/quiche/quic/core/quic_packet_creator.h
index 0d0a060..0d2ba4b 100644
--- a/quiche/quic/core/quic_packet_creator.h
+++ b/quiche/quic/core/quic_packet_creator.h
@@ -646,6 +646,12 @@
QuicStreamOffset offset,
size_t total_bytes_consumed);
+ // Attempts to perform multi-packet chaos protection. If this portion of the
+ // crypto stream isn't supposed to be protected or if anything fails then 0 is
+ // returned. Otherwise returns the amount of crypto data consumed.
+ size_t MultiPacketChaosProtect(EncryptionLevel level, size_t write_length,
+ QuicStreamOffset offset);
+
// Does not own these delegates or the framer.
DelegateInterface* delegate_;
DebugDelegate* debug_delegate_;
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc
index 849fcca..f4e32e9 100644
--- a/quiche/quic/core/quic_packet_creator_test.cc
+++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -647,7 +647,7 @@
header.reset_flag = false;
header.version_flag = false;
header.packet_number = kPacketNumber;
- MockRandom randomizer;
+ ::testing::NiceMock<MockRandom> randomizer;
QuicPathFrameBuffer payload;
randomizer.RandBytes(payload.data(), payload.size());
@@ -1398,7 +1398,7 @@
if (!GetParam().version.UsesCryptoFrames()) {
return;
}
- MockRandom mock_random(2);
+ ::testing::NiceMock<MockRandom> mock_random(2);
QuicPacketCreatorPeer::SetRandom(&creator_, &mock_random);
std::string data("ChAoS_ThEoRy!");
producer_.SaveCryptoData(ENCRYPTION_INITIAL, 0, data);
@@ -2805,8 +2805,15 @@
}
}
+ void TestChaosProtection(bool chaos_protection_enabled,
+ size_t crypto_data_length, int num_packets);
+ void SetupInitialCrypto(size_t crypto_data_length, int num_ack_blocks,
+ bool chaos_protection_enabled);
+ void CheckPackets(int num_ack_blocks, int num_packets,
+ bool chaos_protection_expected);
+
QuicFramer framer_;
- MockRandom random_creator_;
+ ::testing::NiceMock<MockRandom> random_creator_;
StrictMock<MockDelegate> delegate_;
MultiplePacketsTestPacketCreator creator_;
SimpleQuicFramer simple_framer_;
@@ -3043,7 +3050,8 @@
const std::string data = "foo bar";
size_t consumed_bytes = 0;
if (QuicVersionUsesCryptoFrames(framer_.transport_version())) {
- consumed_bytes = creator_.ConsumeCryptoData(ENCRYPTION_INITIAL, data, 0);
+ consumed_bytes =
+ creator_.ConsumeCryptoData(ENCRYPTION_FORWARD_SECURE, data, 0);
} else {
consumed_bytes =
creator_
@@ -3070,6 +3078,258 @@
EXPECT_EQ(kDefaultMaxPacketSize, packets_[0].encrypted_length);
}
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Enabled_OnePacket) {
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/true, /*crypto_data_length=*/1000,
+ /*num_packets=*/1);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Enabled_TwoPackets) {
+ // 1505 bytes is the usual size of the ClientHello when post-quantum
+ // cryptography is enabled.
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/true, /*crypto_data_length=*/1505,
+ /*num_packets=*/2);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Enabled_ThreePackets) {
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/true, /*crypto_data_length=*/3000,
+ /*num_packets=*/3);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Disabled_OnePacket) {
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/false, /*crypto_data_length=*/1000,
+ /*num_packets=*/1);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Disabled_TwoPackets) {
+ // 1505 bytes is the usual size of the ClientHello when post-quantum
+ // cryptography is enabled.
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/false, /*crypto_data_length=*/1505,
+ /*num_packets=*/2);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest,
+ ChaosProtection_Disabled_ThreePackets) {
+ TestChaosProtection(
+ /*chaos_protection_enabled=*/false, /*crypto_data_length=*/3000,
+ /*num_packets=*/3);
+}
+
+void QuicPacketCreatorMultiplePacketsTest::TestChaosProtection(
+ bool chaos_protection_enabled, size_t crypto_data_length, int num_packets) {
+ if (!framer_.version().UsesCryptoFrames()) {
+ return;
+ }
+ SetupInitialCrypto(/*crypto_data_length=*/0, /*num_ack_blocks=*/0,
+ chaos_protection_enabled);
+ std::vector<uint8_t> data_bytes(crypto_data_length);
+ for (size_t i = 0; i < data_bytes.size(); ++i) {
+ data_bytes[i] = (i & 0xFF);
+ }
+ const std::string data(reinterpret_cast<char*>(data_bytes.data()),
+ crypto_data_length);
+ EXPECT_EQ(creator_.ConsumeCryptoData(ENCRYPTION_INITIAL, data, /*offset=*/0),
+ crypto_data_length);
+ creator_.Flush();
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ EXPECT_FALSE(creator_.HasPendingRetransmittableFrames());
+ EXPECT_EQ(kDefaultMaxPacketSize, creator_.max_packet_length());
+
+ QuicIntervalSet<QuicStreamOffset> crypto_data_intervals;
+ int num_crypto_frames = 0;
+ bool first_packet = true;
+ QuicStreamOffset max_crypto_first_packet = 0;
+ QuicStreamOffset min_crypto_subsequent_packets =
+ std::numeric_limits<QuicStreamOffset>::max();
+ for (const auto& packet : packets_) {
+ EXPECT_EQ(packet.encrypted_length, kDefaultMaxPacketSize);
+ ASSERT_TRUE(packet.encrypted_buffer != nullptr);
+ simple_framer_.Reset();
+ ASSERT_TRUE(simple_framer_.ProcessPacket(
+ QuicEncryptedPacket(packet.encrypted_buffer, packet.encrypted_length)));
+ for (const auto& frame : simple_framer_.crypto_frames()) {
+ if (first_packet) {
+ QuicStreamOffset max_crypto = frame->data_length + frame->offset;
+ if (max_crypto > max_crypto_first_packet) {
+ max_crypto_first_packet = max_crypto;
+ }
+ } else {
+ QuicStreamOffset min_crypto = frame->offset;
+ if (min_crypto < min_crypto_subsequent_packets) {
+ min_crypto_subsequent_packets = min_crypto;
+ }
+ }
+ num_crypto_frames++;
+ QuicInterval<QuicStreamOffset> interval(
+ frame->offset, frame->offset + frame->data_length);
+ // Check that we don't repeat the same crypto data in different frames.
+ ASSERT_TRUE(crypto_data_intervals.IsDisjoint(interval));
+ crypto_data_intervals.Add(interval);
+ for (QuicStreamOffset i = 0; i < frame->data_length; ++i) {
+ // Check the crypto data itself is correct.
+ EXPECT_EQ(frame->data_buffer[i],
+ static_cast<char>((frame->offset + i) & 0xFF))
+ << "i = " << i << ", offset = " << frame->offset
+ << ", data_length = " << frame->data_length;
+ }
+ }
+ first_packet = false;
+ }
+ // Make sure that the combination of all crypto frames covers the entire data.
+ EXPECT_EQ(crypto_data_intervals.Size(), 1u);
+ EXPECT_EQ(*crypto_data_intervals.begin(),
+ QuicInterval<QuicStreamOffset>(0, crypto_data_length));
+
+ EXPECT_EQ(packets_.size(), num_packets);
+ if (chaos_protection_enabled) {
+ EXPECT_GT(num_crypto_frames, packets_.size() + 1);
+ } else {
+ EXPECT_EQ(num_crypto_frames, packets_.size());
+ }
+ // Check that multi-packet chaos protection was performed if and only if it
+ // was expected.
+ EXPECT_EQ(chaos_protection_enabled && num_packets > 1,
+ max_crypto_first_packet > min_crypto_subsequent_packets);
+}
+
+void QuicPacketCreatorMultiplePacketsTest::SetupInitialCrypto(
+ size_t crypto_data_length, int num_ack_blocks,
+ bool chaos_protection_enabled) {
+ SetQuicFlag(quic_enable_chaos_protection, chaos_protection_enabled);
+ SetQuicReloadableFlag(quic_enable_new_chaos_protector,
+ chaos_protection_enabled);
+ random_creator_.ResetBase(4);
+ creator_.SetEncrypter(ENCRYPTION_INITIAL,
+ std::make_unique<TaggingEncrypter>(ENCRYPTION_INITIAL));
+ creator_.set_encryption_level(ENCRYPTION_INITIAL);
+ if (simple_framer_.framer()->version().KnowsWhichDecrypterToUse()) {
+ simple_framer_.framer()->InstallDecrypter(
+ ENCRYPTION_INITIAL, std::make_unique<TaggingDecrypter>());
+ }
+ delegate_.SetCanWriteAnything();
+
+ EXPECT_CALL(delegate_, OnSerializedPacket(_))
+ .WillRepeatedly(
+ Invoke(this, &QuicPacketCreatorMultiplePacketsTest::SavePacket));
+
+ if (num_ack_blocks > 0) {
+ std::vector<QuicAckBlock> ack_blocks;
+ for (int i = 1; i <= num_ack_blocks; ++i) {
+ ack_blocks.push_back(
+ {QuicPacketNumber(3 * i), QuicPacketNumber(3 * i + 1)});
+ }
+ ack_frame_ = InitAckFrame(ack_blocks);
+ EXPECT_TRUE(creator_.AddFrame(QuicFrame(&ack_frame_), NOT_RETRANSMISSION));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+ } else {
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ }
+ EXPECT_FALSE(creator_.HasPendingRetransmittableFrames());
+
+ if (crypto_data_length > 0) {
+ const std::string data(crypto_data_length, '?');
+ EXPECT_EQ(
+ creator_.ConsumeCryptoData(ENCRYPTION_INITIAL, data, /*offset=*/0),
+ data.size());
+ }
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ EXPECT_FALSE(creator_.HasPendingRetransmittableFrames());
+ EXPECT_EQ(kDefaultMaxPacketSize, creator_.max_packet_length());
+}
+
+void QuicPacketCreatorMultiplePacketsTest::CheckPackets(
+ int num_ack_blocks, int num_packets, bool chaos_protection_expected) {
+ ASSERT_EQ(packets_.size(), num_packets);
+ // Check first packet.
+ EXPECT_EQ(packets_[0].encrypted_length, kDefaultMaxPacketSize);
+ ASSERT_TRUE(packets_[0].encrypted_buffer != nullptr);
+ ASSERT_TRUE(simple_framer_.ProcessPacket(QuicEncryptedPacket(
+ packets_[0].encrypted_buffer, packets_[0].encrypted_length)));
+ EXPECT_GE(simple_framer_.crypto_frames().size(), 1u);
+ EXPECT_EQ(simple_framer_.ack_frames().size(), num_ack_blocks > 0 ? 1u : 0u);
+ QuicStreamOffset max_crypto_first_packet = 0;
+ for (const auto& frame : simple_framer_.crypto_frames()) {
+ QuicStreamOffset max_crypto = frame->data_length + frame->offset;
+ if (max_crypto > max_crypto_first_packet) {
+ max_crypto_first_packet = max_crypto;
+ }
+ }
+ // Check subsequent packets.
+ QuicStreamOffset min_crypto_subsequent_packets =
+ std::numeric_limits<QuicStreamOffset>::max();
+ for (int i = 1; i < num_packets; ++i) {
+ simple_framer_.Reset();
+ EXPECT_EQ(packets_[i].encrypted_length, kDefaultMaxPacketSize);
+ ASSERT_TRUE(packets_[i].encrypted_buffer != nullptr);
+ ASSERT_TRUE(simple_framer_.ProcessPacket(QuicEncryptedPacket(
+ packets_[i].encrypted_buffer, packets_[i].encrypted_length)));
+ EXPECT_GE(simple_framer_.crypto_frames().size(), 1u);
+ EXPECT_EQ(simple_framer_.ack_frames().size(), 0u);
+ for (const auto& frame : simple_framer_.crypto_frames()) {
+ QuicStreamOffset min_crypto = frame->offset;
+ if (min_crypto < min_crypto_subsequent_packets) {
+ min_crypto_subsequent_packets = min_crypto;
+ }
+ }
+ }
+ EXPECT_EQ(chaos_protection_expected,
+ max_crypto_first_packet > min_crypto_subsequent_packets);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest, ChaosProtectionWithPriorAcks) {
+ // Ensure that multi-packet chaos protection takes into account any pending
+ // non-retransmittable frames.
+ if (!framer_.version().UsesCryptoFrames()) {
+ return;
+ }
+ static constexpr int kNumAckBlocks = 100;
+ // Size the crypto data such that it could fit in one packet by itself but
+ // can't fit with the ack frame.
+ static constexpr size_t kCryptoDataSize =
+ kDefaultMaxPacketSize - 2 * kNumAckBlocks;
+ SetupInitialCrypto(kCryptoDataSize, kNumAckBlocks,
+ /*chaos_protection_enabled=*/true);
+ CheckPackets(kNumAckBlocks, /*num_packets=*/2,
+ /*chaos_protection_expected=*/true);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest, ChaosProtectionFirstPacketFull) {
+ // Ensure that chaos protection returns disabled early when the packet has
+ // more pending data than the amount of crypto data per packet.
+ if (!framer_.version().UsesCryptoFrames()) {
+ return;
+ }
+ static constexpr int kNumAckBlocks = (kDefaultMaxPacketSize - 100) / 2;
+ static constexpr size_t kCryptoDataSize = 2000;
+ SetupInitialCrypto(kCryptoDataSize, kNumAckBlocks,
+ /*chaos_protection_enabled=*/true);
+ CheckPackets(kNumAckBlocks, /*num_packets=*/3,
+ /*chaos_protection_expected=*/false);
+}
+
+TEST_F(QuicPacketCreatorMultiplePacketsTest, ChaosProtectionCantFitFirstFrame) {
+ // Ensure that chaos protection disables itself if we can't fit the first
+ // frame in the first packet.
+ if (!framer_.version().UsesCryptoFrames()) {
+ return;
+ }
+ static constexpr int kNumAckBlocks = (kDefaultMaxPacketSize - 100) / 2;
+ static constexpr size_t kCryptoDataSize = 2400;
+ SetupInitialCrypto(kCryptoDataSize, kNumAckBlocks,
+ /*chaos_protection_enabled=*/true);
+ CheckPackets(kNumAckBlocks, /*num_packets=*/3,
+ /*chaos_protection_expected=*/false);
+}
+
// Test the behavior of ConsumeData when the data is for the crypto handshake
// stream, but padding is disabled.
TEST_F(QuicPacketCreatorMultiplePacketsTest,
@@ -3084,7 +3344,8 @@
const std::string data = "foo";
size_t bytes_consumed = 0;
if (QuicVersionUsesCryptoFrames(framer_.transport_version())) {
- bytes_consumed = creator_.ConsumeCryptoData(ENCRYPTION_INITIAL, data, 0);
+ bytes_consumed =
+ creator_.ConsumeCryptoData(ENCRYPTION_FORWARD_SECURE, data, 0);
} else {
bytes_consumed =
creator_
diff --git a/quiche/quic/test_tools/simple_data_producer.cc b/quiche/quic/test_tools/simple_data_producer.cc
index d788ec3..30e4646 100644
--- a/quiche/quic/test_tools/simple_data_producer.cc
+++ b/quiche/quic/test_tools/simple_data_producer.cc
@@ -58,6 +58,15 @@
QuicDataWriter* writer) {
auto it = crypto_buffer_map_.find(std::make_pair(level, offset));
if (it == crypto_buffer_map_.end() || it->second.length() < data_length) {
+ // If we fail to find an exact match for the offset, try to find a
+ // superset entry that covers the requested range.
+ for (auto& entry : crypto_buffer_map_) {
+ if (entry.first.first == level && entry.first.second <= offset &&
+ offset + data_length <= entry.first.second + entry.second.length()) {
+ return writer->WriteStringPiece(absl::string_view(
+ entry.second.data() + (offset - entry.first.second), data_length));
+ }
+ }
return false;
}
return writer->WriteStringPiece(