blob: 37246c4626b38f5f800352bc20dca01c652d4617 [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 "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined
// in net/third_party/quiche/src/http2/http2_structures.h).
#include <stddef.h>
#include <string>
#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/http2_constants.h"
#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_logging.h"
#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
using ::testing::AssertionResult;
namespace http2 {
namespace test {
namespace {
template <typename T, size_t N>
quiche::QuicheStringPiece ToStringPiece(T (&data)[N]) {
return quiche::QuicheStringPiece(reinterpret_cast<const char*>(data),
N * sizeof(T));
}
template <class S>
std::string SerializeStructure(const S& s) {
Http2FrameBuilder fb;
fb.Append(s);
EXPECT_EQ(S::EncodedSize(), fb.size());
return fb.buffer();
}
template <class S>
class StructureDecoderTest : public ::testing::Test {
protected:
typedef S Structure;
StructureDecoderTest() : random_(), random_decode_count_(100) {}
// Set the fields of |*p| to random values.
void Randomize(S* p) { ::http2::test::Randomize(p, &random_); }
// Fully decodes the Structure at the start of data, and confirms it matches
// *expected (if provided).
void DecodeLeadingStructure(const S* expected,
quiche::QuicheStringPiece data) {
ASSERT_LE(S::EncodedSize(), data.size());
DecodeBuffer db(data);
Randomize(&structure_);
DoDecode(&structure_, &db);
EXPECT_EQ(db.Offset(), S::EncodedSize());
if (expected != nullptr) {
EXPECT_EQ(structure_, *expected);
}
}
template <size_t N>
void DecodeLeadingStructure(const char (&data)[N]) {
DecodeLeadingStructure(nullptr, quiche::QuicheStringPiece(data, N));
}
// Encode the structure |in_s| into bytes, then decode the bytes
// and validate that the decoder produced the same field values.
void EncodeThenDecode(const S& in_s) {
std::string bytes = SerializeStructure(in_s);
EXPECT_EQ(S::EncodedSize(), bytes.size());
DecodeLeadingStructure(&in_s, bytes);
}
// Generate
void TestDecodingRandomizedStructures(size_t count) {
for (size_t i = 0; i < count && !HasFailure(); ++i) {
Structure input;
Randomize(&input);
EncodeThenDecode(input);
}
}
void TestDecodingRandomizedStructures() {
TestDecodingRandomizedStructures(random_decode_count_);
}
Http2Random random_;
const size_t random_decode_count_;
uint32_t decode_offset_ = 0;
S structure_;
size_t fast_decode_count_ = 0;
size_t slow_decode_count_ = 0;
};
class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {};
TEST_F(FrameHeaderDecoderTest, DecodesLiteral) {
{
// Realistic input.
const char kData[] = {
'\x00', '\x00', '\x05', // Payload length: 5
'\x01', // Frame type: HEADERS
'\x08', // Flags: PADDED
'\x00', '\x00', '\x00', '\x01', // Stream ID: 1
'\x04', // Padding length: 4
'\x00', '\x00', '\x00', '\x00', // Padding bytes
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(5u, structure_.payload_length);
EXPECT_EQ(Http2FrameType::HEADERS, structure_.type);
EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags);
EXPECT_EQ(1u, structure_.stream_id);
}
}
{
// Unlikely input.
const char kData[] = {
'\xff', '\xff', '\xff', // Payload length: uint24 max
'\xff', // Frame type: Unknown
'\xff', // Flags: Unknown/All
'\xff', '\xff', '\xff', '\xff', // Stream ID: uint31 max, plus R-bit
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ((1u << 24) - 1, structure_.payload_length);
EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type);
EXPECT_EQ(255, structure_.flags);
EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id);
}
}
}
TEST_F(FrameHeaderDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class PriorityFieldsDecoderTest
: public StructureDecoderTest<Http2PriorityFields> {};
TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x80', '\x00', '\x00', '\x05', // Exclusive (yes) and Dependency (5)
'\xff', // Weight: 256 (after adding 1)
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(5u, structure_.stream_dependency);
EXPECT_EQ(256u, structure_.weight);
EXPECT_EQ(true, structure_.is_exclusive);
}
}
{
const char kData[] = {
'\x7f', '\xff',
'\xff', '\xff', // Exclusive (no) and Dependency (0x7fffffff)
'\x00', // Weight: 1 (after adding 1)
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(StreamIdMask(), structure_.stream_dependency);
EXPECT_EQ(1u, structure_.weight);
EXPECT_FALSE(structure_.is_exclusive);
}
}
}
TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class RstStreamFieldsDecoderTest
: public StructureDecoderTest<Http2RstStreamFields> {};
TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_TRUE(structure_.IsSupportedErrorCode());
EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code);
}
}
{
const char kData[] = {
'\xff', '\xff', '\xff',
'\xff', // Error: max uint32 (Unknown error code)
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_FALSE(structure_.IsSupportedErrorCode());
EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
}
}
}
TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class SettingFieldsDecoderTest
: public StructureDecoderTest<Http2SettingFields> {};
TEST_F(SettingFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x01', // Setting: HEADER_TABLE_SIZE
'\x00', '\x00', '\x40', '\x00', // Value: 16K
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_TRUE(structure_.IsSupportedParameter());
EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE,
structure_.parameter);
EXPECT_EQ(1u << 14, structure_.value);
}
}
{
const char kData[] = {
'\x00', '\x00', // Setting: Unknown (0)
'\xff', '\xff', '\xff', '\xff', // Value: max uint32
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_FALSE(structure_.IsSupportedParameter());
EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter);
}
}
}
TEST_F(SettingFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class PushPromiseFieldsDecoderTest
: public StructureDecoderTest<Http2PushPromiseFields> {};
TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x01', '\x8a', '\x92', // Promised Stream ID: 101010
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(101010u, structure_.promised_stream_id);
}
}
{
// Promised stream id has R-bit (reserved for future use) set, which
// should be cleared by the decoder.
const char kData[] = {
'\xff', '\xff', '\xff',
'\xff', // Promised Stream ID: max uint31 and R-bit
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id);
}
}
}
TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {};
TEST_F(PingFieldsDecoderTest, DecodesLiteral) {
{
// Each byte is different, so can detect if order changed.
const char kData[] = {
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(quiche::QuicheStringPiece(kData, 8),
ToStringPiece(structure_.opaque_bytes));
}
}
{
// All zeros, detect problems handling NULs.
const char kData[] = {
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(quiche::QuicheStringPiece(kData, 8),
ToStringPiece(structure_.opaque_bytes));
}
}
{
const char kData[] = {
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(quiche::QuicheStringPiece(kData, 8),
ToStringPiece(structure_.opaque_bytes));
}
}
}
TEST_F(PingFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> {
};
TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x00', '\x00', '\x00', // Last Stream ID: 0
'\x00', '\x00', '\x00', '\x00', // Error: NO_ERROR (0)
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(0u, structure_.last_stream_id);
EXPECT_TRUE(structure_.IsSupportedErrorCode());
EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code);
}
}
{
const char kData[] = {
'\x00', '\x00', '\x00', '\x01', // Last Stream ID: 1
'\x00', '\x00', '\x00', '\x0d', // Error: HTTP_1_1_REQUIRED
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(1u, structure_.last_stream_id);
EXPECT_TRUE(structure_.IsSupportedErrorCode());
EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code);
}
}
{
const char kData[] = {
'\xff', '\xff',
'\xff', '\xff', // Last Stream ID: max uint31 and R-bit
'\xff', '\xff',
'\xff', '\xff', // Error: max uint32 (Unknown error code)
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit.
EXPECT_FALSE(structure_.IsSupportedErrorCode());
EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
}
}
}
TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class WindowUpdateFieldsDecoderTest
: public StructureDecoderTest<Http2WindowUpdateFields> {};
TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x01', '\x00', '\x00', // Window Size Increment: 2 ^ 16
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(1u << 16, structure_.window_size_increment);
}
}
{
// Increment must be non-zero, but we need to be able to decode the invalid
// zero to detect it.
const char kData[] = {
'\x00', '\x00', '\x00', '\x00', // Window Size Increment: 0
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(0u, structure_.window_size_increment);
}
}
{
// Increment has R-bit (reserved for future use) set, which
// should be cleared by the decoder.
// clang-format off
const char kData[] = {
// Window Size Increment: max uint31 and R-bit
'\xff', '\xff', '\xff', '\xff',
};
// clang-format on
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(StreamIdMask(), structure_.window_size_increment);
}
}
}
TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
//------------------------------------------------------------------------------
class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> {
};
TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) {
{
const char kData[] = {
'\x00', '\x00', // Origin Length: 0
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(0, structure_.origin_length);
}
}
{
const char kData[] = {
'\x00', '\x14', // Origin Length: 20
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(20, structure_.origin_length);
}
}
{
const char kData[] = {
'\xff', '\xff', // Origin Length: uint16 max
};
DecodeLeadingStructure(kData);
if (!HasFailure()) {
EXPECT_EQ(65535, structure_.origin_length);
}
}
}
TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) {
TestDecodingRandomizedStructures();
}
} // namespace
} // namespace test
} // namespace http2