| #include "quiche/http2/test_tools/random_decoder_test_base.h" |
| |
| #include <stddef.h> |
| |
| #include <functional> |
| #include <set> |
| #include <type_traits> |
| |
| #include "quiche/http2/decoder/decode_buffer.h" |
| #include "quiche/http2/decoder/decode_status.h" |
| #include "quiche/http2/test_tools/http2_random.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| |
| namespace http2 { |
| namespace test { |
| namespace { |
| const char kData[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; |
| const bool kReturnNonZeroOnFirst = true; |
| const bool kMayReturnZeroOnFirst = false; |
| |
| // Confirm the behavior of various parts of RandomDecoderTest. |
| class RandomDecoderTestTest : public RandomDecoderTest { |
| public: |
| RandomDecoderTestTest() : data_db_(kData) { |
| QUICHE_CHECK_EQ(sizeof kData, 8u); |
| } |
| |
| protected: |
| typedef std::function<DecodeStatus(DecodeBuffer* db)> DecodingFn; |
| |
| DecodeStatus StartDecoding(DecodeBuffer* db) override { |
| ++start_decoding_calls_; |
| if (start_decoding_fn_) { |
| return start_decoding_fn_(db); |
| } |
| return DecodeStatus::kDecodeError; |
| } |
| |
| DecodeStatus ResumeDecoding(DecodeBuffer* db) override { |
| ++resume_decoding_calls_; |
| if (resume_decoding_fn_) { |
| return resume_decoding_fn_(db); |
| } |
| return DecodeStatus::kDecodeError; |
| } |
| |
| bool StopDecodeOnDone() override { |
| ++stop_decode_on_done_calls_; |
| if (override_stop_decode_on_done_) { |
| return sub_stop_decode_on_done_; |
| } |
| return RandomDecoderTest::StopDecodeOnDone(); |
| } |
| |
| size_t start_decoding_calls_ = 0; |
| size_t resume_decoding_calls_ = 0; |
| size_t stop_decode_on_done_calls_ = 0; |
| |
| DecodingFn start_decoding_fn_; |
| DecodingFn resume_decoding_fn_; |
| |
| DecodeBuffer data_db_; |
| |
| bool sub_stop_decode_on_done_ = true; |
| bool override_stop_decode_on_done_ = true; |
| }; |
| |
| // Decode a single byte on the StartDecoding call, then stop. |
| TEST_F(RandomDecoderTestTest, StopOnStartPartiallyDone) { |
| start_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, start_decoding_calls_); |
| // Make sure the correct buffer is being used. |
| EXPECT_EQ(kData, db->cursor()); |
| EXPECT_EQ(sizeof kData, db->Remaining()); |
| db->DecodeUInt8(); |
| return DecodeStatus::kDecodeDone; |
| }; |
| |
| EXPECT_EQ(DecodeStatus::kDecodeDone, |
| DecodeSegments(&data_db_, SelectRemaining())); |
| EXPECT_EQ(1u, data_db_.Offset()); |
| // StartDecoding should only be called once from each call to DecodeSegments. |
| EXPECT_EQ(1u, start_decoding_calls_); |
| EXPECT_EQ(0u, resume_decoding_calls_); |
| EXPECT_EQ(1u, stop_decode_on_done_calls_); |
| } |
| |
| // Stop decoding upon return from the first ResumeDecoding call. |
| TEST_F(RandomDecoderTestTest, StopOnResumePartiallyDone) { |
| start_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, start_decoding_calls_); |
| db->DecodeUInt8(); |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| resume_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, resume_decoding_calls_); |
| // Make sure the correct buffer is being used. |
| EXPECT_EQ(data_db_.cursor(), db->cursor()); |
| db->DecodeUInt16(); |
| return DecodeStatus::kDecodeDone; |
| }; |
| |
| // Check that the base class honors it's member variable stop_decode_on_done_. |
| override_stop_decode_on_done_ = false; |
| stop_decode_on_done_ = true; |
| |
| EXPECT_EQ(DecodeStatus::kDecodeDone, |
| DecodeSegments(&data_db_, SelectRemaining())); |
| EXPECT_EQ(3u, data_db_.Offset()); |
| EXPECT_EQ(1u, start_decoding_calls_); |
| EXPECT_EQ(1u, resume_decoding_calls_); |
| EXPECT_EQ(1u, stop_decode_on_done_calls_); |
| } |
| |
| // Decode a random sized chunks, always reporting back kDecodeInProgress. |
| TEST_F(RandomDecoderTestTest, InProgressWhenEmpty) { |
| start_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, start_decoding_calls_); |
| // Consume up to 2 bytes. |
| if (db->HasData()) { |
| db->DecodeUInt8(); |
| if (db->HasData()) { |
| db->DecodeUInt8(); |
| } |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| resume_decoding_fn_ = [](DecodeBuffer* db) { |
| // Consume all available bytes. |
| if (db->HasData()) { |
| db->AdvanceCursor(db->Remaining()); |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| |
| EXPECT_EQ(DecodeStatus::kDecodeInProgress, |
| DecodeSegments(&data_db_, SelectRandom(kMayReturnZeroOnFirst))); |
| EXPECT_TRUE(data_db_.Empty()); |
| EXPECT_EQ(1u, start_decoding_calls_); |
| EXPECT_LE(1u, resume_decoding_calls_); |
| EXPECT_EQ(0u, stop_decode_on_done_calls_); |
| } |
| |
| TEST_F(RandomDecoderTestTest, DoneExactlyAtEnd) { |
| start_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, start_decoding_calls_); |
| EXPECT_EQ(1u, db->Remaining()); |
| EXPECT_EQ(1u, db->FullSize()); |
| db->DecodeUInt8(); |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| resume_decoding_fn_ = [this](DecodeBuffer* db) { |
| EXPECT_EQ(1u, db->Remaining()); |
| EXPECT_EQ(1u, db->FullSize()); |
| db->DecodeUInt8(); |
| if (data_db_.Remaining() == 1) { |
| return DecodeStatus::kDecodeDone; |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| override_stop_decode_on_done_ = true; |
| sub_stop_decode_on_done_ = true; |
| |
| EXPECT_EQ(DecodeStatus::kDecodeDone, DecodeSegments(&data_db_, SelectOne())); |
| EXPECT_EQ(0u, data_db_.Remaining()); |
| EXPECT_EQ(1u, start_decoding_calls_); |
| EXPECT_EQ((sizeof kData) - 1, resume_decoding_calls_); |
| // Didn't need to call StopDecodeOnDone because we didn't finish early. |
| EXPECT_EQ(0u, stop_decode_on_done_calls_); |
| } |
| |
| TEST_F(RandomDecoderTestTest, DecodeSeveralWaysToEnd) { |
| // Each call to StartDecoding or ResumeDecoding will consume all that is |
| // available. When all the data has been consumed, returns kDecodeDone. |
| size_t decoded_since_start = 0; |
| auto shared_fn = [&decoded_since_start, this](DecodeBuffer* db) { |
| decoded_since_start += db->Remaining(); |
| db->AdvanceCursor(db->Remaining()); |
| EXPECT_EQ(0u, db->Remaining()); |
| if (decoded_since_start == data_db_.FullSize()) { |
| return DecodeStatus::kDecodeDone; |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| |
| start_decoding_fn_ = [&decoded_since_start, shared_fn](DecodeBuffer* db) { |
| decoded_since_start = 0; |
| return shared_fn(db); |
| }; |
| resume_decoding_fn_ = shared_fn; |
| |
| Validator validator = ValidateDoneAndEmpty(); |
| |
| EXPECT_TRUE(DecodeAndValidateSeveralWays(&data_db_, kMayReturnZeroOnFirst, |
| validator)); |
| |
| // We should have reached the end. |
| EXPECT_EQ(0u, data_db_.Remaining()); |
| |
| // We currently have 4 ways of decoding; update this if that changes. |
| EXPECT_EQ(4u, start_decoding_calls_); |
| |
| // Didn't need to call StopDecodeOnDone because we didn't finish early. |
| EXPECT_EQ(0u, stop_decode_on_done_calls_); |
| } |
| |
| TEST_F(RandomDecoderTestTest, DecodeTwoWaysAndStopEarly) { |
| // On the second decode, return kDecodeDone before finishing. |
| size_t decoded_since_start = 0; |
| auto shared_fn = [&decoded_since_start, this](DecodeBuffer* db) { |
| uint32_t amount = db->Remaining(); |
| if (start_decoding_calls_ == 2 && amount > 1) { |
| amount = 1; |
| } |
| decoded_since_start += amount; |
| db->AdvanceCursor(amount); |
| if (decoded_since_start == data_db_.FullSize()) { |
| return DecodeStatus::kDecodeDone; |
| } |
| if (decoded_since_start > 1 && start_decoding_calls_ == 2) { |
| return DecodeStatus::kDecodeDone; |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| |
| start_decoding_fn_ = [&decoded_since_start, shared_fn](DecodeBuffer* db) { |
| decoded_since_start = 0; |
| return shared_fn(db); |
| }; |
| resume_decoding_fn_ = shared_fn; |
| |
| // We expect the first and second to succeed, but the second to end at a |
| // different offset, which DecodeAndValidateSeveralWays should complain about. |
| Validator validator = [this](const DecodeBuffer& /*input*/, |
| DecodeStatus status) -> AssertionResult { |
| if (start_decoding_calls_ <= 2 && status != DecodeStatus::kDecodeDone) { |
| return ::testing::AssertionFailure() |
| << "Expected DecodeStatus::kDecodeDone, not " << status; |
| } |
| if (start_decoding_calls_ > 2) { |
| return ::testing::AssertionFailure() |
| << "How did we get to pass " << start_decoding_calls_; |
| } |
| return ::testing::AssertionSuccess(); |
| }; |
| |
| EXPECT_FALSE(DecodeAndValidateSeveralWays(&data_db_, kMayReturnZeroOnFirst, |
| validator)); |
| EXPECT_EQ(2u, start_decoding_calls_); |
| EXPECT_EQ(1u, stop_decode_on_done_calls_); |
| } |
| |
| TEST_F(RandomDecoderTestTest, DecodeThreeWaysAndError) { |
| // Return kDecodeError from ResumeDecoding on the third decoding pass. |
| size_t decoded_since_start = 0; |
| auto shared_fn = [&decoded_since_start, this](DecodeBuffer* db) { |
| if (start_decoding_calls_ == 3 && decoded_since_start > 0) { |
| return DecodeStatus::kDecodeError; |
| } |
| uint32_t amount = db->Remaining(); |
| if (start_decoding_calls_ == 3 && amount > 1) { |
| amount = 1; |
| } |
| decoded_since_start += amount; |
| db->AdvanceCursor(amount); |
| if (decoded_since_start == data_db_.FullSize()) { |
| return DecodeStatus::kDecodeDone; |
| } |
| return DecodeStatus::kDecodeInProgress; |
| }; |
| |
| start_decoding_fn_ = [&decoded_since_start, shared_fn](DecodeBuffer* db) { |
| decoded_since_start = 0; |
| return shared_fn(db); |
| }; |
| resume_decoding_fn_ = shared_fn; |
| |
| Validator validator = ValidateDoneAndEmpty(); |
| EXPECT_FALSE(DecodeAndValidateSeveralWays(&data_db_, kReturnNonZeroOnFirst, |
| validator)); |
| EXPECT_EQ(3u, start_decoding_calls_); |
| EXPECT_EQ(0u, stop_decode_on_done_calls_); |
| } |
| |
| // CorruptEnum should produce lots of different values. On the assumption that |
| // the enum gets at least a byte of storage, we should be able to produce |
| // 256 distinct values. |
| TEST(CorruptEnumTest, ManyValues) { |
| std::set<uint64_t> values; |
| DecodeStatus status; |
| QUICHE_LOG(INFO) << "sizeof status = " << sizeof status; |
| Http2Random rng; |
| for (int ndx = 0; ndx < 256; ++ndx) { |
| CorruptEnum(&status, &rng); |
| values.insert(static_cast<uint64_t>(status)); |
| } |
| } |
| |
| // In practice the underlying type is an int, and currently that is 4 bytes. |
| typedef typename std::underlying_type<DecodeStatus>::type DecodeStatusUT; |
| |
| struct CorruptEnumTestStruct { |
| DecodeStatusUT filler1; |
| DecodeStatus status; |
| DecodeStatusUT filler2; |
| }; |
| |
| // CorruptEnum should only overwrite the enum, not any adjacent storage. |
| TEST(CorruptEnumTest, CorruptsOnlyEnum) { |
| Http2Random rng; |
| for (const DecodeStatusUT filler : {DecodeStatusUT(), ~DecodeStatusUT()}) { |
| QUICHE_LOG(INFO) << "filler=0x" << std::hex << filler; |
| CorruptEnumTestStruct s; |
| s.filler1 = filler; |
| s.filler2 = filler; |
| for (int ndx = 0; ndx < 256; ++ndx) { |
| CorruptEnum(&s.status, &rng); |
| EXPECT_EQ(s.filler1, filler); |
| EXPECT_EQ(s.filler2, filler); |
| } |
| } |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace http2 |