| #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 |