blob: 40b1e44e34d3508962b6e688dd032d094a44d0e3 [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.
#ifndef QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
#define QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
// RandomDecoderTest is a base class for tests of decoding various kinds
// of HTTP/2 and HPACK encodings.
// TODO(jamessynge): Move more methods into .cc file.
#include <stddef.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <type_traits>
#include "testing/gtest/include/gtest/gtest.h"
#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_logging.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
namespace http2 {
namespace test {
// Some helpers.
template <typename T, size_t N>
quiche::QuicheStringPiece ToStringPiece(T (&data)[N]) {
return quiche::QuicheStringPiece(reinterpret_cast<const char*>(data),
N * sizeof(T));
}
// Overwrite the enum with some random value, probably not a valid value for
// the enum type, but which fits into its storage.
template <typename T,
typename E = typename std::enable_if<std::is_enum<T>::value>::type>
void CorruptEnum(T* out, Http2Random* rng) {
// Per cppreference.com, if the destination type of a static_cast is
// smaller than the source type (i.e. type of r and uint32 below), the
// resulting value is the smallest unsigned value equal to the source value
// modulo 2^n, where n is the number of bits used to represent the
// destination type unsigned U.
using underlying_type_T = typename std::underlying_type<T>::type;
using unsigned_underlying_type_T =
typename std::make_unsigned<underlying_type_T>::type;
auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32());
*out = static_cast<T>(r);
}
// Base class for tests of the ability to decode a sequence of bytes with
// various boundaries between the DecodeBuffers provided to the decoder.
class RandomDecoderTest : public ::testing::Test {
public:
// SelectSize returns the size of the next DecodeBuffer to be passed to the
// decoder. Note that RandomDecoderTest allows that size to be zero, though
// some decoders can't deal with that on the first byte, hence the |first|
// parameter.
typedef std::function<size_t(bool first, size_t offset, size_t remaining)>
SelectSize;
// Validator returns an AssertionResult so test can do:
// EXPECT_THAT(DecodeAndValidate(..., validator));
typedef ::testing::AssertionResult AssertionResult;
typedef std::function<AssertionResult(const DecodeBuffer& input,
DecodeStatus status)>
Validator;
typedef std::function<AssertionResult()> NoArgValidator;
RandomDecoderTest();
protected:
// TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return
// AssertionResult so that the VERIFY_* methods exported from
// gunit_helpers.h can be widely used.
// Start decoding; call allows sub-class to Reset the decoder, or deal with
// the first byte if that is done in a unique fashion. Might be called with
// a zero byte buffer.
virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0;
// Resume decoding of the input after a prior call to StartDecoding, and
// possibly many calls to ResumeDecoding.
virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0;
// Return true if a decode status of kDecodeDone indicates that
// decoding should stop.
virtual bool StopDecodeOnDone();
// Decode buffer |original| until we run out of input, or kDecodeDone is
// returned by the decoder AND StopDecodeOnDone() returns true. Segments
// (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
// by calling |select_size| to decide how large each buffer should be.
// We do this to test the ability to deal with arbitrary boundaries, as might
// happen in transport.
// Returns the final DecodeStatus.
DecodeStatus DecodeSegments(DecodeBuffer* original,
const SelectSize& select_size);
// Decode buffer |original| until we run out of input, or kDecodeDone is
// returned by the decoder AND StopDecodeOnDone() returns true. Segments
// (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
// by calling |select_size| to decide how large each buffer should be.
// We do this to test the ability to deal with arbitrary boundaries, as might
// happen in transport.
// Invokes |validator| with the final decode status and the original decode
// buffer, with the cursor advanced as far as has been consumed by the decoder
// and returns validator's result.
::testing::AssertionResult DecodeSegmentsAndValidate(
DecodeBuffer* original,
const SelectSize& select_size,
const Validator& validator) {
DecodeStatus status = DecodeSegments(original, select_size);
VERIFY_AND_RETURN_SUCCESS(validator(*original, status));
}
// Returns a SelectSize function for fast decoding, i.e. passing all that
// is available to the decoder.
static SelectSize SelectRemaining() {
return [](bool first, size_t offset, size_t remaining) -> size_t {
return remaining;
};
}
// Returns a SelectSize function for decoding a single byte at a time.
static SelectSize SelectOne() {
return
[](bool first, size_t offset, size_t remaining) -> size_t { return 1; };
}
// Returns a SelectSize function for decoding a single byte at a time, where
// zero byte buffers are also allowed. Alternates between zero and one.
static SelectSize SelectZeroAndOne(bool return_non_zero_on_first);
// Returns a SelectSize function for decoding random sized segments.
SelectSize SelectRandom(bool return_non_zero_on_first);
// Decode |original| multiple times, with different segmentations of the
// decode buffer, validating after each decode, and confirming that they
// each decode the same amount. Returns on the first failure, else returns
// success.
AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original,
bool return_non_zero_on_first,
const Validator& validator);
static Validator ToValidator(std::nullptr_t) {
return [](const DecodeBuffer& input, DecodeStatus status) {
return ::testing::AssertionSuccess();
};
}
static Validator ToValidator(const Validator& validator) {
if (validator == nullptr) {
return ToValidator(nullptr);
}
return validator;
}
static Validator ToValidator(const NoArgValidator& validator) {
if (validator == nullptr) {
return ToValidator(nullptr);
}
return [validator](const DecodeBuffer& input, DecodeStatus status) {
return validator();
};
}
// Wraps a validator with another validator
// that first checks that the DecodeStatus is kDecodeDone and
// that the DecodeBuffer is empty.
// TODO(jamessynge): Replace this overload with the next, as using this method
// usually means that the wrapped function doesn't need to be passed the
// DecodeBuffer nor the DecodeStatus.
static Validator ValidateDoneAndEmpty(const Validator& wrapped) {
return [wrapped](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeDone);
VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
if (wrapped) {
return wrapped(input, status);
}
return ::testing::AssertionSuccess();
};
}
static Validator ValidateDoneAndEmpty(NoArgValidator wrapped) {
return [wrapped](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeDone);
VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
if (wrapped) {
return wrapped();
}
return ::testing::AssertionSuccess();
};
}
static Validator ValidateDoneAndEmpty() {
NoArgValidator validator;
return ValidateDoneAndEmpty(validator);
}
// Wraps a validator with another validator
// that first checks that the DecodeStatus is kDecodeDone and
// that the DecodeBuffer has the expected offset.
// TODO(jamessynge): Replace this overload with the next, as using this method
// usually means that the wrapped function doesn't need to be passed the
// DecodeBuffer nor the DecodeStatus.
static Validator ValidateDoneAndOffset(uint32_t offset,
const Validator& wrapped) {
return [wrapped, offset](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeDone);
VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
if (wrapped) {
return wrapped(input, status);
}
return ::testing::AssertionSuccess();
};
}
static Validator ValidateDoneAndOffset(uint32_t offset,
NoArgValidator wrapped) {
return [wrapped, offset](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeDone);
VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
if (wrapped) {
return wrapped();
}
return ::testing::AssertionSuccess();
};
}
static Validator ValidateDoneAndOffset(uint32_t offset) {
NoArgValidator validator;
return ValidateDoneAndOffset(offset, validator);
}
// Expose |random_| as Http2Random so callers don't have to care about which
// sub-class of Http2Random is used, nor can they rely on the specific
// sub-class that RandomDecoderTest uses.
Http2Random& Random() { return random_; }
Http2Random* RandomPtr() { return &random_; }
uint32_t RandStreamId();
bool stop_decode_on_done_ = true;
private:
Http2Random random_;
};
} // namespace test
} // namespace http2
#endif // QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_