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;