| // Copyright (c) 2021 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/core/quic_chaos_protector.h" | |
| #include <cstddef> | |
| #include <memory> | |
| #include "absl/strings/string_view.h" | |
| #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_packet_number.h" | |
| #include "quiche/quic/core/quic_packets.h" | |
| #include "quiche/quic/core/quic_stream_frame_data_producer.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/core/quic_versions.h" | |
| #include "quiche/quic/platform/api/quic_test.h" | |
| #include "quiche/quic/test_tools/mock_random.h" | |
| #include "quiche/quic/test_tools/quic_test_utils.h" | |
| #include "quiche/quic/test_tools/simple_quic_framer.h" | |
| namespace quic { | |
| namespace test { | |
| class QuicChaosProtectorTest : public QuicTestWithParam<ParsedQuicVersion>, | |
| public QuicStreamFrameDataProducer { | |
| public: | |
| QuicChaosProtectorTest() | |
| : 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), | |
| crypto_frame_(level_, crypto_offset_, crypto_data_length_), | |
| num_padding_bytes_(50), | |
| packet_size_(1000), | |
| packet_buffer_(std::make_unique<char[]>(packet_size_)) { | |
| ReCreateChaosProtector(); | |
| } | |
| void ReCreateChaosProtector() { | |
| chaos_protector_ = std::make_unique<QuicChaosProtector>( | |
| crypto_frame_, num_padding_bytes_, packet_size_, | |
| 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>(i & 0xFF))); | |
| } | |
| 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() { | |
| absl::optional<size_t> length = | |
| chaos_protector_->BuildDataPacket(header_, packet_buffer_.get()); | |
| 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; | |
| crypto_frame_.offset = offset; | |
| ReCreateChaosProtector(); | |
| } | |
| void ResetLength(QuicByteCount length) { | |
| crypto_data_length_ = length; | |
| crypto_frame_.data_length = length; | |
| ReCreateChaosProtector(); | |
| } | |
| ParsedQuicVersion version_; | |
| QuicPacketHeader header_; | |
| QuicFramer framer_; | |
| SimpleQuicFramer validation_framer_; | |
| MockRandom random_; | |
| EncryptionLevel level_; | |
| QuicStreamOffset crypto_offset_; | |
| QuicByteCount crypto_data_length_; | |
| QuicCryptoFrame crypto_frame_; | |
| int num_padding_bytes_; | |
| size_t packet_size_; | |
| std::unique_ptr<char[]> packet_buffer_; | |
| std::unique_ptr<QuicChaosProtector> chaos_protector_; | |
| }; | |
| namespace { | |
| ParsedQuicVersionVector TestVersions() { | |
| ParsedQuicVersionVector versions; | |
| for (const ParsedQuicVersion& version : AllSupportedVersions()) { | |
| if (version.UsesCryptoFrames()) { | |
| versions.push_back(version); | |
| } | |
| } | |
| return versions; | |
| } | |
| INSTANTIATE_TEST_SUITE_P(QuicChaosProtectorTests, QuicChaosProtectorTest, | |
| ::testing::ValuesIn(TestVersions()), | |
| ::testing::PrintToStringParamName()); | |
| TEST_P(QuicChaosProtectorTest, 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(QuicChaosProtectorTest, DifferentRandom) { | |
| random_.ResetBase(4); | |
| BuildEncryptAndParse(); | |
| ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u); | |
| ASSERT_EQ(validation_framer_.ping_frames().size(), 4u); | |
| ASSERT_EQ(validation_framer_.padding_frames().size(), 8u); | |
| } | |
| TEST_P(QuicChaosProtectorTest, RandomnessZero) { | |
| random_.ResetBase(0); | |
| BuildEncryptAndParse(); | |
| ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u); | |
| EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_); | |
| 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(QuicChaosProtectorTest, 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(QuicChaosProtectorTest, 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(QuicChaosProtectorTest, ZeroRemainingBytesAfterSplit) { | |
| QuicPacketLength new_length = 63; | |
| num_padding_bytes_ = QuicFramer::GetMinCryptoFrameSize( | |
| crypto_frame_.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); | |
| ASSERT_EQ(validation_framer_.ping_frames().size(), 0u); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |