| // 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 |