378 lines
14 KiB
C++
378 lines
14 KiB
C++
// Copyright 2021 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/spdy/alps_decoder.h"
|
|
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/hex_utils.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
using ::spdy::AcceptChOriginValuePair;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::Pair;
|
|
|
|
namespace net {
|
|
namespace {
|
|
|
|
TEST(AlpsDecoderTest, EmptyInput) {
|
|
AlpsDecoder decoder;
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
EXPECT_THAT(decoder.GetSettings(), IsEmpty());
|
|
EXPECT_EQ(0, decoder.settings_frame_count());
|
|
|
|
AlpsDecoder::Error error = decoder.Decode({});
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
EXPECT_THAT(decoder.GetSettings(), IsEmpty());
|
|
EXPECT_EQ(0, decoder.settings_frame_count());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, EmptyAcceptChFrame) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000000" // length
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000")); // stream ID
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
EXPECT_THAT(decoder.GetSettings(), IsEmpty());
|
|
EXPECT_EQ(0, decoder.settings_frame_count());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, EmptySettingsFrame) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000000" // length
|
|
"04" // type SETTINGS
|
|
"00" // flags
|
|
"00000000")); // stream ID
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
EXPECT_THAT(decoder.GetSettings(), IsEmpty());
|
|
EXPECT_EQ(1, decoder.settings_frame_count());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, ParseSettingsAndAcceptChFrames) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(HexDecode(
|
|
// ACCEPT_CH frame
|
|
"00003d" // length
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f" // value "foo"
|
|
"0018" // origin length
|
|
"68747470733a2f2f6d61696c" //
|
|
"2e6578616d706c652e636f6d" // origin "https://mail.example.com"
|
|
"0003" // value length
|
|
"626172" // value "bar"
|
|
// SETTINGS frame
|
|
"00000c" // length
|
|
"04" // type
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0dab" // identifier
|
|
"01020304" // value
|
|
"1234" // identifier
|
|
"fedcba98")); // value
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(
|
|
decoder.GetAcceptCh(),
|
|
ElementsAre(AcceptChOriginValuePair{"https://www.example.com", "foo"},
|
|
AcceptChOriginValuePair{"https://mail.example.com", "bar"}));
|
|
EXPECT_THAT(decoder.GetSettings(),
|
|
ElementsAre(Pair(0x0dab, 0x01020304), Pair(0x1234, 0xfedcba98)));
|
|
EXPECT_EQ(1, decoder.settings_frame_count());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, ParseLargeAcceptChFrame) {
|
|
std::string frame = HexDecode(
|
|
// ACCEPT_CH frame
|
|
"0001ab" // length: 427 total bytes
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0190" // value length (400 in hex)
|
|
);
|
|
|
|
// The Accept-CH tokens payload is a string of 400 'x' characters.
|
|
const std::string accept_ch_tokens(400, 'x');
|
|
// Append the value bytes to the frame.
|
|
frame += accept_ch_tokens;
|
|
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(frame);
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(),
|
|
ElementsAre(AcceptChOriginValuePair{"https://www.example.com",
|
|
accept_ch_tokens}));
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, DisableAlpsParsing) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndDisableFeature(features::kAlpsParsing);
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(HexDecode(
|
|
// ACCEPT_CH frame
|
|
"00003d" // length
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f" // value "foo"
|
|
"0018" // origin length
|
|
"68747470733a2f2f6d61696c" //
|
|
"2e6578616d706c652e636f6d" // origin "https://mail.example.com"
|
|
"0003" // value length
|
|
"626172" // value "bar"
|
|
));
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, DisableAlpsClientHintParsing) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndDisableFeature(features::kAlpsClientHintParsing);
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(HexDecode(
|
|
// ACCEPT_CH frame
|
|
"00003d" // length
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f" // value "foo"
|
|
"0018" // origin length
|
|
"68747470733a2f2f6d61696c" //
|
|
"2e6578616d706c652e636f6d" // origin "https://mail.example.com"
|
|
"0003" // value length
|
|
"626172" // value "bar"
|
|
));
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, IncompleteFrame) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("00000c" // length
|
|
"04" // type
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0dab" // identifier
|
|
"01")); // first byte of value
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNotOnFrameBoundary, error);
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, TwoSettingsFrames) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000006" // length
|
|
"04" // type SETTINGS
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"0dab" // identifier
|
|
"01020304" // value
|
|
"000006" // length
|
|
"04" // type SETTINGS
|
|
"00" // flags
|
|
"00000000" // stream ID
|
|
"1234" // identifier
|
|
"fedcba98")); // value
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_EQ(2, decoder.settings_frame_count());
|
|
EXPECT_THAT(decoder.GetSettings(),
|
|
ElementsAre(Pair(0x0dab, 0x01020304), Pair(0x1234, 0xfedcba98)));
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, AcceptChOnInvalidStream) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(
|
|
HexDecode("00001e" // length
|
|
"89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000001" // invalid stream ID: should be zero
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f")); // value "foo"
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kAcceptChInvalidStream, error);
|
|
}
|
|
|
|
// According to
|
|
// https://davidben.github.io/http-client-hint-reliability/ \
|
|
// draft-davidben-http-client-hint-reliability.html#name-http-2-accept_ch-frame
|
|
// "If a user agent receives an ACCEPT_CH frame whose stream [...] flags
|
|
// field is non-zero, it MUST respond with a connection error [...]."
|
|
TEST(AlpsDecoderTest, AcceptChWithInvalidFlags) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(
|
|
HexDecode("00001e" // length
|
|
"89" // type ACCEPT_CH
|
|
"02" // invalid flags: should be zero
|
|
"00000000" // stream ID
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e" //
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f")); // value "foo"
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kAcceptChWithFlags, error);
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, SettingsOnInvalidStream) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000006" // length
|
|
"04" // type SETTINGS
|
|
"00" // flags
|
|
"00000001" // invalid stream ID: should be zero
|
|
"1234" // identifier
|
|
"fedcba98")); // value
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kFramingError, error);
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, SettingsAck) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000000" // length
|
|
"04" // type SETTINGS
|
|
"01" // ACK flag
|
|
"00000000")); // stream ID
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kSettingsWithAck, error);
|
|
}
|
|
|
|
// According to https://httpwg.org/specs/rfc7540.html#FrameHeader:
|
|
// "Flags that have no defined semantics for a particular frame type MUST be
|
|
// ignored [...]"
|
|
TEST(AlpsDecoderTest, SettingsWithInvalidFlags) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000006" // length
|
|
"04" // type SETTINGS
|
|
"02" // invalid flag
|
|
"00000000" // stream ID
|
|
"1234" // identifier
|
|
"fedcba98")); // value
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, ForbiddenFrame) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000003" // length
|
|
"00" // frame type DATA
|
|
"01" // flags END_STREAM
|
|
"00000001" // stream ID
|
|
"666f6f")); // payload "foo"
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kForbiddenFrame, error);
|
|
}
|
|
|
|
TEST(AlpsDecoderTest, UnknownFrame) {
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error =
|
|
decoder.Decode(HexDecode("000003" // length
|
|
"2a" // unknown frame type
|
|
"ff" // flags
|
|
"00000008" // stream ID
|
|
"666f6f")); // payload "foo"
|
|
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
|
|
EXPECT_THAT(decoder.GetSettings(), IsEmpty());
|
|
EXPECT_EQ(0, decoder.settings_frame_count());
|
|
}
|
|
|
|
class AlpsDecoderTestWithFeature : public ::testing::TestWithParam<bool> {
|
|
public:
|
|
bool ShouldKillSessionOnAcceptChMalformed() { return GetParam(); }
|
|
|
|
private:
|
|
void SetUp() override {
|
|
feature_list_.InitWithFeatureState(
|
|
features::kShouldKillSessionOnAcceptChMalformed,
|
|
ShouldKillSessionOnAcceptChMalformed());
|
|
}
|
|
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All, AlpsDecoderTestWithFeature, testing::Bool());
|
|
|
|
TEST_P(AlpsDecoderTestWithFeature, MalformedAcceptChFrame) {
|
|
// Correct, complete payload.
|
|
std::string payload = HexDecode(
|
|
"0017" // origin length
|
|
"68747470733a2f2f7777772e"
|
|
"6578616d706c652e636f6d" // origin "https://www.example.com"
|
|
"0003" // value length
|
|
"666f6f"); // value "foo"
|
|
|
|
for (uint8_t payload_length = 1; payload_length < payload.length();
|
|
payload_length++) {
|
|
base::HistogramTester histogram_tester;
|
|
// First two bytes of length.
|
|
std::string frame = HexDecode("0000");
|
|
// Last byte of length.
|
|
frame.push_back(static_cast<char>(payload_length));
|
|
|
|
frame.append(
|
|
HexDecode("89" // type ACCEPT_CH
|
|
"00" // flags
|
|
"00000000")); // stream ID
|
|
// Incomplete, malformed payload.
|
|
frame.append(payload.data(), payload_length);
|
|
|
|
AlpsDecoder decoder;
|
|
AlpsDecoder::Error error = decoder.Decode(frame);
|
|
if (ShouldKillSessionOnAcceptChMalformed()) {
|
|
EXPECT_EQ(AlpsDecoder::Error::kAcceptChMalformed, error);
|
|
histogram_tester.ExpectUniqueSample(
|
|
"Net.SpdySession.AlpsDecoderStatus.Bypassed",
|
|
static_cast<int>(AlpsDecoder::Error::kNoError), 1);
|
|
} else {
|
|
EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
|
|
histogram_tester.ExpectUniqueSample(
|
|
"Net.SpdySession.AlpsDecoderStatus.Bypassed",
|
|
static_cast<int>(AlpsDecoder::Error::kAcceptChMalformed), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace net
|