blob: b3439e9bb23e0d22bb88edf1acbaadef3743d5c2 [file] [log] [blame]
// 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