blob: 50e75edf454db8f922e71f9dab1c9ac313ff2c1e [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/http2_frame_decoder.h"
// Tests of Http2FrameDecoder.
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#include "net/third_party/quiche/src/http2/http2_constants.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_logging.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h"
#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
namespace http2 {
namespace test {
class Http2FrameDecoderPeer {
public:
static size_t remaining_total_payload(Http2FrameDecoder* decoder) {
return decoder->frame_decoder_state_.remaining_total_payload();
}
};
namespace {
class Http2FrameDecoderTest : public RandomDecoderTest {
protected:
void SetUp() override {
// On any one run of this suite, we'll always choose the same value for
// use_default_reconstruct_ because the random seed is the same for each
// test case, but across runs the random seed changes.
use_default_reconstruct_ = Random().OneIn(2);
}
DecodeStatus StartDecoding(DecodeBuffer* db) override {
HTTP2_DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
collector_.Reset();
PrepareDecoder();
DecodeStatus status = decoder_.DecodeFrame(db);
if (status != DecodeStatus::kDecodeInProgress) {
// Keep track of this so that a concrete test can verify that both fast
// and slow decoding paths have been tested.
++fast_decode_count_;
if (status == DecodeStatus::kDecodeError) {
ConfirmDiscardsRemainingPayload();
}
}
return status;
}
DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
HTTP2_DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
DecodeStatus status = decoder_.DecodeFrame(db);
if (status != DecodeStatus::kDecodeInProgress) {
// Keep track of this so that a concrete test can verify that both fast
// and slow decoding paths have been tested.
++slow_decode_count_;
if (status == DecodeStatus::kDecodeError) {
ConfirmDiscardsRemainingPayload();
}
}
return status;
}
// When an error is returned, the decoder is in state kDiscardPayload, and
// stays there until the remaining bytes of the frame's payload have been
// skipped over. There are no callbacks for this situation.
void ConfirmDiscardsRemainingPayload() {
ASSERT_TRUE(decoder_.IsDiscardingPayload());
size_t remaining =
Http2FrameDecoderPeer::remaining_total_payload(&decoder_);
// The decoder will discard the remaining bytes, but not go beyond that,
// which these conditions verify.
size_t extra = 10;
Http2String junk(remaining + extra, '0');
DecodeBuffer tmp(junk);
EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.DecodeFrame(&tmp));
EXPECT_EQ(remaining, tmp.Offset());
EXPECT_EQ(extra, tmp.Remaining());
EXPECT_FALSE(decoder_.IsDiscardingPayload());
}
void PrepareDecoder() {
// Save and restore the maximum_payload_size when reconstructing
// the decoder.
size_t maximum_payload_size = decoder_.maximum_payload_size();
// Alternate which constructor is used.
if (use_default_reconstruct_) {
Http2DefaultReconstructObject(&decoder_, RandomPtr());
decoder_.set_listener(&collector_);
} else {
Http2ReconstructObject(&decoder_, RandomPtr(), &collector_);
}
decoder_.set_maximum_payload_size(maximum_payload_size);
use_default_reconstruct_ = !use_default_reconstruct_;
}
void ResetDecodeSpeedCounters() {
fast_decode_count_ = 0;
slow_decode_count_ = 0;
}
AssertionResult VerifyCollected(const FrameParts& expected) {
VERIFY_FALSE(collector_.IsInProgress());
VERIFY_EQ(1u, collector_.size());
VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*collector_.frame(0)));
}
AssertionResult DecodePayloadAndValidateSeveralWays(Http2StringPiece payload,
Validator validator) {
DecodeBuffer db(payload);
bool start_decoding_requires_non_empty = false;
return DecodeAndValidateSeveralWays(&db, start_decoding_requires_non_empty,
validator);
}
// Decode one frame's payload and confirm that the listener recorded the
// expected FrameParts instance, and only one FrameParts instance. The
// payload will be decoded several times with different partitionings
// of the payload, and after each the validator will be called.
AssertionResult DecodePayloadAndValidateSeveralWays(
Http2StringPiece payload,
const FrameParts& expected) {
auto validator = [&expected, this](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeDone);
VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
};
ResetDecodeSpeedCounters();
VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
payload, ValidateDoneAndEmpty(validator)));
VERIFY_GT(fast_decode_count_, 0u);
VERIFY_GT(slow_decode_count_, 0u);
// Repeat with more input; it should stop without reading that input.
Http2String next_frame = Random().RandString(10);
Http2String input(payload.data(), payload.size());
input += next_frame;
ResetDecodeSpeedCounters();
VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
payload, ValidateDoneAndOffset(payload.size(), validator)));
VERIFY_GT(fast_decode_count_, 0u);
VERIFY_GT(slow_decode_count_, 0u);
return AssertionSuccess();
}
template <size_t N>
AssertionResult DecodePayloadAndValidateSeveralWays(
const char (&buf)[N],
const FrameParts& expected) {
return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
expected);
}
template <size_t N>
AssertionResult DecodePayloadAndValidateSeveralWays(
const char (&buf)[N],
const Http2FrameHeader& header) {
return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
FrameParts(header));
}
template <size_t N>
AssertionResult DecodePayloadExpectingError(const char (&buf)[N],
const FrameParts& expected) {
auto validator = [&expected, this](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeError);
VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
};
ResetDecodeSpeedCounters();
EXPECT_TRUE(
DecodePayloadAndValidateSeveralWays(ToStringPiece(buf), validator));
EXPECT_GT(fast_decode_count_, 0u);
EXPECT_GT(slow_decode_count_, 0u);
return AssertionSuccess();
}
template <size_t N>
AssertionResult DecodePayloadExpectingFrameSizeError(const char (&buf)[N],
FrameParts expected) {
expected.SetHasFrameSizeError(true);
VERIFY_AND_RETURN_SUCCESS(DecodePayloadExpectingError(buf, expected));
}
template <size_t N>
AssertionResult DecodePayloadExpectingFrameSizeError(
const char (&buf)[N],
const Http2FrameHeader& header) {
return DecodePayloadExpectingFrameSizeError(buf, FrameParts(header));
}
// Count of payloads that are fully decoded by StartDecodingPayload or for
// which an error was detected by StartDecodingPayload.
size_t fast_decode_count_ = 0;
// Count of payloads that required calling ResumeDecodingPayload in order to
// decode completely, or for which an error was detected by
// ResumeDecodingPayload.
size_t slow_decode_count_ = 0;
FramePartsCollectorListener collector_;
Http2FrameDecoder decoder_;
bool use_default_reconstruct_;
};
////////////////////////////////////////////////////////////////////////////////
// Tests that pass the minimum allowed size for the frame type, which is often
// empty. The tests are in order by frame type value (i.e. 0 for DATA frames).
TEST_F(Http2FrameDecoderTest, DataEmpty) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Payload length: 0
'\x00', // DATA
'\x00', // Flags: none
'\x00', '\x00', '\x00',
'\x00', // Stream ID: 0 (invalid but unchecked here)
};
Http2FrameHeader header(0, Http2FrameType::DATA, 0, 0);
FrameParts expected(header, "");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeadersEmpty) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Payload length: 0
'\x01', // HEADERS
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream ID: 0 (REQUIRES ID)
};
Http2FrameHeader header(0, Http2FrameType::HEADERS, 0, 1);
FrameParts expected(header, "");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, Priority) {
const char kFrameData[] = {
'\x00', '\x00', '\x05', // Length: 5
'\x02', // Type: PRIORITY
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream: 2
'\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
'\x10', // Weight: 17
};
Http2FrameHeader header(5, Http2FrameType::PRIORITY, 0, 2);
FrameParts expected(header);
expected.SetOptPriority(Http2PriorityFields(1, 17, true));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, RstStream) {
const char kFrameData[] = {
'\x00', '\x00', '\x04', // Length: 4
'\x03', // Type: RST_STREAM
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
};
Http2FrameHeader header(4, Http2FrameType::RST_STREAM, 0, 1);
FrameParts expected(header);
expected.SetOptRstStreamErrorCode(Http2ErrorCode::PROTOCOL_ERROR);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, SettingsEmpty) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Length: 0
'\x04', // Type: SETTINGS
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here)
};
Http2FrameHeader header(0, Http2FrameType::SETTINGS, 0, 1);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, SettingsAck) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Length: 6
'\x04', // Type: SETTINGS
'\x01', // Flags: ACK
'\x00', '\x00', '\x00', '\x00', // Stream: 0
};
Http2FrameHeader header(0, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
FrameParts expected(header);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, PushPromiseMinimal) {
const char kFrameData[] = {
'\x00', '\x00', '\x04', // Payload length: 4
'\x05', // PUSH_PROMISE
'\x04', // Flags: END_HEADERS
'\x00', '\x00', '\x00',
'\x02', // Stream: 2 (invalid but unchecked here)
'\x00', '\x00', '\x00',
'\x01', // Promised: 1 (invalid but unchecked here)
};
Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
Http2FrameFlag::END_HEADERS, 2);
FrameParts expected(header, "");
expected.SetOptPushPromise(Http2PushPromiseFields{1});
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, Ping) {
const char kFrameData[] = {
'\x00', '\x00', '\x08', // Length: 8
'\x06', // Type: PING
'\xfe', // Flags: no valid flags
'\x00', '\x00', '\x00', '\x00', // Stream: 0
's', 'o', 'm', 'e', // "some"
'd', 'a', 't', 'a', // "data"
};
Http2FrameHeader header(8, Http2FrameType::PING, 0, 0);
FrameParts expected(header);
expected.SetOptPing(
Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, PingAck) {
const char kFrameData[] = {
'\x00', '\x00', '\x08', // Length: 8
'\x06', // Type: PING
'\xff', // Flags: ACK (plus all invalid flags)
'\x00', '\x00', '\x00', '\x00', // Stream: 0
's', 'o', 'm', 'e', // "some"
'd', 'a', 't', 'a', // "data"
};
Http2FrameHeader header(8, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
FrameParts expected(header);
expected.SetOptPing(
Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, GoAwayMinimal) {
const char kFrameData[] = {
'\x00', '\x00', '\x08', // Length: 8 (no opaque data)
'\x07', // Type: GOAWAY
'\xff', // Flags: 0xff (no valid flags)
'\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here)
'\x80', '\x00', '\x00', '\xff', // Last: 255 (plus R bit)
'\x00', '\x00', '\x00', '\x09', // Error: COMPRESSION_ERROR
};
Http2FrameHeader header(8, Http2FrameType::GOAWAY, 0, 1);
FrameParts expected(header);
expected.SetOptGoaway(
Http2GoAwayFields(255, Http2ErrorCode::COMPRESSION_ERROR));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, WindowUpdate) {
const char kFrameData[] = {
'\x00', '\x00', '\x04', // Length: 4
'\x08', // Type: WINDOW_UPDATE
'\x0f', // Flags: 0xff (no valid flags)
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit)
};
Http2FrameHeader header(4, Http2FrameType::WINDOW_UPDATE, 0, 1);
FrameParts expected(header);
expected.SetOptWindowUpdateIncrement(1024);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, ContinuationEmpty) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Payload length: 0
'\x09', // CONTINUATION
'\x00', // Flags: none
'\x00', '\x00', '\x00',
'\x00', // Stream ID: 0 (invalid but unchecked here)
};
Http2FrameHeader header(0, Http2FrameType::CONTINUATION, 0, 0);
FrameParts expected(header);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, AltSvcMinimal) {
const char kFrameData[] = {
'\x00', '\x00', '\x02', // Payload length: 2
'\x0a', // ALTSVC
'\xff', // Flags: none (plus 0xff)
'\x00', '\x00', '\x00',
'\x00', // Stream ID: 0 (invalid but unchecked here)
'\x00', '\x00', // Origin Length: 0
};
Http2FrameHeader header(2, Http2FrameType::ALTSVC, 0, 0);
FrameParts expected(header);
expected.SetOptAltsvcOriginLength(0);
expected.SetOptAltsvcValueLength(0);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, UnknownEmpty) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Payload length: 0
'\x20', // 32 (unknown)
'\xff', // Flags: all
'\x00', '\x00', '\x00', '\x00', // Stream ID: 0
};
Http2FrameHeader header(0, static_cast<Http2FrameType>(32), 0xff, 0);
FrameParts expected(header);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
////////////////////////////////////////////////////////////////////////////////
// Tests of longer payloads, for those frame types that allow longer payloads.
TEST_F(Http2FrameDecoderTest, DataPayload) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Payload length: 7
'\x00', // DATA
'\x80', // Flags: 0x80
'\x00', '\x00', '\x02', '\x02', // Stream ID: 514
'a', 'b', 'c', // Data
};
Http2FrameHeader header(3, Http2FrameType::DATA, 0, 514);
FrameParts expected(header, "abc");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeadersPayload) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Payload length: 3
'\x01', // HEADERS
'\x05', // Flags: END_STREAM | END_HEADERS
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
};
Http2FrameHeader header(
3, Http2FrameType::HEADERS,
Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS, 2);
FrameParts expected(header, "abc");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeadersPriority) {
const char kFrameData[] = {
'\x00', '\x00', '\x05', // Payload length: 5
'\x01', // HEADERS
'\x20', // Flags: PRIORITY
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'\x00', '\x00', '\x00', '\x01', // Parent: 1 (Not Exclusive)
'\xff', // Weight: 256
};
Http2FrameHeader header(5, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
2);
FrameParts expected(header);
expected.SetOptPriority(Http2PriorityFields(1, 256, false));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, Settings) {
const char kFrameData[] = {
'\x00', '\x00', '\x0c', // Length: 12
'\x04', // Type: SETTINGS
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x00', // Stream: 0
'\x00', '\x04', // Param: INITIAL_WINDOW_SIZE
'\x0a', '\x0b', '\x0c', '\x0d', // Value: 168496141
'\x00', '\x02', // Param: ENABLE_PUSH
'\x00', '\x00', '\x00', '\x03', // Value: 3 (invalid but unchecked here)
};
Http2FrameHeader header(12, Http2FrameType::SETTINGS, 0, 0);
FrameParts expected(header);
expected.AppendSetting(Http2SettingFields(
Http2SettingsParameter::INITIAL_WINDOW_SIZE, 168496141));
expected.AppendSetting(
Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, PushPromisePayload) {
const char kFrameData[] = {
'\x00', '\x00', 7, // Payload length: 7
'\x05', // PUSH_PROMISE
'\x04', // Flags: END_HEADERS
'\x00', '\x00', '\x00', '\xff', // Stream ID: 255
'\x00', '\x00', '\x01', '\x00', // Promised: 256
'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
};
Http2FrameHeader header(7, Http2FrameType::PUSH_PROMISE,
Http2FrameFlag::END_HEADERS, 255);
FrameParts expected(header, "abc");
expected.SetOptPushPromise(Http2PushPromiseFields{256});
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, GoAwayOpaqueData) {
const char kFrameData[] = {
'\x00', '\x00', '\x0e', // Length: 14
'\x07', // Type: GOAWAY
'\xff', // Flags: 0xff (no valid flags)
'\x80', '\x00', '\x00', '\x00', // Stream: 0 (plus R bit)
'\x00', '\x00', '\x01', '\x00', // Last: 256
'\x00', '\x00', '\x00', '\x03', // Error: FLOW_CONTROL_ERROR
'o', 'p', 'a', 'q', 'u', 'e',
};
Http2FrameHeader header(14, Http2FrameType::GOAWAY, 0, 0);
FrameParts expected(header, "opaque");
expected.SetOptGoaway(
Http2GoAwayFields(256, Http2ErrorCode::FLOW_CONTROL_ERROR));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, ContinuationPayload) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Payload length: 3
'\x09', // CONTINUATION
'\xff', // Flags: END_HEADERS | 0xfb
'\x00', '\x00', '\x00', '\x02', // Stream ID: 2
'a', 'b', 'c', // Data
};
Http2FrameHeader header(3, Http2FrameType::CONTINUATION,
Http2FrameFlag::END_HEADERS, 2);
FrameParts expected(header, "abc");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, AltSvcPayload) {
const char kFrameData[] = {
'\x00', '\x00', '\x08', // Payload length: 3
'\x0a', // ALTSVC
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream ID: 2
'\x00', '\x03', // Origin Length: 0
'a', 'b', 'c', // Origin
'd', 'e', 'f', // Value
};
Http2FrameHeader header(8, Http2FrameType::ALTSVC, 0, 2);
FrameParts expected(header);
expected.SetAltSvcExpected("abc", "def");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, UnknownPayload) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Payload length: 3
'\x30', // 48 (unknown)
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream ID: 2
'a', 'b', 'c', // Payload
};
Http2FrameHeader header(3, static_cast<Http2FrameType>(48), 0, 2);
FrameParts expected(header, "abc");
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
////////////////////////////////////////////////////////////////////////////////
// Tests of padded payloads, for those frame types that allow padding.
TEST_F(Http2FrameDecoderTest, DataPayloadAndPadding) {
const char kFrameData[] = {
'\x00', '\x00', '\x07', // Payload length: 7
'\x00', // DATA
'\x09', // Flags: END_STREAM | PADDED
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'\x03', // Pad Len
'a', 'b', 'c', // Data
'\x00', '\x00', '\x00', // Padding
};
Http2FrameHeader header(7, Http2FrameType::DATA,
Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
2);
size_t total_pad_length = 4; // Including the Pad Length field.
FrameParts expected(header, "abc", total_pad_length);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeadersPayloadAndPadding) {
const char kFrameData[] = {
'\x00', '\x00', '\x07', // Payload length: 7
'\x01', // HEADERS
'\x08', // Flags: PADDED
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'\x03', // Pad Len
'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
'\x00', '\x00', '\x00', // Padding
};
Http2FrameHeader header(7, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
2);
size_t total_pad_length = 4; // Including the Pad Length field.
FrameParts expected(header, "abc", total_pad_length);
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeadersPayloadPriorityAndPadding) {
const char kFrameData[] = {
'\x00', '\x00', '\x0c', // Payload length: 12
'\x01', // HEADERS
'\xff', // Flags: all, including undefined
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'\x03', // Pad Len
'\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
'\x10', // Weight: 17
'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
'\x00', '\x00', '\x00', // Padding
};
Http2FrameHeader header(12, Http2FrameType::HEADERS,
Http2FrameFlag::END_STREAM |
Http2FrameFlag::END_HEADERS |
Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY,
2);
size_t total_pad_length = 4; // Including the Pad Length field.
FrameParts expected(header, "abc", total_pad_length);
expected.SetOptPriority(Http2PriorityFields(1, 17, true));
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, PushPromisePayloadAndPadding) {
const char kFrameData[] = {
'\x00', '\x00', 11, // Payload length: 11
'\x05', // PUSH_PROMISE
'\xff', // Flags: END_HEADERS | PADDED | 0xf3
'\x00', '\x00', '\x00', '\x01', // Stream ID: 1
'\x03', // Pad Len
'\x00', '\x00', '\x00', '\x02', // Promised: 2
'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
'\x00', '\x00', '\x00', // Padding
};
Http2FrameHeader header(11, Http2FrameType::PUSH_PROMISE,
Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED,
1);
size_t total_pad_length = 4; // Including the Pad Length field.
FrameParts expected(header, "abc", total_pad_length);
expected.SetOptPushPromise(Http2PushPromiseFields{2});
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
}
////////////////////////////////////////////////////////////////////////////////
// Payload too short errors.
TEST_F(Http2FrameDecoderTest, DataMissingPadLengthField) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Payload length: 0
'\x00', // DATA
'\x08', // Flags: PADDED
'\x00', '\x00', '\x00', '\x01', // Stream ID: 1
};
Http2FrameHeader header(0, Http2FrameType::DATA, Http2FrameFlag::PADDED, 1);
FrameParts expected(header);
expected.SetOptMissingLength(1);
EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeaderPaddingTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x02', // Payload length: 0
'\x01', // HEADERS
'\x08', // Flags: PADDED
'\x00', '\x01', '\x00', '\x00', // Stream ID: 65536
'\xff', // Pad Len: 255
'\x00', // Only one byte of padding
};
Http2FrameHeader header(2, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
65536);
FrameParts expected(header);
expected.SetOptMissingLength(254);
EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, HeaderMissingPriority) {
const char kFrameData[] = {
'\x00', '\x00', '\x04', // Payload length: 0
'\x01', // HEADERS
'\x20', // Flags: PRIORITY
'\x00', '\x01', '\x00', '\x00', // Stream ID: 65536
'\x00', '\x00', '\x00', '\x00', // Priority (truncated)
};
Http2FrameHeader header(4, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
65536);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, PriorityTooShort) {
const char kFrameData[] = {
'\x00', '\x00', '\x04', // Length: 5
'\x02', // Type: PRIORITY
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream: 2
'\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
};
Http2FrameHeader header(4, Http2FrameType::PRIORITY, 0, 2);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, RstStreamTooShort) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Length: 4
'\x03', // Type: RST_STREAM
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x00', '\x00', '\x00', // Truncated
};
Http2FrameHeader header(3, Http2FrameType::RST_STREAM, 0, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
// SETTINGS frames must a multiple of 6 bytes long, so an 9 byte payload is
// invalid.
TEST_F(Http2FrameDecoderTest, SettingsWrongSize) {
const char kFrameData[] = {
'\x00', '\x00', '\x09', // Length: 2
'\x04', // Type: SETTINGS
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x00', // Stream: 0
'\x00', '\x02', // Param: ENABLE_PUSH
'\x00', '\x00', '\x00', '\x03', // Value: 1
'\x00', '\x04', // Param: INITIAL_WINDOW_SIZE
'\x00', // Value: Truncated
};
Http2FrameHeader header(9, Http2FrameType::SETTINGS, 0, 0);
FrameParts expected(header);
expected.AppendSetting(
Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, expected));
}
TEST_F(Http2FrameDecoderTest, PushPromiseTooShort) {
const char kFrameData[] = {
'\x00', '\x00', 3, // Payload length: 3
'\x05', // PUSH_PROMISE
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream ID: 1
'\x00', '\x00', '\x00', // Truncated promise id
};
Http2FrameHeader header(3, Http2FrameType::PUSH_PROMISE, 0, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, PushPromisePaddedTruncatedPromise) {
const char kFrameData[] = {
'\x00', '\x00', 4, // Payload length: 4
'\x05', // PUSH_PROMISE
'\x08', // Flags: PADDED
'\x00', '\x00', '\x00', '\x01', // Stream ID: 1
'\x00', // Pad Len
'\x00', '\x00', '\x00', // Truncated promise id
};
Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
Http2FrameFlag::PADDED, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, PingTooShort) {
const char kFrameData[] = {
'\x00', '\x00', '\x07', // Length: 8
'\x06', // Type: PING
'\xfe', // Flags: no valid flags
'\x00', '\x00', '\x00', '\x00', // Stream: 0
's', 'o', 'm', 'e', // "some"
'd', 'a', 't', // Too little
};
Http2FrameHeader header(7, Http2FrameType::PING, 0, 0);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, GoAwayTooShort) {
const char kFrameData[] = {
'\x00', '\x00', '\x00', // Length: 0
'\x07', // Type: GOAWAY
'\xff', // Flags: 0xff (no valid flags)
'\x00', '\x00', '\x00', '\x00', // Stream: 0
};
Http2FrameHeader header(0, Http2FrameType::GOAWAY, 0, 0);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, WindowUpdateTooShort) {
const char kFrameData[] = {
'\x00', '\x00', '\x03', // Length: 3
'\x08', // Type: WINDOW_UPDATE
'\x0f', // Flags: 0xff (no valid flags)
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x80', '\x00', '\x04', // Truncated
};
Http2FrameHeader header(3, Http2FrameType::WINDOW_UPDATE, 0, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOriginLength) {
const char kFrameData[] = {
'\x00', '\x00', '\x01', // Payload length: 3
'\x0a', // ALTSVC
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream ID: 2
'\x00', // Origin Length: truncated
};
Http2FrameHeader header(1, Http2FrameType::ALTSVC, 0, 2);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOrigin) {
const char kFrameData[] = {
'\x00', '\x00', '\x05', // Payload length: 3
'\x0a', // ALTSVC
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream ID: 2
'\x00', '\x04', // Origin Length: 4 (too long)
'a', 'b', 'c', // Origin
};
Http2FrameHeader header(5, Http2FrameType::ALTSVC, 0, 2);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
////////////////////////////////////////////////////////////////////////////////
// Payload too long errors.
// The decoder calls the listener's OnFrameSizeError method if the frame's
// payload is longer than the currently configured maximum payload size.
TEST_F(Http2FrameDecoderTest, BeyondMaximum) {
decoder_.set_maximum_payload_size(2);
const char kFrameData[] = {
'\x00', '\x00', '\x07', // Payload length: 7
'\x00', // DATA
'\x09', // Flags: END_STREAM | PADDED
'\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
'\x03', // Pad Len
'a', 'b', 'c', // Data
'\x00', '\x00', '\x00', // Padding
};
Http2FrameHeader header(7, Http2FrameType::DATA,
Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
2);
FrameParts expected(header);
expected.SetHasFrameSizeError(true);
auto validator = [&expected, this](const DecodeBuffer& input,
DecodeStatus status) -> AssertionResult {
VERIFY_EQ(status, DecodeStatus::kDecodeError);
// The decoder detects this error after decoding the header, and without
// trying to decode the payload.
VERIFY_EQ(input.Offset(), Http2FrameHeader::EncodedSize());
VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
};
ResetDecodeSpeedCounters();
EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(ToStringPiece(kFrameData),
validator));
EXPECT_GT(fast_decode_count_, 0u);
EXPECT_GT(slow_decode_count_, 0u);
}
TEST_F(Http2FrameDecoderTest, PriorityTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x06', // Length: 5
'\x02', // Type: PRIORITY
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x02', // Stream: 2
'\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
'\x10', // Weight: 17
'\x00', // Too much
};
Http2FrameHeader header(6, Http2FrameType::PRIORITY, 0, 2);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, RstStreamTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x05', // Length: 4
'\x03', // Type: RST_STREAM
'\x00', // Flags: none
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
'\x00', // Too much
};
Http2FrameHeader header(5, Http2FrameType::RST_STREAM, 0, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, SettingsAckTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x06', // Length: 6
'\x04', // Type: SETTINGS
'\x01', // Flags: ACK
'\x00', '\x00', '\x00', '\x00', // Stream: 0
'\x00', '\x00', // Extra
'\x00', '\x00', '\x00', '\x00', // Extra
};
Http2FrameHeader header(6, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, PingAckTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x09', // Length: 8
'\x06', // Type: PING
'\xff', // Flags: ACK | 0xfe
'\x00', '\x00', '\x00', '\x00', // Stream: 0
's', 'o', 'm', 'e', // "some"
'd', 'a', 't', 'a', // "data"
'\x00', // Too much
};
Http2FrameHeader header(9, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
TEST_F(Http2FrameDecoderTest, WindowUpdateTooLong) {
const char kFrameData[] = {
'\x00', '\x00', '\x05', // Length: 5
'\x08', // Type: WINDOW_UPDATE
'\x0f', // Flags: 0xff (no valid flags)
'\x00', '\x00', '\x00', '\x01', // Stream: 1
'\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit)
'\x00', // Too much
};
Http2FrameHeader header(5, Http2FrameType::WINDOW_UPDATE, 0, 1);
EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
}
} // namespace
} // namespace test
} // namespace http2