| // Copyright 2016 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/http2/test_tools/random_decoder_test_base.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "quiche/http2/decoder/decode_buffer.h" |
| #include "quiche/http2/decoder/decode_status.h" |
| #include "quiche/http2/http2_constants.h" |
| #include "quiche/http2/test_tools/verify_macros.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| |
| using ::testing::AssertionResult; |
| |
| namespace http2 { |
| namespace test { |
| |
| RandomDecoderTest::RandomDecoderTest() = default; |
| |
| bool RandomDecoderTest::StopDecodeOnDone() { return stop_decode_on_done_; } |
| |
| DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original, |
| const SelectSize& select_size) { |
| DecodeStatus status = DecodeStatus::kDecodeInProgress; |
| bool first = true; |
| QUICHE_VLOG(2) << "DecodeSegments: input size=" << original->Remaining(); |
| while (first || original->HasData()) { |
| size_t remaining = original->Remaining(); |
| size_t size = |
| std::min(remaining, select_size(first, original->Offset(), remaining)); |
| DecodeBuffer db(original->cursor(), size); |
| QUICHE_VLOG(2) << "Decoding " << size << " bytes of " << remaining |
| << " remaining"; |
| if (first) { |
| first = false; |
| status = StartDecoding(&db); |
| } else { |
| status = ResumeDecoding(&db); |
| } |
| // A decoder MUST consume some input (if any is available), else we could |
| // get stuck in infinite loops. |
| if (db.Offset() == 0 && db.HasData() && |
| status != DecodeStatus::kDecodeError) { |
| ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize=" |
| << db.FullSize() |
| << " original.Offset=" << original->Offset(); |
| return DecodeStatus::kDecodeError; |
| } |
| original->AdvanceCursor(db.Offset()); |
| switch (status) { |
| case DecodeStatus::kDecodeDone: |
| if (original->Empty() || StopDecodeOnDone()) { |
| return DecodeStatus::kDecodeDone; |
| } |
| continue; |
| case DecodeStatus::kDecodeInProgress: |
| continue; |
| case DecodeStatus::kDecodeError: |
| return DecodeStatus::kDecodeError; |
| } |
| } |
| return status; |
| } |
| |
| // Decode |original| multiple times, with different segmentations, validating |
| // after each decode, returning on the first failure. |
| AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays( |
| DecodeBuffer* original, bool return_non_zero_on_first, |
| const Validator& validator) { |
| const uint32_t original_remaining = original->Remaining(); |
| QUICHE_VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = " |
| << original_remaining; |
| uint32_t first_consumed; |
| { |
| // Fast decode (no stopping unless decoder does so). |
| DecodeBuffer input(original->cursor(), original_remaining); |
| QUICHE_VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining"; |
| HTTP2_VERIFY_SUCCESS( |
| DecodeSegmentsAndValidate(&input, SelectRemaining(), validator)) |
| << "\nFailed with SelectRemaining; input.Offset=" << input.Offset() |
| << "; input.Remaining=" << input.Remaining(); |
| first_consumed = input.Offset(); |
| } |
| if (original_remaining <= 30) { |
| // Decode again, one byte at a time. |
| DecodeBuffer input(original->cursor(), original_remaining); |
| QUICHE_VLOG(2) << "DecodeSegmentsAndValidate with SelectOne"; |
| HTTP2_VERIFY_SUCCESS( |
| DecodeSegmentsAndValidate(&input, SelectOne(), validator)) |
| << "\nFailed with SelectOne; input.Offset=" << input.Offset() |
| << "; input.Remaining=" << input.Remaining(); |
| HTTP2_VERIFY_EQ(first_consumed, input.Offset()) |
| << "\nFailed with SelectOne"; |
| } |
| if (original_remaining <= 20) { |
| // Decode again, one or zero bytes at a time. |
| DecodeBuffer input(original->cursor(), original_remaining); |
| QUICHE_VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne"; |
| HTTP2_VERIFY_SUCCESS(DecodeSegmentsAndValidate( |
| &input, SelectZeroAndOne(return_non_zero_on_first), validator)) |
| << "\nFailed with SelectZeroAndOne"; |
| HTTP2_VERIFY_EQ(first_consumed, input.Offset()) |
| << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset() |
| << "; input.Remaining=" << input.Remaining(); |
| } |
| { |
| // Decode again, with randomly selected segment sizes. |
| DecodeBuffer input(original->cursor(), original_remaining); |
| QUICHE_VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom"; |
| HTTP2_VERIFY_SUCCESS(DecodeSegmentsAndValidate( |
| &input, SelectRandom(return_non_zero_on_first), validator)) |
| << "\nFailed with SelectRandom; input.Offset=" << input.Offset() |
| << "; input.Remaining=" << input.Remaining(); |
| HTTP2_VERIFY_EQ(first_consumed, input.Offset()) |
| << "\nFailed with SelectRandom"; |
| } |
| HTTP2_VERIFY_EQ(original_remaining, original->Remaining()); |
| original->AdvanceCursor(first_consumed); |
| QUICHE_VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS"; |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // static |
| RandomDecoderTest::SelectSize RandomDecoderTest::SelectZeroAndOne( |
| bool return_non_zero_on_first) { |
| std::shared_ptr<bool> zero_next(new bool); |
| *zero_next = !return_non_zero_on_first; |
| return [zero_next](bool /*first*/, size_t /*offset*/, |
| size_t /*remaining*/) -> size_t { |
| if (*zero_next) { |
| *zero_next = false; |
| return 0; |
| } else { |
| *zero_next = true; |
| return 1; |
| } |
| }; |
| } |
| |
| RandomDecoderTest::SelectSize RandomDecoderTest::SelectRandom( |
| bool return_non_zero_on_first) { |
| return [this, return_non_zero_on_first](bool first, size_t /*offset*/, |
| size_t remaining) -> size_t { |
| uint32_t r = random_.Rand32(); |
| if (first && return_non_zero_on_first) { |
| QUICHE_CHECK_LT(0u, remaining); |
| if (remaining == 1) { |
| return 1; |
| } |
| return 1 + (r % remaining); // size in range [1, remaining). |
| } |
| return r % (remaining + 1); // size in range [0, remaining]. |
| }; |
| } |
| |
| uint32_t RandomDecoderTest::RandStreamId() { |
| return random_.Rand32() & StreamIdMask(); |
| } |
| |
| } // namespace test |
| } // namespace http2 |