Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/http/quic_headers_stream_test.cc b/quic/core/http/quic_headers_stream_test.cc
new file mode 100644
index 0000000..b6409b1
--- /dev/null
+++ b/quic/core/http/quic_headers_stream_test.cc
@@ -0,0 +1,968 @@
+// Copyright 2013 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/quic/core/http/quic_headers_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+using spdy::ERROR_CODE_PROTOCOL_ERROR;
+using spdy::SETTINGS_ENABLE_PUSH;
+using spdy::SETTINGS_HEADER_TABLE_SIZE;
+using spdy::SETTINGS_INITIAL_WINDOW_SIZE;
+using spdy::SETTINGS_MAX_CONCURRENT_STREAMS;
+using spdy::SETTINGS_MAX_FRAME_SIZE;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyAltSvcWireFormat;
+using spdy::SpdyDataIR;
+using spdy::SpdyErrorCode;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyGoAwayIR;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyHeadersHandlerInterface;
+using spdy::SpdyHeadersIR;
+using spdy::SpdyKnownSettingsId;
+using spdy::SpdyPingId;
+using spdy::SpdyPingIR;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdyPushPromiseIR;
+using spdy::SpdyRstStreamIR;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsId;
+using spdy::SpdySettingsIR;
+using spdy::SpdyStreamId;
+using spdy::SpdyWindowUpdateIR;
+using spdy::test::TestHeadersHandler;
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace quic {
+namespace test {
+
+class MockQuicHpackDebugVisitor : public QuicHpackDebugVisitor {
+ public:
+ MockQuicHpackDebugVisitor() : QuicHpackDebugVisitor() {}
+ MockQuicHpackDebugVisitor(const MockQuicHpackDebugVisitor&) = delete;
+ MockQuicHpackDebugVisitor& operator=(const MockQuicHpackDebugVisitor&) =
+ delete;
+
+ MOCK_METHOD1(OnUseEntry, void(QuicTime::Delta elapsed));
+};
+
+namespace {
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+ MOCK_METHOD1(OnError,
+ void(http2::Http2DecoderAdapter::SpdyFramerError error));
+ MOCK_METHOD3(OnDataFrameHeader,
+ void(SpdyStreamId stream_id, size_t length, bool fin));
+ MOCK_METHOD3(OnStreamFrameData,
+ void(SpdyStreamId stream_id, const char* data, size_t len));
+ MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId stream_id));
+ MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId stream_id, size_t len));
+ MOCK_METHOD1(OnHeaderFrameStart,
+ SpdyHeadersHandlerInterface*(SpdyStreamId stream_id));
+ MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id));
+ MOCK_METHOD3(OnControlFrameHeaderData,
+ bool(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len));
+ MOCK_METHOD2(OnRstStream,
+ void(SpdyStreamId stream_id, SpdyErrorCode error_code));
+ MOCK_METHOD0(OnSettings, void());
+ MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t value));
+ MOCK_METHOD0(OnSettingsAck, void());
+ MOCK_METHOD0(OnSettingsEnd, void());
+ MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+ MOCK_METHOD2(OnGoAway,
+ void(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code));
+ MOCK_METHOD7(OnHeaders,
+ void(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end));
+ MOCK_METHOD2(OnWindowUpdate,
+ void(SpdyStreamId stream_id, int delta_window_size));
+ MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id));
+ MOCK_METHOD3(OnPushPromise,
+ void(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end));
+ MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end));
+ MOCK_METHOD3(OnAltSvc,
+ void(SpdyStreamId stream_id,
+ QuicStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector&
+ altsvc_vector));
+ MOCK_METHOD4(OnPriority,
+ void(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive));
+ MOCK_METHOD2(OnUnknownFrame,
+ bool(SpdyStreamId stream_id, uint8_t frame_type));
+};
+
+struct TestParams {
+ TestParams(const ParsedQuicVersion& version, Perspective perspective)
+ : version(version), perspective(perspective) {
+ QUIC_LOG(INFO) << "TestParams: version: "
+ << ParsedQuicVersionToString(version)
+ << ", perspective: " << perspective;
+ }
+
+ TestParams(const TestParams& other)
+ : version(other.version), perspective(other.perspective) {}
+
+ ParsedQuicVersion version;
+ Perspective perspective;
+};
+
+std::vector<TestParams> GetTestParams() {
+ std::vector<TestParams> params;
+ ParsedQuicVersionVector all_supported_versions = AllSupportedVersions();
+ for (size_t i = 0; i < all_supported_versions.size(); ++i) {
+ for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+ params.emplace_back(all_supported_versions[i], p);
+ }
+ }
+ return params;
+}
+
+class QuicHeadersStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+ QuicHeadersStreamTest()
+ : connection_(new StrictMock<MockQuicConnection>(&helper_,
+ &alarm_factory_,
+ perspective(),
+ GetVersion())),
+ session_(connection_),
+ body_("hello world"),
+ stream_frame_(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*fin=*/false,
+ /*offset=*/0,
+ ""),
+ next_promised_stream_id_(2) {
+ session_.Initialize();
+ headers_stream_ = QuicSpdySessionPeer::GetHeadersStream(&session_);
+ headers_[":version"] = "HTTP/1.1";
+ headers_[":status"] = "200 Ok";
+ headers_["content-length"] = "11";
+ framer_ = std::unique_ptr<SpdyFramer>(
+ new SpdyFramer(SpdyFramer::ENABLE_COMPRESSION));
+ deframer_ = std::unique_ptr<http2::Http2DecoderAdapter>(
+ new http2::Http2DecoderAdapter());
+ deframer_->set_visitor(&visitor_);
+ EXPECT_EQ(transport_version(), session_.connection()->transport_version());
+ EXPECT_TRUE(headers_stream_ != nullptr);
+ connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ client_id_1_ = GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0);
+ client_id_2_ = GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 1);
+ client_id_3_ = GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 2);
+ next_stream_id_ =
+ QuicUtils::StreamIdDelta(connection_->transport_version());
+ }
+
+ QuicStreamId GetNthClientInitiatedId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ QuicConsumedData SaveIov(size_t write_length) {
+ char* buf = new char[write_length];
+ QuicDataWriter writer(write_length, buf, NETWORK_BYTE_ORDER);
+ headers_stream_->WriteStreamData(headers_stream_->stream_bytes_written(),
+ write_length, &writer);
+ saved_data_.append(buf, write_length);
+ delete[] buf;
+ return QuicConsumedData(write_length, false);
+ }
+
+ void SavePayload(const char* data, size_t len) {
+ saved_payloads_.append(data, len);
+ }
+
+ bool SaveHeaderData(const char* data, int len) {
+ saved_header_data_.append(data, len);
+ return true;
+ }
+
+ void SaveHeaderDataStringPiece(QuicStringPiece data) {
+ saved_header_data_.append(data.data(), data.length());
+ }
+
+ void SavePromiseHeaderList(QuicStreamId /* stream_id */,
+ QuicStreamId /* promised_stream_id */,
+ size_t size,
+ const QuicHeaderList& header_list) {
+ SaveToHandler(size, header_list);
+ }
+
+ void SaveHeaderList(QuicStreamId /* stream_id */,
+ bool /* fin */,
+ size_t size,
+ const QuicHeaderList& header_list) {
+ SaveToHandler(size, header_list);
+ }
+
+ void SaveToHandler(size_t size, const QuicHeaderList& header_list) {
+ headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+ headers_handler_->OnHeaderBlockStart();
+ for (const auto& p : header_list) {
+ headers_handler_->OnHeader(p.first, p.second);
+ }
+ headers_handler_->OnHeaderBlockEnd(size, size);
+ }
+
+ void WriteAndExpectRequestHeaders(QuicStreamId stream_id,
+ bool fin,
+ SpdyPriority priority) {
+ WriteHeadersAndCheckData(stream_id, fin, priority, true /*is_request*/);
+ }
+
+ void WriteAndExpectResponseHeaders(QuicStreamId stream_id, bool fin) {
+ WriteHeadersAndCheckData(stream_id, fin, 0, false /*is_request*/);
+ }
+
+ void WriteHeadersAndCheckData(QuicStreamId stream_id,
+ bool fin,
+ SpdyPriority priority,
+ bool is_request) {
+ // Write the headers and capture the outgoing data
+ EXPECT_CALL(session_, WritevData(headers_stream_,
+ QuicUtils::GetHeadersStreamId(
+ connection_->transport_version()),
+ _, _, NO_FIN))
+ .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+ QuicSpdySessionPeer::WriteHeadersOnHeadersStream(
+ &session_, stream_id, headers_.Clone(), fin, priority, nullptr);
+
+ // Parse the outgoing data and check that it matches was was written.
+ if (is_request) {
+ EXPECT_CALL(visitor_,
+ OnHeaders(stream_id, kHasPriority,
+ Spdy3PriorityToHttp2Weight(priority),
+ /*parent_stream_id=*/0,
+ /*exclusive=*/false, fin, kFrameComplete));
+ } else {
+ EXPECT_CALL(visitor_,
+ OnHeaders(stream_id, !kHasPriority,
+ /*priority=*/0,
+ /*parent_stream_id=*/0,
+ /*exclusive=*/false, fin, kFrameComplete));
+ }
+ headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+ EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id))
+ .WillOnce(Return(headers_handler_.get()));
+ EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1);
+ if (fin) {
+ EXPECT_CALL(visitor_, OnStreamEnd(stream_id));
+ }
+ deframer_->ProcessInput(saved_data_.data(), saved_data_.length());
+ EXPECT_FALSE(deframer_->HasError())
+ << http2::Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_->spdy_framer_error());
+
+ CheckHeaders();
+ saved_data_.clear();
+ }
+
+ void CheckHeaders() {
+ EXPECT_EQ(headers_, headers_handler_->decoded_block());
+ headers_handler_.reset();
+ }
+
+ Perspective perspective() const { return GetParam().perspective; }
+
+ QuicTransportVersion transport_version() const {
+ return GetParam().version.transport_version;
+ }
+
+ ParsedQuicVersionVector GetVersion() {
+ ParsedQuicVersionVector versions;
+ versions.push_back(GetParam().version);
+ return versions;
+ }
+
+ void TearDownLocalConnectionState() {
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ }
+
+ QuicStreamId NextPromisedStreamId() {
+ return next_promised_stream_id_ += next_stream_id_;
+ }
+
+ static const bool kFrameComplete = true;
+ static const bool kHasPriority = true;
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ StrictMock<MockQuicSpdySession> session_;
+ QuicHeadersStream* headers_stream_;
+ SpdyHeaderBlock headers_;
+ std::unique_ptr<TestHeadersHandler> headers_handler_;
+ QuicString body_;
+ QuicString saved_data_;
+ QuicString saved_header_data_;
+ QuicString saved_payloads_;
+ std::unique_ptr<SpdyFramer> framer_;
+ std::unique_ptr<http2::Http2DecoderAdapter> deframer_;
+ StrictMock<MockVisitor> visitor_;
+ QuicStreamFrame stream_frame_;
+ QuicStreamId next_promised_stream_id_;
+ QuicStreamId client_id_1_;
+ QuicStreamId client_id_2_;
+ QuicStreamId client_id_3_;
+ QuicStreamId next_stream_id_;
+};
+
+// Run all tests with each version and perspective (client or server).
+INSTANTIATE_TEST_SUITE_P(Tests, QuicHeadersStreamTest,
+ ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicHeadersStreamTest, StreamId) {
+ EXPECT_EQ(QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ headers_stream_->id());
+}
+
+TEST_P(QuicHeadersStreamTest, WriteHeaders) {
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ for (bool fin : {false, true}) {
+ if (perspective() == Perspective::IS_SERVER) {
+ WriteAndExpectResponseHeaders(stream_id, fin);
+ } else {
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ // TODO(rch): implement priorities correctly.
+ WriteAndExpectRequestHeaders(stream_id, fin, 0);
+ }
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, WritePushPromises) {
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ QuicStreamId promised_stream_id = NextPromisedStreamId();
+ if (perspective() == Perspective::IS_SERVER) {
+ // Write the headers and capture the outgoing data
+ EXPECT_CALL(session_, WritevData(headers_stream_,
+ QuicUtils::GetHeadersStreamId(
+ connection_->transport_version()),
+ _, _, NO_FIN))
+ .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+ session_.WritePushPromise(stream_id, promised_stream_id,
+ headers_.Clone());
+
+ // Parse the outgoing data and check that it matches was was written.
+ EXPECT_CALL(visitor_,
+ OnPushPromise(stream_id, promised_stream_id, kFrameComplete));
+ headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+ EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id))
+ .WillOnce(Return(headers_handler_.get()));
+ EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1);
+ deframer_->ProcessInput(saved_data_.data(), saved_data_.length());
+ EXPECT_FALSE(deframer_->HasError())
+ << http2::Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_->spdy_framer_error());
+ CheckHeaders();
+ saved_data_.clear();
+ } else {
+ EXPECT_QUIC_BUG(session_.WritePushPromise(stream_id, promised_stream_id,
+ headers_.Clone()),
+ "Client shouldn't send PUSH_PROMISE");
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessRawData) {
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ for (bool fin : {false, true}) {
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ // Replace with "WriteHeadersAndSaveData"
+ SpdySerializedFrame frame;
+ if (perspective() == Perspective::IS_SERVER) {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ headers_frame.set_has_priority(true);
+ headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+ frame = framer_->SerializeFrame(headers_frame);
+ EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+ } else {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ frame = framer_->SerializeFrame(headers_frame);
+ }
+ EXPECT_CALL(session_,
+ OnStreamHeaderList(stream_id, fin, frame.size(), _))
+ .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ stream_frame_.offset += frame.size();
+ CheckHeaders();
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPushPromise) {
+ if (perspective() == Perspective::IS_SERVER) {
+ return;
+ }
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ QuicStreamId promised_stream_id = NextPromisedStreamId();
+ SpdyPushPromiseIR push_promise(stream_id, promised_stream_id,
+ headers_.Clone());
+ SpdySerializedFrame frame(framer_->SerializeFrame(push_promise));
+ if (perspective() == Perspective::IS_SERVER) {
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "PUSH_PROMISE not supported.", _))
+ .WillRepeatedly(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ } else {
+ EXPECT_CALL(session_, OnPromiseHeaderList(stream_id, promised_stream_id,
+ frame.size(), _))
+ .WillOnce(
+ Invoke(this, &QuicHeadersStreamTest::SavePromiseHeaderList));
+ }
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ if (perspective() == Perspective::IS_CLIENT) {
+ stream_frame_.offset += frame.size();
+ CheckHeaders();
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPriorityFrame) {
+ QuicStreamId parent_stream_id = 0;
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ int weight = Spdy3PriorityToHttp2Weight(priority);
+ SpdyPriorityIR priority_frame(stream_id, parent_stream_id, weight, true);
+ SpdySerializedFrame frame(framer_->SerializeFrame(priority_frame));
+ parent_stream_id = stream_id;
+ if (transport_version() <= QUIC_VERSION_39) {
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY PRIORITY frame received.", _))
+ .WillRepeatedly(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ } else if (perspective() == Perspective::IS_CLIENT) {
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Server must not send PRIORITY frames.", _))
+ .WillRepeatedly(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ } else {
+ EXPECT_CALL(session_, OnPriorityFrame(stream_id, priority)).Times(1);
+ }
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ stream_frame_.offset += frame.size();
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPushPromiseDisabledSetting) {
+ session_.OnConfigNegotiated();
+ SpdySettingsIR data;
+ // Respect supported settings frames SETTINGS_ENABLE_PUSH.
+ data.AddSetting(SETTINGS_ENABLE_PUSH, 0);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ if (perspective() == Perspective::IS_CLIENT) {
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Unsupported field of HTTP/2 SETTINGS frame: 2", _));
+ }
+ headers_stream_->OnStreamFrame(stream_frame_);
+ EXPECT_EQ(session_.server_push_enabled(),
+ perspective() == Perspective::IS_CLIENT);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) {
+ QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(&session_, 256 * 1024);
+ // We want to create a frame that is more than the SPDY Framer's max control
+ // frame size, which is 16K, but less than the HPACK decoders max decode
+ // buffer size, which is 32K.
+ headers_["key0"] = QuicString(1 << 13, '.');
+ headers_["key1"] = QuicString(1 << 13, '.');
+ headers_["key2"] = QuicString(1 << 13, '.');
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ for (bool fin : {false, true}) {
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ // Replace with "WriteHeadersAndSaveData"
+ SpdySerializedFrame frame;
+ if (perspective() == Perspective::IS_SERVER) {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ headers_frame.set_has_priority(true);
+ headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+ frame = framer_->SerializeFrame(headers_frame);
+ EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+ } else {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ frame = framer_->SerializeFrame(headers_frame);
+ }
+ EXPECT_CALL(session_,
+ OnStreamHeaderList(stream_id, fin, frame.size(), _))
+ .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ stream_frame_.offset += frame.size();
+ CheckHeaders();
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessBadData) {
+ const char kBadData[] = "blah blah blah";
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+ .Times(::testing::AnyNumber());
+ stream_frame_.data_buffer = kBadData;
+ stream_frame_.data_length = strlen(kBadData);
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) {
+ SpdyDataIR data(/* stream_id = */ 2, "ping");
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY DATA frame received.", _))
+ .WillOnce(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) {
+ SpdyRstStreamIR data(/* stream_id = */ 2, ERROR_CODE_PROTOCOL_ERROR);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY RST_STREAM frame received.", _))
+ .WillOnce(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameSupportedFields) {
+ const uint32_t kTestHeaderTableSize = 1000;
+ SpdySettingsIR data;
+ // Respect supported settings frames SETTINGS_HEADER_TABLE_SIZE,
+ // SETTINGS_MAX_HEADER_LIST_SIZE.
+ data.AddSetting(SETTINGS_HEADER_TABLE_SIZE, kTestHeaderTableSize);
+ data.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, 2000);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+ EXPECT_EQ(kTestHeaderTableSize, QuicSpdySessionPeer::GetSpdyFramer(&session_)
+ .header_encoder_table_size());
+}
+
+TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameUnsupportedFields) {
+ SpdySettingsIR data;
+ // Does not support SETTINGS_MAX_CONCURRENT_STREAMS,
+ // SETTINGS_INITIAL_WINDOW_SIZE, SETTINGS_ENABLE_PUSH and
+ // SETTINGS_MAX_FRAME_SIZE.
+ data.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 100);
+ data.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 100);
+ data.AddSetting(SETTINGS_ENABLE_PUSH, 1);
+ data.AddSetting(SETTINGS_MAX_FRAME_SIZE, 1250);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+ SETTINGS_MAX_CONCURRENT_STREAMS),
+ _));
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+ SETTINGS_INITIAL_WINDOW_SIZE),
+ _));
+ if (session_.perspective() == Perspective::IS_CLIENT) {
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+ SETTINGS_ENABLE_PUSH),
+ _));
+ }
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+ SETTINGS_MAX_FRAME_SIZE),
+ _));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) {
+ SpdyPingIR data(1);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY PING frame received.", _))
+ .WillOnce(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) {
+ SpdyGoAwayIR data(/* last_good_stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR,
+ "go away");
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY GOAWAY frame received.", _))
+ .WillOnce(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) {
+ SpdyWindowUpdateIR data(/* stream_id = */ 1, /* delta = */ 1);
+ SpdySerializedFrame frame(framer_->SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY WINDOW_UPDATE frame received.", _))
+ .WillOnce(InvokeWithoutArgs(
+ this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, NoConnectionLevelFlowControl) {
+ EXPECT_FALSE(QuicStreamPeer::StreamContributesToConnectionFlowControl(
+ headers_stream_));
+}
+
+TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) {
+ auto hpack_decoder_visitor =
+ QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
+ {
+ InSequence seq;
+ // Number of indexed representations generated in headers below.
+ for (int i = 1; i < 28; i++) {
+ EXPECT_CALL(*hpack_decoder_visitor,
+ OnUseEntry(QuicTime::Delta::FromMilliseconds(i)))
+ .Times(4);
+ }
+ }
+ QuicSpdySessionPeer::SetHpackDecoderDebugVisitor(
+ &session_, std::move(hpack_decoder_visitor));
+
+ // Create some headers we expect to generate entries in HPACK's
+ // dynamic table, in addition to content-length.
+ headers_["key0"] = QuicString(1 << 1, '.');
+ headers_["key1"] = QuicString(1 << 2, '.');
+ headers_["key2"] = QuicString(1 << 3, '.');
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ for (bool fin : {false, true}) {
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ // Replace with "WriteHeadersAndSaveData"
+ SpdySerializedFrame frame;
+ if (perspective() == Perspective::IS_SERVER) {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ headers_frame.set_has_priority(true);
+ headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+ frame = framer_->SerializeFrame(headers_frame);
+ EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+ } else {
+ SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+ headers_frame.set_fin(fin);
+ frame = framer_->SerializeFrame(headers_frame);
+ }
+ EXPECT_CALL(session_,
+ OnStreamHeaderList(stream_id, fin, frame.size(), _))
+ .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+ stream_frame_.data_buffer = frame.data();
+ stream_frame_.data_length = frame.size();
+ connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ headers_stream_->OnStreamFrame(stream_frame_);
+ stream_frame_.offset += frame.size();
+ CheckHeaders();
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, HpackEncoderDebugVisitor) {
+ auto hpack_encoder_visitor =
+ QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
+ if (perspective() == Perspective::IS_SERVER) {
+ InSequence seq;
+ for (int i = 1; i < 4; i++) {
+ EXPECT_CALL(*hpack_encoder_visitor,
+ OnUseEntry(QuicTime::Delta::FromMilliseconds(i)));
+ }
+ } else {
+ InSequence seq;
+ for (int i = 1; i < 28; i++) {
+ EXPECT_CALL(*hpack_encoder_visitor,
+ OnUseEntry(QuicTime::Delta::FromMilliseconds(i)));
+ }
+ }
+ QuicSpdySessionPeer::SetHpackEncoderDebugVisitor(
+ &session_, std::move(hpack_encoder_visitor));
+
+ for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+ stream_id += next_stream_id_) {
+ for (bool fin : {false, true}) {
+ if (perspective() == Perspective::IS_SERVER) {
+ WriteAndExpectResponseHeaders(stream_id, fin);
+ connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ } else {
+ for (SpdyPriority priority = 0; priority < 7; ++priority) {
+ // TODO(rch): implement priorities correctly.
+ WriteAndExpectRequestHeaders(stream_id, fin, 0);
+ connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ }
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, AckSentData) {
+ EXPECT_CALL(session_, WritevData(headers_stream_,
+ QuicUtils::GetHeadersStreamId(
+ connection_->transport_version()),
+ _, _, NO_FIN))
+ .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+ InSequence s;
+ QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+ new MockAckListener());
+
+ // Packet 1.
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+
+ // Packet 2.
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+
+ // Packet 3.
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+ // Packet 2 gets retransmitted.
+ EXPECT_CALL(*ack_listener3, OnPacketRetransmitted(7)).Times(1);
+ EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(7)).Times(1);
+ headers_stream_->OnStreamFrameRetransmitted(21, 7, false);
+ headers_stream_->OnStreamFrameRetransmitted(28, 7, false);
+
+ // Packets are acked in order: 2, 3, 1.
+ QuicByteCount newly_acked_length = 0;
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 21, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 28, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 35, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 0, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 7, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+ // Unsent data is acked.
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 14, 10, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(7u, newly_acked_length);
+}
+
+TEST_P(QuicHeadersStreamTest, FrameContainsMultipleHeaders) {
+ // In this test, a stream frame can contain multiple headers.
+ EXPECT_CALL(session_, WritevData(headers_stream_,
+ QuicUtils::GetHeadersStreamId(
+ connection_->transport_version()),
+ _, _, NO_FIN))
+ .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+ InSequence s;
+ QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+ new MockAckListener());
+
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+ // Frame 1 is retransmitted.
+ EXPECT_CALL(*ack_listener1, OnPacketRetransmitted(14));
+ EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(3));
+ headers_stream_->OnStreamFrameRetransmitted(0, 17, false);
+
+ // Frames are acked in order: 2, 3, 1.
+ QuicByteCount newly_acked_length = 0;
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(4, _));
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(2, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 17, 13, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(13u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 30, 12, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(12u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(14, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(3, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 0, 17, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(17u, newly_acked_length);
+}
+
+TEST_P(QuicHeadersStreamTest, HeadersGetAckedMultipleTimes) {
+ EXPECT_CALL(session_, WritevData(headers_stream_,
+ QuicUtils::GetHeadersStreamId(
+ connection_->transport_version()),
+ _, _, NO_FIN))
+ .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+ InSequence s;
+ QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+ new MockAckListener());
+
+ // Send [0, 42).
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+ headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+ headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+ // Ack [15, 20), [5, 25), [10, 17), [0, 12) and [22, 42).
+ QuicByteCount newly_acked_length = 0;
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 15, 5, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(5u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(9, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _));
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(4, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 5, 20, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(15u, newly_acked_length);
+
+ // Duplicate ack.
+ EXPECT_FALSE(headers_stream_->OnStreamFrameAcked(
+ 10, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(0u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(5, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 0, 12, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(5u, newly_acked_length);
+
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(3, _));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+ EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+ EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+ 22, 20, false, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(17u, newly_acked_length);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic