summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quic/core/http
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/third_party/quiche/src/quic/core/http')
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc3915
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc469
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h196
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc510
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc258
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h85
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc186
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/http_frames.h156
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc144
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h114
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info_test.cc354
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.cc48
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h99
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index_test.cc118
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.cc73
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.h89
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_header_list_test.cc83
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.cc157
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.h96
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc969
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc163
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h53
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc172
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.cc37
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h47
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc124
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc278
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h139
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc740
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc183
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h103
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.cc210
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h141
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_test.cc803
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.cc160
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h100
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc232
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.cc48
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.h31
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base_test.cc94
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc702
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h289
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc1808
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc661
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h283
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.cc128
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h75
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer_test.cc241
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc1559
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc355
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h79
-rw-r--r--chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc557
52 files changed, 18714 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc
new file mode 100644
index 00000000000..e537c735377
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc
@@ -0,0 +1,3915 @@
+// Copyright (c) 2012 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 <cstddef>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_framer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_creator.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_writer_wrapper.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_error_code_wrappers.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_port_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_sleep.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.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_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_loopback.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/quic/platform/impl/quic_socket_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/bad_packet_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/packet_reordering_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_client_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_dispatcher_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_server_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_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_id_manager_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_stream_sequencer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_client.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_server.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/server_thread.h"
+#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h"
+#include "net/third_party/quiche/src/quic/tools/quic_client.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_server.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h"
+
+using spdy::kV3LowestPriority;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsIR;
+
+namespace quic {
+namespace test {
+namespace {
+
+const char kFooResponseBody[] = "Artichoke hearts make me happy.";
+const char kBarResponseBody[] = "Palm hearts are pretty delicious, also.";
+const float kSessionToStreamRatio = 1.5;
+
+// Run all tests with the cross products of all versions.
+struct TestParams {
+ TestParams(const ParsedQuicVersionVector& client_supported_versions,
+ const ParsedQuicVersionVector& server_supported_versions,
+ ParsedQuicVersion negotiated_version,
+ bool client_supports_stateless_rejects,
+ bool server_uses_stateless_rejects_if_peer_supported,
+ QuicTag congestion_control_tag,
+ bool use_cheap_stateless_reject)
+ : client_supported_versions(client_supported_versions),
+ server_supported_versions(server_supported_versions),
+ negotiated_version(negotiated_version),
+ client_supports_stateless_rejects(client_supports_stateless_rejects),
+ server_uses_stateless_rejects_if_peer_supported(
+ server_uses_stateless_rejects_if_peer_supported),
+ congestion_control_tag(congestion_control_tag),
+ use_cheap_stateless_reject(use_cheap_stateless_reject) {}
+
+ friend std::ostream& operator<<(std::ostream& os, const TestParams& p) {
+ os << "{ server_supported_versions: "
+ << ParsedQuicVersionVectorToString(p.server_supported_versions);
+ os << " client_supported_versions: "
+ << ParsedQuicVersionVectorToString(p.client_supported_versions);
+ os << " negotiated_version: "
+ << ParsedQuicVersionToString(p.negotiated_version);
+ os << " client_supports_stateless_rejects: "
+ << p.client_supports_stateless_rejects;
+ os << " server_uses_stateless_rejects_if_peer_supported: "
+ << p.server_uses_stateless_rejects_if_peer_supported;
+ os << " congestion_control_tag: "
+ << QuicTagToString(p.congestion_control_tag);
+ os << " use_cheap_stateless_reject: " << p.use_cheap_stateless_reject
+ << " }";
+ return os;
+ }
+
+ ParsedQuicVersionVector client_supported_versions;
+ ParsedQuicVersionVector server_supported_versions;
+ ParsedQuicVersion negotiated_version;
+ bool client_supports_stateless_rejects;
+ bool server_uses_stateless_rejects_if_peer_supported;
+ QuicTag congestion_control_tag;
+ bool use_cheap_stateless_reject;
+};
+
+// Constructs various test permutations.
+std::vector<TestParams> GetTestParams(bool use_tls_handshake,
+ bool test_stateless_rejects) {
+ QuicFlagSaver flags;
+ // Divide the versions into buckets in which the intra-frame format
+ // is compatible. When clients encounter QUIC version negotiation
+ // they simply retransmit all packets using the new version's
+ // QUIC framing. However, they are unable to change the intra-frame
+ // layout (for example to change HTTP/2 headers to SPDY/3, or a change in the
+ // handshake protocol). So these tests need to ensure that clients are never
+ // attempting to do 0-RTT across incompatible versions. Chromium only
+ // supports a single version at a time anyway. :)
+ FLAGS_quic_supports_tls_handshake = use_tls_handshake;
+ ParsedQuicVersionVector all_supported_versions =
+ FilterSupportedVersions(AllSupportedVersions());
+
+ // Buckets are separated by versions: versions prior to QUIC_VERSION_47 use
+ // STREAM frames for the handshake, and only have QUIC crypto as the handshake
+ // protocol. Version 47 and greater use CRYPTO frames for the handshake, and
+ // must also be split based on the handshake protocol. If the handshake
+ // protocol (QUIC crypto or TLS) changes, the ClientHello/CHLO must be
+ // reconstructed for the correct protocol.
+ ParsedQuicVersionVector version_buckets[3];
+
+ for (const ParsedQuicVersion& version : all_supported_versions) {
+ if (!QuicVersionUsesCryptoFrames(version.transport_version)) {
+ version_buckets[0].push_back(version);
+ } else if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+ version_buckets[1].push_back(version);
+ } else {
+ version_buckets[2].push_back(version);
+ }
+ }
+
+ // This must be kept in sync with the number of nested for-loops below as it
+ // is used to prune the number of tests that are run.
+ const int kMaxEnabledOptions = 4;
+ int max_enabled_options = 0;
+ std::vector<TestParams> params;
+ for (const QuicTag congestion_control_tag : {kRENO, kTBBR, kQBIC, kTPCC}) {
+ for (bool server_uses_stateless_rejects_if_peer_supported : {true, false}) {
+ for (bool client_supports_stateless_rejects : {true, false}) {
+ for (bool use_cheap_stateless_reject : {true, false}) {
+ int enabled_options = 0;
+ if (congestion_control_tag != kQBIC) {
+ ++enabled_options;
+ }
+ if (client_supports_stateless_rejects) {
+ ++enabled_options;
+ }
+ if (server_uses_stateless_rejects_if_peer_supported) {
+ ++enabled_options;
+ }
+ if (use_cheap_stateless_reject) {
+ ++enabled_options;
+ }
+ CHECK_GE(kMaxEnabledOptions, enabled_options);
+ if (enabled_options > max_enabled_options) {
+ max_enabled_options = enabled_options;
+ }
+
+ // Run tests with no options, a single option, or all the
+ // options enabled to avoid a combinatorial explosion.
+ if (enabled_options > 1 && enabled_options < kMaxEnabledOptions) {
+ continue;
+ }
+
+ // There are many stateless reject combinations, so don't test them
+ // unless requested.
+ if ((server_uses_stateless_rejects_if_peer_supported ||
+ client_supports_stateless_rejects ||
+ use_cheap_stateless_reject) &&
+ !test_stateless_rejects) {
+ continue;
+ }
+
+ for (const ParsedQuicVersionVector& client_versions :
+ version_buckets) {
+ if (FilterSupportedVersions(client_versions).empty()) {
+ continue;
+ }
+ // Add an entry for server and client supporting all
+ // versions.
+ params.push_back(TestParams(
+ client_versions, all_supported_versions,
+ client_versions.front(), client_supports_stateless_rejects,
+ server_uses_stateless_rejects_if_peer_supported,
+ congestion_control_tag, use_cheap_stateless_reject));
+
+ // Run version negotiation tests tests with no options, or
+ // all the options enabled to avoid a combinatorial
+ // explosion.
+ if (enabled_options > 1 && enabled_options < kMaxEnabledOptions) {
+ continue;
+ }
+
+ // Test client supporting all versions and server supporting
+ // 1 version. Simulate an old server and exercise version
+ // downgrade in the client. Protocol negotiation should
+ // occur. Skip the i = 0 case because it is essentially the
+ // same as the default case.
+ for (size_t i = 1; i < client_versions.size(); ++i) {
+ ParsedQuicVersionVector server_supported_versions;
+ server_supported_versions.push_back(client_versions[i]);
+ if (FilterSupportedVersions(server_supported_versions).empty()) {
+ continue;
+ }
+ params.push_back(TestParams(
+ client_versions, server_supported_versions,
+ server_supported_versions.front(),
+ client_supports_stateless_rejects,
+ server_uses_stateless_rejects_if_peer_supported,
+ congestion_control_tag, use_cheap_stateless_reject));
+ } // End of inner version loop.
+ } // End of outer version loop.
+ } // End of use_cheap_stateless_reject loop.
+ } // End of client_supports_stateless_rejects loop.
+ } // End of server_uses_stateless_rejects_if_peer_supported loop.
+ } // End of congestion_control_tag loop.
+ CHECK_EQ(kMaxEnabledOptions, max_enabled_options);
+ return params;
+}
+
+class ServerDelegate : public PacketDroppingTestWriter::Delegate {
+ public:
+ explicit ServerDelegate(QuicDispatcher* dispatcher)
+ : dispatcher_(dispatcher) {}
+ ~ServerDelegate() override = default;
+ void OnCanWrite() override { dispatcher_->OnCanWrite(); }
+
+ private:
+ QuicDispatcher* dispatcher_;
+};
+
+class ClientDelegate : public PacketDroppingTestWriter::Delegate {
+ public:
+ explicit ClientDelegate(QuicClient* client) : client_(client) {}
+ ~ClientDelegate() override = default;
+ void OnCanWrite() override {
+ QuicEpollEvent event(EPOLLOUT);
+ client_->epoll_network_helper()->OnEvent(client_->GetLatestFD(), &event);
+ }
+
+ private:
+ QuicClient* client_;
+};
+
+class EndToEndTest : public QuicTestWithParam<TestParams> {
+ protected:
+ EndToEndTest()
+ : initialized_(false),
+ connect_to_server_on_initialize_(true),
+ server_address_(
+ QuicSocketAddress(TestLoopback(), QuicPickUnusedPortOrDie())),
+ server_hostname_("test.example.com"),
+ client_writer_(nullptr),
+ server_writer_(nullptr),
+ negotiated_version_(UnsupportedQuicVersion()),
+ chlo_multiplier_(0),
+ stream_factory_(nullptr),
+ support_server_push_(false),
+ override_connection_id_(nullptr),
+ expected_connection_id_length_(kQuicDefaultConnectionIdLength) {
+ FLAGS_quic_supports_tls_handshake = true;
+ SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, true);
+ SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, true);
+ client_supported_versions_ = GetParam().client_supported_versions;
+ server_supported_versions_ = GetParam().server_supported_versions;
+ negotiated_version_ = GetParam().negotiated_version;
+
+ QUIC_LOG(INFO) << "Using Configuration: " << GetParam();
+
+ // Use different flow control windows for client/server.
+ client_config_.SetInitialStreamFlowControlWindowToSend(
+ 2 * kInitialStreamFlowControlWindowForTest);
+ client_config_.SetInitialSessionFlowControlWindowToSend(
+ 2 * kInitialSessionFlowControlWindowForTest);
+ server_config_.SetInitialStreamFlowControlWindowToSend(
+ 3 * kInitialStreamFlowControlWindowForTest);
+ server_config_.SetInitialSessionFlowControlWindowToSend(
+ 3 * kInitialSessionFlowControlWindowForTest);
+
+ // The default idle timeouts can be too strict when running on a busy
+ // machine.
+ const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(30);
+ client_config_.set_max_time_before_crypto_handshake(timeout);
+ client_config_.set_max_idle_time_before_crypto_handshake(timeout);
+ server_config_.set_max_time_before_crypto_handshake(timeout);
+ server_config_.set_max_idle_time_before_crypto_handshake(timeout);
+
+ AddToCache("/foo", 200, kFooResponseBody);
+ AddToCache("/bar", 200, kBarResponseBody);
+ }
+
+ ~EndToEndTest() override { QuicRecyclePort(server_address_.port()); }
+
+ virtual void CreateClientWithWriter() {
+ client_.reset(CreateQuicClient(client_writer_));
+ }
+
+ QuicTestClient* CreateQuicClient(QuicPacketWriterWrapper* writer) {
+ QuicTestClient* client =
+ new QuicTestClient(server_address_, server_hostname_, client_config_,
+ client_supported_versions_,
+ crypto_test_utils::ProofVerifierForTesting());
+ client->UseWriter(writer);
+ if (!pre_shared_key_client_.empty()) {
+ client->client()->SetPreSharedKey(pre_shared_key_client_);
+ }
+ if (override_connection_id_ != nullptr) {
+ client->UseConnectionId(*override_connection_id_);
+ }
+ client->Connect();
+ return client;
+ }
+
+ void set_smaller_flow_control_receive_window() {
+ const uint32_t kClientIFCW = 64 * 1024;
+ const uint32_t kServerIFCW = 1024 * 1024;
+ set_client_initial_stream_flow_control_receive_window(kClientIFCW);
+ set_client_initial_session_flow_control_receive_window(
+ kSessionToStreamRatio * kClientIFCW);
+ set_server_initial_stream_flow_control_receive_window(kServerIFCW);
+ set_server_initial_session_flow_control_receive_window(
+ kSessionToStreamRatio * kServerIFCW);
+ }
+
+ void set_client_initial_stream_flow_control_receive_window(uint32_t window) {
+ CHECK(client_ == nullptr);
+ QUIC_DLOG(INFO) << "Setting client initial stream flow control window: "
+ << window;
+ client_config_.SetInitialStreamFlowControlWindowToSend(window);
+ }
+
+ void set_client_initial_session_flow_control_receive_window(uint32_t window) {
+ CHECK(client_ == nullptr);
+ QUIC_DLOG(INFO) << "Setting client initial session flow control window: "
+ << window;
+ client_config_.SetInitialSessionFlowControlWindowToSend(window);
+ }
+
+ void set_server_initial_stream_flow_control_receive_window(uint32_t window) {
+ CHECK(server_thread_ == nullptr);
+ QUIC_DLOG(INFO) << "Setting server initial stream flow control window: "
+ << window;
+ server_config_.SetInitialStreamFlowControlWindowToSend(window);
+ }
+
+ void set_server_initial_session_flow_control_receive_window(uint32_t window) {
+ CHECK(server_thread_ == nullptr);
+ QUIC_DLOG(INFO) << "Setting server initial session flow control window: "
+ << window;
+ server_config_.SetInitialSessionFlowControlWindowToSend(window);
+ }
+
+ const QuicSentPacketManager* GetSentPacketManagerFromFirstServerSession() {
+ return &GetServerConnection()->sent_packet_manager();
+ }
+
+ QuicConnection* GetServerConnection() {
+ return GetServerSession()->connection();
+ }
+
+ QuicSession* GetServerSession() {
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ EXPECT_EQ(1u, dispatcher->session_map().size());
+ return dispatcher->session_map().begin()->second.get();
+ }
+
+ bool Initialize() {
+ QuicTagVector copt;
+ server_config_.SetConnectionOptionsToSend(copt);
+ copt = client_extra_copts_;
+
+ // TODO(nimia): Consider setting the congestion control algorithm for the
+ // client as well according to the test parameter.
+ copt.push_back(GetParam().congestion_control_tag);
+ if (GetParam().congestion_control_tag == kTPCC &&
+ GetQuicReloadableFlag(quic_enable_pcc3)) {
+ copt.push_back(kTPCC);
+ }
+
+ if (GetParam().client_supports_stateless_rejects) {
+ copt.push_back(kSREJ);
+ }
+ client_config_.SetConnectionOptionsToSend(copt);
+
+ // Start the server first, because CreateQuicClient() attempts
+ // to connect to the server.
+ StartServer();
+
+ if (!connect_to_server_on_initialize_) {
+ initialized_ = true;
+ return true;
+ }
+
+ CreateClientWithWriter();
+ static QuicEpollEvent event(EPOLLOUT);
+ if (client_writer_ != nullptr) {
+ client_writer_->Initialize(
+ QuicConnectionPeer::GetHelper(
+ client_->client()->client_session()->connection()),
+ QuicConnectionPeer::GetAlarmFactory(
+ client_->client()->client_session()->connection()),
+ QuicMakeUnique<ClientDelegate>(client_->client()));
+ }
+ initialized_ = true;
+ return client_->client()->connected();
+ }
+
+ void SetUp() override {
+ // The ownership of these gets transferred to the QuicPacketWriterWrapper
+ // when Initialize() is executed.
+ client_writer_ = new PacketDroppingTestWriter();
+ server_writer_ = new PacketDroppingTestWriter();
+ }
+
+ void TearDown() override {
+ ASSERT_TRUE(initialized_) << "You must call Initialize() in every test "
+ << "case. Otherwise, your test will leak memory.";
+ StopServer();
+ }
+
+ void StartServer() {
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects,
+ GetParam().use_cheap_stateless_reject);
+
+ auto* test_server = new QuicTestServer(
+ crypto_test_utils::ProofSourceForTesting(), server_config_,
+ server_supported_versions_, &memory_cache_backend_,
+ expected_connection_id_length_);
+ server_thread_ = QuicMakeUnique<ServerThread>(test_server, server_address_);
+ if (chlo_multiplier_ != 0) {
+ server_thread_->server()->SetChloMultiplier(chlo_multiplier_);
+ }
+ if (!pre_shared_key_server_.empty()) {
+ server_thread_->server()->SetPreSharedKey(pre_shared_key_server_);
+ }
+ server_thread_->Initialize();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ QuicDispatcherPeer::UseWriter(dispatcher, server_writer_);
+
+ SetQuicReloadableFlag(
+ enable_quic_stateless_reject_support,
+ GetParam().server_uses_stateless_rejects_if_peer_supported);
+
+ server_writer_->Initialize(QuicDispatcherPeer::GetHelper(dispatcher),
+ QuicDispatcherPeer::GetAlarmFactory(dispatcher),
+ QuicMakeUnique<ServerDelegate>(dispatcher));
+ if (stream_factory_ != nullptr) {
+ static_cast<QuicTestServer*>(server_thread_->server())
+ ->SetSpdyStreamFactory(stream_factory_);
+ }
+
+ server_thread_->Start();
+ }
+
+ void StopServer() {
+ if (server_thread_) {
+ server_thread_->Quit();
+ server_thread_->Join();
+ }
+ }
+
+ void AddToCache(QuicStringPiece path,
+ int response_code,
+ QuicStringPiece body) {
+ memory_cache_backend_.AddSimpleResponse(server_hostname_, path,
+ response_code, body);
+ }
+
+ void SetPacketLossPercentage(int32_t loss) {
+ client_writer_->set_fake_packet_loss_percentage(loss);
+ server_writer_->set_fake_packet_loss_percentage(loss);
+ }
+
+ void SetPacketSendDelay(QuicTime::Delta delay) {
+ client_writer_->set_fake_packet_delay(delay);
+ server_writer_->set_fake_packet_delay(delay);
+ }
+
+ void SetReorderPercentage(int32_t reorder) {
+ client_writer_->set_fake_reorder_percentage(reorder);
+ server_writer_->set_fake_reorder_percentage(reorder);
+ }
+
+ // Verifies that the client and server connections were both free of packets
+ // being discarded, based on connection stats.
+ // Calls server_thread_ Pause() and Resume(), which may only be called once
+ // per test.
+ void VerifyCleanConnection(bool had_packet_loss) {
+ QuicConnectionStats client_stats =
+ client_->client()->client_session()->connection()->GetStats();
+ // TODO(ianswett): Determine why this becomes even more flaky with BBR
+ // enabled. b/62141144
+ if (!had_packet_loss && !GetQuicReloadableFlag(quic_default_to_bbr)) {
+ EXPECT_EQ(0u, client_stats.packets_lost);
+ }
+ EXPECT_EQ(0u, client_stats.packets_discarded);
+ // When doing 0-RTT with stateless rejects, the encrypted requests cause
+ // a retranmission of the SREJ packets which are dropped by the client.
+ // When client starts with an unsupported version, the version negotiation
+ // packet sent by server for the old connection (respond for the connection
+ // close packet) will be dropped by the client.
+ if (!BothSidesSupportStatelessRejects() &&
+ !ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(0u, client_stats.packets_dropped);
+ }
+ if (!ClientSupportsIetfQuicNotSupportedByServer()) {
+ // In this case, if client sends 0-RTT POST with v99, receives IETF
+ // version negotiation packet and speaks a GQUIC version. Server processes
+ // this connection in time wait list and keeps sending IETF version
+ // negotiation packet for incoming packets. But these version negotiation
+ // packets cannot be processed by the client speaking GQUIC.
+ EXPECT_EQ(client_stats.packets_received, client_stats.packets_processed);
+ }
+
+ const int num_expected_stateless_rejects =
+ (BothSidesSupportStatelessRejects() &&
+ client_->client()->client_session()->GetNumSentClientHellos() > 0)
+ ? 1
+ : 0;
+ EXPECT_EQ(num_expected_stateless_rejects,
+ client_->client()->num_stateless_rejects_received());
+
+ server_thread_->Pause();
+ QuicConnectionStats server_stats = GetServerConnection()->GetStats();
+ if (!had_packet_loss) {
+ EXPECT_EQ(0u, server_stats.packets_lost);
+ }
+ EXPECT_EQ(0u, server_stats.packets_discarded);
+ // TODO(ianswett): Restore the check for packets_dropped equals 0.
+ // The expect for packets received is equal to packets processed fails
+ // due to version negotiation packets.
+ server_thread_->Resume();
+ }
+
+ bool BothSidesSupportStatelessRejects() {
+ return (GetParam().server_uses_stateless_rejects_if_peer_supported &&
+ GetParam().client_supports_stateless_rejects);
+ }
+
+ // Client supports IETF QUIC, while it is not supported by server.
+ bool ClientSupportsIetfQuicNotSupportedByServer() {
+ return GetParam().client_supported_versions[0].transport_version >
+ QUIC_VERSION_43 &&
+ FilterSupportedVersions(GetParam().server_supported_versions)[0]
+ .transport_version <= QUIC_VERSION_43;
+ }
+
+ // Returns true when client starts with an unsupported version, and client
+ // closes connection when version negotiation is received.
+ bool ServerSendsVersionNegotiation() {
+ return GetQuicReloadableFlag(quic_no_client_conn_ver_negotiation) &&
+ GetParam().client_supported_versions[0] !=
+ GetParam().negotiated_version;
+ }
+
+ bool SupportsIetfQuicWithTls(ParsedQuicVersion version) {
+ return version.transport_version > QUIC_VERSION_43 &&
+ version.handshake_protocol == PROTOCOL_TLS1_3;
+ }
+
+ void ExpectFlowControlsSynced(QuicFlowController* client,
+ QuicFlowController* server) {
+ EXPECT_EQ(QuicFlowControllerPeer::SendWindowSize(client),
+ QuicFlowControllerPeer::ReceiveWindowSize(server));
+ EXPECT_EQ(QuicFlowControllerPeer::ReceiveWindowSize(client),
+ QuicFlowControllerPeer::SendWindowSize(server));
+ }
+
+ // Must be called before Initialize to have effect.
+ void SetSpdyStreamFactory(QuicTestServer::StreamFactory* factory) {
+ stream_factory_ = factory;
+ }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(
+ client_->client()->client_session()->connection()->transport_version(),
+ n);
+ }
+
+ QuicStreamId GetNthServerInitiatedBidirectionalId(int n) {
+ return GetNthServerInitiatedBidirectionalStreamId(
+ client_->client()->client_session()->connection()->transport_version(),
+ n);
+ }
+
+ ScopedEnvironmentForThreads environment_;
+ bool initialized_;
+ // If true, the Initialize() function will create |client_| and starts to
+ // connect to the server.
+ // Default is true.
+ bool connect_to_server_on_initialize_;
+ QuicSocketAddress server_address_;
+ std::string server_hostname_;
+ QuicMemoryCacheBackend memory_cache_backend_;
+ std::unique_ptr<ServerThread> server_thread_;
+ std::unique_ptr<QuicTestClient> client_;
+ PacketDroppingTestWriter* client_writer_;
+ PacketDroppingTestWriter* server_writer_;
+ QuicConfig client_config_;
+ QuicConfig server_config_;
+ ParsedQuicVersionVector client_supported_versions_;
+ ParsedQuicVersionVector server_supported_versions_;
+ QuicTagVector client_extra_copts_;
+ ParsedQuicVersion negotiated_version_;
+ size_t chlo_multiplier_;
+ QuicTestServer::StreamFactory* stream_factory_;
+ bool support_server_push_;
+ std::string pre_shared_key_client_;
+ std::string pre_shared_key_server_;
+ QuicConnectionId* override_connection_id_;
+ uint8_t expected_connection_id_length_;
+};
+
+// Run all end to end tests with all supported versions.
+INSTANTIATE_TEST_SUITE_P(EndToEndTests,
+ EndToEndTest,
+ ::testing::ValuesIn(GetTestParams(false, false)));
+
+class EndToEndTestWithTls : public EndToEndTest {};
+
+INSTANTIATE_TEST_SUITE_P(EndToEndTestsWithTls,
+ EndToEndTestWithTls,
+ ::testing::ValuesIn(GetTestParams(true, false)));
+
+class EndToEndTestWithStatelessReject : public EndToEndTest {};
+
+INSTANTIATE_TEST_SUITE_P(WithStatelessReject,
+ EndToEndTestWithStatelessReject,
+ ::testing::ValuesIn(GetTestParams(false, true)));
+
+TEST_P(EndToEndTestWithTls, HandshakeSuccessful) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+ // There have been occasions where it seemed that negotiated_version_ and the
+ // version in the connection are not in sync. If it is happening, it has not
+ // been recreatable; this assert is here just to check and raise a flag if it
+ // happens.
+ ASSERT_EQ(
+ client_->client()->client_session()->connection()->transport_version(),
+ negotiated_version_.transport_version);
+
+ QuicCryptoStream* crypto_stream = QuicSessionPeer::GetMutableCryptoStream(
+ client_->client()->client_session());
+ QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(crypto_stream);
+ EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+ server_thread_->Pause();
+ crypto_stream = QuicSessionPeer::GetMutableCryptoStream(GetServerSession());
+ sequencer = QuicStreamPeer::sequencer(crypto_stream);
+ EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTestWithStatelessReject, SimpleRequestResponseStatless) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ int expected_num_client_hellos = 2;
+ if (ServerSendsVersionNegotiation()) {
+ ++expected_num_client_hellos;
+ if (BothSidesSupportStatelessRejects()) {
+ ++expected_num_client_hellos;
+ }
+ }
+ EXPECT_EQ(expected_num_client_hellos,
+ client_->client()->GetNumSentClientHellos());
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponse) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ int expected_num_client_hellos = 2;
+ if (ServerSendsVersionNegotiation()) {
+ ++expected_num_client_hellos;
+ if (BothSidesSupportStatelessRejects()) {
+ ++expected_num_client_hellos;
+ }
+ }
+ EXPECT_EQ(expected_num_client_hellos,
+ client_->client()->GetNumSentClientHellos());
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponseZeroConnectionID) {
+ QuicConnectionId connection_id = QuicUtils::CreateZeroConnectionId(
+ GetParam().negotiated_version.transport_version);
+ override_connection_id_ = &connection_id;
+ expected_connection_id_length_ = connection_id.length();
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ int expected_num_client_hellos = 2;
+ if (ServerSendsVersionNegotiation()) {
+ ++expected_num_client_hellos;
+ if (BothSidesSupportStatelessRejects()) {
+ ++expected_num_client_hellos;
+ }
+ }
+ EXPECT_EQ(expected_num_client_hellos,
+ client_->client()->GetNumSentClientHellos());
+ EXPECT_EQ(client_->client()->client_session()->connection()->connection_id(),
+ QuicUtils::CreateZeroConnectionId(
+ GetParam().negotiated_version.transport_version));
+}
+
+TEST_P(EndToEndTest, BadConnectionIdLength) {
+ if (!QuicUtils::VariableLengthConnectionIdAllowedForVersion(
+ GetParam().negotiated_version.transport_version)) {
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ QuicConnectionId connection_id =
+ TestConnectionIdNineBytesLong(UINT64_C(0xBADbadBADbad));
+ override_connection_id_ = &connection_id;
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client()
+ ->client_session()
+ ->connection()
+ ->connection_id()
+ .length());
+}
+
+TEST_P(EndToEndTest, MixGoodAndBadConnectionIdLengths) {
+ if (!QuicUtils::VariableLengthConnectionIdAllowedForVersion(
+ GetParam().negotiated_version.transport_version)) {
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+
+ // Start client_ which will use a bad connection ID length.
+ QuicConnectionId connection_id =
+ TestConnectionIdNineBytesLong(UINT64_C(0xBADbadBADbad));
+ override_connection_id_ = &connection_id;
+ ASSERT_TRUE(Initialize());
+ override_connection_id_ = nullptr;
+
+ // Start client2 which will use a good connection ID length.
+ std::unique_ptr<QuicTestClient> client2(CreateQuicClient(nullptr));
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "3";
+ client2->SendMessage(headers, "", /*fin=*/false);
+ client2->SendData("eep", true);
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client()
+ ->client_session()
+ ->connection()
+ ->connection_id()
+ .length());
+
+ client2->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client2->response_body());
+ EXPECT_EQ("200", client2->response_headers()->find(":status")->second);
+ EXPECT_EQ(kQuicDefaultConnectionIdLength, client2->client()
+ ->client_session()
+ ->connection()
+ ->connection_id()
+ .length());
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponseWithLargeReject) {
+ chlo_multiplier_ = 1;
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(4, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ }
+}
+
+TEST_P(EndToEndTestWithTls, SimpleRequestResponsev6) {
+ server_address_ =
+ QuicSocketAddress(QuicIpAddress::Loopback6(), server_address_.port());
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, SeparateFinPacket) {
+ ASSERT_TRUE(Initialize());
+
+ // Send a request in two parts: the request and then an empty packet with FIN.
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ client_->SendMessage(headers, "", /*fin=*/false);
+ client_->SendData("", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Now do the same thing but with a content length.
+ headers["content-length"] = "3";
+ client_->SendMessage(headers, "", /*fin=*/false);
+ client_->SendData("foo", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultipleRequestResponse) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, MultipleRequestResponseZeroConnectionID) {
+ QuicConnectionId connection_id = QuicUtils::CreateZeroConnectionId(
+ GetParam().negotiated_version.transport_version);
+ override_connection_id_ = &connection_id;
+ expected_connection_id_length_ = connection_id.length();
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultipleStreams) {
+ // Verifies quic_test_client can track responses of all active streams.
+ ASSERT_TRUE(Initialize());
+
+ const int kNumRequests = 10;
+
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "3";
+
+ for (int i = 0; i < kNumRequests; ++i) {
+ client_->SendMessage(headers, "bar", /*fin=*/true);
+ }
+
+ while (kNumRequests > client_->num_responses()) {
+ client_->ClearPerRequestState();
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ }
+}
+
+TEST_P(EndToEndTestWithTls, MultipleClients) {
+ ASSERT_TRUE(Initialize());
+ std::unique_ptr<QuicTestClient> client2(CreateQuicClient(nullptr));
+
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "3";
+
+ client_->SendMessage(headers, "", /*fin=*/false);
+ client2->SendMessage(headers, "", /*fin=*/false);
+
+ client_->SendData("bar", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ client2->SendData("eep", true);
+ client2->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client2->response_body());
+ EXPECT_EQ("200", client2->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, RequestOverMultiplePackets) {
+ // Send a large enough request to guarantee fragmentation.
+ std::string huge_request =
+ "/some/path?query=" + std::string(kMaxOutgoingPacketSize, '.');
+ AddToCache(huge_request, 200, kBarResponseBody);
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest(huge_request));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultiplePacketsRandomOrder) {
+ // Send a large enough request to guarantee fragmentation.
+ std::string huge_request =
+ "/some/path?query=" + std::string(kMaxOutgoingPacketSize, '.');
+ AddToCache(huge_request, 200, kBarResponseBody);
+
+ ASSERT_TRUE(Initialize());
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(50);
+
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest(huge_request));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, PostMissingBytes) {
+ ASSERT_TRUE(Initialize());
+
+ // Add a content length header with no body.
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "3";
+
+ // This should be detected as stream fin without complete request,
+ // triggering an error response.
+ client_->SendCustomSynchronousRequest(headers, "");
+ EXPECT_EQ(QuicSimpleServerStream::kErrorResponseBody,
+ client_->response_body());
+ EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLoss) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // 1 MB body.
+ std::string body(1024 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // TODO(ianswett): There should not be packet loss in this test, but on some
+ // platforms the receive buffer overflows.
+ VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLoss1sRTT) {
+ ASSERT_TRUE(Initialize());
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(1000));
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // 100 KB body.
+ std::string body(100 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, LargePostWithPacketLoss) {
+ if (!BothSidesSupportStatelessRejects()) {
+ // Connect with lower fake packet loss than we'd like to test.
+ // Until b/10126687 is fixed, losing handshake packets is pretty
+ // brutal.
+ // TODO(jokulik): Until we support redundant SREJ packets, don't
+ // drop handshake packets for stateless rejects.
+ SetPacketLossPercentage(5);
+ }
+ ASSERT_TRUE(Initialize());
+
+ // Wait for the server SHLO before upping the packet loss.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(30);
+
+ // 10 KB body.
+ std::string body(1024 * 10, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ VerifyCleanConnection(true);
+}
+
+// Regression test for b/80090281.
+TEST_P(EndToEndTest, LargePostWithPacketLossAndAlwaysBundleWindowUpdates) {
+ ASSERT_TRUE(Initialize());
+
+ // Wait for the server SHLO before upping the packet loss.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Normally server only bundles a retransmittable frame once every other
+ // kMaxConsecutiveNonRetransmittablePackets ack-only packets. Setting the max
+ // to 0 to reliably reproduce b/80090281.
+ server_thread_->Schedule([this]() {
+ QuicConnectionPeer::SetMaxConsecutiveNumPacketsWithNoRetransmittableFrames(
+ GetServerConnection(), 0);
+ });
+
+ SetPacketLossPercentage(30);
+
+ // 10 KB body.
+ std::string body(1024 * 10, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTest, LargePostWithPacketLossAndBlockedSocket) {
+ if (!BothSidesSupportStatelessRejects()) {
+ // Connect with lower fake packet loss than we'd like to test. Until
+ // b/10126687 is fixed, losing handshake packets is pretty brutal.
+ // TODO(jokulik): Until we support redundant SREJ packets, don't
+ // drop handshake packets for stateless rejects.
+ SetPacketLossPercentage(5);
+ }
+ ASSERT_TRUE(Initialize());
+
+ // Wait for the server SHLO before upping the packet loss.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(10);
+ client_writer_->set_fake_blocked_socket_percentage(10);
+
+ // 10 KB body.
+ std::string body(1024 * 10, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLossWithDelayAndReordering) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ // Both of these must be called when the writer is not actively used.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // 1 MB body.
+ std::string body(1024 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+}
+
+TEST_P(EndToEndTest, LargePostZeroRTTFailure) {
+ // Send a request and then disconnect. This prepares the client to attempt
+ // a 0-RTT handshake for the next request.
+ ASSERT_TRUE(Initialize());
+
+ std::string body(20480, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos on the latest
+ // session is 1.
+ const int expected_num_hellos_latest_session =
+ (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+ ? 1
+ : 2;
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // The 0-RTT handshake should succeed.
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+
+ EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // Restart the server so that the 0-RTT handshake will take 1 RTT.
+ StopServer();
+ server_writer_ = new PacketDroppingTestWriter();
+ StartServer();
+
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos sent on the
+ // latest session is 1.
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, SynchronousRequestZeroRTTFailure) {
+ // Send a request and then disconnect. This prepares the client to attempt
+ // a 0-RTT handshake for the next request.
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos on that second
+ // latest session is 1.
+ const int expected_num_hellos_latest_session =
+ (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+ ? 1
+ : 2;
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // The 0-RTT handshake should succeed.
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+ EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // Restart the server so that the 0-RTT handshake will take 1 RTT.
+ StopServer();
+ server_writer_ = new PacketDroppingTestWriter();
+ StartServer();
+
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos sent on the
+ // latest session is 1.
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, LargePostSynchronousRequest) {
+ // Send a request and then disconnect. This prepares the client to attempt
+ // a 0-RTT handshake for the next request.
+ ASSERT_TRUE(Initialize());
+
+ std::string body(20480, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos on the latest
+ // session is 1.
+ const int expected_num_hellos_latest_session =
+ (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+ ? 1
+ : 2;
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // The 0-RTT handshake should succeed.
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+
+ EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
+
+ client_->Disconnect();
+
+ // Restart the server so that the 0-RTT handshake will take 1 RTT.
+ StopServer();
+ server_writer_ = new PacketDroppingTestWriter();
+ StartServer();
+
+ client_->Connect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // In the non-stateless case, the same session is used for both
+ // hellos, so the number of hellos sent on that session is 2. In
+ // the stateless case, the first client session will be completely
+ // torn down after the reject. The number of hellos sent on the
+ // latest session is 1.
+ EXPECT_EQ(expected_num_hellos_latest_session,
+ client_->client()->client_session()->GetNumSentClientHellos());
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ }
+
+ VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, StatelessRejectWithPacketLoss) {
+ // In this test, we intentionally drop the first packet from the
+ // server, which corresponds with the initial REJ/SREJ response from
+ // the server.
+ server_writer_->set_fake_drop_first_n_packets(1);
+ ASSERT_TRUE(Initialize());
+}
+
+TEST_P(EndToEndTest, SetInitialReceivedConnectionOptions) {
+ QuicTagVector initial_received_options;
+ initial_received_options.push_back(kTBBR);
+ initial_received_options.push_back(kIW10);
+ initial_received_options.push_back(kPRST);
+ EXPECT_TRUE(server_config_.SetInitialReceivedConnectionOptions(
+ initial_received_options));
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ EXPECT_FALSE(server_config_.SetInitialReceivedConnectionOptions(
+ initial_received_options));
+
+ // Verify that server's configuration is correct.
+ server_thread_->Pause();
+ EXPECT_TRUE(server_config_.HasReceivedConnectionOptions());
+ EXPECT_TRUE(
+ ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kTBBR));
+ EXPECT_TRUE(
+ ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kIW10));
+ EXPECT_TRUE(
+ ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kPRST));
+}
+
+TEST_P(EndToEndTest, LargePostSmallBandwidthLargeBuffer) {
+ ASSERT_TRUE(Initialize());
+ SetPacketSendDelay(QuicTime::Delta::FromMicroseconds(1));
+ // 256KB per second with a 256KB buffer from server to client. Wireless
+ // clients commonly have larger buffers, but our max CWND is 200.
+ server_writer_->set_max_bandwidth_and_buffer_size(
+ QuicBandwidth::FromBytesPerSecond(256 * 1024), 256 * 1024);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // 1 MB body.
+ std::string body(1024 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ // This connection may drop packets, because the buffer is smaller than the
+ // max CWND.
+ VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTestWithTls, DoNotSetSendAlarmIfConnectionFlowControlBlocked) {
+ // Regression test for b/14677858.
+ // Test that the resume write alarm is not set in QuicConnection::OnCanWrite
+ // if currently connection level flow control blocked. If set, this results in
+ // an infinite loop in the EpollServer, as the alarm fires and is immediately
+ // rescheduled.
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Ensure both stream and connection level are flow control blocked by setting
+ // the send window offset to 0.
+ const uint64_t flow_control_window =
+ server_config_.GetInitialStreamFlowControlWindowToSend();
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ QuicSession* session = client_->client()->client_session();
+ QuicFlowControllerPeer::SetSendWindowOffset(stream->flow_controller(), 0);
+ QuicFlowControllerPeer::SetSendWindowOffset(session->flow_controller(), 0);
+ EXPECT_TRUE(stream->flow_controller()->IsBlocked());
+ EXPECT_TRUE(session->flow_controller()->IsBlocked());
+
+ // Make sure that the stream has data pending so that it will be marked as
+ // write blocked when it receives a stream level WINDOW_UPDATE.
+ stream->WriteOrBufferBody("hello", false);
+
+ // The stream now attempts to write, fails because it is still connection
+ // level flow control blocked, and is added to the write blocked list.
+ QuicWindowUpdateFrame window_update(kInvalidControlFrameId, stream->id(),
+ 2 * flow_control_window);
+ stream->OnWindowUpdateFrame(window_update);
+
+ // Prior to fixing b/14677858 this call would result in an infinite loop in
+ // Chromium. As a proxy for detecting this, we now check whether the
+ // send alarm is set after OnCanWrite. It should not be, as the
+ // connection is still flow control blocked.
+ session->connection()->OnCanWrite();
+
+ QuicAlarm* send_alarm =
+ QuicConnectionPeer::GetSendAlarm(session->connection());
+ EXPECT_FALSE(send_alarm->IsSet());
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, InvalidStream) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ std::string body(kMaxOutgoingPacketSize, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ // Force the client to write with a stream ID belonging to a nonexistent
+ // server-side stream.
+ QuicSpdySession* session = client_->client()->client_session();
+ QuicSessionPeer::SetNextOutgoingBidirectionalStreamId(
+ session, GetNthServerInitiatedBidirectionalId(0));
+
+ client_->SendCustomSynchronousRequest(headers, body);
+ EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
+}
+
+// Test that if the server will close the connection if the client attempts
+// to send a request with overly large headers.
+TEST_P(EndToEndTest, LargeHeaders) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ std::string body(kMaxOutgoingPacketSize, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["key1"] = std::string(15 * 1024, 'a');
+ headers["key2"] = std::string(15 * 1024, 'a');
+ headers["key3"] = std::string(15 * 1024, 'a');
+
+ client_->SendCustomSynchronousRequest(headers, body);
+ EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, client_->stream_error());
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, EarlyResponseWithQuicStreamNoError) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ std::string large_body(1024 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ // Insert an invalid content_length field in request to trigger an early
+ // response from server.
+ headers["content-length"] = "-3";
+
+ client_->SendCustomSynchronousRequest(headers, large_body);
+ EXPECT_EQ("bad", client_->response_body());
+ EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(QUIC_STREAM_NO_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+// TODO(rch): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTestWithTls, QUIC_TEST_DISABLED_IN_CHROME(MultipleTermination)) {
+ ASSERT_TRUE(Initialize());
+
+ // Set the offset so we won't frame. Otherwise when we pick up termination
+ // before HTTP framing is complete, we send an error and close the stream,
+ // and the second write is picked up as writing on a closed stream.
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ ASSERT_TRUE(stream != nullptr);
+ QuicStreamPeer::SetStreamBytesWritten(3, stream);
+
+ client_->SendData("bar", true);
+ client_->WaitForWriteToFlush();
+
+ // By default the stream protects itself from writes after terminte is set.
+ // Override this to test the server handling buggy clients.
+ QuicStreamPeer::SetWriteSideClosed(false, client_->GetOrCreateStream());
+
+ EXPECT_QUIC_BUG(client_->SendData("eep", true), "Fin already buffered");
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, Timeout) {
+ client_config_.SetIdleNetworkTimeout(QuicTime::Delta::FromMicroseconds(500),
+ QuicTime::Delta::FromMicroseconds(500));
+ // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake:
+ // that's enough to validate timeout in this case.
+ Initialize();
+ while (client_->client()->connected()) {
+ client_->client()->WaitForEvents();
+ }
+}
+
+TEST_P(EndToEndTestWithTls, MaxIncomingDynamicStreamsLimitRespected) {
+ // Set a limit on maximum number of incoming dynamic streams.
+ // Make sure the limit is respected.
+ const uint32_t kServerMaxIncomingDynamicStreams = 1;
+ server_config_.SetMaxIncomingDynamicStreamsToSend(
+ kServerMaxIncomingDynamicStreams);
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+
+ // Make the client misbehave after negotiation.
+ const int kServerMaxStreams = kMaxStreamsMinimumIncrement + 1;
+ QuicSessionPeer::SetMaxOpenOutgoingStreams(
+ client_->client()->client_session(), kServerMaxStreams + 1);
+
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "3";
+
+ // The server supports a small number of additional streams beyond the
+ // negotiated limit. Open enough streams to go beyond that limit.
+ for (int i = 0; i < kServerMaxStreams + 1; ++i) {
+ client_->SendMessage(headers, "", /*fin=*/false);
+ }
+ client_->WaitForResponse();
+ if (client_connection->transport_version() != QUIC_VERSION_99) {
+ EXPECT_TRUE(client_->connected());
+ EXPECT_EQ(QUIC_REFUSED_STREAM, client_->stream_error());
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+ } else {
+ // Version 99 disconnects the connection if we exceed the stream limit.
+ EXPECT_FALSE(client_->connected());
+ EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
+ }
+}
+
+TEST_P(EndToEndTest, SetIndependentMaxIncomingDynamicStreamsLimits) {
+ // Each endpoint can set max incoming dynamic streams independently.
+ const uint32_t kClientMaxIncomingDynamicStreams = 2;
+ const uint32_t kServerMaxIncomingDynamicStreams = 1;
+ client_config_.SetMaxIncomingDynamicStreamsToSend(
+ kClientMaxIncomingDynamicStreams);
+ server_config_.SetMaxIncomingDynamicStreamsToSend(
+ kServerMaxIncomingDynamicStreams);
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // The client has received the server's limit and vice versa.
+ QuicSpdyClientSession* client_session = client_->client()->client_session();
+ size_t client_max_open_outgoing_bidirectional_streams =
+ client_session->connection()->transport_version() == QUIC_VERSION_99
+ ? QuicSessionPeer::v99_streamid_manager(client_session)
+ ->max_allowed_outgoing_bidirectional_streams()
+ : QuicSessionPeer::GetStreamIdManager(client_session)
+ ->max_open_outgoing_streams();
+ size_t client_max_open_outgoing_unidirectional_streams =
+ client_session->connection()->transport_version() == QUIC_VERSION_99
+ ? QuicSessionPeer::v99_streamid_manager(client_session)
+ ->max_allowed_outgoing_unidirectional_streams()
+ : QuicSessionPeer::GetStreamIdManager(client_session)
+ ->max_open_outgoing_streams();
+ EXPECT_EQ(kServerMaxIncomingDynamicStreams,
+ client_max_open_outgoing_bidirectional_streams);
+ EXPECT_EQ(kServerMaxIncomingDynamicStreams,
+ client_max_open_outgoing_unidirectional_streams);
+ server_thread_->Pause();
+ QuicSession* server_session = GetServerSession();
+ size_t server_max_open_outgoing_bidirectional_streams =
+ server_session->connection()->transport_version() == QUIC_VERSION_99
+ ? QuicSessionPeer::v99_streamid_manager(server_session)
+ ->max_allowed_outgoing_bidirectional_streams()
+ : QuicSessionPeer::GetStreamIdManager(server_session)
+ ->max_open_outgoing_streams();
+ size_t server_max_open_outgoing_unidirectional_streams =
+ server_session->connection()->transport_version() == QUIC_VERSION_99
+ ? QuicSessionPeer::v99_streamid_manager(server_session)
+ ->max_allowed_outgoing_unidirectional_streams()
+ : QuicSessionPeer::GetStreamIdManager(server_session)
+ ->max_open_outgoing_streams();
+ EXPECT_EQ(kClientMaxIncomingDynamicStreams,
+ server_max_open_outgoing_bidirectional_streams);
+ EXPECT_EQ(kClientMaxIncomingDynamicStreams,
+ server_max_open_outgoing_unidirectional_streams);
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, NegotiateCongestionControl) {
+ ASSERT_TRUE(Initialize());
+
+ // For PCC, the underlying implementation may be a stub with a
+ // different name-tag. Skip the rest of this test.
+ if (GetParam().congestion_control_tag == kTPCC) {
+ return;
+ }
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ CongestionControlType expected_congestion_control_type = kRenoBytes;
+ switch (GetParam().congestion_control_tag) {
+ case kRENO:
+ expected_congestion_control_type = kRenoBytes;
+ break;
+ case kTBBR:
+ expected_congestion_control_type = kBBR;
+ break;
+ case kQBIC:
+ expected_congestion_control_type = kCubicBytes;
+ break;
+ default:
+ QUIC_DLOG(FATAL) << "Unexpected congestion control tag";
+ }
+
+ server_thread_->Pause();
+ EXPECT_EQ(expected_congestion_control_type,
+ QuicSentPacketManagerPeer::GetSendAlgorithm(
+ *GetSentPacketManagerFromFirstServerSession())
+ ->GetCongestionControlType());
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ClientSuggestsRTT) {
+ // Client suggests initial RTT, verify it is used.
+ const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000);
+ client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds());
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(1u, dispatcher->session_map().size());
+ const QuicSentPacketManager& client_sent_packet_manager =
+ client_->client()->client_session()->connection()->sent_packet_manager();
+ const QuicSentPacketManager* server_sent_packet_manager =
+ GetSentPacketManagerFromFirstServerSession();
+
+ EXPECT_EQ(kInitialRTT,
+ client_sent_packet_manager.GetRttStats()->initial_rtt());
+ EXPECT_EQ(kInitialRTT,
+ server_sent_packet_manager->GetRttStats()->initial_rtt());
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ClientSuggestsIgnoredRTT) {
+ // Client suggests initial RTT, but also specifies NRTT, so it's not used.
+ const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000);
+ client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds());
+ QuicTagVector options;
+ options.push_back(kNRTT);
+ client_config_.SetConnectionOptionsToSend(options);
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(1u, dispatcher->session_map().size());
+ const QuicSentPacketManager& client_sent_packet_manager =
+ client_->client()->client_session()->connection()->sent_packet_manager();
+ const QuicSentPacketManager* server_sent_packet_manager =
+ GetSentPacketManagerFromFirstServerSession();
+
+ EXPECT_EQ(kInitialRTT,
+ client_sent_packet_manager.GetRttStats()->initial_rtt());
+ EXPECT_EQ(kInitialRTT,
+ server_sent_packet_manager->GetRttStats()->initial_rtt());
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, MaxInitialRTT) {
+ // Client tries to suggest twice the server's max initial rtt and the server
+ // uses the max.
+ client_config_.SetInitialRoundTripTimeUsToSend(2 *
+ kMaxInitialRoundTripTimeUs);
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ const QuicSentPacketManager& client_sent_packet_manager =
+ client_->client()->client_session()->connection()->sent_packet_manager();
+
+ // Now that acks have been exchanged, the RTT estimate has decreased on the
+ // server and is not infinite on the client.
+ EXPECT_FALSE(
+ client_sent_packet_manager.GetRttStats()->smoothed_rtt().IsInfinite());
+ const RttStats& server_rtt_stats =
+ *GetServerConnection()->sent_packet_manager().GetRttStats();
+ EXPECT_EQ(static_cast<int64_t>(kMaxInitialRoundTripTimeUs),
+ server_rtt_stats.initial_rtt().ToMicroseconds());
+ EXPECT_GE(static_cast<int64_t>(kMaxInitialRoundTripTimeUs),
+ server_rtt_stats.smoothed_rtt().ToMicroseconds());
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, MinInitialRTT) {
+ // Client tries to suggest 0 and the server uses the default.
+ client_config_.SetInitialRoundTripTimeUsToSend(0);
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ const QuicSentPacketManager& client_sent_packet_manager =
+ client_->client()->client_session()->connection()->sent_packet_manager();
+ const QuicSentPacketManager& server_sent_packet_manager =
+ GetServerConnection()->sent_packet_manager();
+
+ // Now that acks have been exchanged, the RTT estimate has decreased on the
+ // server and is not infinite on the client.
+ EXPECT_FALSE(
+ client_sent_packet_manager.GetRttStats()->smoothed_rtt().IsInfinite());
+ // Expect the default rtt of 100ms.
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100),
+ server_sent_packet_manager.GetRttStats()->initial_rtt());
+ // Ensure the bandwidth is valid.
+ client_sent_packet_manager.BandwidthEstimate();
+ server_sent_packet_manager.BandwidthEstimate();
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, 0ByteConnectionId) {
+ client_config_.SetBytesForConnectionIdToSend(0);
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ QuicPacketHeader* header =
+ QuicConnectionPeer::GetLastHeader(client_connection);
+ EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+}
+
+TEST_P(EndToEndTestWithTls, 8ByteConnectionId) {
+ client_config_.SetBytesForConnectionIdToSend(8);
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ QuicPacketHeader* header =
+ QuicConnectionPeer::GetLastHeader(client_connection);
+ if (client_connection->transport_version() > QUIC_VERSION_43) {
+ EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+ } else {
+ EXPECT_EQ(CONNECTION_ID_PRESENT,
+ header->destination_connection_id_included);
+ }
+}
+
+TEST_P(EndToEndTestWithTls, 15ByteConnectionId) {
+ client_config_.SetBytesForConnectionIdToSend(15);
+ ASSERT_TRUE(Initialize());
+
+ // Our server is permissive and allows for out of bounds values.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ QuicPacketHeader* header =
+ QuicConnectionPeer::GetLastHeader(client_connection);
+ if (client_connection->transport_version() > QUIC_VERSION_43) {
+ EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+ } else {
+ EXPECT_EQ(CONNECTION_ID_PRESENT,
+ header->destination_connection_id_included);
+ }
+}
+
+TEST_P(EndToEndTestWithTls, ResetConnection) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ client_->ResetConnection();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, MaxStreamsUberTest) {
+ if (!BothSidesSupportStatelessRejects()) {
+ // Connect with lower fake packet loss than we'd like to test. Until
+ // b/10126687 is fixed, losing handshake packets is pretty brutal.
+ // TODO(jokulik): Until we support redundant SREJ packets, don't
+ // drop handshake packets for stateless rejects.
+ SetPacketLossPercentage(1);
+ }
+ ASSERT_TRUE(Initialize());
+ std::string large_body(10240, 'a');
+ int max_streams = 100;
+
+ AddToCache("/large_response", 200, large_body);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(10);
+
+ for (int i = 0; i < max_streams; ++i) {
+ EXPECT_LT(0, client_->SendRequest("/large_response"));
+ }
+
+ // WaitForEvents waits 50ms and returns true if there are outstanding
+ // requests.
+ while (client_->client()->WaitForEvents() == true) {
+ }
+}
+
+TEST_P(EndToEndTestWithTls, StreamCancelErrorTest) {
+ ASSERT_TRUE(Initialize());
+ std::string small_body(256, 'a');
+
+ AddToCache("/small_response", 200, small_body);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ QuicSession* session = client_->client()->client_session();
+ // Lose the request.
+ SetPacketLossPercentage(100);
+ EXPECT_LT(0, client_->SendRequest("/small_response"));
+ client_->client()->WaitForEvents();
+ // Transmit the cancel, and ensure the connection is torn down properly.
+ SetPacketLossPercentage(0);
+ QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+ session->SendRstStream(stream_id, QUIC_STREAM_CANCELLED, 0);
+
+ // WaitForEvents waits 50ms and returns true if there are outstanding
+ // requests.
+ while (client_->client()->WaitForEvents() == true) {
+ }
+ // It should be completely fine to RST a stream before any data has been
+ // received for that stream.
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, ConnectionMigrationClientIPChanged) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Store the client IP address which was used to send the first request.
+ QuicIpAddress old_host =
+ client_->client()->network_helper()->GetLatestClientAddress().host();
+
+ // Migrate socket to the new IP address.
+ QuicIpAddress new_host = TestLoopback(2);
+ EXPECT_NE(old_host, new_host);
+ ASSERT_TRUE(client_->client()->MigrateSocket(new_host));
+
+ // Send a request using the new socket.
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, ConnectionMigrationClientPortChanged) {
+ // Tests that the client's port can change during an established QUIC
+ // connection, and that doing so does not result in the connection being
+ // closed by the server.
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Store the client address which was used to send the first request.
+ QuicSocketAddress old_address =
+ client_->client()->network_helper()->GetLatestClientAddress();
+ int old_fd = client_->client()->GetLatestFD();
+
+ // Create a new socket before closing the old one, which will result in a new
+ // ephemeral port.
+ QuicClientPeer::CreateUDPSocketAndBind(client_->client());
+
+ // Stop listening and close the old FD.
+ QuicClientPeer::CleanUpUDPSocket(client_->client(), old_fd);
+
+ // The packet writer needs to be updated to use the new FD.
+ client_->client()->network_helper()->CreateQuicPacketWriter();
+
+ // Change the internal state of the client and connection to use the new port,
+ // this is done because in a real NAT rebinding the client wouldn't see any
+ // port change, and so expects no change to incoming port.
+ // This is kind of ugly, but needed as we are simply swapping out the client
+ // FD rather than any more complex NAT rebinding simulation.
+ int new_port =
+ client_->client()->network_helper()->GetLatestClientAddress().port();
+ QuicClientPeer::SetClientPort(client_->client(), new_port);
+ QuicConnectionPeer::SetSelfAddress(
+ client_->client()->client_session()->connection(),
+ QuicSocketAddress(client_->client()
+ ->client_session()
+ ->connection()
+ ->self_address()
+ .host(),
+ new_port));
+
+ // Register the new FD for epoll events.
+ int new_fd = client_->client()->GetLatestFD();
+ QuicEpollServer* eps = client_->epoll_server();
+ eps->RegisterFD(new_fd, client_->client()->epoll_network_helper(),
+ EPOLLIN | EPOLLOUT | EPOLLET);
+
+ // Send a second request, using the new FD.
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Verify that the client's ephemeral port is different.
+ QuicSocketAddress new_address =
+ client_->client()->network_helper()->GetLatestClientAddress();
+ EXPECT_EQ(old_address.host(), new_address.host());
+ EXPECT_NE(old_address.port(), new_address.port());
+}
+
+TEST_P(EndToEndTest, NegotiatedInitialCongestionWindow) {
+ SetQuicReloadableFlag(quic_unified_iw_options, true);
+ client_extra_copts_.push_back(kIW03);
+
+ ASSERT_TRUE(Initialize());
+
+ // Values are exchanged during crypto handshake, so wait for that to finish.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+ server_thread_->Pause();
+
+ QuicPacketCount cwnd =
+ GetServerConnection()->sent_packet_manager().initial_congestion_window();
+ EXPECT_EQ(3u, cwnd);
+}
+
+TEST_P(EndToEndTest, DifferentFlowControlWindows) {
+ // Client and server can set different initial flow control receive windows.
+ // These are sent in CHLO/SHLO. Tests that these values are exchanged properly
+ // in the crypto handshake.
+ const uint32_t kClientStreamIFCW = 123456;
+ const uint32_t kClientSessionIFCW = 234567;
+ set_client_initial_stream_flow_control_receive_window(kClientStreamIFCW);
+ set_client_initial_session_flow_control_receive_window(kClientSessionIFCW);
+
+ uint32_t kServerStreamIFCW = 32 * 1024;
+ uint32_t kServerSessionIFCW = 48 * 1024;
+ set_server_initial_stream_flow_control_receive_window(kServerStreamIFCW);
+ set_server_initial_session_flow_control_receive_window(kServerSessionIFCW);
+
+ ASSERT_TRUE(Initialize());
+
+ // Values are exchanged during crypto handshake, so wait for that to finish.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Open a data stream to make sure the stream level flow control is updated.
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ stream->WriteOrBufferBody("hello", false);
+
+ // Client should have the right values for server's receive window.
+ EXPECT_EQ(kServerStreamIFCW,
+ client_->client()
+ ->client_session()
+ ->config()
+ ->ReceivedInitialStreamFlowControlWindowBytes());
+ EXPECT_EQ(kServerSessionIFCW,
+ client_->client()
+ ->client_session()
+ ->config()
+ ->ReceivedInitialSessionFlowControlWindowBytes());
+ EXPECT_EQ(kServerStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
+ stream->flow_controller()));
+ EXPECT_EQ(kServerSessionIFCW,
+ QuicFlowControllerPeer::SendWindowOffset(
+ client_->client()->client_session()->flow_controller()));
+
+ // Server should have the right values for client's receive window.
+ server_thread_->Pause();
+ QuicSession* session = GetServerSession();
+ EXPECT_EQ(kClientStreamIFCW,
+ session->config()->ReceivedInitialStreamFlowControlWindowBytes());
+ EXPECT_EQ(kClientSessionIFCW,
+ session->config()->ReceivedInitialSessionFlowControlWindowBytes());
+ EXPECT_EQ(kClientSessionIFCW, QuicFlowControllerPeer::SendWindowOffset(
+ session->flow_controller()));
+ server_thread_->Resume();
+}
+
+// Test negotiation of IFWA connection option.
+TEST_P(EndToEndTest, NegotiatedServerInitialFlowControlWindow) {
+ const uint32_t kClientStreamIFCW = 123456;
+ const uint32_t kClientSessionIFCW = 234567;
+ set_client_initial_stream_flow_control_receive_window(kClientStreamIFCW);
+ set_client_initial_session_flow_control_receive_window(kClientSessionIFCW);
+
+ uint32_t kServerStreamIFCW = 32 * 1024;
+ uint32_t kServerSessionIFCW = 48 * 1024;
+ set_server_initial_stream_flow_control_receive_window(kServerStreamIFCW);
+ set_server_initial_session_flow_control_receive_window(kServerSessionIFCW);
+
+ // Bump the window.
+ const uint32_t kExpectedStreamIFCW = 1024 * 1024;
+ const uint32_t kExpectedSessionIFCW = 1.5 * 1024 * 1024;
+ client_extra_copts_.push_back(kIFWA);
+
+ ASSERT_TRUE(Initialize());
+
+ // Values are exchanged during crypto handshake, so wait for that to finish.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ // Open a data stream to make sure the stream level flow control is updated.
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ stream->WriteOrBufferBody("hello", false);
+
+ // Client should have the right values for server's receive window.
+ EXPECT_EQ(kExpectedStreamIFCW,
+ client_->client()
+ ->client_session()
+ ->config()
+ ->ReceivedInitialStreamFlowControlWindowBytes());
+ EXPECT_EQ(kExpectedSessionIFCW,
+ client_->client()
+ ->client_session()
+ ->config()
+ ->ReceivedInitialSessionFlowControlWindowBytes());
+ EXPECT_EQ(kExpectedStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
+ stream->flow_controller()));
+ EXPECT_EQ(kExpectedSessionIFCW,
+ QuicFlowControllerPeer::SendWindowOffset(
+ client_->client()->client_session()->flow_controller()));
+}
+
+TEST_P(EndToEndTest, HeadersAndCryptoStreamsNoConnectionFlowControl) {
+ // The special headers and crypto streams should be subject to per-stream flow
+ // control limits, but should not be subject to connection level flow control
+ const uint32_t kStreamIFCW = 32 * 1024;
+ const uint32_t kSessionIFCW = 48 * 1024;
+ set_client_initial_stream_flow_control_receive_window(kStreamIFCW);
+ set_client_initial_session_flow_control_receive_window(kSessionIFCW);
+ set_server_initial_stream_flow_control_receive_window(kStreamIFCW);
+ set_server_initial_session_flow_control_receive_window(kSessionIFCW);
+
+ ASSERT_TRUE(Initialize());
+
+ // Wait for crypto handshake to finish. This should have contributed to the
+ // crypto stream flow control window, but not affected the session flow
+ // control window.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ QuicCryptoStream* crypto_stream = QuicSessionPeer::GetMutableCryptoStream(
+ client_->client()->client_session());
+ // In v47 and later, the crypto handshake (sent in CRYPTO frames) is not
+ // subject to flow control.
+ if (!QuicVersionUsesCryptoFrames(client_->client()
+ ->client_session()
+ ->connection()
+ ->transport_version())) {
+ EXPECT_LT(QuicFlowControllerPeer::SendWindowSize(
+ crypto_stream->flow_controller()),
+ kStreamIFCW);
+ }
+ EXPECT_EQ(kSessionIFCW,
+ QuicFlowControllerPeer::SendWindowSize(
+ client_->client()->client_session()->flow_controller()));
+
+ // Send a request with no body, and verify that the connection level window
+ // has not been affected.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+ QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+ client_->client()->client_session());
+ EXPECT_LT(
+ QuicFlowControllerPeer::SendWindowSize(headers_stream->flow_controller()),
+ kStreamIFCW);
+ EXPECT_EQ(kSessionIFCW,
+ QuicFlowControllerPeer::SendWindowSize(
+ client_->client()->client_session()->flow_controller()));
+
+ // Server should be in a similar state: connection flow control window should
+ // not have any bytes marked as received.
+ server_thread_->Pause();
+ QuicSession* session = GetServerSession();
+ QuicFlowController* server_connection_flow_controller =
+ session->flow_controller();
+ EXPECT_EQ(kSessionIFCW, QuicFlowControllerPeer::ReceiveWindowSize(
+ server_connection_flow_controller));
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, FlowControlsSynced) {
+ set_smaller_flow_control_receive_window();
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ server_thread_->WaitForCryptoHandshakeConfirmed();
+
+ server_thread_->Pause();
+ QuicSpdySession* const client_session = client_->client()->client_session();
+ auto* server_session = static_cast<QuicSpdySession*>(GetServerSession());
+ ExpectFlowControlsSynced(client_session->flow_controller(),
+ server_session->flow_controller());
+ ExpectFlowControlsSynced(
+ QuicSessionPeer::GetMutableCryptoStream(client_session)
+ ->flow_controller(),
+ QuicSessionPeer::GetMutableCryptoStream(server_session)
+ ->flow_controller());
+ SpdyFramer spdy_framer(SpdyFramer::ENABLE_COMPRESSION);
+ SpdySettingsIR settings_frame;
+ settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE,
+ kDefaultMaxUncompressedHeaderSize);
+ SpdySerializedFrame frame(spdy_framer.SerializeFrame(settings_frame));
+ QuicFlowController* client_header_stream_flow_controller =
+ QuicSpdySessionPeer::GetHeadersStream(client_session)->flow_controller();
+ QuicFlowController* server_header_stream_flow_controller =
+ QuicSpdySessionPeer::GetHeadersStream(server_session)->flow_controller();
+ // Both client and server are sending this SETTINGS frame, and the send
+ // window is consumed. But because of timing issue, the server may send or
+ // not send the frame, and the client may send/ not send / receive / not
+ // receive the frame.
+ // TODO(fayang): Rewrite this part because it is hacky.
+ QuicByteCount win_difference1 = QuicFlowControllerPeer::ReceiveWindowSize(
+ server_header_stream_flow_controller) -
+ QuicFlowControllerPeer::SendWindowSize(
+ client_header_stream_flow_controller);
+ QuicByteCount win_difference2 = QuicFlowControllerPeer::ReceiveWindowSize(
+ client_header_stream_flow_controller) -
+ QuicFlowControllerPeer::SendWindowSize(
+ server_header_stream_flow_controller);
+ EXPECT_TRUE(win_difference1 == 0 || win_difference1 == frame.size());
+ EXPECT_TRUE(win_difference2 == 0 || win_difference2 == frame.size());
+
+ // Client *may* have received the SETTINGs frame.
+ // TODO(fayang): Rewrite this part because it is hacky.
+ float ratio1 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
+ client_session->flow_controller())) /
+ QuicFlowControllerPeer::ReceiveWindowSize(
+ QuicSpdySessionPeer::GetHeadersStream(client_session)
+ ->flow_controller());
+ float ratio2 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
+ client_session->flow_controller())) /
+ (QuicFlowControllerPeer::ReceiveWindowSize(
+ QuicSpdySessionPeer::GetHeadersStream(client_session)
+ ->flow_controller()) +
+ frame.size());
+ EXPECT_TRUE(ratio1 == kSessionToStreamRatio ||
+ ratio2 == kSessionToStreamRatio);
+
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTestWithTls, RequestWithNoBodyWillNeverSendStreamFrameWithFIN) {
+ // A stream created on receipt of a simple request with no body will never get
+ // a stream frame with a FIN. Verify that we don't keep track of the stream in
+ // the locally closed streams map: it will never be removed if so.
+ ASSERT_TRUE(Initialize());
+
+ // Send a simple headers only request, and receive response.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Now verify that the server is not waiting for a final FIN or RST.
+ server_thread_->Pause();
+ QuicSession* session = GetServerSession();
+ EXPECT_EQ(
+ 0u,
+ QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(session).size());
+ server_thread_->Resume();
+}
+
+// A TestAckListener verifies that its OnAckNotification method has been
+// called exactly once on destruction.
+class TestAckListener : public QuicAckListenerInterface {
+ public:
+ explicit TestAckListener(int bytes_to_ack) : bytes_to_ack_(bytes_to_ack) {}
+
+ void OnPacketAcked(int acked_bytes,
+ QuicTime::Delta /*delta_largest_observed*/) override {
+ ASSERT_LE(acked_bytes, bytes_to_ack_);
+ bytes_to_ack_ -= acked_bytes;
+ }
+
+ void OnPacketRetransmitted(int /*retransmitted_bytes*/) override {}
+
+ bool has_been_notified() const { return bytes_to_ack_ == 0; }
+
+ protected:
+ // Object is ref counted.
+ ~TestAckListener() override { EXPECT_EQ(0, bytes_to_ack_); }
+
+ private:
+ int bytes_to_ack_;
+};
+
+class TestResponseListener : public QuicSpdyClientBase::ResponseListener {
+ public:
+ void OnCompleteResponse(QuicStreamId id,
+ const SpdyHeaderBlock& response_headers,
+ const std::string& response_body) override {
+ QUIC_DVLOG(1) << "response for stream " << id << " "
+ << response_headers.DebugString() << "\n"
+ << response_body;
+ }
+};
+
+TEST_P(EndToEndTest, AckNotifierWithPacketLossAndBlockedSocket) {
+ // Verify that even in the presence of packet loss and occasionally blocked
+ // socket, an AckNotifierDelegate will get informed that the data it is
+ // interested in has been ACKed. This tests end-to-end ACK notification, and
+ // demonstrates that retransmissions do not break this functionality.
+ if (!BothSidesSupportStatelessRejects()) {
+ // TODO(jokulik): Until we support redundant SREJ packets, don't
+ // drop handshake packets for stateless rejects.
+ SetPacketLossPercentage(5);
+ }
+ ASSERT_TRUE(Initialize());
+
+ // Wait for the server SHLO before upping the packet loss.
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(30);
+ client_writer_->set_fake_blocked_socket_percentage(10);
+
+ // Create a POST request and send the headers only.
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ client_->SendMessage(headers, "", /*fin=*/false);
+
+ // Test the AckNotifier's ability to track multiple packets by making the
+ // request body exceed the size of a single packet.
+ std::string request_string = "a request body bigger than one packet" +
+ std::string(kMaxOutgoingPacketSize, '.');
+
+ // The TestAckListener will cause a failure if not notified.
+ QuicReferenceCountedPointer<TestAckListener> ack_listener(
+ new TestAckListener(request_string.length()));
+
+ // Send the request, and register the delegate for ACKs.
+ client_->SendData(request_string, true, ack_listener);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Send another request to flush out any pending ACKs on the server.
+ client_->SendSynchronousRequest("/bar");
+
+ // Make sure the delegate does get the notification it expects.
+ while (!ack_listener->has_been_notified()) {
+ // Waits for up to 50 ms.
+ client_->client()->WaitForEvents();
+ }
+}
+
+// Send a public reset from the server.
+TEST_P(EndToEndTestWithTls, ServerSendPublicReset) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ if (SupportsIetfQuicWithTls(client_connection->version())) {
+ // TLS handshake does not support stateless reset token yet.
+ return;
+ }
+ QuicUint128 stateless_reset_token = 0;
+ if (client_connection->version().handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+ QuicConfig* config = client_->client()->session()->config();
+ EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+ stateless_reset_token = config->ReceivedStatelessResetToken();
+ }
+
+ // Send the public reset.
+ QuicConnectionId connection_id = client_connection->connection_id();
+ QuicPublicResetPacket header;
+ header.connection_id = connection_id;
+ QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+ Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
+ std::unique_ptr<QuicEncryptedPacket> packet;
+ if (client_connection->transport_version() > QUIC_VERSION_43) {
+ packet = framer.BuildIetfStatelessResetPacket(connection_id,
+ stateless_reset_token);
+ } else {
+ packet = framer.BuildPublicResetPacket(header);
+ }
+ // We must pause the server's thread in order to call WritePacket without
+ // race conditions.
+ server_thread_->Pause();
+ server_writer_->WritePacket(
+ packet->data(), packet->length(), server_address_.host(),
+ client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+ server_thread_->Resume();
+
+ // The request should fail.
+ EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+ EXPECT_TRUE(client_->response_headers()->empty());
+ EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+}
+
+// Send a public reset from the server for a different connection ID.
+// It should be ignored.
+TEST_P(EndToEndTestWithTls, ServerSendPublicResetWithDifferentConnectionId) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ if (SupportsIetfQuicWithTls(client_connection->version())) {
+ // TLS handshake does not support stateless reset token yet.
+ return;
+ }
+ QuicUint128 stateless_reset_token = 0;
+ if (client_connection->version().handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+ QuicConfig* config = client_->client()->session()->config();
+ EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+ stateless_reset_token = config->ReceivedStatelessResetToken();
+ }
+ // Send the public reset.
+ QuicConnectionId incorrect_connection_id = TestConnectionId(
+ TestConnectionIdToUInt64(client_connection->connection_id()) + 1);
+ QuicPublicResetPacket header;
+ header.connection_id = incorrect_connection_id;
+ QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+ Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
+ std::unique_ptr<QuicEncryptedPacket> packet;
+ testing::NiceMock<MockQuicConnectionDebugVisitor> visitor;
+ client_->client()->client_session()->connection()->set_debug_visitor(
+ &visitor);
+ if (client_connection->transport_version() > QUIC_VERSION_43) {
+ packet = framer.BuildIetfStatelessResetPacket(incorrect_connection_id,
+ stateless_reset_token);
+ EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+ .Times(0);
+ } else {
+ packet = framer.BuildPublicResetPacket(header);
+ EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+ .Times(1);
+ }
+ // We must pause the server's thread in order to call WritePacket without
+ // race conditions.
+ server_thread_->Pause();
+ server_writer_->WritePacket(
+ packet->data(), packet->length(), server_address_.host(),
+ client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+ server_thread_->Resume();
+
+ if (client_connection->transport_version() > QUIC_VERSION_43) {
+ // The request should fail. IETF stateless reset does not include connection
+ // ID.
+ EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+ EXPECT_TRUE(client_->response_headers()->empty());
+ EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+ return;
+ }
+ // The connection should be unaffected.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ client_->client()->client_session()->connection()->set_debug_visitor(nullptr);
+}
+
+// Send a public reset from the client for a different connection ID.
+// It should be ignored.
+TEST_P(EndToEndTestWithTls, ClientSendPublicResetWithDifferentConnectionId) {
+ ASSERT_TRUE(Initialize());
+
+ // Send the public reset.
+ QuicConnectionId incorrect_connection_id = TestConnectionId(
+ TestConnectionIdToUInt64(
+ client_->client()->client_session()->connection()->connection_id()) +
+ 1);
+ QuicPublicResetPacket header;
+ header.connection_id = incorrect_connection_id;
+ QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+ Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
+ std::unique_ptr<QuicEncryptedPacket> packet(
+ framer.BuildPublicResetPacket(header));
+ client_writer_->WritePacket(
+ packet->data(), packet->length(),
+ client_->client()->network_helper()->GetLatestClientAddress().host(),
+ server_address_, nullptr);
+
+ // The connection should be unaffected.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// Send a version negotiation packet from the server for a different
+// connection ID. It should be ignored.
+TEST_P(EndToEndTestWithTls,
+ ServerSendVersionNegotiationWithDifferentConnectionId) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Send the version negotiation packet.
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ QuicConnectionId incorrect_connection_id = TestConnectionId(
+ TestConnectionIdToUInt64(client_connection->connection_id()) + 1);
+ std::unique_ptr<QuicEncryptedPacket> packet(
+ QuicFramer::BuildVersionNegotiationPacket(
+ incorrect_connection_id,
+ client_connection->transport_version() > QUIC_VERSION_43,
+ server_supported_versions_));
+ testing::NiceMock<MockQuicConnectionDebugVisitor> visitor;
+ client_connection->set_debug_visitor(&visitor);
+ EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+ .Times(1);
+ // We must pause the server's thread in order to call WritePacket without
+ // race conditions.
+ server_thread_->Pause();
+ server_writer_->WritePacket(
+ packet->data(), packet->length(), server_address_.host(),
+ client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+ server_thread_->Resume();
+
+ // The connection should be unaffected.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ client_connection->set_debug_visitor(nullptr);
+}
+
+// A bad header shouldn't tear down the connection, because the receiver can't
+// tell the connection ID.
+TEST_P(EndToEndTestWithTls, BadPacketHeaderTruncated) {
+ ASSERT_TRUE(Initialize());
+
+ // Start the connection.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Packet with invalid public flags.
+ char packet[] = {// public flags (8 byte connection_id)
+ 0x3C,
+ // truncated connection ID
+ 0x11};
+ client_writer_->WritePacket(
+ &packet[0], sizeof(packet),
+ client_->client()->network_helper()->GetLatestClientAddress().host(),
+ server_address_, nullptr);
+ // Give the server time to process the packet.
+ QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ EXPECT_EQ(QUIC_INVALID_PACKET_HEADER,
+ QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+ server_thread_->Resume();
+
+ // The connection should not be terminated.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// A bad header shouldn't tear down the connection, because the receiver can't
+// tell the connection ID.
+TEST_P(EndToEndTestWithTls, BadPacketHeaderFlags) {
+ ASSERT_TRUE(Initialize());
+
+ // Start the connection.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Packet with invalid public flags.
+ char packet[] = {
+ // invalid public flags
+ 0xFF,
+ // connection_id
+ 0x10,
+ 0x32,
+ 0x54,
+ 0x76,
+ 0x98,
+ 0xBA,
+ 0xDC,
+ 0xFE,
+ // packet sequence number
+ 0xBC,
+ 0x9A,
+ 0x78,
+ 0x56,
+ 0x34,
+ 0x12,
+ // private flags
+ 0x00,
+ };
+ client_writer_->WritePacket(
+ &packet[0], sizeof(packet),
+ client_->client()->network_helper()->GetLatestClientAddress().host(),
+ server_address_, nullptr);
+ // Give the server time to process the packet.
+ QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ EXPECT_EQ(QUIC_INVALID_PACKET_HEADER,
+ QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+ server_thread_->Resume();
+
+ // The connection should not be terminated.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// Send a packet from the client with bad encrypted data. The server should not
+// tear down the connection.
+TEST_P(EndToEndTestWithTls, BadEncryptedData) {
+ ASSERT_TRUE(Initialize());
+
+ // Start the connection.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+ client_->client()->client_session()->connection()->connection_id(),
+ EmptyQuicConnectionId(), false, false, 1, "At least 20 characters.",
+ CONNECTION_ID_PRESENT, CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER));
+ // Damage the encrypted data.
+ std::string damaged_packet(packet->data(), packet->length());
+ damaged_packet[30] ^= 0x01;
+ QUIC_DLOG(INFO) << "Sending bad packet.";
+ client_writer_->WritePacket(
+ damaged_packet.data(), damaged_packet.length(),
+ client_->client()->network_helper()->GetLatestClientAddress().host(),
+ server_address_, nullptr);
+ // Give the server time to process the packet.
+ QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+ // This error is sent to the connection's OnError (which ignores it), so the
+ // dispatcher doesn't see it.
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ EXPECT_EQ(QUIC_NO_ERROR,
+ QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+ server_thread_->Resume();
+
+ // The connection should not be terminated.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, CanceledStreamDoesNotBecomeZombie) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ // Lose the request.
+ SetPacketLossPercentage(100);
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ client_->SendMessage(headers, "test_body", /*fin=*/false);
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+
+ // Cancel the stream.
+ stream->Reset(QUIC_STREAM_CANCELLED);
+ QuicSession* session = client_->client()->client_session();
+ // Verify canceled stream does not become zombie.
+ EXPECT_TRUE(QuicSessionPeer::zombie_streams(session).empty());
+ EXPECT_EQ(1u, QuicSessionPeer::closed_streams(session).size());
+}
+
+// A test stream that gives |response_body_| as an error response body.
+class ServerStreamWithErrorResponseBody : public QuicSimpleServerStream {
+ public:
+ ServerStreamWithErrorResponseBody(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ std::string response_body)
+ : QuicSimpleServerStream(id,
+ session,
+ BIDIRECTIONAL,
+ quic_simple_server_backend),
+ response_body_(std::move(response_body)) {}
+
+ ~ServerStreamWithErrorResponseBody() override = default;
+
+ protected:
+ void SendErrorResponse() override {
+ QUIC_DLOG(INFO) << "Sending error response for stream " << id();
+ SpdyHeaderBlock headers;
+ headers[":status"] = "500";
+ headers["content-length"] =
+ QuicTextUtils::Uint64ToString(response_body_.size());
+ // This method must call CloseReadSide to cause the test case, StopReading
+ // is not sufficient.
+ QuicStreamPeer::CloseReadSide(this);
+ SendHeadersAndBody(std::move(headers), response_body_);
+ }
+
+ std::string response_body_;
+};
+
+class StreamWithErrorFactory : public QuicTestServer::StreamFactory {
+ public:
+ explicit StreamWithErrorFactory(std::string response_body)
+ : response_body_(std::move(response_body)) {}
+
+ ~StreamWithErrorFactory() override = default;
+
+ QuicSimpleServerStream* CreateStream(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend) override {
+ return new ServerStreamWithErrorResponseBody(
+ id, session, quic_simple_server_backend, response_body_);
+ }
+
+ private:
+ std::string response_body_;
+};
+
+// A test server stream that drops all received body.
+class ServerStreamThatDropsBody : public QuicSimpleServerStream {
+ public:
+ ServerStreamThatDropsBody(QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend)
+ : QuicSimpleServerStream(id,
+ session,
+ BIDIRECTIONAL,
+ quic_simple_server_backend) {}
+
+ ~ServerStreamThatDropsBody() override = default;
+
+ protected:
+ void OnBodyAvailable() override {
+ while (HasBytesToRead()) {
+ struct iovec iov;
+ if (GetReadableRegions(&iov, 1) == 0) {
+ // No more data to read.
+ break;
+ }
+ QUIC_DVLOG(1) << "Processed " << iov.iov_len << " bytes for stream "
+ << id();
+ MarkConsumed(iov.iov_len);
+ }
+
+ if (!sequencer()->IsClosed()) {
+ sequencer()->SetUnblocked();
+ return;
+ }
+
+ // If the sequencer is closed, then all the body, including the fin, has
+ // been consumed.
+ OnFinRead();
+
+ if (write_side_closed() || fin_buffered()) {
+ return;
+ }
+
+ SendResponse();
+ }
+};
+
+class ServerStreamThatDropsBodyFactory : public QuicTestServer::StreamFactory {
+ public:
+ ServerStreamThatDropsBodyFactory() = default;
+
+ ~ServerStreamThatDropsBodyFactory() override = default;
+
+ QuicSimpleServerStream* CreateStream(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend) override {
+ return new ServerStreamThatDropsBody(id, session,
+ quic_simple_server_backend);
+ }
+};
+
+// A test server stream that sends response with body size greater than 4GB.
+class ServerStreamThatSendsHugeResponse : public QuicSimpleServerStream {
+ public:
+ ServerStreamThatSendsHugeResponse(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ int64_t body_bytes)
+ : QuicSimpleServerStream(id,
+ session,
+ BIDIRECTIONAL,
+ quic_simple_server_backend),
+ body_bytes_(body_bytes) {}
+
+ ~ServerStreamThatSendsHugeResponse() override = default;
+
+ protected:
+ void SendResponse() override {
+ QuicBackendResponse response;
+ std::string body(body_bytes_, 'a');
+ response.set_body(body);
+ SendHeadersAndBodyAndTrailers(response.headers().Clone(), response.body(),
+ response.trailers().Clone());
+ }
+
+ private:
+ // Use a explicit int64_t rather than size_t to simulate a 64-bit server
+ // talking to a 32-bit client.
+ int64_t body_bytes_;
+};
+
+class ServerStreamThatSendsHugeResponseFactory
+ : public QuicTestServer::StreamFactory {
+ public:
+ explicit ServerStreamThatSendsHugeResponseFactory(int64_t body_bytes)
+ : body_bytes_(body_bytes) {}
+
+ ~ServerStreamThatSendsHugeResponseFactory() override = default;
+
+ QuicSimpleServerStream* CreateStream(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend) override {
+ return new ServerStreamThatSendsHugeResponse(
+ id, session, quic_simple_server_backend, body_bytes_);
+ }
+
+ int64_t body_bytes_;
+};
+
+TEST_P(EndToEndTest, EarlyResponseFinRecording) {
+ set_smaller_flow_control_receive_window();
+
+ // Verify that an incoming FIN is recorded in a stream object even if the read
+ // side has been closed. This prevents an entry from being made in
+ // locally_close_streams_highest_offset_ (which will never be deleted).
+ // To set up the test condition, the server must do the following in order:
+ // start sending the response and call CloseReadSide
+ // receive the FIN of the request
+ // send the FIN of the response
+
+ // The response body must be larger than the flow control window so the server
+ // must receive a window update from the client before it can finish sending
+ // it.
+ uint32_t response_body_size =
+ 2 * client_config_.GetInitialStreamFlowControlWindowToSend();
+ std::string response_body(response_body_size, 'a');
+
+ StreamWithErrorFactory stream_factory(response_body);
+ SetSpdyStreamFactory(&stream_factory);
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // A POST that gets an early error response, after the headers are received
+ // and before the body is received, due to invalid content-length.
+ // Set an invalid content-length, so the request will receive an early 500
+ // response.
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/garbage";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] = "-1";
+
+ // The body must be large enough that the FIN will be in a different packet
+ // than the end of the headers, but short enough to not require a flow control
+ // update. This allows headers processing to trigger the error response
+ // before the request FIN is processed but receive the request FIN before the
+ // response is sent completely.
+ const uint32_t kRequestBodySize = kMaxOutgoingPacketSize + 10;
+ std::string request_body(kRequestBodySize, 'a');
+
+ // Send the request.
+ client_->SendMessage(headers, request_body);
+ client_->WaitForResponse();
+ EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+
+ // Pause the server so we can access the server's internals without races.
+ server_thread_->Pause();
+
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ QuicDispatcher::SessionMap const& map =
+ QuicDispatcherPeer::session_map(dispatcher);
+ auto it = map.begin();
+ EXPECT_TRUE(it != map.end());
+ QuicSession* server_session = it->second.get();
+
+ // The stream is not waiting for the arrival of the peer's final offset.
+ EXPECT_EQ(
+ 0u, QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(server_session)
+ .size());
+
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTestWithTls, Trailers) {
+ // Test sending and receiving HTTP/2 Trailers (trailing HEADERS frames).
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Set reordering to ensure that Trailers arriving before body is ok.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // Add a response with headers, body, and trailers.
+ const std::string kBody = "body content";
+
+ SpdyHeaderBlock headers;
+ headers[":status"] = "200";
+ headers[":version"] = "HTTP/1.1";
+ headers["content-length"] = QuicTextUtils::Uint64ToString(kBody.size());
+
+ SpdyHeaderBlock trailers;
+ trailers["some-trailing-header"] = "trailing-header-value";
+
+ memory_cache_backend_.AddResponse(server_hostname_, "/trailer_url",
+ std::move(headers), kBody,
+ trailers.Clone());
+
+ EXPECT_EQ(kBody, client_->SendSynchronousRequest("/trailer_url"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_EQ(trailers, client_->response_trailers());
+}
+
+class EndToEndTestServerPush : public EndToEndTest {
+ protected:
+ const size_t kNumMaxStreams = 10;
+
+ EndToEndTestServerPush() : EndToEndTest() {
+ client_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
+ server_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
+ support_server_push_ = true;
+ }
+
+ // Add a request with its response and |num_resources| push resources into
+ // cache.
+ // If |resource_size| == 0, response body of push resources use default string
+ // concatenating with resource url. Otherwise, generate a string of
+ // |resource_size| as body.
+ void AddRequestAndResponseWithServerPush(std::string host,
+ std::string path,
+ std::string response_body,
+ std::string* push_urls,
+ const size_t num_resources,
+ const size_t resource_size) {
+ bool use_large_response = resource_size != 0;
+ std::string large_resource;
+ if (use_large_response) {
+ // Generate a response common body larger than flow control window for
+ // push response.
+ large_resource = std::string(resource_size, 'a');
+ }
+ std::list<QuicBackendResponse::ServerPushInfo> push_resources;
+ for (size_t i = 0; i < num_resources; ++i) {
+ std::string url = push_urls[i];
+ QuicUrl resource_url(url);
+ std::string body =
+ use_large_response
+ ? large_resource
+ : QuicStrCat("This is server push response body for ", url);
+ SpdyHeaderBlock response_headers;
+ response_headers[":version"] = "HTTP/1.1";
+ response_headers[":status"] = "200";
+ response_headers["content-length"] =
+ QuicTextUtils::Uint64ToString(body.size());
+ push_resources.push_back(QuicBackendResponse::ServerPushInfo(
+ resource_url, std::move(response_headers), kV3LowestPriority, body));
+ }
+
+ memory_cache_backend_.AddSimpleResponseWithServerPushResources(
+ host, path, 200, response_body, push_resources);
+ }
+};
+
+// Run all server push end to end tests with all supported versions.
+INSTANTIATE_TEST_SUITE_P(EndToEndTestsServerPush,
+ EndToEndTestServerPush,
+ ::testing::ValuesIn(GetTestParams(false, false)));
+
+TEST_P(EndToEndTestServerPush, ServerPush) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // Add a response with headers, body, and push resources.
+ const std::string kBody = "body content";
+ size_t kNumResources = 4;
+ std::string push_urls[] = {"https://example.com/font.woff",
+ "https://example.com/script.js",
+ "https://fonts.example.com/font.woff",
+ "https://example.com/logo-hires.jpg"};
+ AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+ push_urls, kNumResources, 0);
+
+ client_->client()->set_response_listener(
+ std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+ new TestResponseListener));
+
+ QUIC_DVLOG(1) << "send request for /push_example";
+ EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+ "https://example.com/push_example"));
+ QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+ client_->client()->client_session());
+ QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(headers_stream);
+ // Headers stream's sequencer buffer shouldn't be released because server push
+ // hasn't finished yet.
+ EXPECT_TRUE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+
+ for (const std::string& url : push_urls) {
+ QUIC_DVLOG(1) << "send request for pushed stream on url " << url;
+ std::string expected_body =
+ QuicStrCat("This is server push response body for ", url);
+ std::string response_body = client_->SendSynchronousRequest(url);
+ QUIC_DVLOG(1) << "response body " << response_body;
+ EXPECT_EQ(expected_body, response_body);
+ }
+ EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushUnderLimit) {
+ // Tests that sending a request which has 4 push resources will trigger server
+ // to push those 4 resources and client can handle pushed resources and match
+ // them with requests later.
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // Add a response with headers, body, and push resources.
+ const std::string kBody = "body content";
+ size_t const kNumResources = 4;
+ std::string push_urls[] = {
+ "https://example.com/font.woff",
+ "https://example.com/script.js",
+ "https://fonts.example.com/font.woff",
+ "https://example.com/logo-hires.jpg",
+ };
+ AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+ push_urls, kNumResources, 0);
+ client_->client()->set_response_listener(
+ std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+ new TestResponseListener));
+
+ // Send the first request: this will trigger the server to send all the push
+ // resources associated with this request, and these will be cached by the
+ // client.
+ EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+ "https://example.com/push_example"));
+
+ for (const std::string& url : push_urls) {
+ // Sending subsequent requesets will not actually send anything on the wire,
+ // as the responses are already in the client's cache.
+ QUIC_DVLOG(1) << "send request for pushed stream on url " << url;
+ std::string expected_body =
+ QuicStrCat("This is server push response body for ", url);
+ std::string response_body = client_->SendSynchronousRequest(url);
+ QUIC_DVLOG(1) << "response body " << response_body;
+ EXPECT_EQ(expected_body, response_body);
+ }
+ // Expect only original request has been sent and push responses have been
+ // received as normal response.
+ EXPECT_EQ(1u, client_->num_requests());
+ EXPECT_EQ(1u + kNumResources, client_->num_responses());
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushOverLimitNonBlocking) {
+ // Tests that when streams are not blocked by flow control or congestion
+ // control, pushing even more resources than max number of open outgoing
+ // streams should still work because all response streams get closed
+ // immediately after pushing resources.
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // Add a response with headers, body, and push resources.
+ const std::string kBody = "body content";
+
+ // One more resource than max number of outgoing stream of this session.
+ const size_t kNumResources = 1 + kNumMaxStreams; // 11.
+ std::string push_urls[11];
+ for (size_t i = 0; i < kNumResources; ++i) {
+ push_urls[i] = QuicStrCat("https://example.com/push_resources", i);
+ }
+ AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+ push_urls, kNumResources, 0);
+ client_->client()->set_response_listener(
+ std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+ new TestResponseListener));
+
+ // Send the first request: this will trigger the server to send all the push
+ // resources associated with this request, and these will be cached by the
+ // client.
+ EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+ "https://example.com/push_example"));
+
+ for (const std::string& url : push_urls) {
+ // Sending subsequent requesets will not actually send anything on the wire,
+ // as the responses are already in the client's cache.
+ EXPECT_EQ(QuicStrCat("This is server push response body for ", url),
+ client_->SendSynchronousRequest(url));
+ }
+
+ // Only 1 request should have been sent.
+ EXPECT_EQ(1u, client_->num_requests());
+ // The responses to the original request and all the promised resources
+ // should have been received.
+ EXPECT_EQ(12u, client_->num_responses());
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushOverLimitWithBlocking) {
+ // Tests that when server tries to send more large resources(large enough to
+ // be blocked by flow control window or congestion control window) than max
+ // open outgoing streams , server can open upto max number of outgoing
+ // streams for them, and the rest will be queued up.
+
+ // Reset flow control windows.
+ size_t kFlowControlWnd = 20 * 1024; // 20KB.
+ // Response body is larger than 1 flow controlblock window.
+ size_t kBodySize = kFlowControlWnd * 2;
+ set_client_initial_stream_flow_control_receive_window(kFlowControlWnd);
+ // Make sure conntection level flow control window is large enough not to
+ // block data being sent out though they will be blocked by stream level one.
+ set_client_initial_session_flow_control_receive_window(
+ kBodySize * kNumMaxStreams + 1024);
+
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+ SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+ SetReorderPercentage(30);
+
+ // Add a response with headers, body, and push resources.
+ const std::string kBody = "body content";
+
+ const size_t kNumResources = kNumMaxStreams + 1;
+ std::string push_urls[11];
+ for (size_t i = 0; i < kNumResources; ++i) {
+ push_urls[i] = QuicStrCat("http://example.com/push_resources", i);
+ }
+ AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+ push_urls, kNumResources, kBodySize);
+
+ client_->client()->set_response_listener(
+ std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+ new TestResponseListener));
+
+ client_->SendRequest("https://example.com/push_example");
+
+ // Pause after the first response arrives.
+ while (!client_->response_complete()) {
+ // Because of priority, the first response arrived should be to original
+ // request.
+ client_->WaitForResponse();
+ }
+
+ // Check server session to see if it has max number of outgoing streams opened
+ // though more resources need to be pushed.
+ server_thread_->Pause();
+ EXPECT_EQ(kNumMaxStreams, GetServerSession()->GetNumOpenOutgoingStreams());
+ server_thread_->Resume();
+
+ EXPECT_EQ(1u, client_->num_requests());
+ EXPECT_EQ(1u, client_->num_responses());
+ EXPECT_EQ(kBody, client_->response_body());
+
+ // "Send" request for a promised resources will not really send out it because
+ // its response is being pushed(but blocked). And the following ack and
+ // flow control behavior of SendSynchronousRequests()
+ // will unblock the stream to finish receiving response.
+ client_->SendSynchronousRequest(push_urls[0]);
+ EXPECT_EQ(1u, client_->num_requests());
+ EXPECT_EQ(2u, client_->num_responses());
+
+ // Do same thing for the rest 10 resources.
+ for (size_t i = 1; i < kNumResources; ++i) {
+ client_->SendSynchronousRequest(push_urls[i]);
+ }
+
+ // Because of server push, client gets all pushed resources without actually
+ // sending requests for them.
+ EXPECT_EQ(1u, client_->num_requests());
+ // Including response to original request, 12 responses in total were
+ // received.
+ EXPECT_EQ(12u, client_->num_responses());
+}
+
+// TODO(fayang): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTest, DISABLED_TestHugePostWithPacketLoss) {
+ // This test tests a huge post with introduced packet loss from client to
+ // server and body size greater than 4GB, making sure QUIC code does not break
+ // for 32-bit builds.
+ ServerStreamThatDropsBodyFactory stream_factory;
+ SetSpdyStreamFactory(&stream_factory);
+ ASSERT_TRUE(Initialize());
+ // Set client's epoll server's time out to 0 to make this test be finished
+ // within a short time.
+ client_->epoll_server()->set_timeout_in_us(0);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(1);
+ // To avoid storing the whole request body in memory, use a loop to repeatedly
+ // send body size of kSizeBytes until the whole request body size is reached.
+ const int kSizeBytes = 128 * 1024;
+ // Request body size is 4G plus one more kSizeBytes.
+ int64_t request_body_size_bytes = pow(2, 32) + kSizeBytes;
+ ASSERT_LT(INT64_C(4294967296), request_body_size_bytes);
+ std::string body(kSizeBytes, 'a');
+
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["content-length"] =
+ QuicTextUtils::Uint64ToString(request_body_size_bytes);
+
+ client_->SendMessage(headers, "", /*fin=*/false);
+
+ for (int i = 0; i < request_body_size_bytes / kSizeBytes; ++i) {
+ bool fin = (i == request_body_size_bytes - 1);
+ client_->SendData(std::string(body.data(), kSizeBytes), fin);
+ client_->client()->WaitForEvents();
+ }
+ VerifyCleanConnection(true);
+}
+
+// TODO(fayang): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTest, DISABLED_TestHugeResponseWithPacketLoss) {
+ // This test tests a huge response with introduced loss from server to client
+ // and body size greater than 4GB, making sure QUIC code does not break for
+ // 32-bit builds.
+ const int kSizeBytes = 128 * 1024;
+ int64_t response_body_size_bytes = pow(2, 32) + kSizeBytes;
+ ASSERT_LT(4294967296, response_body_size_bytes);
+ ServerStreamThatSendsHugeResponseFactory stream_factory(
+ response_body_size_bytes);
+ SetSpdyStreamFactory(&stream_factory);
+
+ StartServer();
+
+ // Use a quic client that drops received body.
+ QuicTestClient* client =
+ new QuicTestClient(server_address_, server_hostname_, client_config_,
+ client_supported_versions_);
+ client->client()->set_drop_response_body(true);
+ client->UseWriter(client_writer_);
+ client->Connect();
+ client_.reset(client);
+ static QuicEpollEvent event(EPOLLOUT);
+ client_writer_->Initialize(
+ QuicConnectionPeer::GetHelper(
+ client_->client()->client_session()->connection()),
+ QuicConnectionPeer::GetAlarmFactory(
+ client_->client()->client_session()->connection()),
+ QuicMakeUnique<ClientDelegate>(client_->client()));
+ initialized_ = true;
+ ASSERT_TRUE(client_->client()->connected());
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ SetPacketLossPercentage(1);
+ client_->SendRequest("/huge_response");
+ client_->WaitForResponse();
+ // TODO(fayang): Fix this test to work with stateless rejects.
+ if (!BothSidesSupportStatelessRejects()) {
+ VerifyCleanConnection(true);
+ }
+}
+
+// Regression test for b/111515567
+TEST_P(EndToEndTest, AgreeOnStopWaiting) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ server_thread_->Pause();
+ QuicConnection* server_connection = GetServerConnection();
+ // Verify client and server connections agree on the value of
+ // no_stop_waiting_frames.
+ EXPECT_EQ(QuicConnectionPeer::GetNoStopWaitingFrames(client_connection),
+ QuicConnectionPeer::GetNoStopWaitingFrames(server_connection));
+ server_thread_->Resume();
+}
+
+// Regression test for b/111515567
+TEST_P(EndToEndTest, AgreeOnStopWaitingWithNoStopWaitingOption) {
+ QuicTagVector options;
+ options.push_back(kNSTP);
+ client_config_.SetConnectionOptionsToSend(options);
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ server_thread_->Pause();
+ QuicConnection* server_connection = GetServerConnection();
+ // Verify client and server connections agree on the value of
+ // no_stop_waiting_frames.
+ EXPECT_EQ(QuicConnectionPeer::GetNoStopWaitingFrames(client_connection),
+ QuicConnectionPeer::GetNoStopWaitingFrames(server_connection));
+ server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ReleaseHeadersStreamBufferWhenIdle) {
+ // Tests that when client side has no active request and no waiting
+ // PUSH_PROMISE, its headers stream's sequencer buffer should be released.
+ ASSERT_TRUE(Initialize());
+ client_->SendSynchronousRequest("/foo");
+ QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+ client_->client()->client_session());
+ QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(headers_stream);
+ EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTest, WayTooLongRequestHeaders) {
+ ASSERT_TRUE(Initialize());
+ SpdyHeaderBlock headers;
+ headers[":method"] = "GET";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ headers["key"] = std::string(64 * 1024, 'a');
+
+ client_->SendMessage(headers, "");
+ client_->WaitForResponse();
+ EXPECT_EQ(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+ client_->connection_error());
+}
+
+class WindowUpdateObserver : public QuicConnectionDebugVisitor {
+ public:
+ WindowUpdateObserver() : num_window_update_frames_(0), num_ping_frames_(0) {}
+
+ size_t num_window_update_frames() const { return num_window_update_frames_; }
+
+ size_t num_ping_frames() const { return num_ping_frames_; }
+
+ void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
+ const QuicTime& receive_time) override {
+ ++num_window_update_frames_;
+ }
+
+ void OnPingFrame(const QuicPingFrame& frame) override { ++num_ping_frames_; }
+
+ private:
+ size_t num_window_update_frames_;
+ size_t num_ping_frames_;
+};
+
+TEST_P(EndToEndTest, WindowUpdateInAck) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ WindowUpdateObserver observer;
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ client_connection->set_debug_visitor(&observer);
+ // 100KB body.
+ std::string body(100 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ EXPECT_EQ(kFooResponseBody,
+ client_->SendCustomSynchronousRequest(headers, body));
+ client_->Disconnect();
+ EXPECT_LT(0u, observer.num_window_update_frames());
+ EXPECT_EQ(0u, observer.num_ping_frames());
+}
+
+TEST_P(EndToEndTest, SendStatelessResetTokenInShlo) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ QuicConfig* config = client_->client()->session()->config();
+ EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+ EXPECT_EQ(QuicUtils::GenerateStatelessResetToken(
+ client_->client()->session()->connection()->connection_id()),
+ config->ReceivedStatelessResetToken());
+ client_->Disconnect();
+}
+
+// Regression test for b/116200989.
+TEST_P(EndToEndTest,
+ SendStatelessResetIfServerConnectionClosedLocallyDuringHandshake) {
+ connect_to_server_on_initialize_ = false;
+ ASSERT_TRUE(Initialize());
+
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(0u, dispatcher->session_map().size());
+ // Note: this writer will only used by the server connection, not the time
+ // wait list.
+ QuicDispatcherPeer::UseWriter(
+ dispatcher,
+ // This cause the first server-sent packet, a.k.a REJ, to fail.
+ new BadPacketWriter(/*packet_causing_write_error=*/0, EPERM));
+ server_thread_->Resume();
+
+ client_.reset(CreateQuicClient(client_writer_));
+ EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+
+ if (client_->client()->client_session()->connection()->transport_version() >
+ QUIC_VERSION_43) {
+ EXPECT_EQ(QUIC_HANDSHAKE_FAILED, client_->connection_error());
+ } else {
+ EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+ }
+}
+
+// Regression test for b/116200989.
+TEST_P(EndToEndTest,
+ SendStatelessResetIfServerConnectionClosedLocallyAfterHandshake) {
+ // Prevent the connection from expiring in the time wait list.
+ FLAGS_quic_time_wait_list_seconds = 10000;
+ connect_to_server_on_initialize_ = false;
+ ASSERT_TRUE(Initialize());
+
+ // big_response_body is 64K, which is about 48 full-sized packets.
+ const size_t kBigResponseBodySize = 65536;
+ QuicData big_response_body(new char[kBigResponseBodySize](),
+ kBigResponseBodySize, /*owns_buffer=*/true);
+ AddToCache("/big_response", 200, big_response_body.AsStringPiece());
+
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(0u, dispatcher->session_map().size());
+ QuicDispatcherPeer::UseWriter(
+ dispatcher,
+ // This will cause an server write error with EPERM, while sending the
+ // response for /big_response.
+ new BadPacketWriter(/*packet_causing_write_error=*/20, EPERM));
+ server_thread_->Resume();
+
+ client_.reset(CreateQuicClient(client_writer_));
+
+ // First, a /foo request with small response should succeed.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Second, a /big_response request with big response should fail.
+ EXPECT_LT(client_->SendSynchronousRequest("/big_response").length(),
+ kBigResponseBodySize);
+ EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+}
+
+// Regression test of b/70782529.
+TEST_P(EndToEndTest, DoNotCrashOnPacketWriteError) {
+ ASSERT_TRUE(Initialize());
+ BadPacketWriter* bad_writer =
+ new BadPacketWriter(/*packet_causing_write_error=*/5,
+ /*error_code=*/90);
+ std::unique_ptr<QuicTestClient> client(CreateQuicClient(bad_writer));
+
+ // 1 MB body.
+ std::string body(1024 * 1024, 'a');
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ client->SendCustomSynchronousRequest(headers, body);
+}
+
+// Regression test for b/71711996. This test sends a connectivity probing packet
+// as its last sent packet, and makes sure the server's ACK of that packet does
+// not cause the client to fail.
+TEST_P(EndToEndTest, LastPacketSentIsConnectivityProbing) {
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+ // Wait for the client's ACK (of the response) to be received by the server.
+ client_->WaitForDelayedAcks();
+
+ // We are sending a connectivity probing packet from an unchanged client
+ // address, so the server will not respond to us with a connectivity probing
+ // packet, however the server should send an ack-only packet to us.
+ client_->SendConnectivityProbing();
+
+ // Wait for the server's last ACK to be received by the client.
+ client_->WaitForDelayedAcks();
+}
+
+TEST_P(EndToEndTest, PreSharedKey) {
+ client_config_.set_max_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ client_config_.set_max_idle_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ pre_shared_key_client_ = "foobar";
+ pre_shared_key_server_ = "foobar";
+ ASSERT_TRUE(Initialize());
+
+ ASSERT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyMismatch)) {
+ client_config_.set_max_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ client_config_.set_max_idle_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ pre_shared_key_client_ = "foo";
+ pre_shared_key_server_ = "bar";
+ // One of two things happens when Initialize() returns:
+ // 1. Crypto handshake has completed, and it is unsuccessful. Initialize()
+ // returns false.
+ // 2. Crypto handshake has not completed, Initialize() returns true. The call
+ // to WaitForCryptoHandshakeConfirmed() will wait for the handshake and
+ // return whether it is successful.
+ ASSERT_FALSE(Initialize() &&
+ client_->client()->WaitForCryptoHandshakeConfirmed());
+ EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyNoClient)) {
+ client_config_.set_max_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ client_config_.set_max_idle_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ pre_shared_key_server_ = "foobar";
+ ASSERT_FALSE(Initialize() &&
+ client_->client()->WaitForCryptoHandshakeConfirmed());
+ EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyNoServer)) {
+ client_config_.set_max_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ client_config_.set_max_idle_time_before_crypto_handshake(
+ QuicTime::Delta::FromSeconds(1));
+ pre_shared_key_client_ = "foobar";
+ ASSERT_FALSE(Initialize() &&
+ client_->client()->WaitForCryptoHandshakeConfirmed());
+ EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, RequestAndStreamRstInOnePacket) {
+ // Regression test for b/80234898.
+ ASSERT_TRUE(Initialize());
+
+ // INCOMPLETE_RESPONSE will cause the server to not to send the trailer
+ // (and the FIN) after the response body.
+ std::string response_body(1305, 'a');
+ SpdyHeaderBlock response_headers;
+ response_headers[":status"] = QuicTextUtils::Uint64ToString(200);
+ response_headers["content-length"] =
+ QuicTextUtils::Uint64ToString(response_body.length());
+ memory_cache_backend_.AddSpecialResponse(
+ server_hostname_, "/test_url", std::move(response_headers), response_body,
+ QuicBackendResponse::INCOMPLETE_RESPONSE);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ client_->WaitForDelayedAcks();
+
+ QuicSession* session = client_->client()->client_session();
+ const QuicPacketCount packets_sent_before =
+ session->connection()->GetStats().packets_sent;
+
+ client_->SendRequestAndRstTogether("/test_url");
+
+ // Expect exactly one packet is sent from the block above.
+ ASSERT_EQ(packets_sent_before + 1,
+ session->connection()->GetStats().packets_sent);
+
+ // Wait for the connection to become idle.
+ client_->WaitForDelayedAcks();
+
+ // The real expectation is the test does not crash or timeout.
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, ResetStreamOnTtlExpires) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ if (!client_->client()->client_session()->session_decides_what_to_write()) {
+ return;
+ }
+ SetPacketLossPercentage(30);
+
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ // Set a TTL which expires immediately.
+ stream->MaybeSetTtl(QuicTime::Delta::FromMicroseconds(1));
+
+ // 1 MB body.
+ std::string body(1024 * 1024, 'a');
+ stream->WriteOrBufferBody(body, true);
+ client_->WaitForResponse();
+ EXPECT_EQ(QUIC_STREAM_TTL_EXPIRED, client_->stream_error());
+}
+
+TEST_P(EndToEndTest, SendMessages) {
+ ASSERT_TRUE(Initialize());
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ QuicSession* client_session = client_->client()->client_session();
+ QuicConnection* client_connection = client_session->connection();
+ if (client_connection->transport_version() <= QUIC_VERSION_44) {
+ return;
+ }
+
+ SetPacketLossPercentage(30);
+ ASSERT_GT(kMaxOutgoingPacketSize,
+ client_session->GetCurrentLargestMessagePayload());
+ ASSERT_LT(0, client_session->GetCurrentLargestMessagePayload());
+
+ std::string message_string(kMaxOutgoingPacketSize, 'a');
+ QuicStringPiece message_buffer(message_string);
+ QuicRandom* random =
+ QuicConnectionPeer::GetHelper(client_connection)->GetRandomGenerator();
+ QuicMemSliceStorage storage(nullptr, 0, nullptr, 0);
+ {
+ QuicConnection::ScopedPacketFlusher flusher(
+ client_session->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+ // Verify the largest message gets successfully sent.
+ EXPECT_EQ(
+ MessageResult(MESSAGE_STATUS_SUCCESS, 1),
+ client_session->SendMessage(MakeSpan(
+ client_session->connection()
+ ->helper()
+ ->GetStreamSendBufferAllocator(),
+ QuicStringPiece(message_buffer.data(),
+ client_session->GetCurrentLargestMessagePayload()),
+ &storage)));
+ // Send more messages with size (0, largest_payload] until connection is
+ // write blocked.
+ const int kTestMaxNumberOfMessages = 100;
+ for (size_t i = 2; i <= kTestMaxNumberOfMessages; ++i) {
+ size_t message_length =
+ random->RandUint64() %
+ client_session->GetCurrentLargestMessagePayload() +
+ 1;
+ MessageResult result = client_session->SendMessage(MakeSpan(
+ client_session->connection()
+ ->helper()
+ ->GetStreamSendBufferAllocator(),
+ QuicStringPiece(message_buffer.data(), message_length), &storage));
+ if (result.status == MESSAGE_STATUS_BLOCKED) {
+ // Connection is write blocked.
+ break;
+ }
+ EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, i), result);
+ }
+ }
+
+ client_->WaitForDelayedAcks();
+ EXPECT_EQ(MESSAGE_STATUS_TOO_LARGE,
+ client_session
+ ->SendMessage(MakeSpan(
+ client_session->connection()
+ ->helper()
+ ->GetStreamSendBufferAllocator(),
+ QuicStringPiece(
+ message_buffer.data(),
+ client_session->GetCurrentLargestMessagePayload() + 1),
+ &storage))
+ .status);
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+class EndToEndPacketReorderingTest : public EndToEndTest {
+ public:
+ void CreateClientWithWriter() override {
+ QUIC_LOG(ERROR) << "create client with reorder_writer_";
+ reorder_writer_ = new PacketReorderingWriter();
+ client_.reset(EndToEndTest::CreateQuicClient(reorder_writer_));
+ }
+
+ void SetUp() override {
+ // Don't initialize client writer in base class.
+ server_writer_ = new PacketDroppingTestWriter();
+ }
+
+ protected:
+ PacketReorderingWriter* reorder_writer_;
+};
+
+INSTANTIATE_TEST_SUITE_P(EndToEndPacketReorderingTests,
+ EndToEndPacketReorderingTest,
+ testing::ValuesIn(GetTestParams(false, false)));
+
+TEST_P(EndToEndPacketReorderingTest, ReorderedConnectivityProbing) {
+ ASSERT_TRUE(Initialize());
+
+ // Finish one request to make sure handshake established.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+ // Wait for the connection to become idle, to make sure the packet gets
+ // delayed is the connectivity probing packet.
+ client_->WaitForDelayedAcks();
+
+ QuicSocketAddress old_addr =
+ client_->client()->network_helper()->GetLatestClientAddress();
+
+ // Migrate socket to the new IP address.
+ QuicIpAddress new_host = TestLoopback(2);
+ EXPECT_NE(old_addr.host(), new_host);
+ ASSERT_TRUE(client_->client()->MigrateSocket(new_host));
+
+ // Write a connectivity probing after the next /foo request.
+ reorder_writer_->SetDelay(1);
+ client_->SendConnectivityProbing();
+
+ ASSERT_TRUE(client_->MigrateSocketWithSpecifiedPort(old_addr.host(),
+ old_addr.port()));
+
+ // The (delayed) connectivity probing will be sent after this request.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+ // Send yet another request after the connectivity probing, when this request
+ // returns, the probing is guaranteed to have been received by the server, and
+ // the server's response to probing is guaranteed to have been received by the
+ // client.
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+ server_thread_->Pause();
+ QuicConnection* server_connection = GetServerConnection();
+ EXPECT_EQ(1u,
+ server_connection->GetStats().num_connectivity_probing_received);
+ server_thread_->Resume();
+
+ QuicConnection* client_connection =
+ client_->client()->client_session()->connection();
+ EXPECT_EQ(1u,
+ client_connection->GetStats().num_connectivity_probing_received);
+}
+
+TEST_P(EndToEndPacketReorderingTest, Buffer0RttRequest) {
+ ASSERT_TRUE(Initialize());
+ // Finish one request to make sure handshake established.
+ client_->SendSynchronousRequest("/foo");
+ // Disconnect for next 0-rtt request.
+ client_->Disconnect();
+
+ // Client get valid STK now. Do a 0-rtt request.
+ // Buffer a CHLO till another packets sent out.
+ reorder_writer_->SetDelay(1);
+ // Only send out a CHLO.
+ client_->client()->Initialize();
+ client_->client()->StartConnect();
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_->client()->connected());
+
+ // Send a request before handshake finishes.
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/bar";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+
+ client_->SendMessage(headers, "");
+ client_->WaitForResponse();
+ EXPECT_EQ(kBarResponseBody, client_->response_body());
+ QuicConnectionStats client_stats =
+ client_->client()->client_session()->connection()->GetStats();
+ EXPECT_EQ(0u, client_stats.packets_lost);
+ if (ServerSendsVersionNegotiation()) {
+ EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+ } else {
+ EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+ }
+}
+
+// Test that STOP_SENDING makes it to the other side. Set up a client & server,
+// create a stream (do not close it), and then send a STOP_SENDING from one
+// side. The other side should get a call to QuicStream::OnStopSending.
+// (aside, test cribbed from RequestAndStreamRstInOnePacket)
+TEST_P(EndToEndTest, SimpleStopSendingTest) {
+ const uint16_t kStopSendingTestCode = 123;
+ ASSERT_TRUE(Initialize());
+ if (negotiated_version_.transport_version != QUIC_VERSION_99) {
+ return;
+ }
+ QuicSession* client_session = client_->client()->client_session();
+ ASSERT_NE(nullptr, client_session);
+ QuicConnection* client_connection = client_session->connection();
+ ASSERT_NE(nullptr, client_connection);
+
+ // STOP_SENDING will cause the server to not to send the trailer
+ // (and the FIN) after the response body. Instead, it sends a STOP_SENDING
+ // frame for the stream.
+ std::string response_body(1305, 'a');
+ SpdyHeaderBlock response_headers;
+ response_headers[":status"] = QuicTextUtils::Uint64ToString(200);
+ response_headers["content-length"] =
+ QuicTextUtils::Uint64ToString(response_body.length());
+ memory_cache_backend_.AddStopSendingResponse(
+ server_hostname_, "/test_url", std::move(response_headers), response_body,
+ kStopSendingTestCode);
+
+ EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+ client_->WaitForDelayedAcks();
+
+ QuicSession* session = client_->client()->client_session();
+ const QuicPacketCount packets_sent_before =
+ session->connection()->GetStats().packets_sent;
+
+ QuicStreamId stream_id = session->next_outgoing_bidirectional_stream_id();
+ client_->SendRequest("/test_url");
+
+ // Expect exactly one packet is sent from the block above.
+ ASSERT_EQ(packets_sent_before + 1,
+ session->connection()->GetStats().packets_sent);
+
+ // Wait for the connection to become idle.
+ client_->WaitForDelayedAcks();
+
+ // The real expectation is the test does not crash or timeout.
+ EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+ // And that the stop-sending code is received.
+ QuicSimpleClientStream* client_stream =
+ static_cast<QuicSimpleClientStream*>(client_->latest_created_stream());
+ ASSERT_NE(nullptr, client_stream);
+ // Make sure we have the correct stream
+ EXPECT_EQ(stream_id, client_stream->id());
+ EXPECT_EQ(kStopSendingTestCode, client_stream->last_stop_sending_code());
+}
+
+TEST_P(EndToEndTest, SimpleStopSendingRstStreamTest) {
+ ASSERT_TRUE(Initialize());
+
+ // Send a request without a fin, to keep the stream open
+ SpdyHeaderBlock headers;
+ headers[":method"] = "POST";
+ headers[":path"] = "/foo";
+ headers[":scheme"] = "https";
+ headers[":authority"] = server_hostname_;
+ client_->SendMessage(headers, "", /*fin=*/false);
+ // Stream should be open
+ ASSERT_NE(nullptr, client_->latest_created_stream());
+ EXPECT_FALSE(client_->latest_created_stream()->write_side_closed());
+ EXPECT_FALSE(
+ QuicStreamPeer::read_side_closed(client_->latest_created_stream()));
+
+ // Send a RST_STREAM+STOP_SENDING on the stream
+ // Code is not important.
+ client_->latest_created_stream()->Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ client_->WaitForResponse();
+
+ // Stream should be gone.
+ ASSERT_EQ(nullptr, client_->latest_created_stream());
+}
+
+class BadShloPacketWriter : public QuicPacketWriterWrapper {
+ public:
+ BadShloPacketWriter() : error_returned_(false) {}
+ ~BadShloPacketWriter() override {}
+
+ WriteResult WritePacket(const char* buffer,
+ size_t buf_len,
+ const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ quic::PerPacketOptions* options) override {
+ const WriteResult result = QuicPacketWriterWrapper::WritePacket(
+ buffer, buf_len, self_address, peer_address, options);
+ const uint8_t type_byte = buffer[0];
+ if (!error_returned_ && (type_byte & FLAGS_LONG_HEADER) &&
+ (((type_byte & 0x30) >> 4) == 1 || (type_byte & 0x7F) == 0x7C)) {
+ QUIC_DVLOG(1) << "Return write error for ZERO_RTT_PACKET";
+ error_returned_ = true;
+ return WriteResult(WRITE_STATUS_ERROR, QUIC_EMSGSIZE);
+ }
+ return result;
+ }
+
+ private:
+ bool error_returned_;
+};
+
+TEST_P(EndToEndTest, ZeroRttProtectedConnectionClose) {
+ // This test ensures ZERO_RTT_PROTECTED connection close could close a client
+ // which has switched to forward secure.
+ connect_to_server_on_initialize_ =
+ negotiated_version_.transport_version <= QUIC_VERSION_43;
+ ASSERT_TRUE(Initialize());
+ if (negotiated_version_.transport_version <= QUIC_VERSION_43) {
+ // Only runs for IETF QUIC header.
+ return;
+ }
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(0u, dispatcher->session_map().size());
+ // Note: this writer will only used by the server connection, not the time
+ // wait list.
+ QuicDispatcherPeer::UseWriter(
+ dispatcher,
+ // This causes the first server sent ZERO_RTT_PROTECTED packet (i.e.,
+ // SHLO) to be sent, but WRITE_ERROR is returned. Such that a
+ // ZERO_RTT_PROTECTED connection close would be sent to a client with
+ // encryption level FORWARD_SECURE.
+ new BadShloPacketWriter());
+ server_thread_->Resume();
+
+ client_.reset(CreateQuicClient(client_writer_));
+ EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+ // Verify ZERO_RTT_PROTECTED connection close is successfully processed by
+ // client which switches to FORWARD_SECURE.
+ EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, client_->connection_error());
+}
+
+class BadShloPacketWriter2 : public QuicPacketWriterWrapper {
+ public:
+ BadShloPacketWriter2() : error_returned_(false) {}
+ ~BadShloPacketWriter2() override {}
+
+ WriteResult WritePacket(const char* buffer,
+ size_t buf_len,
+ const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ quic::PerPacketOptions* options) override {
+ const uint8_t type_byte = buffer[0];
+ if ((type_byte & FLAGS_LONG_HEADER) &&
+ (((type_byte & 0x30) >> 4) == 1 || (type_byte & 0x7F) == 0x7C)) {
+ QUIC_DVLOG(1) << "Dropping ZERO_RTT_PACKET packet";
+ return WriteResult(WRITE_STATUS_OK, buf_len);
+ }
+ if (!error_returned_ && !(type_byte & FLAGS_LONG_HEADER)) {
+ QUIC_DVLOG(1) << "Return write error for short header packet";
+ error_returned_ = true;
+ return WriteResult(WRITE_STATUS_ERROR, QUIC_EMSGSIZE);
+ }
+ return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
+ peer_address, options);
+ }
+
+ private:
+ bool error_returned_;
+};
+
+TEST_P(EndToEndTest, ForwardSecureConnectionClose) {
+ // This test ensures ZERO_RTT_PROTECTED connection close is sent to a client
+ // which has ZERO_RTT_PROTECTED encryption level.
+ SetQuicReloadableFlag(quic_fix_termination_packets, true);
+ connect_to_server_on_initialize_ =
+ negotiated_version_.transport_version <= QUIC_VERSION_43;
+ ASSERT_TRUE(Initialize());
+ if (negotiated_version_.transport_version <= QUIC_VERSION_43) {
+ // Only runs for IETF QUIC header.
+ return;
+ }
+ server_thread_->Pause();
+ QuicDispatcher* dispatcher =
+ QuicServerPeer::GetDispatcher(server_thread_->server());
+ ASSERT_EQ(0u, dispatcher->session_map().size());
+ // Note: this writer will only used by the server connection, not the time
+ // wait list.
+ QuicDispatcherPeer::UseWriter(
+ dispatcher,
+ // This causes the all server sent ZERO_RTT_PROTECTED packets to be
+ // dropped, and first short header packet causes write error.
+ new BadShloPacketWriter2());
+ server_thread_->Resume();
+ client_.reset(CreateQuicClient(client_writer_));
+ EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+ // Verify ZERO_RTT_PROTECTED connection close is successfully processed by
+ // client.
+ EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, client_->connection_error());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc
new file mode 100644
index 00000000000..ec0062a0dd0
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc
@@ -0,0 +1,469 @@
+// Copyright (c) 2018 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/http_decoder.h"
+
+#include <type_traits>
+
+#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+
+namespace quic {
+
+namespace {
+
+// Create a mask that sets the last |num_bits| to 1 and the rest to 0.
+inline uint8_t GetMaskFromNumBits(uint8_t num_bits) {
+ return (1u << num_bits) - 1;
+}
+
+// Extract |num_bits| from |flags| offset by |offset|.
+uint8_t ExtractBits(uint8_t flags, uint8_t num_bits, uint8_t offset) {
+ return (flags >> offset) & GetMaskFromNumBits(num_bits);
+}
+
+// Length of the type field of HTTP/3 frames.
+static const QuicByteCount kFrameTypeLength = 1;
+// Length of the weight field of a priority frame.
+static const size_t kPriorityWeightLength = 1;
+// Length of a priority frame's first byte.
+static const size_t kPriorityFirstByteLength = 1;
+
+} // namespace
+
+HttpDecoder::HttpDecoder()
+ : visitor_(nullptr),
+ state_(STATE_READING_FRAME_LENGTH),
+ current_frame_type_(0),
+ current_length_field_size_(0),
+ remaining_length_field_length_(0),
+ current_frame_length_(0),
+ remaining_frame_length_(0),
+ error_(QUIC_NO_ERROR),
+ error_detail_("") {}
+
+HttpDecoder::~HttpDecoder() {}
+
+QuicByteCount HttpDecoder::ProcessInput(const char* data, QuicByteCount len) {
+ QuicDataReader reader(data, len);
+ while (error_ == QUIC_NO_ERROR &&
+ (reader.BytesRemaining() != 0 || state_ == STATE_FINISH_PARSING)) {
+ switch (state_) {
+ case STATE_READING_FRAME_LENGTH:
+ ReadFrameLength(&reader);
+ break;
+ case STATE_READING_FRAME_TYPE:
+ ReadFrameType(&reader);
+ break;
+ case STATE_READING_FRAME_PAYLOAD:
+ ReadFramePayload(&reader);
+ break;
+ case STATE_FINISH_PARSING:
+ FinishParsing();
+ break;
+ case STATE_ERROR:
+ break;
+ default:
+ QUIC_BUG << "Invalid state: " << state_;
+ }
+ }
+
+ if (error_ != QUIC_NO_ERROR) {
+ return 0;
+ }
+
+ return len - reader.BytesRemaining();
+}
+
+void HttpDecoder::ReadFrameLength(QuicDataReader* reader) {
+ DCHECK_NE(0u, reader->BytesRemaining());
+ BufferFrameLength(reader);
+ if (remaining_length_field_length_ != 0) {
+ return;
+ }
+ QuicDataReader length_reader(length_buffer_.data(),
+ current_length_field_size_);
+ if (!length_reader.ReadVarInt62(&current_frame_length_)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+ visitor_->OnError(this);
+ return;
+ }
+
+ state_ = STATE_READING_FRAME_TYPE;
+ remaining_frame_length_ = current_frame_length_;
+}
+
+void HttpDecoder::ReadFrameType(QuicDataReader* reader) {
+ DCHECK_NE(0u, reader->BytesRemaining());
+ if (!reader->ReadUInt8(&current_frame_type_)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame type");
+ return;
+ }
+
+ if (current_frame_length_ > MaxFrameLength(current_frame_type_)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Frame is too large");
+ visitor_->OnError(this);
+ return;
+ }
+
+ // Calling the following two visitor methods does not require parsing of any
+ // frame payload.
+ if (current_frame_type_ == 0x0) {
+ visitor_->OnDataFrameStart(Http3FrameLengths(
+ current_length_field_size_ + kFrameTypeLength, current_frame_length_));
+ } else if (current_frame_type_ == 0x1) {
+ visitor_->OnHeadersFrameStart(Http3FrameLengths(
+ current_length_field_size_ + kFrameTypeLength, current_frame_length_));
+ } else if (current_frame_type_ == 0x4) {
+ visitor_->OnSettingsFrameStart(Http3FrameLengths(
+ current_length_field_size_ + kFrameTypeLength, current_frame_length_));
+ }
+
+ state_ = (remaining_frame_length_ == 0) ? STATE_FINISH_PARSING
+ : STATE_READING_FRAME_PAYLOAD;
+}
+
+void HttpDecoder::ReadFramePayload(QuicDataReader* reader) {
+ DCHECK_NE(0u, reader->BytesRemaining());
+ DCHECK_NE(0u, remaining_frame_length_);
+ switch (current_frame_type_) {
+ case 0x0: { // DATA
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ QuicStringPiece payload;
+ if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+ return;
+ }
+ DCHECK(!payload.empty());
+ visitor_->OnDataFramePayload(payload);
+ remaining_frame_length_ -= payload.length();
+ break;
+ }
+ case 0x1: { // HEADERS
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ QuicStringPiece payload;
+ if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+ return;
+ }
+ DCHECK(!payload.empty());
+ visitor_->OnHeadersFramePayload(payload);
+ remaining_frame_length_ -= payload.length();
+ break;
+ }
+ case 0x2: { // PRIORITY
+ // TODO(rch): avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ BufferFramePayload(reader);
+ break;
+ }
+ case 0x3: { // CANCEL_PUSH
+ BufferFramePayload(reader);
+ break;
+ }
+ case 0x4: { // SETTINGS
+ BufferFramePayload(reader);
+ break;
+ }
+ case 0x5: { // PUSH_PROMISE
+ if (current_frame_length_ == remaining_frame_length_) {
+ QuicByteCount bytes_remaining = reader->BytesRemaining();
+ PushId push_id;
+ // TODO(rch): Handle partial delivery of this field.
+ if (!reader->ReadVarInt62(&push_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+ return;
+ }
+ remaining_frame_length_ -= bytes_remaining - reader->BytesRemaining();
+ visitor_->OnPushPromiseFrameStart(push_id);
+ }
+ DCHECK_LT(remaining_frame_length_, current_frame_length_);
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ if (bytes_to_read == 0) {
+ break;
+ }
+ QuicStringPiece payload;
+ if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+ return;
+ }
+ DCHECK(!payload.empty());
+ visitor_->OnPushPromiseFramePayload(payload);
+ remaining_frame_length_ -= payload.length();
+ break;
+ }
+ case 0x7: { // GOAWAY
+ BufferFramePayload(reader);
+ break;
+ }
+
+ case 0xD: { // MAX_PUSH_ID
+ // TODO(rch): Handle partial delivery.
+ BufferFramePayload(reader);
+ break;
+ }
+
+ case 0xE: { // DUPLICATE_PUSH
+ BufferFramePayload(reader);
+ break;
+ }
+ // Reserved frame types.
+ // TODO(rch): Since these are actually the same behavior as the
+ // default, we probably don't need to special case them here?
+ case 0xB:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 2:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 3:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 4:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 5:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 6:
+ QUIC_FALLTHROUGH_INTENDED;
+ case 0xB + 0x1F * 7:
+ QUIC_FALLTHROUGH_INTENDED;
+ default:
+ DiscardFramePayload(reader);
+ }
+
+ if (remaining_frame_length_ == 0) {
+ state_ = STATE_FINISH_PARSING;
+ }
+}
+
+void HttpDecoder::FinishParsing() {
+ DCHECK_EQ(0u, remaining_frame_length_);
+ switch (current_frame_type_) {
+ case 0x0: { // DATA
+ visitor_->OnDataFrameEnd();
+ break;
+ }
+ case 0x1: { // HEADERS
+ visitor_->OnHeadersFrameEnd();
+ break;
+ }
+ case 0x2: { // PRIORITY
+ // TODO(rch): avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ PriorityFrame frame;
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ if (!ParsePriorityFrame(&reader, &frame)) {
+ return;
+ }
+ visitor_->OnPriorityFrame(frame);
+ break;
+ }
+ case 0x3: { // CANCEL_PUSH
+ // TODO(rch): Handle partial delivery.
+ CancelPushFrame frame;
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ if (!reader.ReadVarInt62(&frame.push_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+ return;
+ }
+ visitor_->OnCancelPushFrame(frame);
+ break;
+ }
+ case 0x4: { // SETTINGS
+ // TODO(rch): Handle overly large SETTINGS frames. Either:
+ // 1. Impose a limit on SETTINGS frame size, and close the connection if
+ // exceeded
+ // 2. Implement a streaming parsing mode.
+ SettingsFrame frame;
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ if (!ParseSettingsFrame(&reader, &frame)) {
+ return;
+ }
+ visitor_->OnSettingsFrame(frame);
+ break;
+ }
+ case 0x5: { // PUSH_PROMISE
+ visitor_->OnPushPromiseFrameEnd();
+ break;
+ }
+ case 0x7: { // GOAWAY
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ GoAwayFrame frame;
+ static_assert(!std::is_same<decltype(frame.stream_id), uint64_t>::value,
+ "Please remove local |stream_id| variable and pass "
+ "&frame.stream_id directly to ReadVarInt62() when changing "
+ "QuicStreamId from uint32_t to uint64_t.");
+ uint64_t stream_id;
+ if (!reader.ReadVarInt62(&stream_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read GOAWAY stream_id");
+ return;
+ }
+ frame.stream_id = static_cast<QuicStreamId>(stream_id);
+ visitor_->OnGoAwayFrame(frame);
+ break;
+ }
+
+ case 0xD: { // MAX_PUSH_ID
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ MaxPushIdFrame frame;
+ if (!reader.ReadVarInt62(&frame.push_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+ return;
+ }
+ visitor_->OnMaxPushIdFrame(frame);
+ break;
+ }
+
+ case 0xE: { // DUPLICATE_PUSH
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ DuplicatePushFrame frame;
+ if (!reader.ReadVarInt62(&frame.push_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+ return;
+ }
+ visitor_->OnDuplicatePushFrame(frame);
+ break;
+ }
+ }
+
+ current_length_field_size_ = 0;
+ state_ = STATE_READING_FRAME_LENGTH;
+}
+
+void HttpDecoder::DiscardFramePayload(QuicDataReader* reader) {
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ QuicStringPiece payload;
+ if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame payload");
+ return;
+ }
+ remaining_frame_length_ -= payload.length();
+ if (remaining_frame_length_ == 0) {
+ state_ = STATE_READING_FRAME_LENGTH;
+ current_length_field_size_ = 0;
+ }
+}
+
+void HttpDecoder::BufferFramePayload(QuicDataReader* reader) {
+ if (current_frame_length_ == remaining_frame_length_) {
+ buffer_.erase(buffer_.size());
+ buffer_.reserve(current_frame_length_);
+ }
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ if (!reader->ReadBytes(
+ &(buffer_[0]) + current_frame_length_ - remaining_frame_length_,
+ bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame payload");
+ return;
+ }
+ remaining_frame_length_ -= bytes_to_read;
+}
+
+void HttpDecoder::BufferFrameLength(QuicDataReader* reader) {
+ if (current_length_field_size_ == 0) {
+ current_length_field_size_ = reader->PeekVarInt62Length();
+ if (current_length_field_size_ == 0) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+ visitor_->OnError(this);
+ return;
+ }
+ remaining_length_field_length_ = current_length_field_size_;
+ }
+ if (current_length_field_size_ == remaining_length_field_length_) {
+ length_buffer_.erase(length_buffer_.size());
+ length_buffer_.reserve(current_length_field_size_);
+ }
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_length_field_length_, reader->BytesRemaining());
+ if (!reader->ReadBytes(&(length_buffer_[0]) + current_length_field_size_ -
+ remaining_length_field_length_,
+ bytes_to_read)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+ visitor_->OnError(this);
+ return;
+ }
+ remaining_length_field_length_ -= bytes_to_read;
+}
+
+void HttpDecoder::RaiseError(QuicErrorCode error, std::string error_detail) {
+ state_ = STATE_ERROR;
+ error_ = error;
+ error_detail_ = std::move(error_detail);
+}
+
+bool HttpDecoder::ParsePriorityFrame(QuicDataReader* reader,
+ PriorityFrame* frame) {
+ uint8_t flags;
+ if (!reader->ReadUInt8(&flags)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read priority frame flags");
+ return false;
+ }
+
+ frame->prioritized_type =
+ static_cast<PriorityElementType>(ExtractBits(flags, 2, 6));
+ frame->dependency_type =
+ static_cast<PriorityElementType>(ExtractBits(flags, 2, 4));
+ frame->exclusive = flags % 2 == 1;
+ if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read prioritized_element_id");
+ return false;
+ }
+ if (!reader->ReadVarInt62(&frame->element_dependency_id)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read element_dependency_id");
+ return false;
+ }
+ if (!reader->ReadUInt8(&frame->weight)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read priority frame weight");
+ return false;
+ }
+ return true;
+}
+
+bool HttpDecoder::ParseSettingsFrame(QuicDataReader* reader,
+ SettingsFrame* frame) {
+ while (!reader->IsDoneReading()) {
+ uint16_t id;
+ if (!reader->ReadUInt16(&id)) {
+ RaiseError(QUIC_INTERNAL_ERROR,
+ "Unable to read settings frame identifier");
+ return false;
+ }
+ uint64_t content;
+ if (!reader->ReadVarInt62(&content)) {
+ RaiseError(QUIC_INTERNAL_ERROR, "Unable to read settings frame content");
+ return false;
+ }
+ frame->values[id] = content;
+ }
+ return true;
+}
+
+QuicByteCount HttpDecoder::MaxFrameLength(uint8_t frame_type) {
+ switch (frame_type) {
+ case 0x2: // PRIORITY
+ return kPriorityFirstByteLength + VARIABLE_LENGTH_INTEGER_LENGTH_8 * 2 +
+ kPriorityWeightLength;
+ case 0x3: // CANCEL_PUSH
+ return sizeof(PushId);
+ case 0x4: // SETTINGS
+ // This limit is arbitrary.
+ return 1024 * 1024;
+ case 0x7: // GOAWAY
+ return sizeof(QuicStreamId);
+ case 0xD: // MAX_PUSH_ID
+ return sizeof(PushId);
+ case 0xE: // DUPLICATE_PUSH
+ return sizeof(PushId);
+ default:
+ // Other frames require no data buffering, so it's safe to have no limit.
+ return std::numeric_limits<QuicByteCount>::max();
+ }
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h
new file mode 100644
index 00000000000..55de8adf3ac
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h
@@ -0,0 +1,196 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_HTTP_DECODER_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_DECODER_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/http/http_frames.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QuicDataReader;
+
+// Struct that stores meta data of an HTTP/3 frame.
+// |header_length| is frame header length in bytes.
+// |payload_length| is frame payload length in bytes.
+struct QUIC_EXPORT_PRIVATE Http3FrameLengths {
+ Http3FrameLengths(QuicByteCount header, QuicByteCount payload)
+ : header_length(header), payload_length(payload) {}
+
+ bool operator==(const Http3FrameLengths& other) const {
+ return (header_length == other.header_length) &&
+ (payload_length == other.payload_length);
+ }
+
+ QuicByteCount header_length;
+ QuicByteCount payload_length;
+};
+
+// A class for decoding the HTTP frames that are exchanged in an HTTP over QUIC
+// session.
+class QUIC_EXPORT_PRIVATE HttpDecoder {
+ public:
+ class QUIC_EXPORT_PRIVATE Visitor {
+ public:
+ virtual ~Visitor() {}
+
+ // Called if an error is detected.
+ virtual void OnError(HttpDecoder* decoder) = 0;
+
+ // Called when a PRIORITY frame has been successfully parsed.
+ virtual void OnPriorityFrame(const PriorityFrame& frame) = 0;
+
+ // Called when a CANCEL_PUSH frame has been successfully parsed.
+ virtual void OnCancelPushFrame(const CancelPushFrame& frame) = 0;
+
+ // Called when a MAX_PUSH_ID frame has been successfully parsed.
+ virtual void OnMaxPushIdFrame(const MaxPushIdFrame& frame) = 0;
+
+ // Called when a GOAWAY frame has been successfully parsed.
+ virtual void OnGoAwayFrame(const GoAwayFrame& frame) = 0;
+
+ // Called when a SETTINGS frame has been received.
+ virtual void OnSettingsFrameStart(Http3FrameLengths frame_length) = 0;
+
+ // Called when a SETTINGS frame has been successfully parsed.
+ virtual void OnSettingsFrame(const SettingsFrame& frame) = 0;
+
+ // Called when a DUPLICATE_PUSH frame has been successfully parsed.
+ virtual void OnDuplicatePushFrame(const DuplicatePushFrame& frame) = 0;
+
+ // Called when a DATA frame has been received.
+ // |frame_length| contains DATA frame length and payload length.
+ virtual void OnDataFrameStart(Http3FrameLengths frame_length) = 0;
+ // Called when part of the payload of a DATA frame has been read. May be
+ // called multiple times for a single frame. |payload| is guaranteed to be
+ // non-empty.
+ virtual void OnDataFramePayload(QuicStringPiece payload) = 0;
+ // Called when a DATA frame has been completely processed.
+ virtual void OnDataFrameEnd() = 0;
+
+ // Called when a HEADERS frame has been received.
+ // |frame_length| contains HEADERS frame length and payload length.
+ virtual void OnHeadersFrameStart(Http3FrameLengths frame_length) = 0;
+ // Called when part of the payload of a HEADERS frame has been read. May be
+ // called multiple times for a single frame. |payload| is guaranteed to be
+ // non-empty.
+ virtual void OnHeadersFramePayload(QuicStringPiece payload) = 0;
+ // Called when a HEADERS frame has been completely processed.
+ // |frame_len| is the length of the HEADERS frame payload.
+ virtual void OnHeadersFrameEnd() = 0;
+
+ // Called when a PUSH_PROMISE frame has been received for |push_id|.
+ virtual void OnPushPromiseFrameStart(PushId push_id) = 0;
+ // Called when part of the payload of a PUSH_PROMISE frame has been read.
+ // May be called multiple times for a single frame. |payload| is guaranteed
+ // to be non-empty.
+ virtual void OnPushPromiseFramePayload(QuicStringPiece payload) = 0;
+ // Called when a PUSH_PROMISE frame has been completely processed.
+ virtual void OnPushPromiseFrameEnd() = 0;
+
+ // TODO(rch): Consider adding methods like:
+ // OnUnknownFrame{Start,Payload,End}()
+ // to allow callers to handle unknown frames.
+ };
+
+ HttpDecoder();
+
+ ~HttpDecoder();
+
+ // Set callbacks to be called from the decoder. A visitor must be set, or
+ // else the decoder will crash. It is acceptable for the visitor to do
+ // nothing. If this is called multiple times, only the last visitor
+ // will be used. |visitor| will be owned by the caller.
+ void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+ // Processes the input and invokes the visitor for any frames.
+ // Returns the number of bytes consumed, or 0 if there was an error, in which
+ // case error() should be consulted.
+ QuicByteCount ProcessInput(const char* data, QuicByteCount len);
+
+ QuicErrorCode error() const { return error_; }
+ const std::string& error_detail() const { return error_detail_; }
+
+ private:
+ // Represents the current state of the parsing state machine.
+ enum HttpDecoderState {
+ STATE_READING_FRAME_LENGTH,
+ STATE_READING_FRAME_TYPE,
+ STATE_READING_FRAME_PAYLOAD,
+ STATE_FINISH_PARSING,
+ STATE_ERROR
+ };
+
+ // Reads the length of a frame from |reader|. Sets error_ and error_detail_
+ // if there are any errors.
+ void ReadFrameLength(QuicDataReader* reader);
+
+ // Reads the type of a frame from |reader|. Sets error_ and error_detail_
+ // if there are any errors. Also calls OnDataFrameStart() or
+ // OnHeadersFrameStart() for appropriate frame types.
+ void ReadFrameType(QuicDataReader* reader);
+
+ // Reads the payload of the current frame from |reader| and processes it,
+ // possibly buffering the data or invoking the visitor.
+ void ReadFramePayload(QuicDataReader* reader);
+
+ // Optionally parses buffered data; calls visitor method to signal that frame
+ // had been parsed completely.
+ void FinishParsing();
+
+ // Discards any remaining frame payload from |reader|.
+ void DiscardFramePayload(QuicDataReader* reader);
+
+ // Buffers any remaining frame payload from |reader| into |buffer_|.
+ void BufferFramePayload(QuicDataReader* reader);
+
+ // Buffers any remaining frame length field from |reader| into
+ // |length_buffer_|
+ void BufferFrameLength(QuicDataReader* reader);
+
+ // Sets |error_| and |error_detail_| accordingly.
+ void RaiseError(QuicErrorCode error, std::string error_detail);
+
+ // Parses the payload of a PRIORITY frame from |reader| into |frame|.
+ bool ParsePriorityFrame(QuicDataReader* reader, PriorityFrame* frame);
+
+ // Parses the payload of a SETTINGS frame from |reader| into |frame|.
+ bool ParseSettingsFrame(QuicDataReader* reader, SettingsFrame* frame);
+
+ // Returns the max frame size of a given |frame_type|.
+ QuicByteCount MaxFrameLength(uint8_t frame_type);
+
+ // Visitor to invoke when messages are parsed.
+ Visitor* visitor_; // Unowned.
+ // Current state of the parsing.
+ HttpDecoderState state_;
+ // Type of the frame currently being parsed.
+ uint8_t current_frame_type_;
+ // Size of the frame's length field.
+ QuicByteCount current_length_field_size_;
+ // Remaining length that's needed for the frame's length field.
+ QuicByteCount remaining_length_field_length_;
+ // Length of the payload of the frame currently being parsed.
+ QuicByteCount current_frame_length_;
+ // Remaining payload bytes to be parsed.
+ QuicByteCount remaining_frame_length_;
+ // Last error.
+ QuicErrorCode error_;
+ // The issue which caused |error_|
+ std::string error_detail_;
+ // Remaining unparsed data.
+ std::string buffer_;
+ // Remaining unparsed length field data.
+ std::string length_buffer_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_HTTP_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc
new file mode 100644
index 00000000000..7f0dcd44048
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc
@@ -0,0 +1,510 @@
+// Copyright (c) 2018 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/http_decoder.h"
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+using testing::InSequence;
+
+namespace quic {
+
+class MockVisitor : public HttpDecoder::Visitor {
+ public:
+ virtual ~MockVisitor() = default;
+
+ // Called if an error is detected.
+ MOCK_METHOD1(OnError, void(HttpDecoder* decoder));
+
+ MOCK_METHOD1(OnPriorityFrame, void(const PriorityFrame& frame));
+ MOCK_METHOD1(OnCancelPushFrame, void(const CancelPushFrame& frame));
+ MOCK_METHOD1(OnMaxPushIdFrame, void(const MaxPushIdFrame& frame));
+ MOCK_METHOD1(OnGoAwayFrame, void(const GoAwayFrame& frame));
+ MOCK_METHOD1(OnSettingsFrameStart, void(Http3FrameLengths frame_lengths));
+ MOCK_METHOD1(OnSettingsFrame, void(const SettingsFrame& frame));
+ MOCK_METHOD1(OnDuplicatePushFrame, void(const DuplicatePushFrame& frame));
+
+ MOCK_METHOD1(OnDataFrameStart, void(Http3FrameLengths frame_lengths));
+ MOCK_METHOD1(OnDataFramePayload, void(QuicStringPiece payload));
+ MOCK_METHOD0(OnDataFrameEnd, void());
+
+ MOCK_METHOD1(OnHeadersFrameStart, void(Http3FrameLengths frame_lengths));
+ MOCK_METHOD1(OnHeadersFramePayload, void(QuicStringPiece payload));
+ MOCK_METHOD0(OnHeadersFrameEnd, void());
+
+ MOCK_METHOD1(OnPushPromiseFrameStart, void(PushId push_id));
+ MOCK_METHOD1(OnPushPromiseFramePayload, void(QuicStringPiece payload));
+ MOCK_METHOD0(OnPushPromiseFrameEnd, void());
+};
+
+class HttpDecoderTest : public QuicTest {
+ public:
+ HttpDecoderTest() { decoder_.set_visitor(&visitor_); }
+ HttpDecoder decoder_;
+ testing::StrictMock<MockVisitor> visitor_;
+};
+
+TEST_F(HttpDecoderTest, InitialState) {
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesNoPayload) {
+ for (int n = 0; n < 8; ++n) {
+ const uint8_t type = 0xB + 0x1F * n;
+ char input[] = {// length
+ 0x00,
+ // type
+ type};
+
+ EXPECT_EQ(2u, decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input))) << n;
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ ASSERT_EQ("", decoder_.error_detail());
+ }
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesSmallPayload) {
+ for (int n = 0; n < 8; ++n) {
+ const uint8_t type = 0xB + 0x1F * n;
+ const uint8_t payload_size = 50;
+ char input[payload_size + 2] = {// length
+ payload_size,
+ // type
+ type};
+
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)))
+ << n;
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ ASSERT_EQ("", decoder_.error_detail());
+ }
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesLargePayload) {
+ for (int n = 0; n < 8; ++n) {
+ const uint8_t type = 0xB + 0x1F * n;
+ const QuicByteCount payload_size = 256;
+ char input[payload_size + 3] = {// length
+ 0x40 + 0x01, 0x00,
+ // type
+ type};
+
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)))
+ << n;
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ ASSERT_EQ("", decoder_.error_detail());
+ }
+}
+
+TEST_F(HttpDecoderTest, CancelPush) {
+ char input[] = {// length
+ 0x1,
+ // type (CANCEL_PUSH)
+ 0x03,
+ // Push Id
+ 0x01};
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1})));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1})));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, PushPromiseFrame) {
+ char input[] = {// length
+ 0x8,
+ // type (PUSH_PROMISE)
+ 0x05,
+ // Push Id
+ 0x01,
+ // Header Block
+ 'H', 'e', 'a', 'd', 'e', 'r', 's'};
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("Headers")));
+ EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("H")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("e")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("a")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("d")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("e")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("r")));
+ EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("s")));
+ EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, MaxPushId) {
+ char input[] = {// length
+ 0x1,
+ // type (MAX_PUSH_ID)
+ 0x0D,
+ // Push Id
+ 0x01};
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnMaxPushIdFrame(MaxPushIdFrame({1})));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnMaxPushIdFrame(MaxPushIdFrame({1})));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, DuplicatePush) {
+ char input[] = {// length
+ 0x1,
+ // type (DUPLICATE_PUSH)
+ 0x0E,
+ // Push Id
+ 0x01};
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnDuplicatePushFrame(DuplicatePushFrame({1})));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnDuplicatePushFrame(DuplicatePushFrame({1})));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, PriorityFrame) {
+ char input[] = {// length
+ 0x4,
+ // type (PRIORITY)
+ 0x2,
+ // request stream, request stream, exclusive
+ 0x01,
+ // prioritized_element_id
+ 0x03,
+ // element_dependency_id
+ 0x04,
+ // weight
+ 0xFF};
+
+ PriorityFrame frame;
+ frame.prioritized_type = REQUEST_STREAM;
+ frame.dependency_type = REQUEST_STREAM;
+ frame.exclusive = true;
+ frame.prioritized_element_id = 0x03;
+ frame.element_dependency_id = 0x04;
+ frame.weight = 0xFF;
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnPriorityFrame(frame));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ /*
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnPriorityFrame(frame));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+ */
+}
+
+TEST_F(HttpDecoderTest, SettingsFrame) {
+ // clang-format off
+ char input[] = {
+ // length
+ 0x06,
+ // type (SETTINGS)
+ 0x04,
+ // identifier (SETTINGS_NUM_PLACEHOLDERS)
+ 0x00,
+ 0x03,
+ // content
+ 0x02,
+ // identifier (SETTINGS_MAX_HEADER_LIST_SIZE)
+ 0x00,
+ 0x06,
+ // content
+ 0x05,
+ };
+ // clang-format on
+
+ SettingsFrame frame;
+ frame.values[3] = 2;
+ frame.values[6] = 5;
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnSettingsFrameStart(Http3FrameLengths(2, 6)));
+ EXPECT_CALL(visitor_, OnSettingsFrame(frame));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnSettingsFrameStart(Http3FrameLengths(2, 6)));
+ EXPECT_CALL(visitor_, OnSettingsFrame(frame));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, DataFrame) {
+ char input[] = {// length
+ 0x05,
+ // type (DATA)
+ 0x00,
+ // data
+ 'D', 'a', 't', 'a', '!'};
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 5)));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("Data!")));
+ EXPECT_CALL(visitor_, OnDataFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 5)));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("D")));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("a")));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("t")));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("a")));
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("!")));
+ EXPECT_CALL(visitor_, OnDataFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, FrameHeaderPartialDelivery) {
+ // A large input that will occupy more than 1 byte in the length field.
+ std::string input(2048, 'x');
+ HttpEncoder encoder;
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder.SerializeDataFrameHeader(input.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ // Partially send only 1 byte of the header to process.
+ EXPECT_EQ(1u, decoder_.ProcessInput(header.data(), 1));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Send the rest of the header.
+ EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(3, 2048)));
+ EXPECT_EQ(header_length - 1,
+ decoder_.ProcessInput(header.data() + 1, header_length - 1));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Send data.
+ EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece(input)));
+ EXPECT_CALL(visitor_, OnDataFrameEnd());
+ EXPECT_EQ(2048u, decoder_.ProcessInput(input.data(), 2048));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, GoAway) {
+ char input[] = {// length
+ 0x1,
+ // type (GOAWAY)
+ 0x07,
+ // StreamId
+ 0x01};
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnGoAwayFrame(GoAwayFrame({1})));
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnGoAwayFrame(GoAwayFrame({1})));
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, HeadersFrame) {
+ char input[] = {// length
+ 0x07,
+ // type (HEADERS)
+ 0x01,
+ // headers
+ 'H', 'e', 'a', 'd', 'e', 'r', 's'};
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnHeadersFrameStart(Http3FrameLengths(2, 7)));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("Headers")));
+ EXPECT_CALL(visitor_, OnHeadersFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnHeadersFrameStart(Http3FrameLengths(2, 7)));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("H")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("e")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("a")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("d")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("e")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("r")));
+ EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("s")));
+ EXPECT_CALL(visitor_, OnHeadersFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, EmptyDataFrame) {
+ char input[] = {0x00, // length
+ 0x00}; // type (DATA)
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 0)));
+ EXPECT_CALL(visitor_, OnDataFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 0)));
+ EXPECT_CALL(visitor_, OnDataFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, EmptyHeadersFrame) {
+ char input[] = {0x00, // length
+ 0x01}; // type (HEADERS)
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnHeadersFrameStart(Http3FrameLengths(2, 0)));
+ EXPECT_CALL(visitor_, OnHeadersFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnHeadersFrameStart(Http3FrameLengths(2, 0)));
+ EXPECT_CALL(visitor_, OnHeadersFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, PushPromiseFrameNoHeaders) {
+ char input[] = {0x01, // length
+ 0x05, // type (PUSH_PROMISE)
+ 0x01}; // Push Id
+
+ // Process the full frame.
+ InSequence s;
+ EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+ EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+ EXPECT_EQ(QUIC_ARRAYSIZE(input),
+ decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incremently.
+ EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+ EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+ for (char c : input) {
+ EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+ }
+ EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, MalformedFrameWithOverlyLargePayload) {
+ char input[] = {0x10, // length
+ 0x03, // type (CANCEL_PUSH)
+ 0x15}; // malformed payload
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnError(&decoder_));
+ EXPECT_EQ(0u, decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_INTERNAL_ERROR, decoder_.error());
+ EXPECT_EQ("Frame is too large", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, MalformedSettingsFrame) {
+ char input[30];
+ QuicDataWriter writer(30, input);
+ // Write length.
+ writer.WriteVarInt62(2048 * 1024);
+ // Write type SETTINGS.
+ writer.WriteUInt8(0x04);
+
+ writer.WriteStringPiece("Malformed payload");
+ EXPECT_CALL(visitor_, OnError(&decoder_));
+ EXPECT_EQ(0u, decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+ EXPECT_EQ(QUIC_INTERNAL_ERROR, decoder_.error());
+ EXPECT_EQ("Frame is too large", decoder_.error_detail());
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc
new file mode 100644
index 00000000000..beeef9b6abb
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2018 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 <string>
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+namespace {
+
+// Set the first byte of a PRIORITY frame according to its fields.
+uint8_t SetPriorityFields(uint8_t num,
+ PriorityElementType type,
+ bool prioritized) {
+ switch (type) {
+ case REQUEST_STREAM:
+ return num;
+ case PUSH_STREAM:
+ if (prioritized) {
+ return num | (1 << 6);
+ }
+ return num | (1 << 4);
+ case PLACEHOLDER:
+ if (prioritized) {
+ return num | (1 << 7);
+ }
+ return num | (1 << 5);
+ case ROOT_OF_TREE:
+ if (prioritized) {
+ num = num | (1 << 6);
+ return num | (1 << 7);
+ }
+ num = num | (1 << 4);
+ return num | (1 << 5);
+ default:
+ QUIC_NOTREACHED();
+ return num;
+ }
+}
+
+// Length of the type field of a frame.
+static const size_t kFrameTypeLength = 1;
+// Length of the weight field of a priority frame.
+static const size_t kPriorityWeightLength = 1;
+// Length of a priority frame's first byte.
+static const size_t kPriorityFirstByteLength = 1;
+// Length of a key in the map of a settings frame.
+static const size_t kSettingsMapKeyLength = 2;
+
+} // namespace
+
+HttpEncoder::HttpEncoder() {}
+
+HttpEncoder::~HttpEncoder() {}
+
+QuicByteCount HttpEncoder::SerializeDataFrameHeader(
+ QuicByteCount payload_length,
+ std::unique_ptr<char[]>* output) {
+ DCHECK_NE(0u, payload_length);
+ QuicByteCount header_length =
+ QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength;
+
+ output->reset(new char[header_length]);
+ QuicDataWriter writer(header_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::DATA, &writer)) {
+ return header_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeHeadersFrameHeader(
+ QuicByteCount payload_length,
+ std::unique_ptr<char[]>* output) {
+ DCHECK_NE(0u, payload_length);
+ QuicByteCount header_length =
+ QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength;
+
+ output->reset(new char[header_length]);
+ QuicDataWriter writer(header_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::HEADERS, &writer)) {
+ return header_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializePriorityFrame(
+ const PriorityFrame& priority,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ kPriorityFirstByteLength +
+ QuicDataWriter::GetVarInt62Len(priority.prioritized_element_id) +
+ QuicDataWriter::GetVarInt62Len(priority.element_dependency_id) +
+ kPriorityWeightLength;
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (!WriteFrameHeader(payload_length, HttpFrameType::PRIORITY, &writer)) {
+ return 0;
+ }
+
+ // Set the first byte of the payload.
+ uint8_t bits = 0;
+ bits = SetPriorityFields(bits, priority.prioritized_type, true);
+ bits = SetPriorityFields(bits, priority.dependency_type, false);
+ if (priority.exclusive) {
+ bits |= 1;
+ }
+
+ if (writer.WriteUInt8(bits) &&
+ writer.WriteVarInt62(priority.prioritized_element_id) &&
+ writer.WriteVarInt62(priority.element_dependency_id) &&
+ writer.WriteUInt8(priority.weight)) {
+ return total_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeCancelPushFrame(
+ const CancelPushFrame& cancel_push,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(cancel_push.push_id);
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::CANCEL_PUSH, &writer) &&
+ writer.WriteVarInt62(cancel_push.push_id)) {
+ return total_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeSettingsFrame(
+ const SettingsFrame& settings,
+ std::unique_ptr<char[]>* output) {
+ // Calculate the key sizes.
+ QuicByteCount payload_length = settings.values.size() * kSettingsMapKeyLength;
+ // Calculate the value sizes.
+ for (auto it = settings.values.begin(); it != settings.values.end(); ++it) {
+ payload_length += QuicDataWriter::GetVarInt62Len(it->second);
+ }
+
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (!WriteFrameHeader(payload_length, HttpFrameType::SETTINGS, &writer)) {
+ return 0;
+ }
+
+ for (auto it = settings.values.begin(); it != settings.values.end(); ++it) {
+ if (!writer.WriteUInt16(it->first) || !writer.WriteVarInt62(it->second)) {
+ return 0;
+ }
+ }
+
+ return total_length;
+}
+
+QuicByteCount HttpEncoder::SerializePushPromiseFrameWithOnlyPushId(
+ const PushPromiseFrame& push_promise,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(push_promise.push_id) +
+ push_promise.headers.length();
+ // GetTotalLength() is not used because headers will not be serialized.
+ QuicByteCount total_length =
+ QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength +
+ QuicDataWriter::GetVarInt62Len(push_promise.push_id);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::PUSH_PROMISE, &writer) &&
+ writer.WriteVarInt62(push_promise.push_id)) {
+ return total_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeGoAwayFrame(
+ const GoAwayFrame& goaway,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(goaway.stream_id);
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::GOAWAY, &writer) &&
+ writer.WriteVarInt62(goaway.stream_id)) {
+ return total_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeMaxPushIdFrame(
+ const MaxPushIdFrame& max_push_id,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(max_push_id.push_id);
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::MAX_PUSH_ID, &writer) &&
+ writer.WriteVarInt62(max_push_id.push_id)) {
+ return total_length;
+ }
+ return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeDuplicatePushFrame(
+ const DuplicatePushFrame& duplicate_push,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(duplicate_push.push_id);
+ QuicByteCount total_length = GetTotalLength(payload_length);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::DUPLICATE_PUSH,
+ &writer) &&
+ writer.WriteVarInt62(duplicate_push.push_id)) {
+ return total_length;
+ }
+ return 0;
+}
+
+bool HttpEncoder::WriteFrameHeader(QuicByteCount length,
+ HttpFrameType type,
+ QuicDataWriter* writer) {
+ return writer->WriteVarInt62(length) &&
+ writer->WriteUInt8(static_cast<uint8_t>(type));
+}
+
+QuicByteCount HttpEncoder::GetTotalLength(QuicByteCount payload_length) {
+ return QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength +
+ payload_length;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h
new file mode 100644
index 00000000000..f04e6e40700
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_HTTP_ENCODER_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_ENCODER_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/http/http_frames.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QuicDataWriter;
+
+// A class for encoding the HTTP frames that are exchanged in an HTTP over QUIC
+// session.
+class QUIC_EXPORT_PRIVATE HttpEncoder {
+ public:
+ HttpEncoder();
+
+ ~HttpEncoder();
+
+ // Serializes a DATA frame header into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeDataFrameHeader(QuicByteCount payload_length,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a HEADERS frame header into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeHeadersFrameHeader(QuicByteCount payload_length,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a PRIORITY frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializePriorityFrame(const PriorityFrame& priority,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a CANCEL_PUSH frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeCancelPushFrame(const CancelPushFrame& cancel_push,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a SETTINGS frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeSettingsFrame(const SettingsFrame& settings,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes the header and push_id of a PUSH_PROMISE frame into a new buffer
+ // stored in |output|. Returns the length of the buffer on success, or 0
+ // otherwise.
+ QuicByteCount SerializePushPromiseFrameWithOnlyPushId(
+ const PushPromiseFrame& push_promise,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a GOAWAY frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeGoAwayFrame(const GoAwayFrame& goaway,
+ std::unique_ptr<char[]>* output);
+
+ // Serializes a MAX_PUSH frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeMaxPushIdFrame(const MaxPushIdFrame& max_push_id,
+ std::unique_ptr<char[]>* output);
+
+ // Serialize a DUPLICATE_PUSH frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ QuicByteCount SerializeDuplicatePushFrame(
+ const DuplicatePushFrame& duplicate_push,
+ std::unique_ptr<char[]>* output);
+
+ private:
+ bool WriteFrameHeader(QuicByteCount length,
+ HttpFrameType type,
+ QuicDataWriter* writer);
+
+ QuicByteCount GetTotalLength(QuicByteCount payload_length);
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_HTTP_ENCODER_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc
new file mode 100644
index 00000000000..154d7688e8d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2018 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 <string>
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+class HttpEncoderTest : public QuicTest {
+ public:
+ HttpEncoderTest() {}
+ HttpEncoder encoder_;
+};
+
+TEST_F(HttpEncoderTest, SerializeDataFrameHeader) {
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ encoder_.SerializeDataFrameHeader(/* payload_length = */ 5, &buffer);
+ char output[] = {// length
+ 0x05,
+ // type (DATA)
+ 0x00};
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("DATA", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeHeadersFrameHeader) {
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ encoder_.SerializeHeadersFrameHeader(/* payload_length = */ 7, &buffer);
+ char output[] = {// length
+ 0x07,
+ // type (HEADERS)
+ 0x01};
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("HEADERS", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializePriorityFrame) {
+ PriorityFrame priority;
+ priority.prioritized_type = REQUEST_STREAM;
+ priority.dependency_type = REQUEST_STREAM;
+ priority.exclusive = true;
+ priority.prioritized_element_id = 0x03;
+ priority.element_dependency_id = 0x04;
+ priority.weight = 0xFF;
+ char output[] = {// length
+ 0x4,
+ // type (PRIORITY)
+ 0x2,
+ // request stream, request stream, exclusive
+ 0x01,
+ // prioritized_element_id
+ 0x03,
+ // element_dependency_id
+ 0x04,
+ // weight
+ 0xFF};
+
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = encoder_.SerializePriorityFrame(priority, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("PRIORITY", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeCancelPushFrame) {
+ CancelPushFrame cancel_push;
+ cancel_push.push_id = 0x01;
+ char output[] = {// length
+ 0x1,
+ // type (CANCEL_PUSH)
+ 0x03,
+ // Push Id
+ 0x01};
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = encoder_.SerializeCancelPushFrame(cancel_push, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("CANCEL_PUSH", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeSettingsFrame) {
+ SettingsFrame settings;
+ settings.values[3] = 2;
+ settings.values[6] = 5;
+ char output[] = {
+ // length
+ 0x06,
+ // type (SETTINGS)
+ 0x04,
+ // identifier (SETTINGS_NUM_PLACEHOLDERS)
+ 0x00,
+ 0x03,
+ // content
+ 0x02,
+ // identifier (SETTINGS_MAX_HEADER_LIST_SIZE)
+ 0x00,
+ 0x06,
+ // content
+ 0x05,
+ };
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = encoder_.SerializeSettingsFrame(settings, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("SETTINGS", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializePushPromiseFrameWithOnlyPushId) {
+ PushPromiseFrame push_promise;
+ push_promise.push_id = 0x01;
+ push_promise.headers = "Headers";
+ char output[] = {// length
+ 0x8,
+ // type (PUSH_PROMISE)
+ 0x05,
+ // Push Id
+ 0x01};
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ encoder_.SerializePushPromiseFrameWithOnlyPushId(push_promise, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("PUSH_PROMISE", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeGoAwayFrame) {
+ GoAwayFrame goaway;
+ goaway.stream_id = 0x1;
+ char output[] = {// length
+ 0x1,
+ // type (GOAWAY)
+ 0x07,
+ // StreamId
+ 0x01};
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = encoder_.SerializeGoAwayFrame(goaway, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("GOAWAY", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeMaxPushIdFrame) {
+ MaxPushIdFrame max_push_id;
+ max_push_id.push_id = 0x1;
+ char output[] = {// length
+ 0x1,
+ // type (MAX_PUSH_ID)
+ 0x0D,
+ // Push Id
+ 0x01};
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = encoder_.SerializeMaxPushIdFrame(max_push_id, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("MAX_PUSH_ID", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeDuplicatePushFrame) {
+ DuplicatePushFrame duplicate_push;
+ duplicate_push.push_id = 0x1;
+ char output[] = {// length
+ 0x1,
+ // type (DUPLICATE_PUSH)
+ 0x0E,
+ // Push Id
+ 0x01};
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ encoder_.SerializeDuplicatePushFrame(duplicate_push, &buffer);
+ EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+ CompareCharArraysWithHexError("DUPLICATE_PUSH", buffer.get(), length, output,
+ QUIC_ARRAYSIZE(output));
+}
+
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h b/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h
new file mode 100644
index 00000000000..ea115571fc2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
+
+#include <map>
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+enum class HttpFrameType : uint8_t {
+ DATA = 0x0,
+ HEADERS = 0x1,
+ PRIORITY = 0X2,
+ CANCEL_PUSH = 0X3,
+ SETTINGS = 0x4,
+ PUSH_PROMISE = 0x5,
+ GOAWAY = 0x7,
+ MAX_PUSH_ID = 0xD,
+ DUPLICATE_PUSH = 0xE
+};
+
+// 4.2.1. DATA
+//
+// DATA frames (type=0x0) convey arbitrary, variable-length sequences of
+// octets associated with an HTTP request or response payload.
+struct DataFrame {
+ QuicStringPiece data;
+};
+
+// 4.2.2. HEADERS
+//
+// The HEADERS frame (type=0x1) is used to carry a header block,
+// compressed using QPACK.
+struct HeadersFrame {
+ QuicStringPiece headers;
+};
+
+// 4.2.3. PRIORITY
+//
+// The PRIORITY (type=0x02) frame specifies the sender-advised priority
+// of a stream
+enum PriorityElementType {
+ REQUEST_STREAM = 0,
+ PUSH_STREAM = 1,
+ PLACEHOLDER = 2,
+ ROOT_OF_TREE = 3
+};
+
+struct PriorityFrame {
+ PriorityElementType prioritized_type;
+ PriorityElementType dependency_type;
+ bool exclusive;
+ uint64_t prioritized_element_id;
+ uint64_t element_dependency_id;
+ uint8_t weight;
+
+ bool operator==(const PriorityFrame& rhs) const {
+ return prioritized_type == rhs.prioritized_type &&
+ dependency_type == rhs.dependency_type &&
+ exclusive == rhs.exclusive &&
+ prioritized_element_id == rhs.prioritized_element_id &&
+ element_dependency_id == rhs.element_dependency_id &&
+ weight == rhs.weight;
+ }
+};
+
+// 4.2.4. CANCEL_PUSH
+//
+// The CANCEL_PUSH frame (type=0x3) is used to request cancellation of
+// server push prior to the push stream being created.
+using PushId = uint64_t;
+
+struct CancelPushFrame {
+ PushId push_id;
+
+ bool operator==(const CancelPushFrame& rhs) const {
+ return push_id == rhs.push_id;
+ }
+};
+
+// 4.2.5. SETTINGS
+//
+// The SETTINGS frame (type=0x4) conveys configuration parameters that
+// affect how endpoints communicate, such as preferences and constraints
+// on peer behavior
+
+using SettingsId = uint16_t;
+using SettingsMap = std::map<SettingsId, uint64_t>;
+
+struct SettingsFrame {
+ SettingsMap values;
+
+ bool operator==(const SettingsFrame& rhs) const {
+ return values == rhs.values;
+ }
+};
+
+// 4.2.6. PUSH_PROMISE
+//
+// The PUSH_PROMISE frame (type=0x05) is used to carry a request header
+// set from server to client, as in HTTP/2.
+struct PushPromiseFrame {
+ PushId push_id;
+ QuicStringPiece headers;
+
+ bool operator==(const PushPromiseFrame& rhs) const {
+ return push_id == rhs.push_id && headers == rhs.headers;
+ }
+};
+
+// 4.2.7. GOAWAY
+//
+// The GOAWAY frame (type=0x7) is used to initiate graceful shutdown of
+// a connection by a server.
+struct GoAwayFrame {
+ QuicStreamId stream_id;
+
+ bool operator==(const GoAwayFrame& rhs) const {
+ return stream_id == rhs.stream_id;
+ }
+};
+
+// 4.2.8. MAX_PUSH_ID
+//
+// The MAX_PUSH_ID frame (type=0xD) is used by clients to control the
+// number of server pushes that the server can initiate.
+struct MaxPushIdFrame {
+ PushId push_id;
+
+ bool operator==(const MaxPushIdFrame& rhs) const {
+ return push_id == rhs.push_id;
+ }
+};
+
+// 4.2.9. DUPLICATE_PUSH
+//
+// The DUPLICATE_PUSH frame (type=0xE) is used by servers to indicate
+// that an existing pushed resource is related to multiple client
+// requests.
+struct DuplicatePushFrame {
+ PushId push_id;
+
+ bool operator==(const DuplicatePushFrame& rhs) const {
+ return push_id == rhs.push_id;
+ }
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc
new file mode 100644
index 00000000000..8b549692e4c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 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/quic/core/http/quic_client_promised_info.h"
+
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.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/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicClientPromisedInfo::QuicClientPromisedInfo(
+ QuicSpdyClientSessionBase* session,
+ QuicStreamId id,
+ std::string url)
+ : session_(session),
+ id_(id),
+ url_(std::move(url)),
+ client_request_delegate_(nullptr) {}
+
+QuicClientPromisedInfo::~QuicClientPromisedInfo() {}
+
+void QuicClientPromisedInfo::CleanupAlarm::OnAlarm() {
+ QUIC_DVLOG(1) << "self GC alarm for stream " << promised_->id_;
+ promised_->session()->OnPushStreamTimedOut(promised_->id_);
+ promised_->Reset(QUIC_PUSH_STREAM_TIMED_OUT);
+}
+
+void QuicClientPromisedInfo::Init() {
+ cleanup_alarm_.reset(session_->connection()->alarm_factory()->CreateAlarm(
+ new QuicClientPromisedInfo::CleanupAlarm(this)));
+ cleanup_alarm_->Set(
+ session_->connection()->helper()->GetClock()->ApproximateNow() +
+ QuicTime::Delta::FromSeconds(kPushPromiseTimeoutSecs));
+}
+
+bool QuicClientPromisedInfo::OnPromiseHeaders(const SpdyHeaderBlock& headers) {
+ // RFC7540, Section 8.2, requests MUST be safe [RFC7231], Section
+ // 4.2.1. GET and HEAD are the methods that are safe and required.
+ SpdyHeaderBlock::const_iterator it = headers.find(spdy::kHttp2MethodHeader);
+ if (it == headers.end()) {
+ QUIC_DVLOG(1) << "Promise for stream " << id_ << " has no method";
+ Reset(QUIC_INVALID_PROMISE_METHOD);
+ return false;
+ }
+ if (!(it->second == "GET" || it->second == "HEAD")) {
+ QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid method "
+ << it->second;
+ Reset(QUIC_INVALID_PROMISE_METHOD);
+ return false;
+ }
+ if (!SpdyUtils::PromisedUrlIsValid(headers)) {
+ QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL "
+ << url_;
+ Reset(QUIC_INVALID_PROMISE_URL);
+ return false;
+ }
+ if (!session_->IsAuthorized(
+ SpdyUtils::GetPromisedHostNameFromHeaders(headers))) {
+ Reset(QUIC_UNAUTHORIZED_PROMISE_URL);
+ return false;
+ }
+ request_headers_ = headers.Clone();
+ return true;
+}
+
+void QuicClientPromisedInfo::OnResponseHeaders(const SpdyHeaderBlock& headers) {
+ response_headers_ = QuicMakeUnique<SpdyHeaderBlock>(headers.Clone());
+ if (client_request_delegate_) {
+ // We already have a client request waiting.
+ FinalValidation();
+ }
+}
+
+void QuicClientPromisedInfo::Reset(QuicRstStreamErrorCode error_code) {
+ QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
+ session_->ResetPromised(id_, error_code);
+ session_->DeletePromised(this);
+ if (delegate) {
+ delegate->OnRendezvousResult(nullptr);
+ }
+}
+
+QuicAsyncStatus QuicClientPromisedInfo::FinalValidation() {
+ if (!client_request_delegate_->CheckVary(
+ client_request_headers_, request_headers_, *response_headers_)) {
+ Reset(QUIC_PROMISE_VARY_MISMATCH);
+ return QUIC_FAILURE;
+ }
+ QuicSpdyStream* stream = session_->GetPromisedStream(id_);
+ if (!stream) {
+ // This shouldn't be possible, as |ClientRequest| guards against
+ // closed stream for the synchronous case. And in the
+ // asynchronous case, a RST can only be caught by |OnAlarm()|.
+ QUIC_BUG << "missing promised stream" << id_;
+ }
+ QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
+ session_->DeletePromised(this);
+ // Stream can start draining now
+ if (delegate) {
+ delegate->OnRendezvousResult(stream);
+ }
+ return QUIC_SUCCESS;
+}
+
+QuicAsyncStatus QuicClientPromisedInfo::HandleClientRequest(
+ const SpdyHeaderBlock& request_headers,
+ QuicClientPushPromiseIndex::Delegate* delegate) {
+ if (session_->IsClosedStream(id_)) {
+ // There was a RST on the response stream.
+ session_->DeletePromised(this);
+ return QUIC_FAILURE;
+ }
+
+ if (is_validating()) {
+ // The push promise has already been matched to another request though
+ // pending for validation. Returns QUIC_FAILURE to the caller as it couldn't
+ // match a new request any more. This will not affect the validation of the
+ // other request.
+ return QUIC_FAILURE;
+ }
+
+ client_request_delegate_ = delegate;
+ client_request_headers_ = request_headers.Clone();
+ if (response_headers_ == nullptr) {
+ return QUIC_PENDING;
+ }
+ return FinalValidation();
+}
+
+void QuicClientPromisedInfo::Cancel() {
+ // Don't fire OnRendezvousResult() for client initiated cancel.
+ client_request_delegate_ = nullptr;
+ Reset(QUIC_STREAM_CANCELLED);
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h
new file mode 100644
index 00000000000..bf614051fc1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h
@@ -0,0 +1,114 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
+
+#include <cstddef>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+namespace test {
+class QuicClientPromisedInfoPeer;
+} // namespace test
+
+// QuicClientPromisedInfo tracks the client state of a server push
+// stream from the time a PUSH_PROMISE is received until rendezvous
+// between the promised response and the corresponding client request
+// is complete.
+class QUIC_EXPORT_PRIVATE QuicClientPromisedInfo
+ : public QuicClientPushPromiseIndex::TryHandle {
+ public:
+ // Interface to QuicSpdyClientStream
+ QuicClientPromisedInfo(QuicSpdyClientSessionBase* session,
+ QuicStreamId id,
+ std::string url);
+ QuicClientPromisedInfo(const QuicClientPromisedInfo&) = delete;
+ QuicClientPromisedInfo& operator=(const QuicClientPromisedInfo&) = delete;
+ virtual ~QuicClientPromisedInfo();
+
+ void Init();
+
+ // Validate promise headers etc. Returns true if headers are valid.
+ bool OnPromiseHeaders(const spdy::SpdyHeaderBlock& headers);
+
+ // Store response, possibly proceed with final validation.
+ void OnResponseHeaders(const spdy::SpdyHeaderBlock& headers);
+
+ // Rendezvous between this promised stream and a client request that
+ // has a matching URL.
+ virtual QuicAsyncStatus HandleClientRequest(
+ const spdy::SpdyHeaderBlock& headers,
+ QuicClientPushPromiseIndex::Delegate* delegate);
+
+ void Cancel() override;
+
+ void Reset(QuicRstStreamErrorCode error_code);
+
+ // Client requests are initially associated to promises by matching
+ // URL in the client request against the URL in the promise headers,
+ // uing the |promised_by_url| map. The push can be cross-origin, so
+ // the client should validate that the session is authoritative for
+ // the promised URL. If not, it should call |RejectUnauthorized|.
+ QuicSpdyClientSessionBase* session() { return session_; }
+
+ // If the promised response contains Vary header, then the fields
+ // specified by Vary must match between the client request header
+ // and the promise headers (see https://crbug.com//554220). Vary
+ // validation requires the response headers (for the actual Vary
+ // field list), the promise headers (taking the role of the "cached"
+ // request), and the client request headers.
+ spdy::SpdyHeaderBlock* request_headers() { return &request_headers_; }
+
+ spdy::SpdyHeaderBlock* response_headers() { return response_headers_.get(); }
+
+ // After validation, client will use this to access the pushed stream.
+
+ QuicStreamId id() const { return id_; }
+
+ const std::string url() const { return url_; }
+
+ // Return true if there's a request pending matching this push promise.
+ bool is_validating() const { return client_request_delegate_ != nullptr; }
+
+ private:
+ friend class test::QuicClientPromisedInfoPeer;
+
+ class CleanupAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit CleanupAlarm(QuicClientPromisedInfo* promised)
+ : promised_(promised) {}
+
+ void OnAlarm() override;
+
+ QuicClientPromisedInfo* promised_;
+ };
+
+ QuicAsyncStatus FinalValidation();
+
+ QuicSpdyClientSessionBase* session_;
+ QuicStreamId id_;
+ std::string url_;
+ spdy::SpdyHeaderBlock request_headers_;
+ std::unique_ptr<spdy::SpdyHeaderBlock> response_headers_;
+ spdy::SpdyHeaderBlock client_request_headers_;
+ QuicClientPushPromiseIndex::Delegate* client_request_delegate_;
+
+ // The promise will commit suicide eventually if it is not claimed by a GET
+ // first.
+ std::unique_ptr<QuicAlarm> cleanup_alarm_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info_test.cc
new file mode 100644
index 00000000000..104c049e87e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info_test.cc
@@ -0,0 +1,354 @@
+// 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/quic/core/http/quic_client_promised_info.h"
+
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.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_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_client_promised_info_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_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+ explicit MockQuicSpdyClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ QuicClientPushPromiseIndex* push_promise_index)
+ : QuicSpdyClientSession(DefaultQuicConfig(),
+ supported_versions,
+ connection,
+ QuicServerId("example.com", 443, false),
+ &crypto_config_,
+ push_promise_index),
+ crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+ TlsClientHandshaker::CreateSslCtx()),
+ authorized_(true) {}
+ MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+ MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+ delete;
+ ~MockQuicSpdyClientSession() override {}
+
+ bool IsAuthorized(const std::string& authority) override {
+ return authorized_;
+ }
+
+ void set_authorized(bool authorized) { authorized_ = authorized; }
+
+ MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+ QuicCryptoClientConfig crypto_config_;
+
+ bool authorized_;
+};
+
+class QuicClientPromisedInfoTest : public QuicTest {
+ public:
+ class StreamVisitor;
+
+ QuicClientPromisedInfoTest()
+ : connection_(new StrictMock<MockQuicConnection>(&helper_,
+ &alarm_factory_,
+ Perspective::IS_CLIENT)),
+ session_(connection_->supported_versions(),
+ connection_,
+ &push_promise_index_),
+ body_("hello world"),
+ promise_id_(
+ QuicUtils::GetInvalidStreamId(connection_->transport_version())) {
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ session_.Initialize();
+
+ headers_[":status"] = "200";
+ headers_["content-length"] = "11";
+
+ stream_ = QuicMakeUnique<QuicSpdyClientStream>(
+ GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0),
+ &session_, BIDIRECTIONAL);
+ stream_visitor_ = QuicMakeUnique<StreamVisitor>();
+ stream_->set_visitor(stream_visitor_.get());
+
+ push_promise_[":path"] = "/bar";
+ push_promise_[":authority"] = "www.google.com";
+ push_promise_[":version"] = "HTTP/1.1";
+ push_promise_[":method"] = "GET";
+ push_promise_[":scheme"] = "https";
+
+ promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
+
+ client_request_ = push_promise_.Clone();
+ promise_id_ = GetNthServerInitiatedUnidirectionalStreamId(
+ connection_->transport_version(), 0);
+ }
+
+ class StreamVisitor : public QuicSpdyClientStream::Visitor {
+ void OnClose(QuicSpdyStream* stream) override {
+ QUIC_DVLOG(1) << "stream " << stream->id();
+ }
+ };
+
+ void ReceivePromise(QuicStreamId id) {
+ auto headers = AsHeaderList(push_promise_);
+ stream_->OnPromiseHeaderList(id, headers.uncompressed_header_bytes(),
+ headers);
+ }
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ QuicClientPushPromiseIndex push_promise_index_;
+
+ MockQuicSpdyClientSession session_;
+ std::unique_ptr<QuicSpdyClientStream> stream_;
+ std::unique_ptr<StreamVisitor> stream_visitor_;
+ std::unique_ptr<QuicSpdyClientStream> promised_stream_;
+ SpdyHeaderBlock headers_;
+ std::string body_;
+ SpdyHeaderBlock push_promise_;
+ QuicStreamId promise_id_;
+ std::string promise_url_;
+ SpdyHeaderBlock client_request_;
+};
+
+TEST_F(QuicClientPromisedInfoTest, PushPromise) {
+ ReceivePromise(promise_id_);
+
+ // Verify that the promise is in the unclaimed streams map.
+ EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseCleanupAlarm) {
+ ReceivePromise(promise_id_);
+
+ // Verify that the promise is in the unclaimed streams map.
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_NE(promised, nullptr);
+
+ // Fire the alarm that will cancel the promised stream.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_PUSH_STREAM_TIMED_OUT));
+ alarm_factory_.FireAlarm(QuicClientPromisedInfoPeer::GetAlarm(promised));
+
+ // Verify that the promise is gone after the alarm fires.
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+ EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidMethod) {
+ // Promise with an unsafe method
+ push_promise_[":method"] = "PUT";
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
+ ReceivePromise(promise_id_);
+
+ // Verify that the promise headers were ignored
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+ EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseMissingMethod) {
+ // Promise with a missing method
+ push_promise_.erase(":method");
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
+ ReceivePromise(promise_id_);
+
+ // Verify that the promise headers were ignored
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+ EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidUrl) {
+ // Remove required header field to make URL invalid
+ push_promise_.erase(":authority");
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_URL));
+ ReceivePromise(promise_id_);
+
+ // Verify that the promise headers were ignored
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+ EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseUnauthorizedUrl) {
+ session_.set_authorized(false);
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_UNAUTHORIZED_PROMISE_URL));
+
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_EQ(promised, nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseMismatch) {
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_NE(promised, nullptr);
+
+ // Need to send the promised response headers and initiate the
+ // rendezvous for secondary validation to proceed.
+ QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+ session_.GetOrCreateStream(promise_id_));
+ auto headers = AsHeaderList(headers_);
+ promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+
+ TestPushPromiseDelegate delegate(/*match=*/false);
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_PROMISE_VARY_MISMATCH));
+ EXPECT_CALL(session_, CloseStream(promise_id_));
+
+ promised->HandleClientRequest(client_request_, &delegate);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryWaits) {
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ EXPECT_FALSE(promised->is_validating());
+ ASSERT_NE(promised, nullptr);
+
+ // Now initiate rendezvous.
+ TestPushPromiseDelegate delegate(/*match=*/true);
+ promised->HandleClientRequest(client_request_, &delegate);
+ EXPECT_TRUE(promised->is_validating());
+
+ // Promise is still there, waiting for response.
+ EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+
+ // Send Response, should trigger promise validation and complete rendezvous
+ QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+ session_.GetOrCreateStream(promise_id_));
+ ASSERT_NE(promise_stream, nullptr);
+ auto headers = AsHeaderList(headers_);
+ promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+
+ // Promise is gone
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryNoWait) {
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_NE(promised, nullptr);
+
+ QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+ session_.GetOrCreateStream(promise_id_));
+ ASSERT_NE(promise_stream, nullptr);
+
+ // Send Response, should trigger promise validation and complete rendezvous
+ auto headers = AsHeaderList(headers_);
+ promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+
+ // Now initiate rendezvous.
+ TestPushPromiseDelegate delegate(/*match=*/true);
+ promised->HandleClientRequest(client_request_, &delegate);
+
+ // Promise is gone
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+ // Have a push stream
+ EXPECT_TRUE(delegate.rendezvous_fired());
+
+ EXPECT_NE(delegate.rendezvous_stream(), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseWaitCancels) {
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_NE(promised, nullptr);
+
+ // Now initiate rendezvous.
+ TestPushPromiseDelegate delegate(/*match=*/true);
+ promised->HandleClientRequest(client_request_, &delegate);
+
+ // Promise is still there, waiting for response.
+ EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+
+ // Create response stream, but no data yet.
+ session_.GetOrCreateStream(promise_id_);
+
+ // Cancel the promised stream.
+ EXPECT_CALL(session_, CloseStream(promise_id_));
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(promise_id_, QUIC_STREAM_CANCELLED));
+ promised->Cancel();
+
+ // Promise is gone
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseDataClosed) {
+ ReceivePromise(promise_id_);
+
+ QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+ ASSERT_NE(promised, nullptr);
+
+ QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+ session_.GetOrCreateStream(promise_id_));
+ ASSERT_NE(promise_stream, nullptr);
+
+ // Send response, rendezvous will be able to finish synchronously.
+ auto headers = AsHeaderList(headers_);
+ promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+
+ EXPECT_CALL(session_, CloseStream(promise_id_));
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promise_id_, QUIC_STREAM_PEER_GOING_AWAY));
+ session_.SendRstStream(promise_id_, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+ // Now initiate rendezvous.
+ TestPushPromiseDelegate delegate(/*match=*/true);
+ EXPECT_EQ(promised->HandleClientRequest(client_request_, &delegate),
+ QUIC_FAILURE);
+
+ // Got an indication of the stream failure, client should retry
+ // request.
+ EXPECT_FALSE(delegate.rendezvous_fired());
+ EXPECT_EQ(delegate.rendezvous_stream(), nullptr);
+
+ // Promise is gone
+ EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.cc
new file mode 100644
index 00000000000..0f11af40ba3
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.cc
@@ -0,0 +1,48 @@
+// 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/quic/core/http/quic_client_push_promise_index.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicClientPushPromiseIndex::QuicClientPushPromiseIndex() {}
+
+QuicClientPushPromiseIndex::~QuicClientPushPromiseIndex() {}
+
+QuicClientPushPromiseIndex::TryHandle::~TryHandle() {}
+
+QuicClientPromisedInfo* QuicClientPushPromiseIndex::GetPromised(
+ const std::string& url) {
+ auto it = promised_by_url_.find(url);
+ if (it == promised_by_url_.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+QuicAsyncStatus QuicClientPushPromiseIndex::Try(
+ const spdy::SpdyHeaderBlock& request,
+ QuicClientPushPromiseIndex::Delegate* delegate,
+ TryHandle** handle) {
+ std::string url(SpdyUtils::GetPromisedUrlFromHeaders(request));
+ auto it = promised_by_url_.find(url);
+ if (it != promised_by_url_.end()) {
+ QuicClientPromisedInfo* promised = it->second;
+ QuicAsyncStatus rv = promised->HandleClientRequest(request, delegate);
+ if (rv == QUIC_PENDING) {
+ *handle = promised;
+ }
+ return rv;
+ }
+ return QUIC_FAILURE;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h
new file mode 100644
index 00000000000..10e83c81eae
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h
@@ -0,0 +1,99 @@
+// 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// QuicClientPushPromiseIndex is the interface to support rendezvous
+// between client requests and resources delivered via server push.
+// The same index can be shared across multiple sessions (e.g. for the
+// same browser users profile), since cross-origin pushes are allowed
+// (subject to authority constraints).
+
+class QUIC_EXPORT_PRIVATE QuicClientPushPromiseIndex {
+ public:
+ // Delegate is used to complete the rendezvous that began with
+ // |Try()|.
+ class QUIC_EXPORT_PRIVATE Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // The primary lookup matched request with push promise by URL. A
+ // secondary match is necessary to ensure Vary (RFC 2616, 14.14)
+ // is honored. If Vary is not present, return true. If Vary is
+ // present, return whether designated header fields of
+ // |promise_request| and |client_request| match.
+ virtual bool CheckVary(const spdy::SpdyHeaderBlock& client_request,
+ const spdy::SpdyHeaderBlock& promise_request,
+ const spdy::SpdyHeaderBlock& promise_response) = 0;
+
+ // On rendezvous success, provides the promised |stream|. Callee
+ // does not inherit ownership of |stream|. On rendezvous failure,
+ // |stream| is |nullptr| and the client should retry the request.
+ // Rendezvous can fail due to promise validation failure or RST on
+ // promised stream. |url| will have been removed from the index
+ // before |OnRendezvousResult()| is invoked, so a recursive call to
+ // |Try()| will return |QUIC_FAILURE|, which may be convenient for
+ // retry purposes.
+ virtual void OnRendezvousResult(QuicSpdyStream* stream) = 0;
+ };
+
+ class QUIC_EXPORT_PRIVATE TryHandle {
+ public:
+ // Cancel the request.
+ virtual void Cancel() = 0;
+
+ protected:
+ TryHandle() {}
+ TryHandle(const TryHandle&) = delete;
+ TryHandle& operator=(const TryHandle&) = delete;
+ ~TryHandle();
+ };
+
+ QuicClientPushPromiseIndex();
+ QuicClientPushPromiseIndex(const QuicClientPushPromiseIndex&) = delete;
+ QuicClientPushPromiseIndex& operator=(const QuicClientPushPromiseIndex&) =
+ delete;
+ virtual ~QuicClientPushPromiseIndex();
+
+ // Called by client code, used to enforce affinity between requests
+ // for promised streams and the session the promise came from.
+ QuicClientPromisedInfo* GetPromised(const std::string& url);
+
+ // Called by client code, to initiate rendezvous between a request
+ // and a server push stream. If |request|'s url is in the index,
+ // rendezvous will be attempted and may complete immediately or
+ // asynchronously. If the matching promise and response headers
+ // have already arrived, the delegate's methods will fire
+ // recursively from within |Try()|. Returns |QUIC_SUCCESS| if the
+ // rendezvous was a success. Returns |QUIC_FAILURE| if there was no
+ // matching promise, or if there was but the rendezvous has failed.
+ // Returns QUIC_PENDING if a matching promise was found, but the
+ // rendezvous needs to complete asynchronously because the promised
+ // response headers are not yet available. If result is
+ // QUIC_PENDING, then |*handle| will set so that the caller may
+ // cancel the request if need be. The caller does not inherit
+ // ownership of |*handle|, and it ceases to be valid if the caller
+ // invokes |handle->Cancel()| or if |delegate->OnReponse()| fires.
+ QuicAsyncStatus Try(const spdy::SpdyHeaderBlock& request,
+ Delegate* delegate,
+ TryHandle** handle);
+
+ QuicPromisedByUrlMap* promised_by_url() { return &promised_by_url_; }
+
+ private:
+ QuicPromisedByUrlMap promised_by_url_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index_test.cc
new file mode 100644
index 00000000000..c12a3285af8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index_test.cc
@@ -0,0 +1,118 @@
+// 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/quic/core/http/quic_client_push_promise_index.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_client_promised_info.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_test_utils.h"
+
+using testing::_;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+ explicit MockQuicSpdyClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ QuicClientPushPromiseIndex* push_promise_index)
+ : QuicSpdyClientSession(DefaultQuicConfig(),
+ supported_versions,
+ connection,
+ QuicServerId("example.com", 443, false),
+ &crypto_config_,
+ push_promise_index),
+ crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+ TlsClientHandshaker::CreateSslCtx()) {}
+ MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+ MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+ delete;
+ ~MockQuicSpdyClientSession() override {}
+
+ MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+ QuicCryptoClientConfig crypto_config_;
+};
+
+class QuicClientPushPromiseIndexTest : public QuicTest {
+ public:
+ QuicClientPushPromiseIndexTest()
+ : connection_(new StrictMock<MockQuicConnection>(&helper_,
+ &alarm_factory_,
+ Perspective::IS_CLIENT)),
+ session_(connection_->supported_versions(), connection_, &index_),
+ promised_(&session_,
+ GetNthServerInitiatedUnidirectionalStreamId(
+ connection_->transport_version(),
+ 0),
+ url_) {
+ request_[":path"] = "/bar";
+ request_[":authority"] = "www.google.com";
+ request_[":version"] = "HTTP/1.1";
+ request_[":method"] = "GET";
+ request_[":scheme"] = "https";
+ url_ = SpdyUtils::GetPromisedUrlFromHeaders(request_);
+ }
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ MockQuicSpdyClientSession session_;
+ QuicClientPushPromiseIndex index_;
+ spdy::SpdyHeaderBlock request_;
+ std::string url_;
+ MockQuicClientPromisedInfo promised_;
+ QuicClientPushPromiseIndex::TryHandle* handle_;
+};
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestSuccess) {
+ (*index_.promised_by_url())[url_] = &promised_;
+ EXPECT_CALL(promised_, HandleClientRequest(_, _))
+ .WillOnce(Return(QUIC_SUCCESS));
+ EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_SUCCESS);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestPending) {
+ (*index_.promised_by_url())[url_] = &promised_;
+ EXPECT_CALL(promised_, HandleClientRequest(_, _))
+ .WillOnce(Return(QUIC_PENDING));
+ EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_PENDING);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestFailure) {
+ (*index_.promised_by_url())[url_] = &promised_;
+ EXPECT_CALL(promised_, HandleClientRequest(_, _))
+ .WillOnce(Return(QUIC_FAILURE));
+ EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_FAILURE);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryNoPromise) {
+ EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_FAILURE);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, GetNoPromise) {
+ EXPECT_EQ(index_.GetPromised(url_), nullptr);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, GetPromise) {
+ (*index_.promised_by_url())[url_] = &promised_;
+ EXPECT_EQ(index_.GetPromised(url_), &promised_);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.cc
new file mode 100644
index 00000000000..f9b730bcb4c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 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/quic/core/http/quic_header_list.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace quic {
+
+QuicHeaderList::QuicHeaderList()
+ : max_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+ current_header_list_size_(0),
+ uncompressed_header_bytes_(0),
+ compressed_header_bytes_(0) {}
+
+QuicHeaderList::QuicHeaderList(QuicHeaderList&& other) = default;
+
+QuicHeaderList::QuicHeaderList(const QuicHeaderList& other) = default;
+
+QuicHeaderList& QuicHeaderList::operator=(const QuicHeaderList& other) =
+ default;
+
+QuicHeaderList& QuicHeaderList::operator=(QuicHeaderList&& other) = default;
+
+QuicHeaderList::~QuicHeaderList() {}
+
+void QuicHeaderList::OnHeaderBlockStart() {
+ QUIC_BUG_IF(current_header_list_size_ != 0)
+ << "OnHeaderBlockStart called more than once!";
+}
+
+void QuicHeaderList::OnHeader(QuicStringPiece name, QuicStringPiece value) {
+ // Avoid infinite buffering of headers. No longer store headers
+ // once the current headers are over the limit.
+ if (current_header_list_size_ < max_header_list_size_) {
+ current_header_list_size_ += name.size();
+ current_header_list_size_ += value.size();
+ current_header_list_size_ += spdy::kPerHeaderOverhead;
+ header_list_.emplace_back(std::string(name), std::string(value));
+ }
+}
+
+void QuicHeaderList::OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+ size_t compressed_header_bytes) {
+ uncompressed_header_bytes_ = uncompressed_header_bytes;
+ compressed_header_bytes_ = compressed_header_bytes;
+ if (current_header_list_size_ > max_header_list_size_) {
+ Clear();
+ }
+}
+
+void QuicHeaderList::Clear() {
+ header_list_.clear();
+ current_header_list_size_ = 0;
+ uncompressed_header_bytes_ = 0;
+ compressed_header_bytes_ = 0;
+}
+
+std::string QuicHeaderList::DebugString() const {
+ std::string s = "{ ";
+ for (const auto& p : *this) {
+ s.append(p.first + "=" + p.second + ", ");
+ }
+ s.append("}");
+ return s;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.h
new file mode 100644
index 00000000000..cc6e7bc210d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
+
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+
+namespace quic {
+
+// A simple class that accumulates header pairs
+class QUIC_EXPORT_PRIVATE QuicHeaderList
+ : public spdy::SpdyHeadersHandlerInterface {
+ public:
+ using ListType = QuicDeque<std::pair<std::string, std::string>>;
+ using value_type = ListType::value_type;
+ using const_iterator = ListType::const_iterator;
+
+ QuicHeaderList();
+ QuicHeaderList(QuicHeaderList&& other);
+ QuicHeaderList(const QuicHeaderList& other);
+ QuicHeaderList& operator=(QuicHeaderList&& other);
+ QuicHeaderList& operator=(const QuicHeaderList& other);
+ ~QuicHeaderList() override;
+
+ // From SpdyHeadersHandlerInteface.
+ void OnHeaderBlockStart() override;
+ void OnHeader(QuicStringPiece name, QuicStringPiece value) override;
+ void OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+ size_t compressed_header_bytes) override;
+
+ void Clear();
+
+ const_iterator begin() const { return header_list_.begin(); }
+ const_iterator end() const { return header_list_.end(); }
+
+ bool empty() const { return header_list_.empty(); }
+ size_t uncompressed_header_bytes() const {
+ return uncompressed_header_bytes_;
+ }
+ size_t compressed_header_bytes() const { return compressed_header_bytes_; }
+
+ void set_max_header_list_size(size_t max_header_list_size) {
+ max_header_list_size_ = max_header_list_size;
+ }
+
+ size_t max_header_list_size() const { return max_header_list_size_; }
+
+ std::string DebugString() const;
+
+ private:
+ QuicDeque<std::pair<std::string, std::string>> header_list_;
+
+ // The limit on the size of the header list (defined by spec as name + value +
+ // overhead for each header field). Headers over this limit will not be
+ // buffered, and the list will be cleared upon OnHeaderBlockEnd.
+ size_t max_header_list_size_;
+
+ // Defined per the spec as the size of all header fields with an additional
+ // overhead for each field.
+ size_t current_header_list_size_;
+
+ // TODO(dahollings) Are these fields necessary?
+ size_t uncompressed_header_bytes_;
+ size_t compressed_header_bytes_;
+};
+
+inline bool operator==(const QuicHeaderList& l1, const QuicHeaderList& l2) {
+ auto pred = [](const std::pair<std::string, std::string>& p1,
+ const std::pair<std::string, std::string>& p2) {
+ return p1.first == p2.first && p1.second == p2.second;
+ };
+ return std::equal(l1.begin(), l1.end(), l2.begin(), pred);
+}
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list_test.cc
new file mode 100644
index 00000000000..67bd35f2644
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_header_list_test.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 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/quic/core/http/quic_header_list.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+using ::testing::ElementsAre;
+using ::testing::Pair;
+
+namespace quic {
+
+class QuicHeaderListTest : public QuicTest {};
+
+// This test verifies that QuicHeaderList accumulates header pairs in order.
+TEST_F(QuicHeaderListTest, OnHeader) {
+ QuicHeaderList headers;
+ headers.OnHeader("foo", "bar");
+ headers.OnHeader("april", "fools");
+ headers.OnHeader("beep", "");
+
+ EXPECT_THAT(headers, ElementsAre(Pair("foo", "bar"), Pair("april", "fools"),
+ Pair("beep", "")));
+}
+
+TEST_F(QuicHeaderListTest, DebugString) {
+ QuicHeaderList headers;
+ headers.OnHeader("foo", "bar");
+ headers.OnHeader("april", "fools");
+ headers.OnHeader("beep", "");
+
+ EXPECT_EQ("{ foo=bar, april=fools, beep=, }", headers.DebugString());
+}
+
+TEST_F(QuicHeaderListTest, TooLarge) {
+ QuicHeaderList headers;
+ std::string key = "key";
+ std::string value(1 << 18, '1');
+ // Send a header that exceeds max_header_list_size.
+ headers.OnHeader(key, value);
+ // Send a second header exceeding max_header_list_size.
+ headers.OnHeader(key + "2", value);
+ // We should not allocate more memory after exceeding max_header_list_size.
+ EXPECT_LT(headers.DebugString().size(), 2 * value.size());
+ size_t total_bytes = 2 * (key.size() + value.size()) + 1;
+ headers.OnHeaderBlockEnd(total_bytes, total_bytes);
+ EXPECT_TRUE(headers.empty());
+
+ EXPECT_EQ("{ }", headers.DebugString());
+}
+
+TEST_F(QuicHeaderListTest, NotTooLarge) {
+ QuicHeaderList headers;
+ headers.set_max_header_list_size(1 << 20);
+ std::string key = "key";
+ std::string value(1 << 18, '1');
+ headers.OnHeader(key, value);
+ size_t total_bytes = key.size() + value.size();
+ headers.OnHeaderBlockEnd(total_bytes, total_bytes);
+ EXPECT_FALSE(headers.empty());
+}
+
+// This test verifies that QuicHeaderList is copyable and assignable.
+TEST_F(QuicHeaderListTest, IsCopyableAndAssignable) {
+ QuicHeaderList headers;
+ headers.OnHeader("foo", "bar");
+ headers.OnHeader("april", "fools");
+ headers.OnHeader("beep", "");
+
+ QuicHeaderList headers2(headers);
+ QuicHeaderList headers3 = headers;
+
+ EXPECT_THAT(headers2, ElementsAre(Pair("foo", "bar"), Pair("april", "fools"),
+ Pair("beep", "")));
+ EXPECT_THAT(headers3, ElementsAre(Pair("foo", "bar"), Pair("april", "fools"),
+ Pair("beep", "")));
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.cc
new file mode 100644
index 00000000000..9c99ab7e41e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.cc
@@ -0,0 +1,157 @@
+// 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 "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+QuicHeadersStream::CompressedHeaderInfo::CompressedHeaderInfo(
+ QuicStreamOffset headers_stream_offset,
+ QuicStreamOffset full_length,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener)
+ : headers_stream_offset(headers_stream_offset),
+ full_length(full_length),
+ unacked_length(full_length),
+ ack_listener(std::move(ack_listener)) {}
+
+QuicHeadersStream::CompressedHeaderInfo::CompressedHeaderInfo(
+ const CompressedHeaderInfo& other) = default;
+
+QuicHeadersStream::CompressedHeaderInfo::~CompressedHeaderInfo() {}
+
+QuicHeadersStream::QuicHeadersStream(QuicSpdySession* session)
+ : QuicStream(QuicUtils::GetHeadersStreamId(
+ session->connection()->transport_version()),
+ session,
+ /*is_static=*/true,
+ BIDIRECTIONAL),
+ spdy_session_(session) {
+ // The headers stream is exempt from connection level flow control.
+ DisableConnectionFlowControlForThisStream();
+}
+
+QuicHeadersStream::~QuicHeadersStream() {}
+
+void QuicHeadersStream::OnDataAvailable() {
+ struct iovec iov;
+ while (sequencer()->GetReadableRegion(&iov)) {
+ if (spdy_session_->ProcessHeaderData(iov) != iov.iov_len) {
+ // Error processing data.
+ return;
+ }
+ sequencer()->MarkConsumed(iov.iov_len);
+ MaybeReleaseSequencerBuffer();
+ }
+}
+
+void QuicHeadersStream::MaybeReleaseSequencerBuffer() {
+ if (spdy_session_->ShouldReleaseHeadersStreamSequencerBuffer()) {
+ sequencer()->ReleaseBufferIfEmpty();
+ }
+}
+
+bool QuicHeadersStream::OnStreamFrameAcked(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_acked,
+ QuicTime::Delta ack_delay_time,
+ QuicByteCount* newly_acked_length) {
+ QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
+ newly_acked.Difference(bytes_acked());
+ for (const auto& acked : newly_acked) {
+ QuicStreamOffset acked_offset = acked.min();
+ QuicByteCount acked_length = acked.max() - acked.min();
+ for (CompressedHeaderInfo& header : unacked_headers_) {
+ if (acked_offset < header.headers_stream_offset) {
+ // This header frame offset belongs to headers with smaller offset, stop
+ // processing.
+ break;
+ }
+
+ if (acked_offset >= header.headers_stream_offset + header.full_length) {
+ // This header frame belongs to headers with larger offset.
+ continue;
+ }
+
+ QuicByteCount header_offset = acked_offset - header.headers_stream_offset;
+ QuicByteCount header_length =
+ std::min(acked_length, header.full_length - header_offset);
+
+ if (header.unacked_length < header_length) {
+ QUIC_BUG << "Unsent stream data is acked. unacked_length: "
+ << header.unacked_length << " acked_length: " << header_length;
+ CloseConnectionWithDetails(QUIC_INTERNAL_ERROR,
+ "Unsent stream data is acked");
+ return false;
+ }
+ if (header.ack_listener != nullptr && header_length > 0) {
+ header.ack_listener->OnPacketAcked(header_length, ack_delay_time);
+ }
+ header.unacked_length -= header_length;
+ acked_offset += header_length;
+ acked_length -= header_length;
+ }
+ }
+ // Remove headers which are fully acked. Please note, header frames can be
+ // acked out of order, but unacked_headers_ is cleaned up in order.
+ while (!unacked_headers_.empty() &&
+ unacked_headers_.front().unacked_length == 0) {
+ unacked_headers_.pop_front();
+ }
+ return QuicStream::OnStreamFrameAcked(offset, data_length, fin_acked,
+ ack_delay_time, newly_acked_length);
+}
+
+void QuicHeadersStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool /*fin_retransmitted*/) {
+ QuicStream::OnStreamFrameRetransmitted(offset, data_length, false);
+ for (CompressedHeaderInfo& header : unacked_headers_) {
+ if (offset < header.headers_stream_offset) {
+ // This header frame offset belongs to headers with smaller offset, stop
+ // processing.
+ break;
+ }
+
+ if (offset >= header.headers_stream_offset + header.full_length) {
+ // This header frame belongs to headers with larger offset.
+ continue;
+ }
+
+ QuicByteCount header_offset = offset - header.headers_stream_offset;
+ QuicByteCount retransmitted_length =
+ std::min(data_length, header.full_length - header_offset);
+ if (header.ack_listener != nullptr && retransmitted_length > 0) {
+ header.ack_listener->OnPacketRetransmitted(retransmitted_length);
+ }
+ offset += retransmitted_length;
+ data_length -= retransmitted_length;
+ }
+}
+
+void QuicHeadersStream::OnDataBuffered(
+ QuicStreamOffset offset,
+ QuicByteCount data_length,
+ const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener) {
+ // Populate unacked_headers_.
+ if (!unacked_headers_.empty() &&
+ (offset == unacked_headers_.back().headers_stream_offset +
+ unacked_headers_.back().full_length) &&
+ ack_listener == unacked_headers_.back().ack_listener) {
+ // Try to combine with latest inserted entry if they belong to the same
+ // header (i.e., having contiguous offset and the same ack listener).
+ unacked_headers_.back().full_length += data_length;
+ unacked_headers_.back().unacked_length += data_length;
+ } else {
+ unacked_headers_.push_back(
+ CompressedHeaderInfo(offset, data_length, ack_listener));
+ }
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.h
new file mode 100644
index 00000000000..d07c85f17e8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream.h
@@ -0,0 +1,96 @@
+// 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
+
+#include <cstddef>
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+namespace test {
+class QuicHeadersStreamPeer;
+} // namespace test
+
+// Headers in QUIC are sent as HTTP/2 HEADERS or PUSH_PROMISE frames over a
+// reserved stream with the id 3. Each endpoint (client and server) will
+// allocate an instance of QuicHeadersStream to send and receive headers.
+class QUIC_EXPORT_PRIVATE QuicHeadersStream : public QuicStream {
+ public:
+ explicit QuicHeadersStream(QuicSpdySession* session);
+ QuicHeadersStream(const QuicHeadersStream&) = delete;
+ QuicHeadersStream& operator=(const QuicHeadersStream&) = delete;
+ ~QuicHeadersStream() override;
+
+ // QuicStream implementation
+ void OnDataAvailable() override;
+
+ // Release underlying buffer if allowed.
+ void MaybeReleaseSequencerBuffer();
+
+ bool OnStreamFrameAcked(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_acked,
+ QuicTime::Delta ack_delay_time,
+ QuicByteCount* newly_acked_length) override;
+
+ void OnStreamFrameRetransmitted(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_retransmitted) override;
+
+ private:
+ friend class test::QuicHeadersStreamPeer;
+
+ // CompressedHeaderInfo includes simple information of a header, including
+ // offset in headers stream, unacked length and ack listener of this header.
+ struct QUIC_EXPORT_PRIVATE CompressedHeaderInfo {
+ CompressedHeaderInfo(
+ QuicStreamOffset headers_stream_offset,
+ QuicStreamOffset full_length,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+ CompressedHeaderInfo(const CompressedHeaderInfo& other);
+ ~CompressedHeaderInfo();
+
+ // Offset the header was sent on the headers stream.
+ QuicStreamOffset headers_stream_offset;
+ // The full length of the header.
+ QuicByteCount full_length;
+ // The remaining bytes to be acked.
+ QuicByteCount unacked_length;
+ // Ack listener of this header, and it is notified once any of the bytes has
+ // been acked or retransmitted.
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener;
+ };
+
+ // Returns true if the session is still connected.
+ bool IsConnected();
+
+ // Override to store mapping from offset, length to ack_listener. This
+ // ack_listener is notified once data within [offset, offset + length] is
+ // acked or retransmitted.
+ void OnDataBuffered(
+ QuicStreamOffset offset,
+ QuicByteCount data_length,
+ const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener)
+ override;
+
+ QuicSpdySession* spdy_session_;
+
+ // Headers that have not been fully acked.
+ QuicDeque<CompressedHeaderInfo> unacked_headers_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc
new file mode 100644
index 00000000000..2f669df22c3
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc
@@ -0,0 +1,969 @@
+// 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 <string>
+#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_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_;
+ std::string body_;
+ std::string saved_data_;
+ std::string saved_header_data_;
+ std::string 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"] = std::string(1 << 13, '.');
+ headers_["key1"] = std::string(1 << 13, '.');
+ headers_["key2"] = std::string(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"] = std::string(1 << 1, '.');
+ headers_["key1"] = std::string(1 << 2, '.');
+ headers_["key2"] = std::string(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
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc
new file mode 100644
index 00000000000..4c625c0df04
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc
@@ -0,0 +1,163 @@
+// 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_receive_control_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+const uint16_t kSettingsMaxHeaderListSize = 6;
+const uint16_t kSettingsNumPlaceholders = 8;
+
+// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
+// the connection on unexpected frames.
+class QuicReceiveControlStream::HttpDecoderVisitor
+ : public HttpDecoder::Visitor {
+ public:
+ explicit HttpDecoderVisitor(QuicReceiveControlStream* stream)
+ : stream_(stream) {}
+ HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
+ HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
+
+ void OnError(HttpDecoder* decoder) override {
+ stream_->session()->connection()->CloseConnection(
+ QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ }
+
+ void OnPriorityFrame(const PriorityFrame& frame) override {
+ CloseConnectionOnWrongFrame("Priority");
+ }
+
+ void OnCancelPushFrame(const CancelPushFrame& frame) override {
+ CloseConnectionOnWrongFrame("Cancel Push");
+ }
+
+ void OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
+ CloseConnectionOnWrongFrame("Max Push Id");
+ }
+
+ void OnGoAwayFrame(const GoAwayFrame& frame) override {
+ CloseConnectionOnWrongFrame("Goaway");
+ }
+
+ void OnSettingsFrameStart(Http3FrameLengths frame_lengths) override {
+ stream_->OnSettingsFrameStart(frame_lengths);
+ }
+
+ void OnSettingsFrame(const SettingsFrame& frame) override {
+ stream_->OnSettingsFrame(frame);
+ }
+
+ void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
+ CloseConnectionOnWrongFrame("Duplicate Push");
+ }
+
+ void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
+ CloseConnectionOnWrongFrame("Data");
+ }
+
+ void OnDataFramePayload(QuicStringPiece payload) override {
+ CloseConnectionOnWrongFrame("Data");
+ }
+
+ void OnDataFrameEnd() override { CloseConnectionOnWrongFrame("Data"); }
+
+ void OnHeadersFrameStart(Http3FrameLengths frame_length) override {
+ CloseConnectionOnWrongFrame("Headers");
+ }
+
+ void OnHeadersFramePayload(QuicStringPiece payload) override {
+ CloseConnectionOnWrongFrame("Headers");
+ }
+
+ void OnHeadersFrameEnd() override { CloseConnectionOnWrongFrame("Headers"); }
+
+ void OnPushPromiseFrameStart(PushId push_id) override {
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ void OnPushPromiseFramePayload(QuicStringPiece payload) override {
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ void OnPushPromiseFrameEnd() override {
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ private:
+ void CloseConnectionOnWrongFrame(std::string frame_type) {
+ // TODO(renjietang): Change to HTTP/3 error type.
+ stream_->session()->connection()->CloseConnection(
+ QUIC_HTTP_DECODER_ERROR,
+ frame_type + " frame received on control stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ }
+
+ QuicReceiveControlStream* stream_;
+};
+
+QuicReceiveControlStream::QuicReceiveControlStream(QuicStreamId id,
+ QuicSpdySession* session)
+ : QuicStream(id, session, /*is_static = */ true, READ_UNIDIRECTIONAL),
+ received_settings_length_(0),
+ http_decoder_visitor_(new HttpDecoderVisitor(this)) {
+ decoder_.set_visitor(http_decoder_visitor_.get());
+ sequencer()->set_level_triggered(true);
+}
+
+QuicReceiveControlStream::~QuicReceiveControlStream() {}
+
+void QuicReceiveControlStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+ // TODO(renjietang) Change the error code to H/3 specific
+ // HTTP_CLOSED_CRITICAL_STREAM.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Attempt to reset receive control stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicReceiveControlStream::OnDataAvailable() {
+ iovec iov;
+ while (!reading_stopped() && sequencer()->PrefetchNextRegion(&iov)) {
+ decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
+ iov.iov_len);
+ }
+}
+
+void QuicReceiveControlStream::OnSettingsFrameStart(
+ Http3FrameLengths frame_lengths) {
+ if (received_settings_length_ != 0) {
+ // TODO(renjietang): Change error code to HTTP_UNEXPECTED_FRAME.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Settings frames are received twice.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ received_settings_length_ +=
+ frame_lengths.header_length + frame_lengths.payload_length;
+}
+
+void QuicReceiveControlStream::OnSettingsFrame(const SettingsFrame& settings) {
+ QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session());
+ for (auto& it : settings.values) {
+ uint16_t setting_id = it.first;
+ switch (setting_id) {
+ case kSettingsMaxHeaderListSize:
+ spdy_session->set_max_inbound_header_list_size(it.second);
+ break;
+ case kSettingsNumPlaceholders:
+ // TODO: Support placeholder setting
+ break;
+ default:
+ break;
+ }
+ }
+ sequencer()->MarkConsumed(received_settings_length_);
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h
new file mode 100644
index 00000000000..1805d6ccc08
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_RECEIVE_CONTROL_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_RECEIVE_CONTROL_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+// 3.2.1 Control Stream.
+// The receive control stream is peer initiated and is read only.
+class QUIC_EXPORT_PRIVATE QuicReceiveControlStream : public QuicStream {
+ public:
+ // |session| can't be nullptr, and the ownership is not passed. The stream can
+ // only be accessed through the session.
+ explicit QuicReceiveControlStream(QuicStreamId id, QuicSpdySession* session);
+ QuicReceiveControlStream(const QuicReceiveControlStream&) = delete;
+ QuicReceiveControlStream& operator=(const QuicReceiveControlStream&) = delete;
+ ~QuicReceiveControlStream() override;
+
+ // Overriding QuicStream::OnStreamReset to make sure control stream is never
+ // closed before connection.
+ void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+ // Implementation of QuicStream.
+ void OnDataAvailable() override;
+
+ protected:
+ // Called from HttpDecoderVisitor.
+ void OnSettingsFrameStart(Http3FrameLengths frame_lengths);
+ void OnSettingsFrame(const SettingsFrame& settings);
+
+ private:
+ class HttpDecoderVisitor;
+
+ HttpDecoder decoder_;
+
+ // Track the number of settings bytes received.
+ size_t received_settings_length_;
+
+ // HttpDecoder's visitor.
+ std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_RECEIVE_CONTROL_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc
new file mode 100644
index 00000000000..1861d17495c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc
@@ -0,0 +1,172 @@
+// 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_receive_control_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.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_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.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_test_utils.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+using testing::_;
+using testing::StrictMock;
+
+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 (const auto& version : AllSupportedVersions()) {
+ if (!VersionHasControlStreams(version.transport_version)) {
+ continue;
+ }
+ for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+ params.emplace_back(version, p);
+ }
+ }
+ return params;
+}
+
+class QuicReceiveControlStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+ QuicReceiveControlStreamTest()
+ : connection_(new StrictMock<MockQuicConnection>(
+ &helper_,
+ &alarm_factory_,
+ perspective(),
+ SupportedVersions(GetParam().version))),
+ session_(connection_) {
+ session_.Initialize();
+ receive_control_stream_ = QuicMakeUnique<QuicReceiveControlStream>(
+ QuicUtils::GetFirstUnidirectionalStreamId(
+ GetParam().version.transport_version,
+ perspective() == Perspective::IS_CLIENT ? Perspective::IS_SERVER
+ : Perspective::IS_CLIENT),
+ &session_);
+ }
+
+ Perspective perspective() const { return GetParam().perspective; }
+
+ std::string EncodeSettings(const SettingsFrame& settings) {
+ HttpEncoder encoder;
+ std::unique_ptr<char[]> buffer;
+ auto header_length = encoder.SerializeSettingsFrame(settings, &buffer);
+ return std::string(buffer.get(), header_length);
+ }
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ StrictMock<MockQuicSpdySession> session_;
+ HttpDecoder decoder_;
+ std::unique_ptr<QuicReceiveControlStream> receive_control_stream_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicReceiveControlStreamTest,
+ ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicReceiveControlStreamTest, ResetControlStream) {
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
+ receive_control_stream_->id(),
+ QUIC_STREAM_CANCELLED, 1234);
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ receive_control_stream_->OnStreamReset(rst_frame);
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettings) {
+ SettingsFrame settings;
+ settings.values[3] = 2;
+ settings.values[6] = 5;
+ std::string data = EncodeSettings(settings);
+ QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+ QuicStringPiece(data));
+ EXPECT_NE(5u, session_.max_inbound_header_list_size());
+ receive_control_stream_->OnStreamFrame(frame);
+ EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsTwice) {
+ SettingsFrame settings;
+ settings.values[3] = 2;
+ settings.values[6] = 5;
+ std::string data = EncodeSettings(settings);
+ QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+ QuicStringPiece(data));
+ QuicStreamFrame frame2(receive_control_stream_->id(), false, data.length(),
+ QuicStringPiece(data));
+ receive_control_stream_->OnStreamFrame(frame);
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Settings frames are received twice.", _));
+ receive_control_stream_->OnStreamFrame(frame2);
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsFragments) {
+ SettingsFrame settings;
+ settings.values[3] = 2;
+ settings.values[6] = 5;
+ std::string data = EncodeSettings(settings);
+ std::string data1 = data.substr(0, 1);
+ std::string data2 = data.substr(1, data.length() - 1);
+
+ QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+ QuicStringPiece(data.data(), 1));
+ QuicStreamFrame frame2(receive_control_stream_->id(), false, 1,
+ QuicStringPiece(data.data() + 1, data.length() - 1));
+ EXPECT_NE(5u, session_.max_inbound_header_list_size());
+ receive_control_stream_->OnStreamFrame(frame);
+ receive_control_stream_->OnStreamFrame(frame2);
+ EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) {
+ GoAwayFrame goaway;
+ goaway.stream_id = 0x1;
+ HttpEncoder encoder;
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer);
+ std::string data = std::string(buffer.get(), header_length);
+
+ QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+ QuicStringPiece(data));
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _));
+ receive_control_stream_->OnStreamFrame(frame);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.cc
new file mode 100644
index 00000000000..40b6111133b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.cc
@@ -0,0 +1,37 @@
+// 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_send_control_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+QuicSendControlStream::QuicSendControlStream(QuicStreamId id,
+ QuicSpdySession* session)
+ : QuicStream(id, session, /*is_static = */ true, WRITE_UNIDIRECTIONAL),
+ settings_sent_(false) {}
+
+void QuicSendControlStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+ // TODO(renjietang) Change the error code to H/3 specific
+ // HTTP_CLOSED_CRITICAL_STREAM.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Attempt to reset send control stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicSendControlStream::SendSettingsFrame(const SettingsFrame& settings) {
+ DCHECK(!settings_sent_);
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount frame_length =
+ encoder_.SerializeSettingsFrame(settings, &buffer);
+ WriteOrBufferData(QuicStringPiece(buffer.get(), frame_length),
+ /*fin = */ false, nullptr);
+ settings_sent_ = true;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h
new file mode 100644
index 00000000000..09bdafb26a6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SEND_CONTROL_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SEND_CONTROL_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+// 3.2.1 Control Stream.
+// The send control stream is self initiated and is write only.
+class QUIC_EXPORT_PRIVATE QuicSendControlStream : public QuicStream {
+ public:
+ // |session| can't be nullptr, and the ownership is not passed. The stream can
+ // only be accessed through the session.
+ explicit QuicSendControlStream(QuicStreamId id, QuicSpdySession* session);
+ QuicSendControlStream(const QuicSendControlStream&) = delete;
+ QuicSendControlStream& operator=(const QuicSendControlStream&) = delete;
+ ~QuicSendControlStream() override = default;
+
+ // Overriding QuicStream::OnStreamReset to make sure control stream is never
+ // closed before connection.
+ void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+ // Send |settings| on this stream.
+ // Settings frame must be the first frame sent on this stream.
+ void SendSettingsFrame(const SettingsFrame& settings);
+
+ // The send control stream is write unidirectional, so this method should
+ // never be called.
+ void OnDataAvailable() override { QUIC_NOTREACHED(); }
+
+ private:
+ HttpEncoder encoder_;
+ // Track if a settings frame is already sent.
+ bool settings_sent_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SEND_CONTROL_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc
new file mode 100644
index 00000000000..980cdf54133
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc
@@ -0,0 +1,124 @@
+// 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_send_control_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.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_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.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_test_utils.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+using testing::_;
+using testing::Invoke;
+using testing::StrictMock;
+
+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 (const auto& version : AllSupportedVersions()) {
+ if (!VersionHasControlStreams(version.transport_version)) {
+ continue;
+ }
+ for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+ params.emplace_back(version, p);
+ }
+ }
+ return params;
+}
+
+class QuicSendControlStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+ QuicSendControlStreamTest()
+ : connection_(new StrictMock<MockQuicConnection>(
+ &helper_,
+ &alarm_factory_,
+ perspective(),
+ SupportedVersions(GetParam().version))),
+ session_(connection_) {
+ session_.Initialize();
+ send_control_stream_ = QuicMakeUnique<QuicSendControlStream>(
+ QuicSpdySessionPeer::GetNextOutgoingUnidirectionalStreamId(&session_),
+ &session_);
+ ON_CALL(session_, WritevData(_, _, _, _, _))
+ .WillByDefault(Invoke(MockQuicSession::ConsumeData));
+ }
+
+ Perspective perspective() const { return GetParam().perspective; }
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ StrictMock<MockQuicSpdySession> session_;
+ HttpEncoder encoder_;
+ std::unique_ptr<QuicSendControlStream> send_control_stream_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSendControlStreamTest,
+ ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicSendControlStreamTest, WriteSettingsOnStartUp) {
+ SettingsFrame settings;
+ settings.values[3] = 2;
+ settings.values[6] = 5;
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount frame_length =
+ encoder_.SerializeSettingsFrame(settings, &buffer);
+
+ EXPECT_CALL(session_, WritevData(_, _, frame_length, _, _));
+ send_control_stream_->SendSettingsFrame(settings);
+}
+
+TEST_P(QuicSendControlStreamTest, ResetControlStream) {
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
+ send_control_stream_->id(),
+ QUIC_STREAM_CANCELLED, 1234);
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ send_control_stream_->OnStreamReset(rst_frame);
+}
+
+TEST_P(QuicSendControlStreamTest, ReceiveDataOnSendControlStream) {
+ QuicStreamFrame frame(send_control_stream_->id(), false, 0, "test");
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_DATA_RECEIVED_ON_WRITE_UNIDIRECTIONAL_STREAM, _, _));
+ send_control_stream_->OnStreamFrame(frame);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc
new file mode 100644
index 00000000000..5cdb2c46451
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 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_server_session_base.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/proto/cached_network_parameters.pb.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.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_flag_utils.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"
+
+namespace quic {
+
+QuicServerSessionBase::QuicServerSessionBase(
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ Visitor* visitor,
+ QuicCryptoServerStream::Helper* helper,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache)
+ : QuicSpdySession(connection, visitor, config, supported_versions),
+ crypto_config_(crypto_config),
+ compressed_certs_cache_(compressed_certs_cache),
+ helper_(helper),
+ bandwidth_resumption_enabled_(false),
+ bandwidth_estimate_sent_to_client_(QuicBandwidth::Zero()),
+ last_scup_time_(QuicTime::Zero()) {}
+
+QuicServerSessionBase::~QuicServerSessionBase() {}
+
+void QuicServerSessionBase::Initialize() {
+ crypto_stream_.reset(
+ CreateQuicCryptoServerStream(crypto_config_, compressed_certs_cache_));
+ QuicSpdySession::Initialize();
+}
+
+void QuicServerSessionBase::OnConfigNegotiated() {
+ QuicSpdySession::OnConfigNegotiated();
+
+ if (!config()->HasReceivedConnectionOptions()) {
+ return;
+ }
+
+ // Enable bandwidth resumption if peer sent correct connection options.
+ const bool last_bandwidth_resumption =
+ ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWRE);
+ const bool max_bandwidth_resumption =
+ ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWMX);
+ bandwidth_resumption_enabled_ =
+ last_bandwidth_resumption || max_bandwidth_resumption;
+
+ // If the client has provided a bandwidth estimate from the same serving
+ // region as this server, then decide whether to use the data for bandwidth
+ // resumption.
+ const CachedNetworkParameters* cached_network_params =
+ crypto_stream_->PreviousCachedNetworkParams();
+ if (cached_network_params != nullptr &&
+ cached_network_params->serving_region() == serving_region_) {
+ // Log the received connection parameters, regardless of how they
+ // get used for bandwidth resumption.
+ connection()->OnReceiveConnectionState(*cached_network_params);
+
+ if (bandwidth_resumption_enabled_) {
+ // Only do bandwidth resumption if estimate is recent enough.
+ const uint64_t seconds_since_estimate =
+ connection()->clock()->WallNow().ToUNIXSeconds() -
+ cached_network_params->timestamp();
+ if (seconds_since_estimate <= kNumSecondsPerHour) {
+ connection()->ResumeConnectionState(*cached_network_params,
+ max_bandwidth_resumption);
+ }
+ }
+ }
+}
+
+void QuicServerSessionBase::OnConnectionClosed(QuicErrorCode error,
+ const std::string& error_details,
+ ConnectionCloseSource source) {
+ QuicSession::OnConnectionClosed(error, error_details, source);
+ // In the unlikely event we get a connection close while doing an asynchronous
+ // crypto event, make sure we cancel the callback.
+ if (crypto_stream_ != nullptr) {
+ crypto_stream_->CancelOutstandingCallbacks();
+ }
+}
+
+void QuicServerSessionBase::OnCongestionWindowChange(QuicTime now) {
+ if (!bandwidth_resumption_enabled_) {
+ return;
+ }
+ // Only send updates when the application has no data to write.
+ if (HasDataToWrite()) {
+ return;
+ }
+
+ // If not enough time has passed since the last time we sent an update to the
+ // client, or not enough packets have been sent, then return early.
+ const QuicSentPacketManager& sent_packet_manager =
+ connection()->sent_packet_manager();
+ int64_t srtt_ms =
+ sent_packet_manager.GetRttStats()->smoothed_rtt().ToMilliseconds();
+ int64_t now_ms = (now - last_scup_time_).ToMilliseconds();
+ int64_t packets_since_last_scup = 0;
+ const QuicPacketNumber largest_sent_packet =
+ connection()->sent_packet_manager().GetLargestSentPacket();
+ if (largest_sent_packet.IsInitialized()) {
+ packets_since_last_scup =
+ last_scup_packet_number_.IsInitialized()
+ ? largest_sent_packet - last_scup_packet_number_
+ : largest_sent_packet.ToUint64();
+ }
+ if (now_ms < (kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms) ||
+ now_ms < kMinIntervalBetweenServerConfigUpdatesMs ||
+ packets_since_last_scup < kMinPacketsBetweenServerConfigUpdates) {
+ return;
+ }
+
+ // If the bandwidth recorder does not have a valid estimate, return early.
+ const QuicSustainedBandwidthRecorder* bandwidth_recorder =
+ sent_packet_manager.SustainedBandwidthRecorder();
+ if (bandwidth_recorder == nullptr || !bandwidth_recorder->HasEstimate()) {
+ return;
+ }
+
+ // The bandwidth recorder has recorded at least one sustained bandwidth
+ // estimate. Check that it's substantially different from the last one that
+ // we sent to the client, and if so, send the new one.
+ QuicBandwidth new_bandwidth_estimate =
+ bandwidth_recorder->BandwidthEstimate();
+
+ int64_t bandwidth_delta =
+ std::abs(new_bandwidth_estimate.ToBitsPerSecond() -
+ bandwidth_estimate_sent_to_client_.ToBitsPerSecond());
+
+ // Define "substantial" difference as a 50% increase or decrease from the
+ // last estimate.
+ bool substantial_difference =
+ bandwidth_delta >
+ 0.5 * bandwidth_estimate_sent_to_client_.ToBitsPerSecond();
+ if (!substantial_difference) {
+ return;
+ }
+
+ bandwidth_estimate_sent_to_client_ = new_bandwidth_estimate;
+ QUIC_DVLOG(1) << "Server: sending new bandwidth estimate (KBytes/s): "
+ << bandwidth_estimate_sent_to_client_.ToKBytesPerSecond();
+
+ // Include max bandwidth in the update.
+ QuicBandwidth max_bandwidth_estimate =
+ bandwidth_recorder->MaxBandwidthEstimate();
+ int32_t max_bandwidth_timestamp = bandwidth_recorder->MaxBandwidthTimestamp();
+
+ // Fill the proto before passing it to the crypto stream to send.
+ const int32_t bw_estimate_bytes_per_second =
+ BandwidthToCachedParameterBytesPerSecond(
+ bandwidth_estimate_sent_to_client_);
+ const int32_t max_bw_estimate_bytes_per_second =
+ BandwidthToCachedParameterBytesPerSecond(max_bandwidth_estimate);
+ QUIC_BUG_IF(max_bw_estimate_bytes_per_second < 0)
+ << max_bw_estimate_bytes_per_second;
+ QUIC_BUG_IF(bw_estimate_bytes_per_second < 0) << bw_estimate_bytes_per_second;
+
+ CachedNetworkParameters cached_network_params;
+ cached_network_params.set_bandwidth_estimate_bytes_per_second(
+ bw_estimate_bytes_per_second);
+ cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
+ max_bw_estimate_bytes_per_second);
+ cached_network_params.set_max_bandwidth_timestamp_seconds(
+ max_bandwidth_timestamp);
+ cached_network_params.set_min_rtt_ms(
+ sent_packet_manager.GetRttStats()->min_rtt().ToMilliseconds());
+ cached_network_params.set_previous_connection_state(
+ bandwidth_recorder->EstimateRecordedDuringSlowStart()
+ ? CachedNetworkParameters::SLOW_START
+ : CachedNetworkParameters::CONGESTION_AVOIDANCE);
+ cached_network_params.set_timestamp(
+ connection()->clock()->WallNow().ToUNIXSeconds());
+ if (!serving_region_.empty()) {
+ cached_network_params.set_serving_region(serving_region_);
+ }
+
+ crypto_stream_->SendServerConfigUpdate(&cached_network_params);
+
+ connection()->OnSendConnectionState(cached_network_params);
+
+ last_scup_time_ = now;
+ last_scup_packet_number_ =
+ connection()->sent_packet_manager().GetLargestSentPacket();
+}
+
+bool QuicServerSessionBase::ShouldCreateIncomingStream(QuicStreamId id) {
+ if (!connection()->connected()) {
+ QUIC_BUG << "ShouldCreateIncomingStream called when disconnected";
+ return false;
+ }
+
+ if (QuicUtils::IsServerInitiatedStreamId(connection()->transport_version(),
+ id)) {
+ QUIC_DLOG(INFO) << "Invalid incoming even stream_id:" << id;
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Client created even numbered stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+ return true;
+}
+
+bool QuicServerSessionBase::ShouldCreateOutgoingBidirectionalStream() {
+ if (!connection()->connected()) {
+ QUIC_BUG
+ << "ShouldCreateOutgoingBidirectionalStream called when disconnected";
+ return false;
+ }
+ if (!crypto_stream_->encryption_established()) {
+ QUIC_BUG << "Encryption not established so no outgoing stream created.";
+ return false;
+ }
+
+ if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+ connection()->transport_version() != QUIC_VERSION_99) {
+ if (GetNumOpenOutgoingStreams() >=
+ stream_id_manager().max_open_outgoing_streams()) {
+ VLOG(1) << "No more streams should be created. "
+ << "Already " << GetNumOpenOutgoingStreams() << " open.";
+ return false;
+ }
+ }
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_common_stream_check, 2, 2);
+ return CanOpenNextOutgoingBidirectionalStream();
+}
+
+bool QuicServerSessionBase::ShouldCreateOutgoingUnidirectionalStream() {
+ if (!connection()->connected()) {
+ QUIC_BUG
+ << "ShouldCreateOutgoingUnidirectionalStream called when disconnected";
+ return false;
+ }
+ if (!crypto_stream_->encryption_established()) {
+ QUIC_BUG << "Encryption not established so no outgoing stream created.";
+ return false;
+ }
+
+ if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+ connection()->transport_version() != QUIC_VERSION_99) {
+ if (GetNumOpenOutgoingStreams() >=
+ stream_id_manager().max_open_outgoing_streams()) {
+ VLOG(1) << "No more streams should be created. "
+ << "Already " << GetNumOpenOutgoingStreams() << " open.";
+ return false;
+ }
+ }
+
+ return CanOpenNextOutgoingUnidirectionalStream();
+}
+
+QuicCryptoServerStreamBase* QuicServerSessionBase::GetMutableCryptoStream() {
+ return crypto_stream_.get();
+}
+
+const QuicCryptoServerStreamBase* QuicServerSessionBase::GetCryptoStream()
+ const {
+ return crypto_stream_.get();
+}
+
+int32_t QuicServerSessionBase::BandwidthToCachedParameterBytesPerSecond(
+ const QuicBandwidth& bandwidth) {
+ return static_cast<int32_t>(std::min<int64_t>(
+ bandwidth.ToBytesPerSecond(), std::numeric_limits<uint32_t>::max()));
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h
new file mode 100644
index 00000000000..8f071f3fed4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 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.
+
+// A server specific QuicSession subclass.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
+
+#include <cstdint>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_compressed_certs_cache.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicConfig;
+class QuicConnection;
+class QuicCryptoServerConfig;
+
+namespace test {
+class QuicServerSessionBasePeer;
+class QuicSimpleServerSessionPeer;
+} // namespace test
+
+class QUIC_EXPORT_PRIVATE QuicServerSessionBase : public QuicSpdySession {
+ public:
+ // Does not take ownership of |connection|. |crypto_config| must outlive the
+ // session. |helper| must outlive any created crypto streams.
+ QuicServerSessionBase(const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ QuicCryptoServerStream::Helper* helper,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache);
+ QuicServerSessionBase(const QuicServerSessionBase&) = delete;
+ QuicServerSessionBase& operator=(const QuicServerSessionBase&) = delete;
+
+ // Override the base class to cancel any ongoing asychronous crypto.
+ void OnConnectionClosed(QuicErrorCode error,
+ const std::string& error_details,
+ ConnectionCloseSource source) override;
+
+ // Sends a server config update to the client, containing new bandwidth
+ // estimate.
+ void OnCongestionWindowChange(QuicTime now) override;
+
+ ~QuicServerSessionBase() override;
+
+ void Initialize() override;
+
+ const QuicCryptoServerStreamBase* crypto_stream() const {
+ return crypto_stream_.get();
+ }
+
+ // Override base class to process bandwidth related config received from
+ // client.
+ void OnConfigNegotiated() override;
+
+ void set_serving_region(const std::string& serving_region) {
+ serving_region_ = serving_region;
+ }
+
+ protected:
+ // QuicSession methods(override them with return type of QuicSpdyStream*):
+ QuicCryptoServerStreamBase* GetMutableCryptoStream() override;
+
+ const QuicCryptoServerStreamBase* GetCryptoStream() const override;
+
+ // If an outgoing stream can be created, return true.
+ // Return false when connection is closed or forward secure encryption hasn't
+ // established yet or number of server initiated streams already reaches the
+ // upper limit.
+ bool ShouldCreateOutgoingBidirectionalStream() override;
+ bool ShouldCreateOutgoingUnidirectionalStream() override;
+
+ // If we should create an incoming stream, returns true. Otherwise
+ // does error handling, including communicating the error to the client and
+ // possibly closing the connection, and returns false.
+ bool ShouldCreateIncomingStream(QuicStreamId id) override;
+
+ virtual QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache) = 0;
+
+ const QuicCryptoServerConfig* crypto_config() { return crypto_config_; }
+
+ QuicCryptoServerStream::Helper* stream_helper() { return helper_; }
+
+ private:
+ friend class test::QuicServerSessionBasePeer;
+ friend class test::QuicSimpleServerSessionPeer;
+
+ const QuicCryptoServerConfig* crypto_config_;
+
+ // The cache which contains most recently compressed certs.
+ // Owned by QuicDispatcher.
+ QuicCompressedCertsCache* compressed_certs_cache_;
+
+ std::unique_ptr<QuicCryptoServerStreamBase> crypto_stream_;
+
+ // Pointer to the helper used to create crypto server streams. Must outlive
+ // streams created via CreateQuicCryptoServerStream.
+ QuicCryptoServerStream::Helper* helper_;
+
+ // Whether bandwidth resumption is enabled for this connection.
+ bool bandwidth_resumption_enabled_;
+
+ // The most recent bandwidth estimate sent to the client.
+ QuicBandwidth bandwidth_estimate_sent_to_client_;
+
+ // Text describing server location. Sent to the client as part of the bandwith
+ // estimate in the source-address token. Optional, can be left empty.
+ std::string serving_region_;
+
+ // Time at which we send the last SCUP to the client.
+ QuicTime last_scup_time_;
+
+ // Number of packets sent to the peer, at the time we last sent a SCUP.
+ QuicPacketNumber last_scup_packet_number_;
+
+ // Converts QuicBandwidth to an int32 bytes/second that can be
+ // stored in CachedNetworkParameters. TODO(jokulik): This function
+ // should go away once we fix http://b//27897982
+ int32_t BandwidthToCachedParameterBytesPerSecond(
+ const QuicBandwidth& bandwidth);
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc
new file mode 100644
index 00000000000..e5945e1c9d6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc
@@ -0,0 +1,740 @@
+// 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_server_session_base.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/proto/cached_network_parameters.pb.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.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_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/fake_proof_source.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_session_visitor.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_crypto_server_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_server_session_base_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_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_id_manager_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_sustained_bandwidth_recorder_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h"
+
+using testing::_;
+using testing::StrictMock;
+
+using testing::AtLeast;
+using testing::Return;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestServerSession : public QuicServerSessionBase {
+ public:
+ TestServerSession(const QuicConfig& config,
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ QuicCryptoServerStream::Helper* helper,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicSimpleServerBackend* quic_simple_server_backend)
+ : QuicServerSessionBase(config,
+ CurrentSupportedVersions(),
+ connection,
+ visitor,
+ helper,
+ crypto_config,
+ compressed_certs_cache),
+ quic_simple_server_backend_(quic_simple_server_backend) {}
+
+ ~TestServerSession() override { delete connection(); }
+
+ protected:
+ QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override {
+ if (!ShouldCreateIncomingStream(id)) {
+ return nullptr;
+ }
+ QuicSpdyStream* stream = new QuicSimpleServerStream(
+ id, this, BIDIRECTIONAL, quic_simple_server_backend_);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ QuicSpdyStream* CreateIncomingStream(PendingStream pending) override {
+ QuicSpdyStream* stream = new QuicSimpleServerStream(
+ std::move(pending), this, BIDIRECTIONAL, quic_simple_server_backend_);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ QuicSpdyStream* CreateOutgoingBidirectionalStream() override {
+ DCHECK(false);
+ return nullptr;
+ }
+
+ QuicSpdyStream* CreateOutgoingUnidirectionalStream() override {
+ if (!ShouldCreateOutgoingUnidirectionalStream()) {
+ return nullptr;
+ }
+
+ QuicSpdyStream* stream = new QuicSimpleServerStream(
+ GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL,
+ quic_simple_server_backend_);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache) override {
+ return new QuicCryptoServerStream(
+ crypto_config, compressed_certs_cache,
+ GetQuicReloadableFlag(enable_quic_stateless_reject_support), this,
+ stream_helper());
+ }
+
+ private:
+ QuicSimpleServerBackend*
+ quic_simple_server_backend_; // Owned by QuicServerSessionBaseTest
+};
+
+const size_t kMaxStreamsForTest = 10;
+
+class QuicServerSessionBaseTest : public QuicTestWithParam<ParsedQuicVersion> {
+ protected:
+ QuicServerSessionBaseTest()
+ : QuicServerSessionBaseTest(crypto_test_utils::ProofSourceForTesting()) {}
+
+ explicit QuicServerSessionBaseTest(std::unique_ptr<ProofSource> proof_source)
+ : crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance(),
+ std::move(proof_source),
+ KeyExchangeSource::Default(),
+ TlsServerHandshaker::CreateSslCtx()),
+ compressed_certs_cache_(
+ QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
+ config_.SetMaxIncomingDynamicStreamsToSend(kMaxStreamsForTest);
+ QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(&config_,
+ kMaxStreamsForTest);
+ config_.SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+
+ ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam());
+ connection_ = new StrictMock<MockQuicConnection>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ session_ = QuicMakeUnique<TestServerSession>(
+ config_, connection_, &owner_, &stream_helper_, &crypto_config_,
+ &compressed_certs_cache_, &memory_cache_backend_);
+ MockClock clock;
+ handshake_message_ = crypto_config_.AddDefaultConfig(
+ QuicRandom::GetInstance(), &clock,
+ QuicCryptoServerConfig::ConfigOptions());
+ session_->Initialize();
+ QuicSessionPeer::GetMutableCryptoStream(session_.get())
+ ->OnSuccessfulVersionNegotiation(supported_versions.front());
+ visitor_ = QuicConnectionPeer::GetVisitor(connection_);
+ }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
+ return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ QuicTransportVersion transport_version() const {
+ return connection_->transport_version();
+ }
+
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+ // one-way close. This method can be used to inject a STOP_SENDING, which
+ // would cause a close in the opposite direction. This allows tests to do the
+ // extra work to get a two-way (full) close where desired. Also sets up
+ // expects needed to ensure that the STOP_SENDING worked as expected.
+ void InjectStopSendingFrame(QuicStreamId stream_id,
+ QuicRstStreamErrorCode rst_stream_code) {
+ if (transport_version() != QUIC_VERSION_99) {
+ // Only needed for version 99/IETF QUIC. Noop otherwise.
+ return;
+ }
+ QuicStopSendingFrame stop_sending(
+ kInvalidControlFrameId, stream_id,
+ static_cast<QuicApplicationErrorCode>(rst_stream_code));
+ EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
+ // Expect the RESET_STREAM that is generated in response to receiving a
+ // STOP_SENDING.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream_id, rst_stream_code));
+ session_->OnStopSendingFrame(stop_sending);
+ }
+
+ StrictMock<MockQuicSessionVisitor> owner_;
+ StrictMock<MockQuicCryptoServerStreamHelper> stream_helper_;
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ QuicConfig config_;
+ QuicCryptoServerConfig crypto_config_;
+ QuicCompressedCertsCache compressed_certs_cache_;
+ QuicMemoryCacheBackend memory_cache_backend_;
+ std::unique_ptr<TestServerSession> session_;
+ std::unique_ptr<CryptoHandshakeMessage> handshake_message_;
+ QuicConnectionVisitorInterface* visitor_;
+};
+
+// Compares CachedNetworkParameters.
+MATCHER_P(EqualsProto, network_params, "") {
+ CachedNetworkParameters reference(network_params);
+ return (arg->bandwidth_estimate_bytes_per_second() ==
+ reference.bandwidth_estimate_bytes_per_second() &&
+ arg->bandwidth_estimate_bytes_per_second() ==
+ reference.bandwidth_estimate_bytes_per_second() &&
+ arg->max_bandwidth_estimate_bytes_per_second() ==
+ reference.max_bandwidth_estimate_bytes_per_second() &&
+ arg->max_bandwidth_timestamp_seconds() ==
+ reference.max_bandwidth_timestamp_seconds() &&
+ arg->min_rtt_ms() == reference.min_rtt_ms() &&
+ arg->previous_connection_state() ==
+ reference.previous_connection_state());
+}
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicServerSessionBaseTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+TEST_P(QuicServerSessionBaseTest, CloseStreamDueToReset) {
+ // Open a stream, then reset it.
+ // Send two bytes of payload to open it.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ session_->OnStreamFrame(data1);
+ EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+
+ // Send a reset (and expect the peer to send a RST in response).
+ QuicRstStreamFrame rst1(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ if (transport_version() != QUIC_VERSION_99) {
+ // For non-version 99, the RESET_STREAM will do the full close.
+ // Set up expects accordingly.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst1);
+
+ // For version-99 will create and receive a stop-sending, completing
+ // the full-close expected by this test.
+ InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+ // Send the same two bytes of payload in a new packet.
+ visitor_->OnStreamFrame(data1);
+
+ // The stream should not be re-opened.
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, NeverOpenStreamDueToReset) {
+ // Send a reset (and expect the peer to send a RST in response).
+ QuicRstStreamFrame rst1(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ if (transport_version() != QUIC_VERSION_99) {
+ // For non-version 99, the RESET_STREAM will do the full close.
+ // Set up expects accordingly.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst1);
+
+ // For version-99 will create and receive a stop-sending, completing
+ // the full-close expected by this test.
+ InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+ // Send two bytes of payload.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ visitor_->OnStreamFrame(data1);
+
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, AcceptClosedStream) {
+ // Send (empty) compressed headers followed by two bytes of data.
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("\1\0\0\0\0\0\0\0HT"));
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ QuicStringPiece("\2\0\0\0\0\0\0\0HT"));
+ visitor_->OnStreamFrame(frame1);
+ visitor_->OnStreamFrame(frame2);
+ EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams());
+
+ // Send a reset (and expect the peer to send a RST in response).
+ QuicRstStreamFrame rst(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ if (transport_version() != QUIC_VERSION_99) {
+ // For non-version 99, the RESET_STREAM will do the full close.
+ // Set up expects accordingly.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst);
+
+ // For version-99 will create and receive a stop-sending, completing
+ // the full-close expected by this test.
+ InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ // If we were tracking, we'd probably want to reject this because it's data
+ // past the reset point of stream 3. As it's a closed stream we just drop the
+ // data on the floor, but accept the packet because it has data for stream 5.
+ QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false, 2,
+ QuicStringPiece("TP"));
+ QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2,
+ QuicStringPiece("TP"));
+ visitor_->OnStreamFrame(frame3);
+ visitor_->OnStreamFrame(frame4);
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, MaxOpenStreams) {
+ // Test that the server refuses if a client attempts to open too many data
+ // streams. For versions other than version 99, the server accepts slightly
+ // more than the negotiated stream limit to deal with rare cases where a
+ // client FIN/RST is lost.
+
+ session_->OnConfigNegotiated();
+ if (transport_version() != QUIC_VERSION_99) {
+ // The slightly increased stream limit is set during config negotiation. It
+ // is either an increase of 10 over negotiated limit, or a fixed percentage
+ // scaling, whichever is larger. Test both before continuing.
+ EXPECT_LT(kMaxStreamsMultiplier * kMaxStreamsForTest,
+ kMaxStreamsForTest + kMaxStreamsMinimumIncrement);
+ EXPECT_EQ(kMaxStreamsForTest + kMaxStreamsMinimumIncrement,
+ session_->max_open_incoming_bidirectional_streams());
+ }
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+ // Open the max configured number of streams, should be no problem.
+ for (size_t i = 0; i < kMaxStreamsForTest; ++i) {
+ EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), stream_id));
+ stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+ }
+
+ if (transport_version() != QUIC_VERSION_99) {
+ // Open more streams: server should accept slightly more than the limit.
+ // Excess streams are for non-version-99 only.
+ for (size_t i = 0; i < kMaxStreamsMinimumIncrement; ++i) {
+ EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), stream_id));
+ stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+ }
+ }
+ // Now violate the server's internal stream limit.
+ stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+
+ if (transport_version() != QUIC_VERSION_99) {
+ // For non-version 99, QUIC responds to an attempt to exceed the stream
+ // limit by resetting the stream.
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream_id, QUIC_REFUSED_STREAM));
+ } else {
+ // In version 99 QUIC responds to an attempt to exceed the stream limit by
+ // closing the connection.
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ }
+ // Even if the connection remains open, the stream creation should fail.
+ EXPECT_FALSE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), stream_id));
+}
+
+TEST_P(QuicServerSessionBaseTest, MaxAvailableBidirectionalStreams) {
+ // Test that the server closes the connection if a client makes too many data
+ // streams available. The server accepts slightly more than the negotiated
+ // stream limit to deal with rare cases where a client FIN/RST is lost.
+
+ session_->OnConfigNegotiated();
+ const size_t kAvailableStreamLimit =
+ session_->MaxAvailableBidirectionalStreams();
+
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), GetNthClientInitiatedBidirectionalId(0)));
+
+ // Establish available streams up to the server's limit.
+ QuicStreamId next_id =
+ QuicUtils::StreamIdDelta(connection_->transport_version());
+ const int kLimitingStreamId =
+ GetNthClientInitiatedBidirectionalId(kAvailableStreamLimit + 1);
+ if (transport_version() != QUIC_VERSION_99) {
+ // This exceeds the stream limit. In versions other than 99
+ // this is allowed. Version 99 hews to the IETF spec and does
+ // not allow it.
+ EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), kLimitingStreamId));
+ // A further available stream will result in connection close.
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_TOO_MANY_AVAILABLE_STREAMS, _, _));
+ } else {
+ // A further available stream will result in connection close.
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ }
+
+ // This forces stream kLimitingStreamId + 2 to become available, which
+ // violates the quota.
+ EXPECT_FALSE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), kLimitingStreamId + 2 * next_id));
+}
+
+TEST_P(QuicServerSessionBaseTest, GetEvenIncomingError) {
+ // Incoming streams on the server session must be odd.
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ EXPECT_EQ(nullptr,
+ QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), GetNthServerInitiatedUnidirectionalId(0)));
+}
+
+TEST_P(QuicServerSessionBaseTest, GetStreamDisconnected) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Don't create new streams if the connection is disconnected.
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ EXPECT_QUIC_BUG(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+ session_.get(), GetNthClientInitiatedBidirectionalId(0)),
+ "ShouldCreateIncomingStream called when disconnected");
+}
+
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+ explicit MockQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicServerSessionBase* session,
+ QuicCryptoServerStream::Helper* helper)
+ : QuicCryptoServerStream(
+ crypto_config,
+ compressed_certs_cache,
+ GetQuicReloadableFlag(enable_quic_stateless_reject_support),
+ session,
+ helper) {}
+ MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+ MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+ delete;
+ ~MockQuicCryptoServerStream() override {}
+
+ MOCK_METHOD1(SendServerConfigUpdate,
+ void(const CachedNetworkParameters* cached_network_parameters));
+};
+
+TEST_P(QuicServerSessionBaseTest, BandwidthEstimates) {
+ // Test that bandwidth estimate updates are sent to the client, only when
+ // bandwidth resumption is enabled, the bandwidth estimate has changed
+ // sufficiently, enough time has passed,
+ // and we don't have any other data to write.
+
+ // Client has sent kBWRE connection option to trigger bandwidth resumption.
+ QuicTagVector copt;
+ copt.push_back(kBWRE);
+ QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+ session_->OnConfigNegotiated();
+ EXPECT_TRUE(
+ QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+
+ int32_t bandwidth_estimate_kbytes_per_second = 123;
+ int32_t max_bandwidth_estimate_kbytes_per_second = 134;
+ int32_t max_bandwidth_estimate_timestamp = 1122334455;
+ const std::string serving_region = "not a real region";
+ session_->set_serving_region(serving_region);
+
+ session_->UnregisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true);
+ QuicServerSessionBasePeer::SetCryptoStream(session_.get(), nullptr);
+ MockQuicCryptoServerStream* crypto_stream =
+ new MockQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_,
+ session_.get(), &stream_helper_);
+ QuicServerSessionBasePeer::SetCryptoStream(session_.get(), crypto_stream);
+ session_->RegisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true, QuicStream::kDefaultPriority);
+
+ // Set some initial bandwidth values.
+ QuicSentPacketManager* sent_packet_manager =
+ QuicConnectionPeer::GetSentPacketManager(session_->connection());
+ QuicSustainedBandwidthRecorder& bandwidth_recorder =
+ QuicSentPacketManagerPeer::GetBandwidthRecorder(sent_packet_manager);
+ // Seed an rtt measurement equal to the initial default rtt.
+ RttStats* rtt_stats =
+ const_cast<RttStats*>(sent_packet_manager->GetRttStats());
+ rtt_stats->UpdateRtt(rtt_stats->initial_rtt(), QuicTime::Delta::Zero(),
+ QuicTime::Zero());
+ QuicSustainedBandwidthRecorderPeer::SetBandwidthEstimate(
+ &bandwidth_recorder, bandwidth_estimate_kbytes_per_second);
+ QuicSustainedBandwidthRecorderPeer::SetMaxBandwidthEstimate(
+ &bandwidth_recorder, max_bandwidth_estimate_kbytes_per_second,
+ max_bandwidth_estimate_timestamp);
+ // Queue up some pending data.
+ session_->MarkConnectionLevelWriteBlocked(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()));
+ EXPECT_TRUE(session_->HasDataToWrite());
+
+ // There will be no update sent yet - not enough time has passed.
+ QuicTime now = QuicTime::Zero();
+ session_->OnCongestionWindowChange(now);
+
+ // Bandwidth estimate has now changed sufficiently but not enough time has
+ // passed to send a Server Config Update.
+ bandwidth_estimate_kbytes_per_second =
+ bandwidth_estimate_kbytes_per_second * 1.6;
+ session_->OnCongestionWindowChange(now);
+
+ // Bandwidth estimate has now changed sufficiently and enough time has passed,
+ // but not enough packets have been sent.
+ int64_t srtt_ms =
+ sent_packet_manager->GetRttStats()->smoothed_rtt().ToMilliseconds();
+ now = now + QuicTime::Delta::FromMilliseconds(
+ kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms);
+ session_->OnCongestionWindowChange(now);
+
+ // The connection no longer has pending data to be written.
+ session_->OnCanWrite();
+ EXPECT_FALSE(session_->HasDataToWrite());
+ session_->OnCongestionWindowChange(now);
+
+ // Bandwidth estimate has now changed sufficiently, enough time has passed,
+ // and enough packets have been sent.
+ SerializedPacket packet(
+ QuicPacketNumber(1) + kMinPacketsBetweenServerConfigUpdates,
+ PACKET_4BYTE_PACKET_NUMBER, nullptr, 1000, false, false);
+ sent_packet_manager->OnPacketSent(&packet, QuicPacketNumber(), now,
+ NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA);
+
+ // Verify that the proto has exactly the values we expect.
+ CachedNetworkParameters expected_network_params;
+ expected_network_params.set_bandwidth_estimate_bytes_per_second(
+ bandwidth_recorder.BandwidthEstimate().ToBytesPerSecond());
+ expected_network_params.set_max_bandwidth_estimate_bytes_per_second(
+ bandwidth_recorder.MaxBandwidthEstimate().ToBytesPerSecond());
+ expected_network_params.set_max_bandwidth_timestamp_seconds(
+ bandwidth_recorder.MaxBandwidthTimestamp());
+ expected_network_params.set_min_rtt_ms(session_->connection()
+ ->sent_packet_manager()
+ .GetRttStats()
+ ->min_rtt()
+ .ToMilliseconds());
+ expected_network_params.set_previous_connection_state(
+ CachedNetworkParameters::CONGESTION_AVOIDANCE);
+ expected_network_params.set_timestamp(
+ session_->connection()->clock()->WallNow().ToUNIXSeconds());
+ expected_network_params.set_serving_region(serving_region);
+
+ EXPECT_CALL(*crypto_stream,
+ SendServerConfigUpdate(EqualsProto(expected_network_params)))
+ .Times(1);
+ EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(1);
+ session_->OnCongestionWindowChange(now);
+}
+
+TEST_P(QuicServerSessionBaseTest, BandwidthResumptionExperiment) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test relies on resumption, which is not currently supported by the
+ // TLS handshake.
+ // TODO(nharper): Add support for resumption to the TLS handshake.
+ return;
+ }
+ // Test that if a client provides a CachedNetworkParameters with the same
+ // serving region as the current server, and which was made within an hour of
+ // now, that this data is passed down to the send algorithm.
+
+ // Client has sent kBWRE connection option to trigger bandwidth resumption.
+ QuicTagVector copt;
+ copt.push_back(kBWRE);
+ QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+
+ const std::string kTestServingRegion = "a serving region";
+ session_->set_serving_region(kTestServingRegion);
+
+ // Set the time to be one hour + one second from the 0 baseline.
+ connection_->AdvanceTime(
+ QuicTime::Delta::FromSeconds(kNumSecondsPerHour + 1));
+
+ QuicCryptoServerStream* crypto_stream = static_cast<QuicCryptoServerStream*>(
+ QuicSessionPeer::GetMutableCryptoStream(session_.get()));
+
+ // No effect if no CachedNetworkParameters provided.
+ EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+ session_->OnConfigNegotiated();
+
+ // No effect if CachedNetworkParameters provided, but different serving
+ // regions.
+ CachedNetworkParameters cached_network_params;
+ cached_network_params.set_bandwidth_estimate_bytes_per_second(1);
+ cached_network_params.set_serving_region("different serving region");
+ crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+ EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+ session_->OnConfigNegotiated();
+
+ // Same serving region, but timestamp is too old, should have no effect.
+ cached_network_params.set_serving_region(kTestServingRegion);
+ cached_network_params.set_timestamp(0);
+ crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+ EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+ session_->OnConfigNegotiated();
+
+ // Same serving region, and timestamp is recent: estimate is stored.
+ cached_network_params.set_timestamp(
+ connection_->clock()->WallNow().ToUNIXSeconds());
+ crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+ EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(1);
+ session_->OnConfigNegotiated();
+}
+
+TEST_P(QuicServerSessionBaseTest, BandwidthMaxEnablesResumption) {
+ EXPECT_FALSE(
+ QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+
+ // Client has sent kBWMX connection option to trigger bandwidth resumption.
+ QuicTagVector copt;
+ copt.push_back(kBWMX);
+ QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+ session_->OnConfigNegotiated();
+ EXPECT_TRUE(
+ QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+}
+
+TEST_P(QuicServerSessionBaseTest, NoBandwidthResumptionByDefault) {
+ EXPECT_FALSE(
+ QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+ session_->OnConfigNegotiated();
+ EXPECT_FALSE(
+ QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+}
+
+// Tests which check the lifetime management of data members of
+// QuicCryptoServerStream objects when async GetProof is in use.
+class StreamMemberLifetimeTest : public QuicServerSessionBaseTest {
+ public:
+ StreamMemberLifetimeTest()
+ : QuicServerSessionBaseTest(
+ std::unique_ptr<FakeProofSource>(new FakeProofSource())),
+ crypto_config_peer_(&crypto_config_) {
+ GetFakeProofSource()->Activate();
+ }
+
+ FakeProofSource* GetFakeProofSource() const {
+ return static_cast<FakeProofSource*>(crypto_config_peer_.GetProofSource());
+ }
+
+ private:
+ QuicCryptoServerConfigPeer crypto_config_peer_;
+};
+
+INSTANTIATE_TEST_SUITE_P(StreamMemberLifetimeTests,
+ StreamMemberLifetimeTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+// Trigger an operation which causes an async invocation of
+// ProofSource::GetProof. Delay the completion of the operation until after the
+// stream has been destroyed, and verify that there are no memory bugs.
+TEST_P(StreamMemberLifetimeTest, Basic) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test depends on the QUIC crypto protocol, so it is disabled for the
+ // TLS handshake.
+ // TODO(nharper): Fix this test so it doesn't rely on QUIC crypto.
+ return;
+ }
+ SetQuicReloadableFlag(enable_quic_stateless_reject_support, true);
+ SetQuicReloadableFlag(quic_use_cheap_stateless_rejects, true);
+
+ const QuicClock* clock = helper_.GetClock();
+ CryptoHandshakeMessage chlo = crypto_test_utils::GenerateDefaultInchoateCHLO(
+ clock, GetParam().transport_version, &crypto_config_);
+ chlo.SetVector(kCOPT, QuicTagVector{kSREJ});
+ std::vector<ParsedQuicVersion> packet_version_list = {GetParam()};
+ std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+ TestConnectionId(1), EmptyQuicConnectionId(), true, false, 1,
+ std::string(chlo.GetSerialized().AsStringPiece()), CONNECTION_ID_PRESENT,
+ CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER, &packet_version_list));
+
+ EXPECT_CALL(stream_helper_, CanAcceptClientHello(_, _, _, _, _))
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(stream_helper_, GenerateConnectionIdForReject(_, _))
+ .WillOnce(testing::Return(TestConnectionId(12345)));
+
+ // Set the current packet
+ QuicConnectionPeer::SetCurrentPacket(session_->connection(),
+ packet->AsStringPiece());
+
+ // Yes, this is horrible. But it's the easiest way to trigger the behavior we
+ // need to exercise.
+ QuicCryptoServerStreamBase* crypto_stream =
+ const_cast<QuicCryptoServerStreamBase*>(session_->crypto_stream());
+
+ // Feed the CHLO into the crypto stream, which will trigger a call to
+ // ProofSource::GetProof
+ crypto_test_utils::SendHandshakeMessageToStream(crypto_stream, chlo,
+ Perspective::IS_CLIENT);
+ ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+ // Destroy the stream
+ session_.reset();
+
+ // Allow the async ProofSource::GetProof call to complete. Verify (under
+ // memory access checkers) that this does not result in accesses to any
+ // freed memory from the session or its subobjects.
+ GetFakeProofSource()->InvokePendingCallback(0);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc
new file mode 100644
index 00000000000..458b8a05e81
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2012 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_spdy_client_session.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_server_id.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_flag_utils.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"
+
+namespace quic {
+
+QuicSpdyClientSession::QuicSpdyClientSession(
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ const QuicServerId& server_id,
+ QuicCryptoClientConfig* crypto_config,
+ QuicClientPushPromiseIndex* push_promise_index)
+ : QuicSpdyClientSessionBase(connection,
+ push_promise_index,
+ config,
+ supported_versions),
+ server_id_(server_id),
+ crypto_config_(crypto_config),
+ respect_goaway_(true) {}
+
+QuicSpdyClientSession::~QuicSpdyClientSession() = default;
+
+void QuicSpdyClientSession::Initialize() {
+ crypto_stream_ = CreateQuicCryptoStream();
+ QuicSpdyClientSessionBase::Initialize();
+}
+
+void QuicSpdyClientSession::OnProofValid(
+ const QuicCryptoClientConfig::CachedState& /*cached*/) {}
+
+void QuicSpdyClientSession::OnProofVerifyDetailsAvailable(
+ const ProofVerifyDetails& /*verify_details*/) {}
+
+bool QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() {
+ if (!crypto_stream_->encryption_established()) {
+ QUIC_DLOG(INFO) << "Encryption not active so no outgoing stream created.";
+ return false;
+ }
+ if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+ connection()->transport_version() != QUIC_VERSION_99) {
+ if (GetNumOpenOutgoingStreams() >=
+ stream_id_manager().max_open_outgoing_streams()) {
+ QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already " << GetNumOpenOutgoingStreams() << " open.";
+ return false;
+ }
+ if (goaway_received() && respect_goaway_) {
+ QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already received goaway.";
+ return false;
+ }
+ return true;
+ }
+ if (goaway_received() && respect_goaway_) {
+ QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already received goaway.";
+ return false;
+ }
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_common_stream_check, 1, 2);
+ return CanOpenNextOutgoingBidirectionalStream();
+}
+
+bool QuicSpdyClientSession::ShouldCreateOutgoingUnidirectionalStream() {
+ QUIC_BUG << "Try to create outgoing unidirectional client data streams";
+ return false;
+}
+
+QuicSpdyClientStream*
+QuicSpdyClientSession::CreateOutgoingBidirectionalStream() {
+ if (!ShouldCreateOutgoingBidirectionalStream()) {
+ return nullptr;
+ }
+ std::unique_ptr<QuicSpdyClientStream> stream = CreateClientStream();
+ QuicSpdyClientStream* stream_ptr = stream.get();
+ ActivateStream(std::move(stream));
+ return stream_ptr;
+}
+
+QuicSpdyClientStream*
+QuicSpdyClientSession::CreateOutgoingUnidirectionalStream() {
+ QUIC_BUG << "Try to create outgoing unidirectional client data streams";
+ return nullptr;
+}
+
+std::unique_ptr<QuicSpdyClientStream>
+QuicSpdyClientSession::CreateClientStream() {
+ return QuicMakeUnique<QuicSpdyClientStream>(
+ GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+}
+
+QuicCryptoClientStreamBase* QuicSpdyClientSession::GetMutableCryptoStream() {
+ return crypto_stream_.get();
+}
+
+const QuicCryptoClientStreamBase* QuicSpdyClientSession::GetCryptoStream()
+ const {
+ return crypto_stream_.get();
+}
+
+void QuicSpdyClientSession::CryptoConnect() {
+ DCHECK(flow_controller());
+ crypto_stream_->CryptoConnect();
+}
+
+int QuicSpdyClientSession::GetNumSentClientHellos() const {
+ return crypto_stream_->num_sent_client_hellos();
+}
+
+int QuicSpdyClientSession::GetNumReceivedServerConfigUpdates() const {
+ return crypto_stream_->num_scup_messages_received();
+}
+
+bool QuicSpdyClientSession::ShouldCreateIncomingStream(QuicStreamId id) {
+ if (!connection()->connected()) {
+ QUIC_BUG << "ShouldCreateIncomingStream called when disconnected";
+ return false;
+ }
+ if (goaway_received() && respect_goaway_) {
+ QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already received goaway.";
+ return false;
+ }
+ if (QuicUtils::IsClientInitiatedStreamId(connection()->transport_version(),
+ id) ||
+ (connection()->transport_version() == QUIC_VERSION_99 &&
+ QuicUtils::IsBidirectionalStreamId(id))) {
+ QUIC_LOG(WARNING) << "Received invalid push stream id " << id;
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID,
+ "Server created non write unidirectional stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+ return true;
+}
+
+QuicSpdyStream* QuicSpdyClientSession::CreateIncomingStream(
+ PendingStream pending) {
+ QuicSpdyStream* stream =
+ new QuicSpdyClientStream(std::move(pending), this, READ_UNIDIRECTIONAL);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+}
+
+QuicSpdyStream* QuicSpdyClientSession::CreateIncomingStream(QuicStreamId id) {
+ if (!ShouldCreateIncomingStream(id)) {
+ return nullptr;
+ }
+ QuicSpdyStream* stream =
+ new QuicSpdyClientStream(id, this, READ_UNIDIRECTIONAL);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+}
+
+std::unique_ptr<QuicCryptoClientStreamBase>
+QuicSpdyClientSession::CreateQuicCryptoStream() {
+ return QuicMakeUnique<QuicCryptoClientStream>(
+ server_id_, this,
+ crypto_config_->proof_verifier()->CreateDefaultContext(), crypto_config_,
+ this);
+}
+
+bool QuicSpdyClientSession::IsAuthorized(const std::string& authority) {
+ return true;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h
new file mode 100644
index 00000000000..747033a2fe8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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.
+
+// A client specific QuicSession subclass.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
+
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+
+namespace quic {
+
+class QuicConnection;
+class QuicServerId;
+
+class QuicSpdyClientSession : public QuicSpdyClientSessionBase {
+ public:
+ // Takes ownership of |connection|. Caller retains ownership of
+ // |promised_by_url|.
+ QuicSpdyClientSession(const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ const QuicServerId& server_id,
+ QuicCryptoClientConfig* crypto_config,
+ QuicClientPushPromiseIndex* push_promise_index);
+ QuicSpdyClientSession(const QuicSpdyClientSession&) = delete;
+ QuicSpdyClientSession& operator=(const QuicSpdyClientSession&) = delete;
+ ~QuicSpdyClientSession() override;
+ // Set up the QuicSpdyClientSession. Must be called prior to use.
+ void Initialize() override;
+
+ // QuicSession methods:
+ QuicSpdyClientStream* CreateOutgoingBidirectionalStream() override;
+ QuicSpdyClientStream* CreateOutgoingUnidirectionalStream() override;
+ QuicCryptoClientStreamBase* GetMutableCryptoStream() override;
+ const QuicCryptoClientStreamBase* GetCryptoStream() const override;
+
+ bool IsAuthorized(const std::string& authority) override;
+
+ // QuicSpdyClientSessionBase methods:
+ void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
+ void OnProofVerifyDetailsAvailable(
+ const ProofVerifyDetails& verify_details) override;
+
+ // Performs a crypto handshake with the server.
+ virtual void CryptoConnect();
+
+ // Returns the number of client hello messages that have been sent on the
+ // crypto stream. If the handshake has completed then this is one greater
+ // than the number of round-trips needed for the handshake.
+ int GetNumSentClientHellos() const;
+
+ int GetNumReceivedServerConfigUpdates() const;
+
+ void set_respect_goaway(bool respect_goaway) {
+ respect_goaway_ = respect_goaway;
+ }
+
+ protected:
+ // QuicSession methods:
+ QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override;
+ QuicSpdyStream* CreateIncomingStream(PendingStream pending) override;
+ // If an outgoing stream can be created, return true.
+ bool ShouldCreateOutgoingBidirectionalStream() override;
+ bool ShouldCreateOutgoingUnidirectionalStream() override;
+
+ // If an incoming stream can be created, return true.
+ // TODO(fayang): move this up to QuicSpdyClientSessionBase.
+ bool ShouldCreateIncomingStream(QuicStreamId id) override;
+
+ // Create the crypto stream. Called by Initialize().
+ virtual std::unique_ptr<QuicCryptoClientStreamBase> CreateQuicCryptoStream();
+
+ // Unlike CreateOutgoingBidirectionalStream, which applies a bunch of
+ // sanity checks, this simply returns a new QuicSpdyClientStream. This may be
+ // used by subclasses which want to use a subclass of QuicSpdyClientStream for
+ // streams but wish to use the sanity checks in
+ // CreateOutgoingBidirectionalStream.
+ virtual std::unique_ptr<QuicSpdyClientStream> CreateClientStream();
+
+ const QuicServerId& server_id() { return server_id_; }
+ QuicCryptoClientConfig* crypto_config() { return crypto_config_; }
+
+ private:
+ std::unique_ptr<QuicCryptoClientStreamBase> crypto_stream_;
+ QuicServerId server_id_;
+ QuicCryptoClientConfig* crypto_config_;
+
+ // If this is set to false, the client will ignore server GOAWAYs and allow
+ // the creation of streams regardless of the high chance they will fail.
+ bool respect_goaway_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.cc
new file mode 100644
index 00000000000..d388d2e1931
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.cc
@@ -0,0 +1,210 @@
+// Copyright 2014 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_spdy_client_session_base.h"
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.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"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicSpdyClientSessionBase::QuicSpdyClientSessionBase(
+ QuicConnection* connection,
+ QuicClientPushPromiseIndex* push_promise_index,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions)
+ : QuicSpdySession(connection, nullptr, config, supported_versions),
+ push_promise_index_(push_promise_index),
+ largest_promised_stream_id_(
+ QuicUtils::GetInvalidStreamId(connection->transport_version())) {}
+
+QuicSpdyClientSessionBase::~QuicSpdyClientSessionBase() {
+ // all promised streams for this session
+ for (auto& it : promised_by_id_) {
+ QUIC_DVLOG(1) << "erase stream " << it.first << " url " << it.second->url();
+ push_promise_index_->promised_by_url()->erase(it.second->url());
+ }
+ delete connection();
+}
+
+void QuicSpdyClientSessionBase::OnConfigNegotiated() {
+ QuicSpdySession::OnConfigNegotiated();
+}
+
+void QuicSpdyClientSessionBase::OnCryptoHandshakeEvent(
+ CryptoHandshakeEvent event) {
+ QuicSpdySession::OnCryptoHandshakeEvent(event);
+}
+
+void QuicSpdyClientSessionBase::OnInitialHeadersComplete(
+ QuicStreamId stream_id,
+ const SpdyHeaderBlock& response_headers) {
+ // Note that the strong ordering of the headers stream means that
+ // QuicSpdyClientStream::OnPromiseHeadersComplete must have already
+ // been called (on the associated stream) if this is a promised
+ // stream. However, this stream may not have existed at this time,
+ // hence the need to query the session.
+ QuicClientPromisedInfo* promised = GetPromisedById(stream_id);
+ if (!promised)
+ return;
+
+ promised->OnResponseHeaders(response_headers);
+}
+
+void QuicSpdyClientSessionBase::OnPromiseHeaderList(
+ QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ if (QuicContainsKey(static_streams(), stream_id)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ if (promised_stream_id !=
+ QuicUtils::GetInvalidStreamId(connection()->transport_version()) &&
+ largest_promised_stream_id_ !=
+ QuicUtils::GetInvalidStreamId(connection()->transport_version()) &&
+ promised_stream_id <= largest_promised_stream_id_) {
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID,
+ "Received push stream id lesser or equal to the"
+ " last accepted before",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ if (!IsIncomingStream(promised_stream_id)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Received push stream id for outgoing stream.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ largest_promised_stream_id_ = promised_stream_id;
+
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnPromiseHeaderList(promised_stream_id, frame_len, header_list);
+}
+
+bool QuicSpdyClientSessionBase::HandlePromised(QuicStreamId /* associated_id */,
+ QuicStreamId promised_id,
+ const SpdyHeaderBlock& headers) {
+ // Due to pathalogical packet re-ordering, it is possible that
+ // frames for the promised stream have already arrived, and the
+ // promised stream could be active or closed.
+ if (IsClosedStream(promised_id)) {
+ // There was a RST on the data stream already, perhaps
+ // QUIC_REFUSED_STREAM?
+ QUIC_DVLOG(1) << "Promise ignored for stream " << promised_id
+ << " that is already closed";
+ return false;
+ }
+
+ if (push_promise_index_->promised_by_url()->size() >= get_max_promises()) {
+ QUIC_DVLOG(1) << "Too many promises, rejecting promise for stream "
+ << promised_id;
+ ResetPromised(promised_id, QUIC_REFUSED_STREAM);
+ return false;
+ }
+
+ const std::string url = SpdyUtils::GetPromisedUrlFromHeaders(headers);
+ QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url);
+ if (old_promised) {
+ QUIC_DVLOG(1) << "Promise for stream " << promised_id
+ << " is duplicate URL " << url
+ << " of previous promise for stream " << old_promised->id();
+ ResetPromised(promised_id, QUIC_DUPLICATE_PROMISE_URL);
+ return false;
+ }
+
+ if (GetPromisedById(promised_id)) {
+ // OnPromiseHeadersComplete() would have closed the connection if
+ // promised id is a duplicate.
+ QUIC_BUG << "Duplicate promise for id " << promised_id;
+ return false;
+ }
+
+ QuicClientPromisedInfo* promised =
+ new QuicClientPromisedInfo(this, promised_id, url);
+ std::unique_ptr<QuicClientPromisedInfo> promised_owner(promised);
+ promised->Init();
+ QUIC_DVLOG(1) << "stream " << promised_id << " emplace url " << url;
+ (*push_promise_index_->promised_by_url())[url] = promised;
+ promised_by_id_[promised_id] = std::move(promised_owner);
+ bool result = promised->OnPromiseHeaders(headers);
+ if (result) {
+ DCHECK(promised_by_id_.find(promised_id) != promised_by_id_.end());
+ }
+ return result;
+}
+
+QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedByUrl(
+ const std::string& url) {
+ auto it = push_promise_index_->promised_by_url()->find(url);
+ if (it != push_promise_index_->promised_by_url()->end()) {
+ return it->second;
+ }
+ return nullptr;
+}
+
+QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedById(
+ const QuicStreamId id) {
+ auto it = promised_by_id_.find(id);
+ if (it != promised_by_id_.end()) {
+ return it->second.get();
+ }
+ return nullptr;
+}
+
+QuicSpdyStream* QuicSpdyClientSessionBase::GetPromisedStream(
+ const QuicStreamId id) {
+ DynamicStreamMap::iterator it = dynamic_streams().find(id);
+ if (it != dynamic_streams().end()) {
+ return static_cast<QuicSpdyStream*>(it->second.get());
+ }
+ return nullptr;
+}
+
+void QuicSpdyClientSessionBase::DeletePromised(
+ QuicClientPromisedInfo* promised) {
+ push_promise_index_->promised_by_url()->erase(promised->url());
+ // Since promised_by_id_ contains the unique_ptr, this will destroy
+ // promised.
+ promised_by_id_.erase(promised->id());
+ headers_stream()->MaybeReleaseSequencerBuffer();
+}
+
+void QuicSpdyClientSessionBase::OnPushStreamTimedOut(QuicStreamId stream_id) {}
+
+void QuicSpdyClientSessionBase::ResetPromised(
+ QuicStreamId id,
+ QuicRstStreamErrorCode error_code) {
+ SendRstStream(id, error_code, 0);
+ if (!IsOpenStream(id)) {
+ MaybeIncreaseLargestPeerStreamId(id);
+ }
+}
+
+void QuicSpdyClientSessionBase::CloseStreamInner(QuicStreamId stream_id,
+ bool locally_reset) {
+ QuicSpdySession::CloseStreamInner(stream_id, locally_reset);
+ headers_stream()->MaybeReleaseSequencerBuffer();
+}
+
+bool QuicSpdyClientSessionBase::ShouldReleaseHeadersStreamSequencerBuffer() {
+ return !HasActiveRequestStreams() && promised_by_id_.empty();
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h
new file mode 100644
index 00000000000..aec5e75947f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h
@@ -0,0 +1,141 @@
+// Copyright 2014 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
+
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicClientPromisedInfo;
+class QuicClientPushPromiseIndex;
+class QuicSpdyClientStream;
+
+// For client/http layer code. Lookup promised streams based on
+// matching promised request url. The same map can be shared across
+// multiple sessions, since cross-origin pushes are allowed (subject
+// to authority constraints). Clients should use this map to enforce
+// session affinity for requests corresponding to cross-origin push
+// promised streams.
+using QuicPromisedByUrlMap =
+ QuicUnorderedMap<std::string, QuicClientPromisedInfo*>;
+
+// The maximum time a promises stream can be reserved without being
+// claimed by a client request.
+const int64_t kPushPromiseTimeoutSecs = 60;
+
+// Base class for all client-specific QuicSession subclasses.
+class QUIC_EXPORT_PRIVATE QuicSpdyClientSessionBase
+ : public QuicSpdySession,
+ public QuicCryptoClientStream::ProofHandler {
+ public:
+ // Takes ownership of |connection|. Caller retains ownership of
+ // |promised_by_url|.
+ QuicSpdyClientSessionBase(QuicConnection* connection,
+ QuicClientPushPromiseIndex* push_promise_index,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions);
+ QuicSpdyClientSessionBase(const QuicSpdyClientSessionBase&) = delete;
+ QuicSpdyClientSessionBase& operator=(const QuicSpdyClientSessionBase&) =
+ delete;
+
+ ~QuicSpdyClientSessionBase() override;
+
+ void OnConfigNegotiated() override;
+
+ // Override base class to set FEC policy before any data is sent by client.
+ void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+ // Called by |headers_stream_| when push promise headers have been
+ // completely received.
+ void OnPromiseHeaderList(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list) override;
+
+ // Called by |QuicSpdyClientStream| on receipt of response headers,
+ // needed to detect promised server push streams, as part of
+ // client-request to push-stream rendezvous.
+ void OnInitialHeadersComplete(QuicStreamId stream_id,
+ const spdy::SpdyHeaderBlock& response_headers);
+
+ // Called by |QuicSpdyClientStream| on receipt of PUSH_PROMISE, does
+ // some session level validation and creates the
+ // |QuicClientPromisedInfo| inserting into maps by (promised) id and
+ // url. Returns true if a new push promise is accepted. Resets the promised
+ // stream and returns false otherwise.
+ virtual bool HandlePromised(QuicStreamId associated_id,
+ QuicStreamId promised_id,
+ const spdy::SpdyHeaderBlock& headers);
+
+ // For cross-origin server push, this should verify the server is
+ // authoritative per [RFC2818], Section 3. Roughly, subjectAltName
+ // list in the certificate should contain a matching DNS name, or IP
+ // address. |hostname| is derived from the ":authority" header field of
+ // the PUSH_PROMISE frame, port if present there will be dropped.
+ virtual bool IsAuthorized(const std::string& hostname) = 0;
+
+ // Session retains ownership.
+ QuicClientPromisedInfo* GetPromisedByUrl(const std::string& url);
+ // Session retains ownership.
+ QuicClientPromisedInfo* GetPromisedById(const QuicStreamId id);
+
+ //
+ QuicSpdyStream* GetPromisedStream(const QuicStreamId id);
+
+ // Removes |promised| from the maps by url.
+ void ErasePromisedByUrl(QuicClientPromisedInfo* promised);
+
+ // Removes |promised| from the maps by url and id and destroys
+ // promised.
+ virtual void DeletePromised(QuicClientPromisedInfo* promised);
+
+ virtual void OnPushStreamTimedOut(QuicStreamId stream_id);
+
+ // Sends Rst for the stream, and makes sure that future calls to
+ // IsClosedStream(id) return true, which ensures that any subsequent
+ // frames related to this stream will be ignored (modulo flow
+ // control accounting).
+ void ResetPromised(QuicStreamId id, QuicRstStreamErrorCode error_code);
+
+ // Release headers stream's sequencer buffer if it's empty.
+ void CloseStreamInner(QuicStreamId stream_id, bool locally_reset) override;
+
+ // Returns true if there are no active requests and no promised streams.
+ bool ShouldReleaseHeadersStreamSequencerBuffer() override;
+
+ size_t get_max_promises() const {
+ return max_open_incoming_unidirectional_streams() *
+ kMaxPromisedStreamsMultiplier;
+ }
+
+ QuicClientPushPromiseIndex* push_promise_index() {
+ return push_promise_index_;
+ }
+
+ private:
+ // For QuicSpdyClientStream to detect that a response corresponds to a
+ // promise.
+ using QuicPromisedByIdMap =
+ QuicUnorderedMap<QuicStreamId, std::unique_ptr<QuicClientPromisedInfo>>;
+
+ // As per rfc7540, section 10.5: track promise streams in "reserved
+ // (remote)". The primary key is URL from the promise request
+ // headers. The promised stream id is a secondary key used to get
+ // promise info when the response headers of the promised stream
+ // arrive.
+ QuicClientPushPromiseIndex* push_promise_index_;
+ QuicPromisedByIdMap promised_by_id_;
+ QuicStreamId largest_promised_stream_id_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_test.cc
new file mode 100644
index 00000000000..9a99ca7bfc8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_test.cc
@@ -0,0 +1,803 @@
+// Copyright (c) 2012 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_spdy_client_session.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_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_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::AnyNumber;
+using testing::Invoke;
+using testing::Truly;
+
+namespace quic {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "test.example.com";
+const uint16_t kPort = 443;
+
+class TestQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+ explicit TestQuicSpdyClientSession(
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ const QuicServerId& server_id,
+ QuicCryptoClientConfig* crypto_config,
+ QuicClientPushPromiseIndex* push_promise_index)
+ : QuicSpdyClientSession(config,
+ supported_versions,
+ connection,
+ server_id,
+ crypto_config,
+ push_promise_index) {}
+
+ std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override {
+ return QuicMakeUnique<MockQuicSpdyClientStream>(
+ GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+ }
+
+ MockQuicSpdyClientStream* CreateIncomingStream(QuicStreamId id) override {
+ if (!ShouldCreateIncomingStream(id)) {
+ return nullptr;
+ }
+ MockQuicSpdyClientStream* stream =
+ new MockQuicSpdyClientStream(id, this, READ_UNIDIRECTIONAL);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+};
+
+class QuicSpdyClientSessionTest : public QuicTestWithParam<ParsedQuicVersion> {
+ protected:
+ QuicSpdyClientSessionTest()
+ : crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+ TlsClientHandshaker::CreateSslCtx()),
+ promised_stream_id_(
+ QuicUtils::GetInvalidStreamId(GetParam().transport_version)),
+ associated_stream_id_(
+ QuicUtils::GetInvalidStreamId(GetParam().transport_version)) {
+ Initialize();
+ // Advance the time, because timers do not like uninitialized times.
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ }
+
+ ~QuicSpdyClientSessionTest() override {
+ // Session must be destroyed before promised_by_url_
+ session_.reset(nullptr);
+ }
+
+ void Initialize() {
+ session_.reset();
+ connection_ = new PacketSavingConnection(&helper_, &alarm_factory_,
+ Perspective::IS_CLIENT,
+ SupportedVersions(GetParam()));
+ session_ = QuicMakeUnique<TestQuicSpdyClientSession>(
+ DefaultQuicConfig(), SupportedVersions(GetParam()), connection_,
+ QuicServerId(kServerHostname, kPort, false), &crypto_config_,
+ &push_promise_index_);
+ session_->Initialize();
+ push_promise_[":path"] = "/bar";
+ push_promise_[":authority"] = "www.google.com";
+ push_promise_[":version"] = "HTTP/1.1";
+ push_promise_[":method"] = "GET";
+ push_promise_[":scheme"] = "https";
+ promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
+ promised_stream_id_ = GetNthServerInitiatedUnidirectionalStreamId(
+ connection_->transport_version(), 0);
+ associated_stream_id_ = GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0);
+ }
+
+ // The function ensures that A) the max stream id frames get properly deleted
+ // (since the test uses a 'did we leak memory' check ... if we just lose the
+ // frame, the test fails) and B) returns true (instead of the default, false)
+ // which ensures that the rest of the system thinks that the frame actually
+ // was transmitted.
+ bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
+ if (frame.type == MAX_STREAM_ID_FRAME) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+ return false;
+ }
+
+ public:
+ bool ClearStreamIdBlockedControlFrame(const QuicFrame& frame) {
+ if (frame.type == STREAM_ID_BLOCKED_FRAME) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ void CompleteCryptoHandshake() {
+ CompleteCryptoHandshake(kDefaultMaxStreamsPerConnection);
+ }
+
+ void CompleteCryptoHandshake(uint32_t server_max_incoming_streams) {
+ if (connection_->transport_version() == QUIC_VERSION_99) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .Times(testing::AnyNumber())
+ .WillRepeatedly(Invoke(
+ this, &QuicSpdyClientSessionTest::ClearMaxStreamIdControlFrame));
+ }
+ session_->CryptoConnect();
+ QuicCryptoClientStream* stream = static_cast<QuicCryptoClientStream*>(
+ session_->GetMutableCryptoStream());
+ crypto_test_utils::FakeServerOptions options;
+ QuicConfig config = DefaultQuicConfig();
+ config.SetMaxIncomingDynamicStreamsToSend(server_max_incoming_streams);
+ crypto_test_utils::HandshakeWithFakeServer(
+ &config, &helper_, &alarm_factory_, connection_, stream, options);
+ }
+
+ QuicCryptoClientConfig crypto_config_;
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ PacketSavingConnection* connection_;
+ std::unique_ptr<TestQuicSpdyClientSession> session_;
+ QuicClientPushPromiseIndex push_promise_index_;
+ SpdyHeaderBlock push_promise_;
+ std::string promise_url_;
+ QuicStreamId promised_stream_id_;
+ QuicStreamId associated_stream_id_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSpdyClientSessionTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyClientSessionTest, CryptoConnect) {
+ CompleteCryptoHandshake();
+}
+
+TEST_P(QuicSpdyClientSessionTest, NoEncryptionAfterInitialEncryption) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test relies on resumption and is QUIC crypto specific, so it is
+ // disabled for TLS.
+ // TODO(nharper): Add support for resumption to the TLS handshake, and fix
+ // this test to not rely on QUIC crypto.
+ return;
+ }
+ // Complete a handshake in order to prime the crypto config for 0-RTT.
+ CompleteCryptoHandshake();
+
+ // Now create a second session using the same crypto config.
+ Initialize();
+
+ EXPECT_CALL(*connection_, OnCanWrite());
+ // Starting the handshake should move immediately to encryption
+ // established and will allow streams to be created.
+ session_->CryptoConnect();
+ EXPECT_TRUE(session_->IsEncryptionEstablished());
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_TRUE(stream != nullptr);
+ if (!QuicVersionUsesCryptoFrames(GetParam().transport_version)) {
+ EXPECT_NE(QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+ stream->id());
+ }
+
+ // Process an "inchoate" REJ from the server which will cause
+ // an inchoate CHLO to be sent and will leave the encryption level
+ // at NONE.
+ CryptoHandshakeMessage rej;
+ crypto_test_utils::FillInDummyReject(&rej, /* stateless */ false);
+ EXPECT_TRUE(session_->IsEncryptionEstablished());
+ crypto_test_utils::SendHandshakeMessageToStream(
+ session_->GetMutableCryptoStream(), rej, Perspective::IS_CLIENT);
+ EXPECT_FALSE(session_->IsEncryptionEstablished());
+ EXPECT_EQ(ENCRYPTION_INITIAL,
+ QuicPacketCreatorPeer::GetEncryptionLevel(
+ QuicConnectionPeer::GetPacketCreator(connection_)));
+ // Verify that no new streams may be created.
+ EXPECT_TRUE(session_->CreateOutgoingBidirectionalStream() == nullptr);
+ // Verify that no data may be send on existing streams.
+ char data[] = "hello world";
+ QuicConsumedData consumed = session_->WritevData(
+ stream, stream->id(), QUIC_ARRAYSIZE(data), 0, NO_FIN);
+ EXPECT_FALSE(consumed.fin_consumed);
+ EXPECT_EQ(0u, consumed.bytes_consumed);
+}
+
+TEST_P(QuicSpdyClientSessionTest, MaxNumStreamsWithNoFinOrRst) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test relies on the MIDS transport parameter, which is not yet
+ // supported in TLS 1.3.
+ // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+ return;
+ }
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
+ EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
+
+ const uint32_t kServerMaxIncomingStreams = 1;
+ CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_TRUE(stream);
+ EXPECT_FALSE(session_->CreateOutgoingBidirectionalStream());
+
+ // Close the stream, but without having received a FIN or a RST_STREAM
+ // or MAX_STREAM_ID (V99) and check that a new one can not be created.
+ session_->CloseStream(stream->id());
+ EXPECT_EQ(1u, session_->GetNumOpenOutgoingStreams());
+
+ stream = session_->CreateOutgoingBidirectionalStream();
+ EXPECT_FALSE(stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, MaxNumStreamsWithRst) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test relies on the MIDS transport parameter, which is not yet
+ // supported in TLS 1.3.
+ // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+ return;
+ }
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
+ EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
+
+ const uint32_t kServerMaxIncomingStreams = 1;
+ CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_NE(nullptr, stream);
+ EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+
+ // Close the stream and receive an RST frame to remove the unfinished stream
+ session_->CloseStream(stream->id());
+ session_->OnRstStream(QuicRstStreamFrame(kInvalidControlFrameId, stream->id(),
+ QUIC_RST_ACKNOWLEDGEMENT, 0));
+ // Check that a new one can be created.
+ EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
+ if (GetParam().transport_version == QUIC_VERSION_99) {
+ // In V99 the stream limit increases only if we get a MAX_STREAM_ID
+ // frame; pretend we got one.
+
+ // Note that this is to be the second stream created, but GetNth... starts
+ // numbering at 0 (the first stream is 0, second is 1...)
+ QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 1));
+ session_->OnMaxStreamIdFrame(frame);
+ }
+ stream = session_->CreateOutgoingBidirectionalStream();
+ EXPECT_NE(nullptr, stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ResetAndTrailers) {
+ if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+ // This test relies on the MIDS transport parameter, which is not yet
+ // supported in TLS 1.3.
+ // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+ return;
+ }
+ // Tests the situation in which the client sends a RST at the same time that
+ // the server sends trailing headers (trailers). Receipt of the trailers by
+ // the client should result in all outstanding stream state being tidied up
+ // (including flow control, and number of available outgoing streams).
+ const uint32_t kServerMaxIncomingStreams = 1;
+ CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_NE(nullptr, stream);
+
+ if (GetParam().transport_version == QUIC_VERSION_99) {
+ // For v99, trying to open a stream and failing due to lack
+ // of stream ids will result in a STREAM_ID_BLOCKED. Make
+ // sure we get one. Also clear out the frame because if it's
+ // left sitting, the later SendRstStream will not actually
+ // transmit the RST_STREAM because the connection will be in write-blocked
+ // state. This means that the SendControlFrame that is expected w.r.t. the
+ // RST_STREAM, below, will not be satisfied.
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(
+ this,
+ &QuicSpdyClientSessionTest::ClearStreamIdBlockedControlFrame));
+ }
+
+ EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+
+ QuicStreamId stream_id = stream->id();
+
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+ EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(1);
+ session_->SendRstStream(stream_id, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+ // A new stream cannot be created as the reset stream still counts as an open
+ // outgoing stream until closed by the server.
+ EXPECT_EQ(1u, session_->GetNumOpenOutgoingStreams());
+ stream = session_->CreateOutgoingBidirectionalStream();
+ EXPECT_EQ(nullptr, stream);
+
+ // The stream receives trailers with final byte offset: this is one of three
+ // ways that a peer can signal the end of a stream (the others being RST,
+ // stream data + FIN).
+ QuicHeaderList trailers;
+ trailers.OnHeaderBlockStart();
+ trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+ trailers.OnHeaderBlockEnd(0, 0);
+ session_->OnStreamHeaderList(stream_id, /*fin=*/false, 0, trailers);
+
+ // The stream is now complete from the client's perspective, and it should
+ // be able to create a new outgoing stream.
+ EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
+ if (GetParam().transport_version == QUIC_VERSION_99) {
+ // Note that this is to be the second stream created, but GetNth... starts
+ // numbering at 0 (the first stream is 0, second is 1...)
+ QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 1));
+ session_->OnMaxStreamIdFrame(frame);
+ }
+ stream = session_->CreateOutgoingBidirectionalStream();
+ EXPECT_NE(nullptr, stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ReceivedMalformedTrailersAfterSendingRst) {
+ // Tests the situation where the client has sent a RST to the server, and has
+ // received trailing headers with a malformed final byte offset value.
+ CompleteCryptoHandshake();
+
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_NE(nullptr, stream);
+
+ // Send the RST, which results in the stream being closed locally (but some
+ // state remains while the client waits for a response from the server).
+ QuicStreamId stream_id = stream->id();
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+ EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(1);
+ session_->SendRstStream(stream_id, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+ // The stream receives trailers with final byte offset, but the header value
+ // is non-numeric and should be treated as malformed.
+ QuicHeaderList trailers;
+ trailers.OnHeaderBlockStart();
+ trailers.OnHeader(kFinalOffsetHeaderKey, "invalid non-numeric value");
+ trailers.OnHeaderBlockEnd(0, 0);
+
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ session_->OnStreamHeaderList(stream_id, /*fin=*/false, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnStreamHeaderListWithStaticStream) {
+ // Test situation where OnStreamHeaderList is called by stream with static id.
+ CompleteCryptoHandshake();
+
+ QuicHeaderList trailers;
+ trailers.OnHeaderBlockStart();
+ trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+ trailers.OnHeaderBlockEnd(0, 0);
+
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ session_->OnStreamHeaderList(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+ /*fin=*/false, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnPromiseHeaderListWithStaticStream) {
+ // Test situation where OnPromiseHeaderList is called by stream with static
+ // id.
+ CompleteCryptoHandshake();
+
+ QuicHeaderList trailers;
+ trailers.OnHeaderBlockStart();
+ trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+ trailers.OnHeaderBlockEnd(0, 0);
+
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ session_->OnPromiseHeaderList(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+ promised_stream_id_, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, GoAwayReceived) {
+ if (connection_->transport_version() == QUIC_VERSION_99) {
+ return;
+ }
+ CompleteCryptoHandshake();
+
+ // After receiving a GoAway, I should no longer be able to create outgoing
+ // streams.
+ session_->connection()->OnGoAwayFrame(QuicGoAwayFrame(
+ kInvalidControlFrameId, QUIC_PEER_GOING_AWAY, 1u, "Going away."));
+ EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+}
+
+static bool CheckForDecryptionError(QuicFramer* framer) {
+ return framer->error() == QUIC_DECRYPTION_FAILURE;
+}
+
+// Various sorts of invalid packets that should not cause a connection
+// to be closed.
+TEST_P(QuicSpdyClientSessionTest, InvalidPacketReceived) {
+ QuicSocketAddress server_address(TestPeerIPAddress(), kTestPort);
+ QuicSocketAddress client_address(TestPeerIPAddress(), kTestPort);
+
+ EXPECT_CALL(*connection_, ProcessUdpPacket(server_address, client_address, _))
+ .WillRepeatedly(Invoke(static_cast<MockQuicConnection*>(connection_),
+ &MockQuicConnection::ReallyProcessUdpPacket));
+ EXPECT_CALL(*connection_, OnCanWrite()).Times(AnyNumber());
+ EXPECT_CALL(*connection_, OnError(_)).Times(1);
+
+ // Verify that empty packets don't close the connection.
+ QuicReceivedPacket zero_length_packet(nullptr, 0, QuicTime::Zero(), false);
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ session_->ProcessUdpPacket(client_address, server_address,
+ zero_length_packet);
+
+ // Verifiy that small, invalid packets don't close the connection.
+ char buf[2] = {0x00, 0x01};
+ QuicConnectionId connection_id = session_->connection()->connection_id();
+ QuicReceivedPacket valid_packet(buf, 2, QuicTime::Zero(), false);
+ // Close connection shouldn't be called.
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ if (connection_->transport_version() > QUIC_VERSION_44) {
+ // Illegal fixed bit value.
+ EXPECT_CALL(*connection_, OnError(_)).Times(1);
+ }
+ session_->ProcessUdpPacket(client_address, server_address, valid_packet);
+
+ // Verify that a non-decryptable packet doesn't close the connection.
+ QuicFramerPeer::SetLastSerializedConnectionId(
+ QuicConnectionPeer::GetFramer(connection_), connection_id);
+ ParsedQuicVersionVector versions = SupportedVersions(GetParam());
+ std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+ connection_id, EmptyQuicConnectionId(), false, false, 100, "data",
+ CONNECTION_ID_ABSENT, CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER,
+ &versions, Perspective::IS_SERVER));
+ std::unique_ptr<QuicReceivedPacket> received(
+ ConstructReceivedPacket(*packet, QuicTime::Zero()));
+ // Change the last byte of the encrypted data.
+ *(const_cast<char*>(received->data() + received->length() - 1)) += 1;
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ EXPECT_CALL(*connection_, OnError(Truly(CheckForDecryptionError))).Times(1);
+ session_->ProcessUdpPacket(client_address, server_address, *received);
+}
+
+// A packet with invalid framing should cause a connection to be closed.
+TEST_P(QuicSpdyClientSessionTest, InvalidFramedPacketReceived) {
+ QuicSocketAddress server_address(TestPeerIPAddress(), kTestPort);
+ QuicSocketAddress client_address(TestPeerIPAddress(), kTestPort);
+
+ EXPECT_CALL(*connection_, ProcessUdpPacket(server_address, client_address, _))
+ .WillRepeatedly(Invoke(static_cast<MockQuicConnection*>(connection_),
+ &MockQuicConnection::ReallyProcessUdpPacket));
+ EXPECT_CALL(*connection_, OnError(_)).Times(1);
+
+ // Verify that a decryptable packet with bad frames does close the connection.
+ QuicConnectionId destination_connection_id =
+ session_->connection()->connection_id();
+ QuicConnectionId source_connection_id = EmptyQuicConnectionId();
+ QuicFramerPeer::SetLastSerializedConnectionId(
+ QuicConnectionPeer::GetFramer(connection_), destination_connection_id);
+ ParsedQuicVersionVector versions = {GetParam()};
+ bool version_flag = false;
+ QuicConnectionIdIncluded scid_included = CONNECTION_ID_ABSENT;
+ if (GetParam().transport_version > QUIC_VERSION_43) {
+ version_flag = true;
+ source_connection_id = destination_connection_id;
+ scid_included = CONNECTION_ID_PRESENT;
+ }
+ std::unique_ptr<QuicEncryptedPacket> packet(ConstructMisFramedEncryptedPacket(
+ destination_connection_id, source_connection_id, version_flag, false, 100,
+ "data", CONNECTION_ID_ABSENT, scid_included, PACKET_4BYTE_PACKET_NUMBER,
+ &versions, Perspective::IS_SERVER));
+ std::unique_ptr<QuicReceivedPacket> received(
+ ConstructReceivedPacket(*packet, QuicTime::Zero()));
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ session_->ProcessUdpPacket(client_address, server_address, *received);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOnPromiseHeaders) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+ session_->CreateOutgoingBidirectionalStream());
+
+ EXPECT_CALL(*stream, OnPromiseHeaderList(_, _, _));
+ session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+ QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOnPromiseHeadersAlreadyClosed) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(associated_stream_id_, QUIC_REFUSED_STREAM));
+ session_->ResetPromised(associated_stream_id_, QUIC_REFUSED_STREAM);
+
+ session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+ QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOutOfOrder) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+ session_->CreateOutgoingBidirectionalStream());
+
+ EXPECT_CALL(*stream, OnPromiseHeaderList(promised_stream_id_, _, _));
+ session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+ QuicHeaderList());
+ associated_stream_id_ +=
+ QuicUtils::StreamIdDelta(connection_->transport_version());
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Received push stream id lesser or equal to the"
+ " last accepted before",
+ _));
+ session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+ QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOutgoingStreamId) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+ session_->CreateOutgoingBidirectionalStream());
+
+ // Promise an illegal (outgoing) stream id.
+ promised_stream_id_ = GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0);
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Received push stream id for outgoing stream.", _));
+
+ session_->OnPromiseHeaderList(stream->id(), promised_stream_id_, 0,
+ QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseHandlePromise) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+
+ EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+
+ EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseAlreadyClosed) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+ session_->GetOrCreateStream(promised_stream_id_);
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+
+ session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+ SpdyHeaderBlock promise_headers;
+ EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, promise_headers));
+
+ // Verify that the promise was not created.
+ EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseDuplicateUrl) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+
+ EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+
+ EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ promised_stream_id_ +=
+ QuicUtils::StreamIdDelta(connection_->transport_version());
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_DUPLICATE_PROMISE_URL));
+
+ EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+
+ // Verify that the promise was not created.
+ EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) {
+ for (size_t i = 0u; i < session_->get_max_promises(); i++) {
+ push_promise_[":path"] = QuicStringPrintf("/bar%zu", i);
+
+ QuicStreamId id =
+ promised_stream_id_ +
+ i * QuicUtils::StreamIdDelta(connection_->transport_version());
+
+ EXPECT_TRUE(
+ session_->HandlePromised(associated_stream_id_, id, push_promise_));
+
+ // Verify that the promise is in the unclaimed streams map.
+ std::string promise_url(
+ SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url), nullptr);
+ EXPECT_NE(session_->GetPromisedById(id), nullptr);
+ }
+
+ // One more promise, this should be refused.
+ int i = session_->get_max_promises();
+ push_promise_[":path"] = QuicStringPrintf("/bar%d", i);
+
+ QuicStreamId id =
+ promised_stream_id_ +
+ i * QuicUtils::StreamIdDelta(connection_->transport_version());
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(id, QUIC_REFUSED_STREAM));
+ EXPECT_FALSE(
+ session_->HandlePromised(associated_stream_id_, id, push_promise_));
+
+ // Verify that the promise was not created.
+ std::string promise_url(SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
+ EXPECT_EQ(session_->GetPromisedById(id), nullptr);
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, IsClosedTrueAfterResetPromisedAlreadyOpen) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->GetOrCreateStream(promised_stream_id_);
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+ session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+ EXPECT_TRUE(session_->IsClosedStream(promised_stream_id_));
+}
+
+TEST_P(QuicSpdyClientSessionTest, IsClosedTrueAfterResetPromisedNonexistant) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+ session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+ EXPECT_TRUE(session_->IsClosedStream(promised_stream_id_));
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnInitialHeadersCompleteIsPush) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+ session_->GetOrCreateStream(promised_stream_id_);
+ EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+ EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_NE(session_->GetPromisedStream(promised_stream_id_), nullptr);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ session_->OnInitialHeadersComplete(promised_stream_id_, SpdyHeaderBlock());
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnInitialHeadersCompleteIsNotPush) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+ session_->CreateOutgoingBidirectionalStream();
+ session_->OnInitialHeadersComplete(promised_stream_id_, SpdyHeaderBlock());
+}
+
+TEST_P(QuicSpdyClientSessionTest, DeletePromised) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+ session_->GetOrCreateStream(promised_stream_id_);
+ EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+ QuicClientPromisedInfo* promised =
+ session_->GetPromisedById(promised_stream_id_);
+ EXPECT_NE(promised, nullptr);
+ EXPECT_NE(session_->GetPromisedStream(promised_stream_id_), nullptr);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ session_->DeletePromised(promised);
+ EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ResetPromised) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+ session_->GetOrCreateStream(promised_stream_id_);
+ EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_STREAM_PEER_GOING_AWAY));
+ session_->SendRstStream(promised_stream_id_, QUIC_STREAM_PEER_GOING_AWAY, 0);
+ QuicClientPromisedInfo* promised =
+ session_->GetPromisedById(promised_stream_id_);
+ EXPECT_NE(promised, nullptr);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+ EXPECT_EQ(session_->GetPromisedStream(promised_stream_id_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseInvalidMethod) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_INVALID_PROMISE_METHOD));
+
+ push_promise_[":method"] = "POST";
+ EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+
+ EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseInvalidHost) {
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ session_->CreateOutgoingBidirectionalStream();
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(promised_stream_id_, QUIC_INVALID_PROMISE_URL));
+
+ push_promise_[":authority"] = "";
+ EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+ promised_stream_id_, push_promise_));
+
+ EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest,
+ TryToCreateServerInitiatedBidirectionalStream) {
+ if (connection_->transport_version() == QUIC_VERSION_99) {
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ } else {
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ }
+ session_->GetOrCreateStream(GetNthServerInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0));
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.cc
new file mode 100644
index 00000000000..d4e8a69bb17
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 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_spdy_client_stream.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
+ QuicSpdyClientSession* session,
+ StreamType type)
+ : QuicSpdyStream(id, session, type),
+ content_length_(-1),
+ response_code_(0),
+ header_bytes_read_(0),
+ header_bytes_written_(0),
+ session_(session),
+ has_preliminary_headers_(false) {}
+
+QuicSpdyClientStream::QuicSpdyClientStream(PendingStream pending,
+ QuicSpdyClientSession* session,
+ StreamType type)
+ : QuicSpdyStream(std::move(pending), session, type),
+ content_length_(-1),
+ response_code_(0),
+ header_bytes_read_(0),
+ header_bytes_written_(0),
+ session_(session),
+ has_preliminary_headers_(false) {}
+
+QuicSpdyClientStream::~QuicSpdyClientStream() = default;
+
+void QuicSpdyClientStream::OnInitialHeadersComplete(
+ bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
+
+ DCHECK(headers_decompressed());
+ header_bytes_read_ += frame_len;
+ if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
+ &response_headers_)) {
+ QUIC_DLOG(ERROR) << "Failed to parse header list: "
+ << header_list.DebugString();
+ Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+
+ if (!ParseHeaderStatusCode(response_headers_, &response_code_)) {
+ QUIC_DLOG(ERROR) << "Received invalid response code: "
+ << response_headers_[":status"].as_string();
+ Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+
+ if (response_code_ == 100 && !has_preliminary_headers_) {
+ // These are preliminary 100 Continue headers, not the actual response
+ // headers.
+ set_headers_decompressed(false);
+ has_preliminary_headers_ = true;
+ preliminary_headers_ = std::move(response_headers_);
+ }
+
+ ConsumeHeaderList();
+ QUIC_DVLOG(1) << "headers complete for stream " << id();
+
+ session_->OnInitialHeadersComplete(id(), response_headers_);
+}
+
+void QuicSpdyClientStream::OnTrailingHeadersComplete(
+ bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
+ MarkTrailersConsumed();
+}
+
+void QuicSpdyClientStream::OnPromiseHeaderList(
+ QuicStreamId promised_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ header_bytes_read_ += frame_len;
+ int64_t content_length = -1;
+ SpdyHeaderBlock promise_headers;
+ if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
+ &promise_headers)) {
+ QUIC_DLOG(ERROR) << "Failed to parse promise headers: "
+ << header_list.DebugString();
+ Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+
+ session_->HandlePromised(id(), promised_id, promise_headers);
+ if (visitor() != nullptr) {
+ visitor()->OnPromiseHeadersComplete(promised_id, frame_len);
+ }
+}
+
+void QuicSpdyClientStream::OnBodyAvailable() {
+ // For push streams, visitor will not be set until the rendezvous
+ // between server promise and client request is complete.
+ if (visitor() == nullptr)
+ return;
+
+ while (HasBytesToRead()) {
+ struct iovec iov;
+ if (GetReadableRegions(&iov, 1) == 0) {
+ // No more data to read.
+ break;
+ }
+ QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
+ << id();
+ data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
+
+ if (content_length_ >= 0 &&
+ data_.size() > static_cast<uint64_t>(content_length_)) {
+ QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
+ << ") with data of size " << data_.size();
+ Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+ MarkConsumed(iov.iov_len);
+ }
+ if (sequencer()->IsClosed()) {
+ OnFinRead();
+ } else {
+ sequencer()->SetUnblocked();
+ }
+}
+
+size_t QuicSpdyClientStream::SendRequest(SpdyHeaderBlock headers,
+ QuicStringPiece body,
+ bool fin) {
+ QuicConnection::ScopedPacketFlusher flusher(
+ session_->connection(), QuicConnection::SEND_ACK_IF_QUEUED);
+ bool send_fin_with_headers = fin && body.empty();
+ size_t bytes_sent = body.size();
+ header_bytes_written_ =
+ WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
+ bytes_sent += header_bytes_written_;
+
+ if (!body.empty()) {
+ WriteOrBufferBody(body, fin);
+ }
+
+ return bytes_sent;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h
new file mode 100644
index 00000000000..ba59ca622ef
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
+
+#include <cstddef>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QuicSpdyClientSession;
+
+// All this does right now is send an SPDY request, and aggregate the
+// SPDY response.
+class QuicSpdyClientStream : public QuicSpdyStream {
+ public:
+ QuicSpdyClientStream(QuicStreamId id,
+ QuicSpdyClientSession* session,
+ StreamType type);
+ QuicSpdyClientStream(PendingStream pending,
+ QuicSpdyClientSession* spdy_session,
+ StreamType type);
+ QuicSpdyClientStream(const QuicSpdyClientStream&) = delete;
+ QuicSpdyClientStream& operator=(const QuicSpdyClientStream&) = delete;
+ ~QuicSpdyClientStream() override;
+
+ // Override the base class to parse and store headers.
+ void OnInitialHeadersComplete(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) override;
+
+ // Override the base class to parse and store trailers.
+ void OnTrailingHeadersComplete(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) override;
+
+ // Override the base class to handle creation of the push stream.
+ void OnPromiseHeaderList(QuicStreamId promised_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list) override;
+
+ // QuicStream implementation called by the session when there's data for us.
+ void OnBodyAvailable() override;
+
+ // Serializes the headers and body, sends it to the server, and
+ // returns the number of bytes sent.
+ size_t SendRequest(spdy::SpdyHeaderBlock headers,
+ QuicStringPiece body,
+ bool fin);
+
+ // Returns the response data.
+ const std::string& data() { return data_; }
+
+ // Returns whatever headers have been received for this stream.
+ const spdy::SpdyHeaderBlock& response_headers() { return response_headers_; }
+
+ const spdy::SpdyHeaderBlock& preliminary_headers() {
+ return preliminary_headers_;
+ }
+
+ size_t header_bytes_read() const { return header_bytes_read_; }
+
+ size_t header_bytes_written() const { return header_bytes_written_; }
+
+ int response_code() const { return response_code_; }
+
+ // While the server's SetPriority shouldn't be called externally, the creator
+ // of client-side streams should be able to set the priority.
+ using QuicSpdyStream::SetPriority;
+
+ private:
+ // The parsed headers received from the server.
+ spdy::SpdyHeaderBlock response_headers_;
+
+ // The parsed content-length, or -1 if none is specified.
+ int64_t content_length_;
+ int response_code_;
+ std::string data_;
+ size_t header_bytes_read_;
+ size_t header_bytes_written_;
+
+ QuicSpdyClientSession* session_;
+
+ // These preliminary headers are used for the 100 Continue headers
+ // that may arrive before the response headers when the request has
+ // Expect: 100-continue.
+ bool has_preliminary_headers_;
+ spdy::SpdyHeaderBlock preliminary_headers_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc
new file mode 100644
index 00000000000..007bba08e48
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc
@@ -0,0 +1,232 @@
+// 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_spdy_client_stream.h"
+
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.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_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.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_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+ explicit MockQuicSpdyClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ QuicClientPushPromiseIndex* push_promise_index)
+ : QuicSpdyClientSession(DefaultQuicConfig(),
+ supported_versions,
+ connection,
+ QuicServerId("example.com", 443, false),
+ &crypto_config_,
+ push_promise_index),
+ crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+ TlsClientHandshaker::CreateSslCtx()) {}
+ MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+ MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+ delete;
+ ~MockQuicSpdyClientSession() override = default;
+
+ MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+ QuicCryptoClientConfig crypto_config_;
+};
+
+class QuicSpdyClientStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+ class StreamVisitor;
+
+ QuicSpdyClientStreamTest()
+ : connection_(
+ new StrictMock<MockQuicConnection>(&helper_,
+ &alarm_factory_,
+ Perspective::IS_CLIENT,
+ SupportedVersions(GetParam()))),
+ session_(connection_->supported_versions(),
+ connection_,
+ &push_promise_index_),
+ body_("hello world") {
+ session_.Initialize();
+
+ headers_[":status"] = "200";
+ headers_["content-length"] = "11";
+
+ stream_ = QuicMakeUnique<QuicSpdyClientStream>(
+ GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), 0),
+ &session_, BIDIRECTIONAL);
+ stream_visitor_ = QuicMakeUnique<StreamVisitor>();
+ stream_->set_visitor(stream_visitor_.get());
+ }
+
+ class StreamVisitor : public QuicSpdyClientStream::Visitor {
+ void OnClose(QuicSpdyStream* stream) override {
+ QUIC_DVLOG(1) << "stream " << stream->id();
+ }
+ };
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ QuicClientPushPromiseIndex push_promise_index_;
+
+ MockQuicSpdyClientSession session_;
+ std::unique_ptr<QuicSpdyClientStream> stream_;
+ std::unique_ptr<StreamVisitor> stream_visitor_;
+ SpdyHeaderBlock headers_;
+ std::string body_;
+ HttpEncoder encoder_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSpdyClientStreamTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyClientStreamTest, TestReceivingIllegalResponseStatusCode) {
+ headers_[":status"] = "200 ok";
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ EXPECT_EQ(QUIC_BAD_APPLICATION_PAYLOAD, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFraming) {
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = VersionHasDataFrameHeader(connection_->transport_version())
+ ? header + body_
+ : body_;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+ EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+ EXPECT_EQ(200, stream_->response_code());
+ EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFraming100Continue) {
+ headers_[":status"] = "100";
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, body_));
+ EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
+ EXPECT_EQ(0u, stream_->response_headers().size());
+ EXPECT_EQ(100, stream_->response_code());
+ EXPECT_EQ("", stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = VersionHasDataFrameHeader(connection_->transport_version())
+ ? header + body_
+ : body_;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+ EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+ EXPECT_EQ(200, stream_->response_code());
+ EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest,
+ QUIC_TEST_DISABLED_IN_CHROME(TestFramingExtraData)) {
+ std::string large_body = "hello world!!!!!!";
+
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ // The headers should parse successfully.
+ EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+ EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+ EXPECT_EQ(200, stream_->response_code());
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(large_body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = VersionHasDataFrameHeader(connection_->transport_version())
+ ? header + large_body
+ : large_body;
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+
+ EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) {
+ // Test that receiving trailing headers, containing a final offset, results in
+ // the stream being closed at that byte offset.
+
+ // Send headers as usual.
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+
+ // Send trailers before sending the body. Even though a FIN has been received
+ // the stream should not be closed, as it does not yet have all the data bytes
+ // promised by the final offset field.
+ SpdyHeaderBlock trailer_block;
+ trailer_block["trailer key"] = "trailer value";
+ trailer_block[kFinalOffsetHeaderKey] =
+ QuicTextUtils::Uint64ToString(body_.size());
+ auto trailers = AsHeaderList(trailer_block);
+ stream_->OnStreamHeaderList(true, trailers.uncompressed_header_bytes(),
+ trailers);
+
+ // Now send the body, which should close the stream as the FIN has been
+ // received, as well as all data.
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = VersionHasDataFrameHeader(connection_->transport_version())
+ ? header + body_
+ : body_;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+ EXPECT_TRUE(stream_->reading_stopped());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.cc
new file mode 100644
index 00000000000..cbe479efab7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 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/quic/core/http/quic_spdy_server_stream_base.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+QuicSpdyServerStreamBase::QuicSpdyServerStreamBase(QuicStreamId id,
+ QuicSpdySession* session,
+ StreamType type)
+ : QuicSpdyStream(id, session, type) {}
+
+QuicSpdyServerStreamBase::QuicSpdyServerStreamBase(PendingStream pending,
+ QuicSpdySession* session,
+ StreamType type)
+ : QuicSpdyStream(std::move(pending), session, type) {}
+
+void QuicSpdyServerStreamBase::CloseWriteSide() {
+ if (!fin_received() && !rst_received() && sequencer()->ignore_read_data() &&
+ !rst_sent()) {
+ // Early cancel the stream if it has stopped reading before receiving FIN
+ // or RST.
+ DCHECK(fin_sent() || !session()->connection()->connected());
+ // Tell the peer to stop sending further data.
+ QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
+ Reset(QUIC_STREAM_NO_ERROR);
+ }
+
+ QuicSpdyStream::CloseWriteSide();
+}
+
+void QuicSpdyServerStreamBase::StopReading() {
+ if (!fin_received() && !rst_received() && write_side_closed() &&
+ !rst_sent()) {
+ DCHECK(fin_sent());
+ // Tell the peer to stop sending further data.
+ QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
+ Reset(QUIC_STREAM_NO_ERROR);
+ }
+ QuicSpdyStream::StopReading();
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.h
new file mode 100644
index 00000000000..438d152b941
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+
+namespace quic {
+
+class QuicSpdyServerStreamBase : public QuicSpdyStream {
+ public:
+ QuicSpdyServerStreamBase(QuicStreamId id,
+ QuicSpdySession* session,
+ StreamType type);
+ QuicSpdyServerStreamBase(PendingStream pending,
+ QuicSpdySession* session,
+ StreamType type);
+ QuicSpdyServerStreamBase(const QuicSpdyServerStreamBase&) = delete;
+ QuicSpdyServerStreamBase& operator=(const QuicSpdyServerStreamBase&) = delete;
+
+ // Override the base class to send QUIC_STREAM_NO_ERROR to the peer
+ // when the stream has not received all the data.
+ void CloseWriteSide() override;
+ void StopReading() override;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base_test.cc
new file mode 100644
index 00000000000..4e1aa1bd3e9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 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/quic/core/http/quic_spdy_server_stream_base.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.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_test_utils.h"
+
+using testing::_;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestQuicSpdyServerStream : public QuicSpdyServerStreamBase {
+ public:
+ TestQuicSpdyServerStream(QuicStreamId id,
+ QuicSpdySession* session,
+ StreamType type)
+ : QuicSpdyServerStreamBase(id, session, type) {}
+
+ void OnBodyAvailable() override {}
+};
+
+class QuicSpdyServerStreamBaseTest : public QuicTest {
+ protected:
+ QuicSpdyServerStreamBaseTest()
+ : session_(new MockQuicConnection(&helper_,
+ &alarm_factory_,
+ Perspective::IS_SERVER)) {
+ stream_ = new TestQuicSpdyServerStream(
+ GetNthClientInitiatedBidirectionalStreamId(
+ session_.connection()->transport_version(), 0),
+ &session_, BIDIRECTIONAL);
+ session_.ActivateStream(QuicWrapUnique(stream_));
+ helper_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ }
+
+ QuicSpdyServerStreamBase* stream_ = nullptr;
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ MockQuicSpdySession session_;
+};
+
+TEST_F(QuicSpdyServerStreamBaseTest,
+ SendQuicRstStreamNoErrorWithEarlyResponse) {
+ stream_->StopReading();
+ EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(1);
+ stream_->set_fin_sent(true);
+ stream_->CloseWriteSide();
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest,
+ DoNotSendQuicRstStreamNoErrorWithRstReceived) {
+ EXPECT_FALSE(stream_->reading_stopped());
+
+ EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0);
+
+ if (session_.connection()->transport_version() != QUIC_VERSION_99) {
+ EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _))
+ .Times(1);
+ } else {
+ // Intercept & check that the call to the QuicConnection's OnStreamReast
+ // has correct stream ID and error code -- for V99/IETF Quic, it should
+ // have the STREAM_CANCELLED error code, not RST_ACK... Capture
+ // OnStreamReset (rather than SendRstStream) because the V99 path bypasses
+ // SendRstStream, calling SendRstStreamInner directly. Mocking
+ // SendRstStreamInner is problematic since the test relies on it to perform
+ // the closing operations and getting the stream in the correct state.
+ EXPECT_CALL(*(static_cast<MockQuicConnection*>(session_.connection())),
+ OnStreamReset(stream_->id(), QUIC_STREAM_CANCELLED));
+ }
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+ QUIC_STREAM_CANCELLED, 1234);
+ stream_->OnStreamReset(rst_frame);
+ if (session_.connection()->transport_version() == QUIC_VERSION_99) {
+ // Create and inject a STOP SENDING frame to complete the close
+ // of the stream. This is only needed for version 99/IETF QUIC.
+ QuicStopSendingFrame stop_sending(
+ kInvalidControlFrameId, stream_->id(),
+ static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+ session_.OnStopSendingFrame(stop_sending);
+ }
+
+ EXPECT_TRUE(stream_->reading_stopped());
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc
new file mode 100644
index 00000000000..bda2e172d01
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc
@@ -0,0 +1,702 @@
+// Copyright (c) 2015 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_spdy_session.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.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_fallthrough.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.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_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+
+using http2::Http2DecoderAdapter;
+using spdy::HpackEntry;
+using spdy::HpackHeaderTable;
+using spdy::Http2WeightToSpdy3Priority;
+using spdy::SETTINGS_ENABLE_PUSH;
+using spdy::SETTINGS_HEADER_TABLE_SIZE;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyErrorCode;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerDebugVisitorInterface;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyFrameType;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyHeadersHandlerInterface;
+using spdy::SpdyHeadersIR;
+using spdy::SpdyKnownSettingsId;
+using spdy::SpdyPingId;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdyPushPromiseIR;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsId;
+using spdy::SpdySettingsIR;
+using spdy::SpdyStreamId;
+
+namespace quic {
+
+namespace {
+
+class HeaderTableDebugVisitor : public HpackHeaderTable::DebugVisitorInterface {
+ public:
+ HeaderTableDebugVisitor(const QuicClock* clock,
+ std::unique_ptr<QuicHpackDebugVisitor> visitor)
+ : clock_(clock), headers_stream_hpack_visitor_(std::move(visitor)) {}
+ HeaderTableDebugVisitor(const HeaderTableDebugVisitor&) = delete;
+ HeaderTableDebugVisitor& operator=(const HeaderTableDebugVisitor&) = delete;
+
+ int64_t OnNewEntry(const HpackEntry& entry) override {
+ QUIC_DVLOG(1) << entry.GetDebugString();
+ return (clock_->ApproximateNow() - QuicTime::Zero()).ToMicroseconds();
+ }
+
+ void OnUseEntry(const HpackEntry& entry) override {
+ const QuicTime::Delta elapsed(
+ clock_->ApproximateNow() -
+ QuicTime::Delta::FromMicroseconds(entry.time_added()) -
+ QuicTime::Zero());
+ QUIC_DVLOG(1) << entry.GetDebugString() << " " << elapsed.ToMilliseconds()
+ << " ms";
+ headers_stream_hpack_visitor_->OnUseEntry(elapsed);
+ }
+
+ private:
+ const QuicClock* clock_;
+ std::unique_ptr<QuicHpackDebugVisitor> headers_stream_hpack_visitor_;
+};
+
+} // namespace
+
+// A SpdyFramerVisitor that passes HEADERS frames to the QuicSpdyStream, and
+// closes the connection if any unexpected frames are received.
+class QuicSpdySession::SpdyFramerVisitor
+ : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface {
+ public:
+ explicit SpdyFramerVisitor(QuicSpdySession* session) : session_(session) {}
+ SpdyFramerVisitor(const SpdyFramerVisitor&) = delete;
+ SpdyFramerVisitor& operator=(const SpdyFramerVisitor&) = delete;
+
+ SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId /* stream_id */) override {
+ return &header_list_;
+ }
+
+ void OnHeaderFrameEnd(SpdyStreamId /* stream_id */) override {
+ if (session_->IsConnected()) {
+ session_->OnHeaderList(header_list_);
+ }
+ header_list_.Clear();
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) override {
+ CloseConnection("SPDY DATA frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnStreamEnd(SpdyStreamId stream_id) override {
+ // The framer invokes OnStreamEnd after processing a frame that had the fin
+ // bit set.
+ }
+
+ void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
+ CloseConnection("SPDY frame padding received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnError(Http2DecoderAdapter::SpdyFramerError error) override {
+ QuicErrorCode code = QUIC_INVALID_HEADERS_STREAM_DATA;
+ switch (error) {
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE:
+ code = QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE;
+ break;
+ default:
+ break;
+ }
+ CloseConnection(
+ QuicStrCat("SPDY framing error: ",
+ Http2DecoderAdapter::SpdyFramerErrorToString(error)),
+ code);
+ }
+
+ void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override {
+ CloseConnection("SPDY DATA frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {
+ CloseConnection("SPDY RST_STREAM frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnSetting(SpdySettingsId id, uint32_t value) override {
+ switch (id) {
+ case SETTINGS_HEADER_TABLE_SIZE:
+ session_->UpdateHeaderEncoderTableSize(value);
+ break;
+ case SETTINGS_ENABLE_PUSH:
+ if (session_->perspective() == Perspective::IS_SERVER) {
+ // See rfc7540, Section 6.5.2.
+ if (value > 1) {
+ CloseConnection(
+ QuicStrCat("Invalid value for SETTINGS_ENABLE_PUSH: ", value),
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return;
+ }
+ session_->UpdateEnableServerPush(value > 0);
+ break;
+ } else {
+ CloseConnection(
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id),
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+ break;
+ // TODO(fayang): Need to support SETTINGS_MAX_HEADER_LIST_SIZE when
+ // clients are actually sending it.
+ case SETTINGS_MAX_HEADER_LIST_SIZE:
+ break;
+ default:
+ CloseConnection(
+ QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id),
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+ }
+
+ void OnSettingsEnd() override {}
+
+ void OnPing(SpdyPingId unique_id, bool is_ack) override {
+ CloseConnection("SPDY PING frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code) override {
+ CloseConnection("SPDY GOAWAY frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId /*parent_stream_id*/,
+ bool /*exclusive*/,
+ bool fin,
+ bool end) override {
+ if (!session_->IsConnected()) {
+ return;
+ }
+
+ // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through
+ // QuicHeadersStream.
+ SpdyPriority priority =
+ has_priority ? Http2WeightToSpdy3Priority(weight) : 0;
+ session_->OnHeaders(stream_id, has_priority, priority, fin);
+ }
+
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
+ CloseConnection("SPDY WINDOW_UPDATE frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) override {
+ if (!session_->supports_push_promise()) {
+ CloseConnection("PUSH_PROMISE not supported.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return;
+ }
+ if (!session_->IsConnected()) {
+ return;
+ }
+ session_->OnPushPromise(stream_id, promised_stream_id, end);
+ }
+
+ void OnContinuation(SpdyStreamId stream_id, bool end) override {}
+
+ void OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_id,
+ int weight,
+ bool exclusive) override {
+ if (session_->connection()->transport_version() <= QUIC_VERSION_39) {
+ CloseConnection("SPDY PRIORITY frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return;
+ }
+ if (!session_->IsConnected()) {
+ return;
+ }
+ // TODO (wangyix): implement real HTTP/2 weights and dependencies instead of
+ // converting to SpdyPriority.
+ SpdyPriority priority = Http2WeightToSpdy3Priority(weight);
+ session_->OnPriority(stream_id, priority);
+ }
+
+ bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override {
+ CloseConnection("Unknown frame type received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return false;
+ }
+
+ // SpdyFramerDebugVisitorInterface implementation
+ void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) override {
+ if (payload_len == 0) {
+ QUIC_BUG << "Zero payload length.";
+ return;
+ }
+ int compression_pct = 100 - (100 * frame_len) / payload_len;
+ QUIC_DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct;
+ }
+
+ void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) override {
+ if (session_->IsConnected()) {
+ session_->OnCompressedFrameSize(frame_len);
+ }
+ }
+
+ void set_max_uncompressed_header_bytes(
+ size_t set_max_uncompressed_header_bytes) {
+ header_list_.set_max_header_list_size(set_max_uncompressed_header_bytes);
+ }
+
+ private:
+ void CloseConnection(const std::string& details, QuicErrorCode code) {
+ if (session_->IsConnected()) {
+ session_->CloseConnectionWithDetails(code, details);
+ }
+ }
+
+ private:
+ QuicSpdySession* session_;
+ QuicHeaderList header_list_;
+};
+
+QuicHpackDebugVisitor::QuicHpackDebugVisitor() {}
+
+QuicHpackDebugVisitor::~QuicHpackDebugVisitor() {}
+
+QuicSpdySession::QuicSpdySession(
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions)
+ : QuicSession(connection, visitor, config, supported_versions),
+ max_inbound_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+ server_push_enabled_(true),
+ stream_id_(
+ QuicUtils::GetInvalidStreamId(connection->transport_version())),
+ promised_stream_id_(
+ QuicUtils::GetInvalidStreamId(connection->transport_version())),
+ fin_(false),
+ frame_len_(0),
+ uncompressed_frame_len_(0),
+ supports_push_promise_(perspective() == Perspective::IS_CLIENT),
+ spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
+ spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
+ h2_deframer_.set_visitor(spdy_framer_visitor_.get());
+ h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
+ spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+}
+
+QuicSpdySession::~QuicSpdySession() {
+ // Set the streams' session pointers in closed and dynamic stream lists
+ // to null to avoid subsequent use of this session.
+ for (auto& stream : *closed_streams()) {
+ static_cast<QuicSpdyStream*>(stream.get())->ClearSession();
+ }
+ for (auto const& kv : zombie_streams()) {
+ static_cast<QuicSpdyStream*>(kv.second.get())->ClearSession();
+ }
+ for (auto const& kv : dynamic_streams()) {
+ static_cast<QuicSpdyStream*>(kv.second.get())->ClearSession();
+ }
+}
+
+void QuicSpdySession::Initialize() {
+ QuicSession::Initialize();
+
+ if (perspective() == Perspective::IS_SERVER) {
+ set_largest_peer_created_stream_id(
+ QuicUtils::GetHeadersStreamId(connection()->transport_version()));
+ } else {
+ QuicStreamId headers_stream_id = GetNextOutgoingBidirectionalStreamId();
+ DCHECK_EQ(headers_stream_id,
+ QuicUtils::GetHeadersStreamId(connection()->transport_version()));
+ }
+
+ if (VersionUsesQpack(connection()->transport_version())) {
+ qpack_encoder_ = QuicMakeUnique<QpackEncoder>(this, this);
+ qpack_decoder_ = QuicMakeUnique<QpackDecoder>(this, this);
+ }
+
+ headers_stream_ = QuicMakeUnique<QuicHeadersStream>((this));
+ DCHECK_EQ(QuicUtils::GetHeadersStreamId(connection()->transport_version()),
+ headers_stream_->id());
+ RegisterStaticStream(
+ QuicUtils::GetHeadersStreamId(connection()->transport_version()),
+ headers_stream_.get());
+
+ set_max_uncompressed_header_bytes(max_inbound_header_list_size_);
+
+ // Limit HPACK buffering to 2x header list size limit.
+ set_max_decode_buffer_size_bytes(2 * max_inbound_header_list_size_);
+}
+
+void QuicSpdySession::OnDecoderStreamError(QuicStringPiece error_message) {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ // TODO(112770235): Signal connection error on decoder stream errors.
+ QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::WriteEncoderStreamData(QuicStringPiece data) {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ // TODO(112770235): Send encoder stream data on encoder stream.
+ QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::OnEncoderStreamError(QuicStringPiece error_message) {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ // TODO(112770235): Signal connection error on encoder stream errors.
+ QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::WriteDecoderStreamData(QuicStringPiece data) {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ // TODO(112770235): Send decoder stream data on decoder stream.
+ QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::OnStreamHeadersPriority(QuicStreamId stream_id,
+ SpdyPriority priority) {
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeadersPriority(priority);
+}
+
+void QuicSpdySession::OnStreamHeaderList(QuicStreamId stream_id,
+ bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ if (QuicContainsKey(static_streams(), stream_id)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (stream == nullptr) {
+ // The stream no longer exists, but trailing headers may contain the final
+ // byte offset necessary for flow control and open stream accounting.
+ size_t final_byte_offset = 0;
+ for (const auto& header : header_list) {
+ const std::string& header_key = header.first;
+ const std::string& header_value = header.second;
+ if (header_key == kFinalOffsetHeaderKey) {
+ if (!QuicTextUtils::StringToSizeT(header_value, &final_byte_offset)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Trailers are malformed (no final offset)",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ DVLOG(1) << "Received final byte offset in trailers for stream "
+ << stream_id << ", which no longer exists.";
+ OnFinalByteOffsetReceived(stream_id, final_byte_offset);
+ }
+ }
+
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeaderList(fin, frame_len, header_list);
+}
+
+void QuicSpdySession::OnPriorityFrame(QuicStreamId stream_id,
+ SpdyPriority priority) {
+ QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive a PRIORITY frame after a stream has been
+ // reset.
+ return;
+ }
+ stream->OnPriorityFrame(priority);
+}
+
+size_t QuicSpdySession::ProcessHeaderData(const struct iovec& iov) {
+ return h2_deframer_.ProcessInput(static_cast<char*>(iov.iov_base),
+ iov.iov_len);
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStream(
+ QuicStreamId id,
+ SpdyHeaderBlock headers,
+ bool fin,
+ SpdyPriority priority,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ return WriteHeadersOnHeadersStreamImpl(
+ id, std::move(headers), fin,
+ /* parent_stream_id = */ 0, Spdy3PriorityToHttp2Weight(priority),
+ /* exclusive = */ false, std::move(ack_listener));
+}
+
+size_t QuicSpdySession::WritePriority(QuicStreamId id,
+ QuicStreamId parent_stream_id,
+ int weight,
+ bool exclusive) {
+ if (connection()->transport_version() <= QUIC_VERSION_39) {
+ return 0;
+ }
+ SpdyPriorityIR priority_frame(id, parent_stream_id, weight, exclusive);
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(priority_frame));
+ headers_stream_->WriteOrBufferData(
+ QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+ return frame.size();
+}
+
+size_t QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ SpdyHeaderBlock headers) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ QUIC_BUG << "Client shouldn't send PUSH_PROMISE";
+ return 0;
+ }
+
+ SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id,
+ std::move(headers));
+ // PUSH_PROMISE must not be the last frame sent out, at least followed by
+ // response headers.
+ push_promise.set_fin(false);
+
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise));
+ headers_stream_->WriteOrBufferData(
+ QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+ return frame.size();
+}
+
+size_t QuicSpdySession::SendMaxHeaderListSize(size_t value) {
+ SpdySettingsIR settings_frame;
+ settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, value);
+
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(settings_frame));
+ headers_stream_->WriteOrBufferData(
+ QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+ return frame.size();
+}
+
+QpackEncoder* QuicSpdySession::qpack_encoder() {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ return qpack_encoder_.get();
+}
+
+QpackDecoder* QuicSpdySession::qpack_decoder() {
+ DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+ return qpack_decoder_.get();
+}
+
+QuicSpdyStream* QuicSpdySession::GetSpdyDataStream(
+ const QuicStreamId stream_id) {
+ return static_cast<QuicSpdyStream*>(GetOrCreateDynamicStream(stream_id));
+}
+
+void QuicSpdySession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+ QuicSession::OnCryptoHandshakeEvent(event);
+ if (event == HANDSHAKE_CONFIRMED && config()->SupportMaxHeaderListSize()) {
+ SendMaxHeaderListSize(max_inbound_header_list_size_);
+ }
+}
+
+// True if there are open HTTP requests.
+bool QuicSpdySession::ShouldKeepConnectionAlive() const {
+ // Change to check if there are open HTTP requests.
+ // When IETF QUIC control and QPACK streams are used, those will need to be
+ // subtracted from this count to ensure only request streams are counted.
+ return GetNumOpenDynamicStreams() > 0;
+}
+
+bool QuicSpdySession::ShouldBufferIncomingStream(QuicStreamId id) const {
+ DCHECK_EQ(QUIC_VERSION_99, connection()->transport_version());
+ return !QuicUtils::IsBidirectionalStreamId(id);
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStreamImpl(
+ QuicStreamId id,
+ spdy::SpdyHeaderBlock headers,
+ bool fin,
+ QuicStreamId parent_stream_id,
+ int weight,
+ bool exclusive,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ SpdyHeadersIR headers_frame(id, std::move(headers));
+ headers_frame.set_fin(fin);
+ if (perspective() == Perspective::IS_CLIENT) {
+ headers_frame.set_has_priority(true);
+ headers_frame.set_parent_stream_id(parent_stream_id);
+ headers_frame.set_weight(weight);
+ headers_frame.set_exclusive(exclusive);
+ }
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame));
+ headers_stream_->WriteOrBufferData(
+ QuicStringPiece(frame.data(), frame.size()), false,
+ std::move(ack_listener));
+ return frame.size();
+}
+
+void QuicSpdySession::OnPromiseHeaderList(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ std::string error =
+ "OnPromiseHeaderList should be overridden in client code.";
+ QUIC_BUG << error;
+ connection()->CloseConnection(QUIC_INTERNAL_ERROR, error,
+ ConnectionCloseBehavior::SILENT_CLOSE);
+}
+
+bool QuicSpdySession::ShouldReleaseHeadersStreamSequencerBuffer() {
+ return false;
+}
+
+void QuicSpdySession::OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ SpdyPriority priority,
+ bool fin) {
+ if (has_priority) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Server must not send priorities.");
+ return;
+ }
+ OnStreamHeadersPriority(stream_id, priority);
+ } else {
+ if (perspective() == Perspective::IS_SERVER) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Client must send priorities.");
+ return;
+ }
+ }
+ DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+ stream_id_);
+ DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+ promised_stream_id_);
+ stream_id_ = stream_id;
+ fin_ = fin;
+}
+
+void QuicSpdySession::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) {
+ DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+ stream_id_);
+ DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+ promised_stream_id_);
+ stream_id_ = stream_id;
+ promised_stream_id_ = promised_stream_id;
+}
+
+// TODO (wangyix): Why is SpdyStreamId used instead of QuicStreamId?
+// This occurs in many places in this file.
+void QuicSpdySession::OnPriority(SpdyStreamId stream_id,
+ SpdyPriority priority) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Server must not send PRIORITY frames.");
+ return;
+ }
+ OnPriorityFrame(stream_id, priority);
+}
+
+void QuicSpdySession::OnHeaderList(const QuicHeaderList& header_list) {
+ QUIC_DVLOG(1) << "Received header list for stream " << stream_id_ << ": "
+ << header_list.DebugString();
+ if (promised_stream_id_ ==
+ QuicUtils::GetInvalidStreamId(connection()->transport_version())) {
+ OnStreamHeaderList(stream_id_, fin_, frame_len_, header_list);
+ } else {
+ OnPromiseHeaderList(stream_id_, promised_stream_id_, frame_len_,
+ header_list);
+ }
+ // Reset state for the next frame.
+ promised_stream_id_ =
+ QuicUtils::GetInvalidStreamId(connection()->transport_version());
+ stream_id_ = QuicUtils::GetInvalidStreamId(connection()->transport_version());
+ fin_ = false;
+ frame_len_ = 0;
+ uncompressed_frame_len_ = 0;
+}
+
+void QuicSpdySession::OnCompressedFrameSize(size_t frame_len) {
+ frame_len_ += frame_len;
+}
+
+void QuicSpdySession::SetHpackEncoderDebugVisitor(
+ std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+ spdy_framer_.SetEncoderHeaderTableDebugVisitor(
+ std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
+ connection()->helper()->GetClock(), std::move(visitor))));
+}
+
+void QuicSpdySession::SetHpackDecoderDebugVisitor(
+ std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+ h2_deframer_.SetDecoderHeaderTableDebugVisitor(
+ QuicMakeUnique<HeaderTableDebugVisitor>(
+ connection()->helper()->GetClock(), std::move(visitor)));
+}
+
+void QuicSpdySession::UpdateHeaderEncoderTableSize(uint32_t value) {
+ spdy_framer_.UpdateHeaderEncoderTableSize(value);
+}
+
+void QuicSpdySession::UpdateEnableServerPush(bool value) {
+ set_server_push_enabled(value);
+}
+
+void QuicSpdySession::set_max_uncompressed_header_bytes(
+ size_t set_max_uncompressed_header_bytes) {
+ spdy_framer_visitor_->set_max_uncompressed_header_bytes(
+ set_max_uncompressed_header_bytes);
+}
+
+void QuicSpdySession::CloseConnectionWithDetails(QuicErrorCode error,
+ const std::string& details) {
+ connection()->CloseConnection(
+ error, details, ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+bool QuicSpdySession::HasActiveRequestStreams() const {
+ // TODO(renjietang): Exclude static streams.
+ return !dynamic_streams().empty();
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h
new file mode 100644
index 00000000000..0ff1150293e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h
@@ -0,0 +1,289 @@
+// Copyright (c) 2015 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+
+namespace quic {
+
+namespace test {
+class QuicSpdySessionPeer;
+} // namespace test
+
+// QuicHpackDebugVisitor gathers data used for understanding HPACK HoL
+// dynamics. Specifically, it is to help predict the compression
+// penalty of avoiding HoL by chagning how the dynamic table is used.
+// In chromium, the concrete instance populates an UMA
+// histogram with the data.
+class QUIC_EXPORT_PRIVATE QuicHpackDebugVisitor {
+ public:
+ QuicHpackDebugVisitor();
+ QuicHpackDebugVisitor(const QuicHpackDebugVisitor&) = delete;
+ QuicHpackDebugVisitor& operator=(const QuicHpackDebugVisitor&) = delete;
+
+ virtual ~QuicHpackDebugVisitor();
+
+ // For each HPACK indexed representation processed, |elapsed| is
+ // the time since the corresponding entry was added to the dynamic
+ // table.
+ virtual void OnUseEntry(QuicTime::Delta elapsed) = 0;
+};
+
+// A QUIC session with a headers stream.
+class QUIC_EXPORT_PRIVATE QuicSpdySession
+ : public QuicSession,
+ public QpackEncoder::DecoderStreamErrorDelegate,
+ public QpackEncoderStreamSender::Delegate,
+ public QpackDecoder::EncoderStreamErrorDelegate,
+ public QpackDecoderStreamSender::Delegate {
+ public:
+ // Does not take ownership of |connection| or |visitor|.
+ QuicSpdySession(QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions);
+ QuicSpdySession(const QuicSpdySession&) = delete;
+ QuicSpdySession& operator=(const QuicSpdySession&) = delete;
+
+ ~QuicSpdySession() override;
+
+ void Initialize() override;
+
+ // QpackEncoder::DecoderStreamErrorDelegate implementation.
+ void OnDecoderStreamError(QuicStringPiece error_message) override;
+
+ // QpackEncoderStreamSender::Delegate implemenation.
+ void WriteEncoderStreamData(QuicStringPiece data) override;
+
+ // QpackDecoder::EncoderStreamErrorDelegate implementation.
+ void OnEncoderStreamError(QuicStringPiece error_message) override;
+
+ // QpackDecoderStreamSender::Delegate implementation.
+ void WriteDecoderStreamData(QuicStringPiece data) override;
+
+ // Called by |headers_stream_| when headers with a priority have been
+ // received for a stream. This method will only be called for server streams.
+ virtual void OnStreamHeadersPriority(QuicStreamId stream_id,
+ spdy::SpdyPriority priority);
+
+ // Called by |headers_stream_| when headers have been completely received
+ // for a stream. |fin| will be true if the fin flag was set in the headers
+ // frame.
+ virtual void OnStreamHeaderList(QuicStreamId stream_id,
+ bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+
+ // Called by |headers_stream_| when push promise headers have been
+ // completely received. |fin| will be true if the fin flag was set
+ // in the headers.
+ virtual void OnPromiseHeaderList(QuicStreamId stream_id,
+ QuicStreamId promised_stream_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+
+ // Called by |headers_stream_| when a PRIORITY frame has been received for a
+ // stream. This method will only be called for server streams.
+ virtual void OnPriorityFrame(QuicStreamId stream_id,
+ spdy::SpdyPriority priority);
+
+ // Sends contents of |iov| to h2_deframer_, returns number of bytes processed.
+ size_t ProcessHeaderData(const struct iovec& iov);
+
+ // Writes |headers| for the stream |id| to the dedicated headers stream.
+ // If |fin| is true, then no more data will be sent for the stream |id|.
+ // If provided, |ack_notifier_delegate| will be registered to be notified when
+ // we have seen ACKs for all packets resulting from this call.
+ virtual size_t WriteHeadersOnHeadersStream(
+ QuicStreamId id,
+ spdy::SpdyHeaderBlock headers,
+ bool fin,
+ spdy::SpdyPriority priority,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+ // Writes a PRIORITY frame the to peer. Returns the size in bytes of the
+ // resulting PRIORITY frame for QUIC_VERSION_43 and above. Otherwise, does
+ // nothing and returns 0.
+ size_t WritePriority(QuicStreamId id,
+ QuicStreamId parent_stream_id,
+ int weight,
+ bool exclusive);
+
+ // Write |headers| for |promised_stream_id| on |original_stream_id| in a
+ // PUSH_PROMISE frame to peer.
+ // Return the size, in bytes, of the resulting PUSH_PROMISE frame.
+ virtual size_t WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ spdy::SpdyHeaderBlock headers);
+
+ // Sends SETTINGS_MAX_HEADER_LIST_SIZE SETTINGS frame.
+ size_t SendMaxHeaderListSize(size_t value);
+
+ QpackEncoder* qpack_encoder();
+ QpackDecoder* qpack_decoder();
+ QuicHeadersStream* headers_stream() { return headers_stream_.get(); }
+
+ bool server_push_enabled() const { return server_push_enabled_; }
+
+ // Called by |QuicHeadersStream::UpdateEnableServerPush()| with
+ // value from SETTINGS_ENABLE_PUSH.
+ void set_server_push_enabled(bool enable) { server_push_enabled_ = enable; }
+
+ // Return true if this session wants to release headers stream's buffer
+ // aggressively.
+ virtual bool ShouldReleaseHeadersStreamSequencerBuffer();
+
+ void CloseConnectionWithDetails(QuicErrorCode error,
+ const std::string& details);
+
+ void set_max_inbound_header_list_size(size_t max_inbound_header_list_size) {
+ max_inbound_header_list_size_ = max_inbound_header_list_size;
+ }
+
+ size_t max_inbound_header_list_size() const {
+ return max_inbound_header_list_size_;
+ }
+
+ // Returns true if the session has active request streams.
+ bool HasActiveRequestStreams() const;
+
+ protected:
+ // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
+ // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
+ // make sure that all data streams are QuicSpdyStreams.
+ QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override = 0;
+ QuicSpdyStream* CreateIncomingStream(PendingStream pending) override = 0;
+ virtual QuicSpdyStream* CreateOutgoingBidirectionalStream() = 0;
+ virtual QuicSpdyStream* CreateOutgoingUnidirectionalStream() = 0;
+
+ QuicSpdyStream* GetSpdyDataStream(const QuicStreamId stream_id);
+
+ // If an incoming stream can be created, return true.
+ virtual bool ShouldCreateIncomingStream(QuicStreamId id) = 0;
+
+ // If an outgoing bidirectional/unidirectional stream can be created, return
+ // true.
+ virtual bool ShouldCreateOutgoingBidirectionalStream() = 0;
+ virtual bool ShouldCreateOutgoingUnidirectionalStream() = 0;
+
+ // Returns true if there are open HTTP requests.
+ bool ShouldKeepConnectionAlive() const override;
+
+ // Overridden to buffer incoming streams for version 99.
+ bool ShouldBufferIncomingStream(QuicStreamId id) const override;
+
+ size_t WriteHeadersOnHeadersStreamImpl(
+ QuicStreamId id,
+ spdy::SpdyHeaderBlock headers,
+ bool fin,
+ QuicStreamId parent_stream_id,
+ int weight,
+ bool exclusive,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+ void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+ bool supports_push_promise() { return supports_push_promise_; }
+
+ // Optional, enables instrumentation related to go/quic-hpack.
+ void SetHpackEncoderDebugVisitor(
+ std::unique_ptr<QuicHpackDebugVisitor> visitor);
+ void SetHpackDecoderDebugVisitor(
+ std::unique_ptr<QuicHpackDebugVisitor> visitor);
+
+ // Sets the maximum size of the header compression table spdy_framer_ is
+ // willing to use to encode header blocks.
+ void UpdateHeaderEncoderTableSize(uint32_t value);
+
+ // Called when SETTINGS_ENABLE_PUSH is received, only supported on
+ // server side.
+ void UpdateEnableServerPush(bool value);
+
+ bool IsConnected() { return connection()->connected(); }
+
+ // Sets how much encoded data the hpack decoder of h2_deframer_ is willing to
+ // buffer.
+ void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes) {
+ h2_deframer_.GetHpackDecoder()->set_max_decode_buffer_size_bytes(
+ max_decode_buffer_size_bytes);
+ }
+
+ void set_max_uncompressed_header_bytes(
+ size_t set_max_uncompressed_header_bytes);
+
+ private:
+ friend class test::QuicSpdySessionPeer;
+
+ class SpdyFramerVisitor;
+
+ // The following methods are called by the SimpleVisitor.
+
+ // Called when a HEADERS frame has been received.
+ void OnHeaders(spdy::SpdyStreamId stream_id,
+ bool has_priority,
+ spdy::SpdyPriority priority,
+ bool fin);
+
+ // Called when a PUSH_PROMISE frame has been received.
+ void OnPushPromise(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStreamId promised_stream_id,
+ bool end);
+
+ // Called when a PRIORITY frame has been received.
+ void OnPriority(spdy::SpdyStreamId stream_id, spdy::SpdyPriority priority);
+
+ // Called when the complete list of headers is available.
+ void OnHeaderList(const QuicHeaderList& header_list);
+
+ // Called when the size of the compressed frame payload is available.
+ void OnCompressedFrameSize(size_t frame_len);
+
+ std::unique_ptr<QpackEncoder> qpack_encoder_;
+ std::unique_ptr<QpackDecoder> qpack_decoder_;
+
+ // TODO(123528590): Remove this member.
+ std::unique_ptr<QuicHeadersStream> headers_stream_;
+
+ // The maximum size of a header block that will be accepted from the peer,
+ // defined per spec as key + value + overhead per field (uncompressed).
+ size_t max_inbound_header_list_size_;
+
+ // Set during handshake. If true, resources in x-associated-content and link
+ // headers will be pushed.
+ bool server_push_enabled_;
+
+ // Data about the stream whose headers are being processed.
+ QuicStreamId stream_id_;
+ QuicStreamId promised_stream_id_;
+ bool fin_;
+ size_t frame_len_;
+ size_t uncompressed_frame_len_;
+
+ bool supports_push_promise_;
+
+ spdy::SpdyFramer spdy_framer_;
+ http2::Http2DecoderAdapter h2_deframer_;
+ std::unique_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc
new file mode 100644
index 00000000000..04605e1faf7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc
@@ -0,0 +1,1808 @@
+// Copyright (c) 2012 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_spdy_session.h"
+
+#include <cstdint>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.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_map_util.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_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_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_stream_send_buffer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+using spdy::kV3HighestPriority;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdySerializedFrame;
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker {
+ public:
+ explicit TestCryptoStream(QuicSession* session)
+ : QuicCryptoStream(session),
+ QuicCryptoHandshaker(this, session),
+ encryption_established_(false),
+ handshake_confirmed_(false),
+ params_(new QuicCryptoNegotiatedParameters) {}
+
+ void OnHandshakeMessage(const CryptoHandshakeMessage& /*message*/) override {
+ encryption_established_ = true;
+ handshake_confirmed_ = true;
+ CryptoHandshakeMessage msg;
+ std::string error_details;
+ session()->config()->SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ session()->config()->SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+ session()->config()->ToHandshakeMessage(&msg);
+ const QuicErrorCode error =
+ session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ session()->OnConfigNegotiated();
+ session()->connection()->SetDefaultEncryptionLevel(
+ ENCRYPTION_FORWARD_SECURE);
+ session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+ }
+
+ // QuicCryptoStream implementation
+ bool encryption_established() const override {
+ return encryption_established_;
+ }
+ bool handshake_confirmed() const override { return handshake_confirmed_; }
+ const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
+ const override {
+ return *params_;
+ }
+ CryptoMessageParser* crypto_message_parser() override {
+ return QuicCryptoHandshaker::crypto_message_parser();
+ }
+
+ MOCK_METHOD0(OnCanWrite, void());
+
+ bool HasPendingCryptoRetransmission() override { return false; }
+
+ MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
+
+ private:
+ using QuicCryptoStream::session;
+
+ bool encryption_established_;
+ bool handshake_confirmed_;
+ QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
+};
+
+class TestHeadersStream : public QuicHeadersStream {
+ public:
+ explicit TestHeadersStream(QuicSpdySession* session)
+ : QuicHeadersStream(session) {}
+
+ MOCK_METHOD0(OnCanWrite, void());
+};
+
+class TestStream : public QuicSpdyStream {
+ public:
+ TestStream(QuicStreamId id, QuicSpdySession* session, StreamType type)
+ : QuicSpdyStream(id, session, type) {}
+
+ TestStream(PendingStream pending, QuicSpdySession* session, StreamType type)
+ : QuicSpdyStream(std::move(pending), session, type) {}
+
+ using QuicStream::CloseWriteSide;
+
+ void OnBodyAvailable() override {}
+
+ MOCK_METHOD0(OnCanWrite, void());
+ MOCK_METHOD3(RetransmitStreamData,
+ bool(QuicStreamOffset, QuicByteCount, bool));
+
+ MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
+};
+
+class TestSession : public QuicSpdySession {
+ public:
+ explicit TestSession(QuicConnection* connection)
+ : QuicSpdySession(connection,
+ nullptr,
+ DefaultQuicConfig(),
+ CurrentSupportedVersions()),
+ crypto_stream_(this),
+ writev_consumes_all_data_(false) {
+ Initialize();
+ this->connection()->SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ QuicMakeUnique<NullEncrypter>(connection->perspective()));
+ }
+
+ ~TestSession() override { delete connection(); }
+
+ TestCryptoStream* GetMutableCryptoStream() override {
+ return &crypto_stream_;
+ }
+
+ const TestCryptoStream* GetCryptoStream() const override {
+ return &crypto_stream_;
+ }
+
+ TestStream* CreateOutgoingBidirectionalStream() override {
+ TestStream* stream = new TestStream(GetNextOutgoingBidirectionalStreamId(),
+ this, BIDIRECTIONAL);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ TestStream* CreateOutgoingUnidirectionalStream() override {
+ TestStream* stream = new TestStream(GetNextOutgoingUnidirectionalStreamId(),
+ this, WRITE_UNIDIRECTIONAL);
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ TestStream* CreateIncomingStream(QuicStreamId id) override {
+ // Enforce the limit on the number of open streams.
+ if (GetNumOpenIncomingStreams() + 1 >
+ max_open_incoming_bidirectional_streams() &&
+ connection()->transport_version() != QUIC_VERSION_99) {
+ connection()->CloseConnection(
+ QUIC_TOO_MANY_OPEN_STREAMS, "Too many streams!",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return nullptr;
+ } else {
+ TestStream* stream = new TestStream(
+ id, this,
+ DetermineStreamType(id, connection()->transport_version(),
+ perspective(), /*is_incoming=*/true,
+ BIDIRECTIONAL));
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+ }
+
+ TestStream* CreateIncomingStream(PendingStream pending) override {
+ QuicStreamId id = pending.id();
+ TestStream* stream =
+ new TestStream(std::move(pending), this,
+ DetermineStreamType(
+ id, connection()->transport_version(), perspective(),
+ /*is_incoming=*/true, BIDIRECTIONAL));
+ ActivateStream(QuicWrapUnique(stream));
+ return stream;
+ }
+
+ bool ShouldCreateIncomingStream(QuicStreamId /*id*/) override { return true; }
+
+ bool ShouldCreateOutgoingBidirectionalStream() override { return true; }
+ bool ShouldCreateOutgoingUnidirectionalStream() override { return true; }
+
+ bool IsClosedStream(QuicStreamId id) {
+ return QuicSession::IsClosedStream(id);
+ }
+
+ QuicStream* GetOrCreateDynamicStream(QuicStreamId stream_id) {
+ return QuicSpdySession::GetOrCreateDynamicStream(stream_id);
+ }
+
+ QuicConsumedData WritevData(QuicStream* stream,
+ QuicStreamId id,
+ size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state) override {
+ bool fin = state != NO_FIN;
+ QuicConsumedData consumed(write_length, fin);
+ if (!writev_consumes_all_data_) {
+ consumed =
+ QuicSession::WritevData(stream, id, write_length, offset, state);
+ }
+ if (fin && consumed.fin_consumed) {
+ stream->set_fin_sent(true);
+ }
+ QuicSessionPeer::GetWriteBlockedStreams(this)->UpdateBytesForStream(
+ id, consumed.bytes_consumed);
+ return consumed;
+ }
+
+ void set_writev_consumes_all_data(bool val) {
+ writev_consumes_all_data_ = val;
+ }
+
+ QuicConsumedData SendStreamData(QuicStream* stream) {
+ struct iovec iov;
+ if ((QuicVersionUsesCryptoFrames(connection()->transport_version()) ||
+ stream->id() !=
+ QuicUtils::GetCryptoStreamId(connection()->transport_version())) &&
+ connection()->encryption_level() != ENCRYPTION_FORWARD_SECURE) {
+ this->connection()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ }
+ MakeIOVector("not empty", &iov);
+ QuicStreamPeer::SendBuffer(stream).SaveStreamData(&iov, 1, 0, 9);
+ QuicConsumedData consumed = WritevData(stream, stream->id(), 9, 0, FIN);
+ QuicStreamPeer::SendBuffer(stream).OnStreamDataConsumed(
+ consumed.bytes_consumed);
+ return consumed;
+ }
+
+ bool ClearControlFrame(const QuicFrame& frame) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+
+ QuicConsumedData SendLargeFakeData(QuicStream* stream, int bytes) {
+ DCHECK(writev_consumes_all_data_);
+ return WritevData(stream, stream->id(), bytes, 0, FIN);
+ }
+
+ using QuicSession::closed_streams;
+ using QuicSession::zombie_streams;
+ using QuicSpdySession::ShouldBufferIncomingStream;
+
+ private:
+ StrictMock<TestCryptoStream> crypto_stream_;
+
+ bool writev_consumes_all_data_;
+};
+
+class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+ bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
+ if (frame.type == MAX_STREAM_ID_FRAME) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ explicit QuicSpdySessionTestBase(Perspective perspective)
+ : connection_(
+ new StrictMock<MockQuicConnection>(&helper_,
+ &alarm_factory_,
+ perspective,
+ SupportedVersions(GetParam()))),
+ session_(connection_) {
+ session_.config()->SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ session_.config()->SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+ headers_[":host"] = "www.google.com";
+ headers_[":path"] = "/index.hml";
+ headers_[":scheme"] = "http";
+ headers_["cookie"] =
+ "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+ "__utmc=160408618; "
+ "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+ "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+ "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+ "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+ "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+ "1zFMi5vzcns38-8_Sns; "
+ "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+ "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+ "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+ "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+ "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+ "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+ "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+ "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+ "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+ "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+ "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+ "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+ "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+ "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+ "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+ .Times(testing::AnyNumber());
+ }
+
+ void CheckClosedStreams() {
+ QuicStreamId first_stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+ connection_->transport_version(), Perspective::IS_CLIENT);
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ first_stream_id =
+ QuicUtils::GetCryptoStreamId(connection_->transport_version());
+ }
+ for (QuicStreamId i = first_stream_id; i < 100; i++) {
+ if (!QuicContainsKey(closed_streams_, i)) {
+ EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i;
+ } else {
+ EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i;
+ }
+ }
+ }
+
+ void CloseStream(QuicStreamId id) {
+ if (!IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ } else {
+ // V99 has two frames, RST_STREAM and STOP_SENDING
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .Times(2)
+ .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+ }
+ EXPECT_CALL(*connection_, OnStreamReset(id, _));
+ session_.CloseStream(id);
+ closed_streams_.insert(id);
+ }
+
+ QuicTransportVersion transport_version() const {
+ return connection_->transport_version();
+ }
+
+ bool IsVersion99() const { return transport_version() == QUIC_VERSION_99; }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(transport_version(), n);
+ }
+
+ QuicStreamId GetNthServerInitiatedBidirectionalId(int n) {
+ return GetNthServerInitiatedBidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ QuicStreamId IdDelta() {
+ return QuicUtils::StreamIdDelta(connection_->transport_version());
+ }
+
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnection>* connection_;
+ TestSession session_;
+ std::set<QuicStreamId> closed_streams_;
+ SpdyHeaderBlock headers_;
+};
+
+class QuicSpdySessionTestServer : public QuicSpdySessionTestBase {
+ protected:
+ QuicSpdySessionTestServer()
+ : QuicSpdySessionTestBase(Perspective::IS_SERVER) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSpdySessionTestServer,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdySessionTestServer, ShouldBufferIncomingStreamUnidirectional) {
+ if (!IsVersion99()) {
+ return;
+ }
+ EXPECT_TRUE(session_.ShouldBufferIncomingStream(
+ QuicUtils::GetFirstUnidirectionalStreamId(
+ connection_->transport_version(), Perspective::IS_CLIENT)));
+}
+
+TEST_P(QuicSpdySessionTestServer, ShouldBufferIncomingStreamBidirectional) {
+ if (!IsVersion99()) {
+ return;
+ }
+ EXPECT_FALSE(session_.ShouldBufferIncomingStream(
+ QuicUtils::GetFirstBidirectionalStreamId(connection_->transport_version(),
+ Perspective::IS_CLIENT)));
+}
+
+TEST_P(QuicSpdySessionTestServer, PeerAddress) {
+ EXPECT_EQ(QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort),
+ session_.peer_address());
+}
+
+TEST_P(QuicSpdySessionTestServer, SelfAddress) {
+ EXPECT_TRUE(session_.self_address().IsInitialized());
+}
+
+TEST_P(QuicSpdySessionTestServer, IsCryptoHandshakeConfirmed) {
+ EXPECT_FALSE(session_.IsCryptoHandshakeConfirmed());
+ CryptoHandshakeMessage message;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(message);
+ EXPECT_TRUE(session_.IsCryptoHandshakeConfirmed());
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamDefault) {
+ // Ensure that no streams are initially closed.
+ QuicStreamId first_stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+ connection_->transport_version(), Perspective::IS_CLIENT);
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ first_stream_id =
+ QuicUtils::GetCryptoStreamId(connection_->transport_version());
+ }
+ for (QuicStreamId i = first_stream_id; i < 100; i++) {
+ EXPECT_FALSE(session_.IsClosedStream(i)) << "stream id: " << i;
+ }
+}
+
+TEST_P(QuicSpdySessionTestServer, AvailableStreams) {
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthClientInitiatedBidirectionalId(2)) != nullptr);
+ // Both client initiated streams with smaller stream IDs are available.
+ EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+ &session_, GetNthClientInitiatedBidirectionalId(0)));
+ EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+ &session_, GetNthClientInitiatedBidirectionalId(1)));
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthClientInitiatedBidirectionalId(1)) != nullptr);
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthClientInitiatedBidirectionalId(0)) != nullptr);
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamLocallyCreated) {
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_EQ(GetNthServerInitiatedBidirectionalId(0), stream2->id());
+ QuicSpdyStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_EQ(GetNthServerInitiatedBidirectionalId(1), stream4->id());
+
+ CheckClosedStreams();
+ CloseStream(GetNthServerInitiatedBidirectionalId(0));
+ CheckClosedStreams();
+ CloseStream(GetNthServerInitiatedBidirectionalId(1));
+ CheckClosedStreams();
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamPeerCreated) {
+ QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
+ QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1);
+ session_.GetOrCreateDynamicStream(stream_id1);
+ session_.GetOrCreateDynamicStream(stream_id2);
+
+ CheckClosedStreams();
+ CloseStream(stream_id1);
+ CheckClosedStreams();
+ CloseStream(stream_id2);
+ // Create a stream, and make another available.
+ QuicStream* stream3 = session_.GetOrCreateDynamicStream(stream_id2 + 4);
+ CheckClosedStreams();
+ // Close one, but make sure the other is still not closed
+ CloseStream(stream3->id());
+ CheckClosedStreams();
+}
+
+TEST_P(QuicSpdySessionTestServer, MaximumAvailableOpenedStreams) {
+ if (IsVersion99()) {
+ // For IETF QUIC, we should be able to obtain the max allowed
+ // stream ID, the next ID should fail. Since the actual limit
+ // is not the number of open streams, we allocate the max and the max+2.
+ // Get the max allowed stream ID, this should succeed.
+ EXPECT_NE(nullptr,
+ session_.GetOrCreateDynamicStream(
+ QuicSessionPeer::v99_streamid_manager(&session_)
+ ->actual_max_allowed_incoming_bidirectional_stream_id()));
+ EXPECT_NE(
+ nullptr,
+ session_.GetOrCreateDynamicStream(
+ QuicSessionPeer::v99_streamid_manager(&session_)
+ ->actual_max_allowed_incoming_unidirectional_stream_id()));
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ // Get the (max allowed stream ID)++, this should fail.
+ EXPECT_EQ(nullptr,
+ session_.GetOrCreateDynamicStream(
+ QuicSessionPeer::v99_streamid_manager(&session_)
+ ->actual_max_allowed_incoming_bidirectional_stream_id() +
+ IdDelta()));
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+ EXPECT_EQ(nullptr,
+ session_.GetOrCreateDynamicStream(
+ QuicSessionPeer::v99_streamid_manager(&session_)
+ ->actual_max_allowed_incoming_unidirectional_stream_id() +
+ IdDelta()));
+ } else {
+ QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+ session_.GetOrCreateDynamicStream(stream_id);
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ EXPECT_NE(
+ nullptr,
+ session_.GetOrCreateDynamicStream(
+ stream_id +
+ IdDelta() *
+ (session_.max_open_incoming_bidirectional_streams() - 1)));
+ }
+}
+
+TEST_P(QuicSpdySessionTestServer, TooManyAvailableStreams) {
+ QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
+ QuicStreamId stream_id2;
+ EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id1));
+ // A stream ID which is too large to create.
+ stream_id2 = GetNthClientInitiatedBidirectionalId(
+ 2 * session_.MaxAvailableBidirectionalStreams() + 4);
+ if (IsVersion99()) {
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ } else {
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_TOO_MANY_AVAILABLE_STREAMS, _, _));
+ }
+ EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id2));
+}
+
+TEST_P(QuicSpdySessionTestServer, ManyAvailableStreams) {
+ // When max_open_streams_ is 200, should be able to create 200 streams
+ // out-of-order, that is, creating the one with the largest stream ID first.
+ QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+ QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+ // Create one stream.
+ session_.GetOrCreateDynamicStream(stream_id);
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+ // Stream count is 200, GetNth... starts counting at 0, so the 200'th stream
+ // is 199.
+ EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
+ GetNthClientInitiatedBidirectionalId(199)));
+}
+
+TEST_P(QuicSpdySessionTestServer,
+ DebugDFatalIfMarkingClosedStreamWriteBlocked) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ QuicStreamId closed_stream_id = stream2->id();
+ // Close the stream.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(closed_stream_id, _));
+ stream2->Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ std::string msg =
+ QuicStrCat("Marking unknown stream ", closed_stream_id, " blocked.");
+ EXPECT_QUIC_BUG(session_.MarkConnectionLevelWriteBlocked(closed_stream_id),
+ msg);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWrite) {
+ session_.set_writev_consumes_all_data(true);
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+ InSequence s;
+
+ // Reregister, to test the loop limit.
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ }));
+ // 2 will get called a second time as it didn't finish its block
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ }));
+ EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+ session_.SendStreamData(stream6);
+ }));
+ // 4 will not get called, as we exceeded the loop limit.
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, TestBatchedWrites) {
+ session_.set_writev_consumes_all_data(true);
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.set_writev_consumes_all_data(true);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+ // With two sessions blocked, we should get two write calls. They should both
+ // go to the first stream as it will only write 6k and mark itself blocked
+ // again.
+ InSequence s;
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendLargeFakeData(stream2, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ }));
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendLargeFakeData(stream2, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ }));
+ session_.OnCanWrite();
+
+ // We should get one more call for stream2, at which point it has used its
+ // write quota and we move over to stream 4.
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendLargeFakeData(stream2, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ }));
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendLargeFakeData(stream4, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ }));
+ session_.OnCanWrite();
+
+ // Now let stream 4 do the 2nd of its 3 writes, but add a block for a high
+ // priority stream 6. 4 should be preempted. 6 will write but *not* block so
+ // will cede back to 4.
+ stream6->SetPriority(kV3HighestPriority);
+ EXPECT_CALL(*stream4, OnCanWrite())
+ .WillOnce(Invoke([this, stream4, stream6]() {
+ session_.SendLargeFakeData(stream4, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+ }));
+ EXPECT_CALL(*stream6, OnCanWrite())
+ .WillOnce(Invoke([this, stream4, stream6]() {
+ session_.SendStreamData(stream6);
+ session_.SendLargeFakeData(stream4, 6000);
+ }));
+ session_.OnCanWrite();
+
+ // Stream4 alread did 6k worth of writes, so after doing another 12k it should
+ // cede and 2 should resume.
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendLargeFakeData(stream4, 12000);
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ }));
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendLargeFakeData(stream2, 6000);
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ }));
+ session_.OnCanWrite();
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteBundlesStreams) {
+ if (IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillRepeatedly(Invoke(
+ this, &QuicSpdySessionTestServer::ClearMaxStreamIdControlFrame));
+ }
+ // Encryption needs to be established before data can be sent.
+ CryptoHandshakeMessage msg;
+ MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(session_.connection()));
+ EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+ .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+
+ // Drive congestion control manually.
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+ EXPECT_CALL(*send_algorithm, GetCongestionWindow())
+ .WillRepeatedly(Return(kMaxOutgoingPacketSize * 10));
+ EXPECT_CALL(*send_algorithm, InRecovery()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ }));
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendStreamData(stream4);
+ }));
+ EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+ session_.SendStreamData(stream6);
+ }));
+
+ // Expect that we only send one packet, the writes from different streams
+ // should be bundled together.
+ EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+ .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+ EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _));
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+ session_.OnCanWrite();
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteCongestionControlBlocks) {
+ session_.set_writev_consumes_all_data(true);
+ InSequence s;
+
+ // Drive congestion control manually.
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ }));
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+ session_.SendStreamData(stream6);
+ }));
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(false));
+ // stream4->OnCanWrite is not called.
+
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+ // Still congestion-control blocked.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(false));
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+ // stream4->OnCanWrite is called once the connection stops being
+ // congestion-control blocked.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendStreamData(stream4);
+ }));
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+ session_.OnCanWrite();
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteWriterBlocks) {
+ // Drive congestion control manually in order to ensure that
+ // application-limited signaling is handled correctly.
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+
+ // Drive packet writer manually.
+ MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(session_.connection()));
+ EXPECT_CALL(*writer, IsWriteBlocked()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)).Times(0);
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+
+ EXPECT_CALL(*stream2, OnCanWrite()).Times(0);
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_)).Times(0);
+
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, BufferedHandshake) {
+ session_.set_writev_consumes_all_data(true);
+ EXPECT_FALSE(session_.HasPendingHandshake()); // Default value.
+
+ // Test that blocking other streams does not change our status.
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ EXPECT_FALSE(session_.HasPendingHandshake());
+
+ TestStream* stream3 = session_.CreateOutgoingBidirectionalStream();
+ session_.MarkConnectionLevelWriteBlocked(stream3->id());
+ EXPECT_FALSE(session_.HasPendingHandshake());
+
+ // Blocking (due to buffering of) the Crypto stream is detected.
+ session_.MarkConnectionLevelWriteBlocked(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()));
+ EXPECT_TRUE(session_.HasPendingHandshake());
+
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ EXPECT_TRUE(session_.HasPendingHandshake());
+
+ InSequence s;
+ // Force most streams to re-register, which is common scenario when we block
+ // the Crypto stream, and only the crypto stream can "really" write.
+
+ // Due to prioritization, we *should* be asked to write the crypto stream
+ // first.
+ // Don't re-register the crypto stream (which signals complete writing).
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ EXPECT_CALL(*crypto_stream, OnCanWrite());
+
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ }));
+ EXPECT_CALL(*stream3, OnCanWrite()).WillOnce(Invoke([this, stream3]() {
+ session_.SendStreamData(stream3);
+ }));
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendStreamData(stream4);
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ }));
+
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+ EXPECT_FALSE(session_.HasPendingHandshake()); // Crypto stream wrote.
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteWithClosedStream) {
+ session_.set_writev_consumes_all_data(true);
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ CloseStream(stream6->id());
+
+ InSequence s;
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+ session_.SendStreamData(stream2);
+ }));
+ EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+ session_.SendStreamData(stream4);
+ }));
+ session_.OnCanWrite();
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+ OnCanWriteLimitsNumWritesIfFlowControlBlocked) {
+ // Drive congestion control manually in order to ensure that
+ // application-limited signaling is handled correctly.
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+
+ // Ensure connection level flow control blockage.
+ QuicFlowControllerPeer::SetSendWindowOffset(session_.flow_controller(), 0);
+ EXPECT_TRUE(session_.flow_controller()->IsBlocked());
+ EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+
+ // Mark the crypto and headers streams as write blocked, we expect them to be
+ // allowed to write later.
+ session_.MarkConnectionLevelWriteBlocked(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()));
+
+ // Create a data stream, and although it is write blocked we never expect it
+ // to be allowed to write as we are connection level flow control blocked.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+ session_.MarkConnectionLevelWriteBlocked(stream->id());
+ EXPECT_CALL(*stream, OnCanWrite()).Times(0);
+
+ // The crypto and headers streams should be called even though we are
+ // connection flow control blocked.
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ EXPECT_CALL(*crypto_stream, OnCanWrite());
+ QuicSpdySessionPeer::SetHeadersStream(&session_, nullptr);
+ TestHeadersStream* headers_stream = new TestHeadersStream(&session_);
+ QuicSpdySessionPeer::SetHeadersStream(&session_, headers_stream);
+ session_.MarkConnectionLevelWriteBlocked(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()));
+ EXPECT_CALL(*headers_stream, OnCanWrite());
+
+ // After the crypto and header streams perform a write, the connection will be
+ // blocked by the flow control, hence it should become application-limited.
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+
+ session_.OnCanWrite();
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, SendGoAway) {
+ if (IsVersion99()) {
+ // GoAway frames are not in version 99
+ return;
+ }
+ MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(session_.connection()));
+ EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+ .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallySendControlFrame));
+ session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+ EXPECT_TRUE(session_.goaway_sent());
+
+ const QuicStreamId kTestStreamId = 5u;
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+ EXPECT_CALL(*connection_,
+ OnStreamReset(kTestStreamId, QUIC_STREAM_PEER_GOING_AWAY))
+ .Times(0);
+ EXPECT_TRUE(session_.GetOrCreateDynamicStream(kTestStreamId));
+}
+
+TEST_P(QuicSpdySessionTestServer, DoNotSendGoAwayTwice) {
+ if (IsVersion99()) {
+ // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
+ // supported.
+ return;
+ }
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+ EXPECT_TRUE(session_.goaway_sent());
+ session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidGoAway) {
+ if (IsVersion99()) {
+ // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
+ // supported.
+ return;
+ }
+ QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY,
+ session_.next_outgoing_bidirectional_stream_id(), "");
+ session_.OnGoAway(go_away);
+}
+
+// Test that server session will send a connectivity probe in response to a
+// connectivity probe on the same path.
+TEST_P(QuicSpdySessionTestServer, ServerReplyToConnecitivityProbe) {
+ QuicSocketAddress old_peer_address =
+ QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort);
+ EXPECT_EQ(old_peer_address, session_.peer_address());
+
+ QuicSocketAddress new_peer_address =
+ QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort + 1);
+
+ EXPECT_CALL(*connection_,
+ SendConnectivityProbingResponsePacket(new_peer_address));
+ if (IsVersion99()) {
+ // Need to explicitly do this to emulate the reception of a PathChallenge,
+ // which stores its payload for use in generating the response.
+ connection_->OnPathChallengeFrame(
+ QuicPathChallengeFrame(0, {{0, 1, 2, 3, 4, 5, 6, 7}}));
+ }
+ session_.OnConnectivityProbeReceived(session_.self_address(),
+ new_peer_address);
+ EXPECT_EQ(old_peer_address, session_.peer_address());
+}
+
+TEST_P(QuicSpdySessionTestServer, IncreasedTimeoutAfterCryptoHandshake) {
+ EXPECT_EQ(kInitialIdleTimeoutSecs + 3,
+ QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+ CryptoHandshakeMessage msg;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+ EXPECT_EQ(kMaximumIdleTimeoutSecs + 3,
+ QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+}
+
+TEST_P(QuicSpdySessionTestServer, RstStreamBeforeHeadersDecompressed) {
+ // Send two bytes of payload.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ session_.OnStreamFrame(data1);
+ EXPECT_EQ(1u, session_.GetNumOpenIncomingStreams());
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ if (!IsVersion99()) {
+ // For version99, OnStreamReset gets called because of the STOP_SENDING,
+ // below. EXPECT the call there.
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0), _));
+ }
+ QuicRstStreamFrame rst1(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ session_.OnRstStream(rst1);
+
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+ // one-way close.
+ if (IsVersion99()) {
+ // Only needed for version 99/IETF QUIC.
+ QuicStopSendingFrame stop_sending(
+ kInvalidControlFrameId, GetNthClientInitiatedBidirectionalId(0),
+ static_cast<QuicApplicationErrorCode>(QUIC_ERROR_PROCESSING_STREAM));
+ // Expect the RESET_STREAM that is generated in response to receiving a
+ // STOP_SENDING.
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM));
+ session_.OnStopSendingFrame(stop_sending);
+ }
+
+ EXPECT_EQ(0u, session_.GetNumOpenIncomingStreams());
+ // Connection should remain alive.
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameFinStaticStreamId) {
+ // Send two bytes of payload.
+ QuicStreamFrame data1(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()), true, 0,
+ QuicStringPiece("HT"));
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Attempt to close a static stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+ session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnRstStreamStaticStreamId) {
+ // Send two bytes of payload.
+ QuicRstStreamFrame rst1(
+ kInvalidControlFrameId,
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Attempt to reset a static stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+ session_.OnRstStream(rst1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameInvalidStreamId) {
+ // Send two bytes of payload.
+ QuicStreamFrame data1(
+ QuicUtils::GetInvalidStreamId(connection_->transport_version()), true, 0,
+ QuicStringPiece("HT"));
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Received data for an invalid stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+ session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnRstStreamInvalidStreamId) {
+ // Send two bytes of payload.
+ QuicRstStreamFrame rst1(
+ kInvalidControlFrameId,
+ QuicUtils::GetInvalidStreamId(connection_->transport_version()),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INVALID_STREAM_ID, "Received data for an invalid stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+ session_.OnRstStream(rst1);
+}
+
+TEST_P(QuicSpdySessionTestServer, HandshakeUnblocksFlowControlBlockedStream) {
+ // Test that if a stream is flow control blocked, then on receipt of the SHLO
+ // containing a suitable send window offset, the stream becomes unblocked.
+
+ // Ensure that Writev consumes all the data it is given (simulate no socket
+ // blocking).
+ session_.set_writev_consumes_all_data(true);
+
+ // Create a stream, and send enough data to make it flow control blocked.
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ std::string body(kMinimumFlowControlSendWindow, '.');
+ EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AtLeast(1));
+ stream2->WriteOrBufferBody(body, false);
+ EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+ EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+
+ // Now complete the crypto handshake, resulting in an increased flow control
+ // send window.
+ CryptoHandshakeMessage msg;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+ EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(&session_, stream2->id()));
+ // Stream is now unblocked.
+ EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+ HandshakeUnblocksFlowControlBlockedCryptoStream) {
+ if (QuicVersionUsesCryptoFrames(GetParam().transport_version)) {
+ // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this
+ // test doesn't make sense for those versions.
+ return;
+ }
+ // Test that if the crypto stream is flow control blocked, then if the SHLO
+ // contains a larger send window offset, the stream becomes unblocked.
+ session_.set_writev_consumes_all_data(true);
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ QuicHeadersStream* headers_stream =
+ QuicSpdySessionPeer::GetHeadersStream(&session_);
+ EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ if (IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ } else {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ }
+ for (QuicStreamId i = 0;
+ !crypto_stream->flow_controller()->IsBlocked() && i < 1000u; i++) {
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ QuicStreamOffset offset = crypto_stream->stream_bytes_written();
+ QuicConfig config;
+ CryptoHandshakeMessage crypto_message;
+ config.ToHandshakeMessage(&crypto_message);
+ crypto_stream->SendHandshakeMessage(crypto_message);
+ char buf[1000];
+ QuicDataWriter writer(1000, buf, NETWORK_BYTE_ORDER);
+ crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer);
+ }
+ EXPECT_TRUE(crypto_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+ EXPECT_FALSE(session_.HasDataToWrite());
+ EXPECT_TRUE(crypto_stream->HasBufferedData());
+
+ // Now complete the crypto handshake, resulting in an increased flow control
+ // send window.
+ CryptoHandshakeMessage msg;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+ EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
+ &session_,
+ QuicUtils::GetCryptoStreamId(connection_->transport_version())));
+ // Stream is now unblocked and will no longer have buffered data.
+ EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+#if !defined(OS_IOS)
+// This test is failing flakily for iOS bots.
+// http://crbug.com/425050
+// NOTE: It's not possible to use the standard MAYBE_ convention to disable
+// this test on iOS because when this test gets instantiated it ends up with
+// various names that are dependent on the parameters passed.
+TEST_P(QuicSpdySessionTestServer,
+ HandshakeUnblocksFlowControlBlockedHeadersStream) {
+ // Test that if the header stream is flow control blocked, then if the SHLO
+ // contains a larger send window offset, the stream becomes unblocked.
+ session_.set_writev_consumes_all_data(true);
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ QuicHeadersStream* headers_stream =
+ QuicSpdySessionPeer::GetHeadersStream(&session_);
+ EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ QuicStreamId stream_id = 5;
+ // Write until the header stream is flow control blocked.
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ SpdyHeaderBlock headers;
+ SimpleRandom random;
+ while (!headers_stream->flow_controller()->IsBlocked() && stream_id < 2000) {
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ headers["header"] = QuicStrCat(random.RandUint64(), random.RandUint64(),
+ random.RandUint64());
+ session_.WriteHeadersOnHeadersStream(stream_id, headers.Clone(), true, 0,
+ nullptr);
+ stream_id += IdDelta();
+ }
+ // Write once more to ensure that the headers stream has buffered data. The
+ // random headers may have exactly filled the flow control window.
+ session_.WriteHeadersOnHeadersStream(stream_id, std::move(headers), true, 0,
+ nullptr);
+ EXPECT_TRUE(headers_stream->HasBufferedData());
+
+ EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+ EXPECT_FALSE(session_.HasDataToWrite());
+
+ // Now complete the crypto handshake, resulting in an increased flow control
+ // send window.
+ CryptoHandshakeMessage msg;
+ session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+
+ // Stream is now unblocked and will no longer have buffered data.
+ EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+ EXPECT_TRUE(headers_stream->HasBufferedData());
+ EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
+ &session_,
+ QuicUtils::GetHeadersStreamId(connection_->transport_version())));
+}
+#endif // !defined(OS_IOS)
+
+TEST_P(QuicSpdySessionTestServer,
+ ConnectionFlowControlAccountingRstOutOfOrder) {
+ // Test that when we receive an out of order stream RST we correctly adjust
+ // our connection level flow control receive window.
+ // On close, the stream should mark as consumed all bytes between the highest
+ // byte consumed so far and the final byte offset from the RST frame.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+
+ const QuicStreamOffset kByteOffset =
+ 1 + kInitialSessionFlowControlWindowForTest / 2;
+
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .Times(2)
+ .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+ if (!IsVersion99()) {
+ // For version99 the call to OnStreamReset happens as a result of receiving
+ // the STOP_SENDING, so set up the EXPECT there.
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ }
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+ QUIC_STREAM_CANCELLED, kByteOffset);
+ session_.OnRstStream(rst_frame);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+ // one-way close.
+ if (IsVersion99()) {
+ // Only needed for version 99/IETF QUIC.
+ QuicStopSendingFrame stop_sending(
+ kInvalidControlFrameId, stream->id(),
+ static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+ // Expect the RESET_STREAM that is generated in response to receiving a
+ // STOP_SENDING.
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream->id(), QUIC_STREAM_CANCELLED));
+ session_.OnStopSendingFrame(stop_sending);
+ }
+
+ EXPECT_EQ(kByteOffset, session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+ ConnectionFlowControlAccountingFinAndLocalReset) {
+ // Test the situation where we receive a FIN on a stream, and before we fully
+ // consume all the data from the sequencer buffer we locally RST the stream.
+ // The bytes between highest consumed byte, and the final byte offset that we
+ // determined when the FIN arrived, should be marked as consumed at the
+ // connection level flow controller when the stream is reset.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+
+ const QuicStreamOffset kByteOffset =
+ kInitialSessionFlowControlWindowForTest / 2 - 1;
+ QuicStreamFrame frame(stream->id(), true, kByteOffset, ".");
+ session_.OnStreamFrame(frame);
+ EXPECT_TRUE(connection_->connected());
+
+ EXPECT_EQ(0u, stream->flow_controller()->bytes_consumed());
+ EXPECT_EQ(kByteOffset + frame.data_length,
+ stream->flow_controller()->highest_received_byte_offset());
+
+ // Reset stream locally.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ stream->Reset(QUIC_STREAM_CANCELLED);
+ EXPECT_EQ(kByteOffset + frame.data_length,
+ session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingFinAfterRst) {
+ // Test that when we RST the stream (and tear down stream state), and then
+ // receive a FIN from the peer, we correctly adjust our connection level flow
+ // control receive window.
+
+ // Connection starts with some non-zero highest received byte offset,
+ // due to other active streams.
+ const uint64_t kInitialConnectionBytesConsumed = 567;
+ const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
+ EXPECT_LT(kInitialConnectionBytesConsumed,
+ kInitialConnectionHighestReceivedOffset);
+ session_.flow_controller()->UpdateHighestReceivedOffset(
+ kInitialConnectionHighestReceivedOffset);
+ session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+ // Reset our stream: this results in the stream being closed locally.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ stream->Reset(QUIC_STREAM_CANCELLED);
+
+ // Now receive a response from the peer with a FIN. We should handle this by
+ // adjusting the connection level flow control receive window to take into
+ // account the total number of bytes sent by the peer.
+ const QuicStreamOffset kByteOffset = 5678;
+ std::string body = "hello";
+ QuicStreamFrame frame(stream->id(), true, kByteOffset, QuicStringPiece(body));
+ session_.OnStreamFrame(frame);
+
+ QuicStreamOffset total_stream_bytes_sent_by_peer =
+ kByteOffset + body.length();
+ EXPECT_EQ(kInitialConnectionBytesConsumed + total_stream_bytes_sent_by_peer,
+ session_.flow_controller()->bytes_consumed());
+ EXPECT_EQ(
+ kInitialConnectionHighestReceivedOffset + total_stream_bytes_sent_by_peer,
+ session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingRstAfterRst) {
+ // Test that when we RST the stream (and tear down stream state), and then
+ // receive a RST from the peer, we correctly adjust our connection level flow
+ // control receive window.
+
+ // Connection starts with some non-zero highest received byte offset,
+ // due to other active streams.
+ const uint64_t kInitialConnectionBytesConsumed = 567;
+ const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
+ EXPECT_LT(kInitialConnectionBytesConsumed,
+ kInitialConnectionHighestReceivedOffset);
+ session_.flow_controller()->UpdateHighestReceivedOffset(
+ kInitialConnectionHighestReceivedOffset);
+ session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+ // Reset our stream: this results in the stream being closed locally.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ stream->Reset(QUIC_STREAM_CANCELLED);
+ EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream));
+
+ // Now receive a RST from the peer. We should handle this by adjusting the
+ // connection level flow control receive window to take into account the total
+ // number of bytes sent by the peer.
+ const QuicStreamOffset kByteOffset = 5678;
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+ QUIC_STREAM_CANCELLED, kByteOffset);
+ session_.OnRstStream(rst_frame);
+
+ EXPECT_EQ(kInitialConnectionBytesConsumed + kByteOffset,
+ session_.flow_controller()->bytes_consumed());
+ EXPECT_EQ(kInitialConnectionHighestReceivedOffset + kByteOffset,
+ session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidStreamFlowControlWindowInHandshake) {
+ // Test that receipt of an invalid (< default) stream flow control window from
+ // the peer results in the connection being torn down.
+ const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+ QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(session_.config(),
+ kInvalidWindow);
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+ session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidSessionFlowControlWindowInHandshake) {
+ // Test that receipt of an invalid (< default) session flow control window
+ // from the peer results in the connection being torn down.
+ const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+ QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
+ kInvalidWindow);
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+ session_.OnConfigNegotiated();
+}
+
+// Test negotiation of custom server initial flow control window.
+TEST_P(QuicSpdySessionTestServer, CustomFlowControlWindow) {
+ QuicTagVector copt;
+ copt.push_back(kIFW7);
+ QuicConfigPeer::SetReceivedConnectionOptions(session_.config(), copt);
+
+ session_.OnConfigNegotiated();
+ EXPECT_EQ(192 * 1024u, QuicFlowControllerPeer::ReceiveWindowSize(
+ session_.flow_controller()));
+}
+
+TEST_P(QuicSpdySessionTestServer, FlowControlWithInvalidFinalOffset) {
+ // Test that if we receive a stream RST with a highest byte offset that
+ // violates flow control, that we close the connection.
+ const uint64_t kLargeOffset = kInitialSessionFlowControlWindowForTest + 1;
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _))
+ .Times(2);
+
+ // Check that stream frame + FIN results in connection close.
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ stream->Reset(QUIC_STREAM_CANCELLED);
+ QuicStreamFrame frame(stream->id(), true, kLargeOffset, QuicStringPiece());
+ session_.OnStreamFrame(frame);
+
+ // Check that RST results in connection close.
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+ QUIC_STREAM_CANCELLED, kLargeOffset);
+ session_.OnRstStream(rst_frame);
+}
+
+TEST_P(QuicSpdySessionTestServer, WindowUpdateUnblocksHeadersStream) {
+ // Test that a flow control blocked headers stream gets unblocked on recipt of
+ // a WINDOW_UPDATE frame.
+
+ // Set the headers stream to be flow control blocked.
+ QuicHeadersStream* headers_stream =
+ QuicSpdySessionPeer::GetHeadersStream(&session_);
+ QuicFlowControllerPeer::SetSendWindowOffset(headers_stream->flow_controller(),
+ 0);
+ EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+
+ // Unblock the headers stream by supplying a WINDOW_UPDATE.
+ QuicWindowUpdateFrame window_update_frame(kInvalidControlFrameId,
+ headers_stream->id(),
+ 2 * kMinimumFlowControlSendWindow);
+ session_.OnWindowUpdateFrame(window_update_frame);
+ EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+ EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+ EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+ TooManyUnfinishedStreamsCauseServerRejectStream) {
+ // If a buggy/malicious peer creates too many streams that are not ended
+ // with a FIN or RST then we send an RST to refuse streams for versions other
+ // than version 99. In version 99 the connection gets closed.
+ const QuicStreamId kMaxStreams = 5;
+ QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+ const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
+ const QuicStreamId kFinalStreamId =
+ GetNthClientInitiatedBidirectionalId(kMaxStreams);
+ // Create kMaxStreams data streams, and close them all without receiving a
+ // FIN or a RST_STREAM from the client.
+ const QuicStreamId kNextId =
+ QuicUtils::StreamIdDelta(connection_->transport_version());
+ for (QuicStreamId i = kFirstStreamId; i < kFinalStreamId; i += kNextId) {
+ QuicStreamFrame data1(i, false, 0, QuicStringPiece("HT"));
+ session_.OnStreamFrame(data1);
+ // EXPECT_EQ(1u, session_.GetNumOpenStreams());
+ if (!IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ } else {
+ // V99 has two frames, RST_STREAM and STOP_SENDING
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .Times(2)
+ .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+ }
+ // Close the stream only if not version 99. If we are version 99
+ // then closing the stream opens up the available stream id space,
+ // so we never bump into the limit.
+ EXPECT_CALL(*connection_, OnStreamReset(i, _));
+ session_.CloseStream(i);
+ }
+ // Try and open a stream that exceeds the limit.
+ if (!IsVersion99()) {
+ // On versions other than 99, opening such a stream results in a
+ // RST_STREAM.
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+ EXPECT_CALL(*connection_,
+ OnStreamReset(kFinalStreamId, QUIC_REFUSED_STREAM))
+ .Times(1);
+ } else {
+ // On version 99 opening such a stream results in a connection close.
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Stream id 28 above 24", _));
+ }
+ // Create one more data streams to exceed limit of open stream.
+ QuicStreamFrame data1(kFinalStreamId, false, 0, QuicStringPiece("HT"));
+ session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, DrainingStreamsDoNotCountAsOpened) {
+ // Verify that a draining stream (which has received a FIN but not consumed
+ // it) does not count against the open quota (because it is closed from the
+ // protocol point of view).
+ if (IsVersion99()) {
+ // Version 99 will result in a MAX_STREAM_ID frame as streams are consumed
+ // (via the OnStreamFrame call) and then released (via
+ // StreamDraining). Eventually this node will believe that the peer is
+ // running low on available stream ids and then send a MAX_STREAM_ID frame,
+ // caught by this EXPECT_CALL.
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+ } else {
+ EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+ }
+ EXPECT_CALL(*connection_, OnStreamReset(_, QUIC_REFUSED_STREAM)).Times(0);
+ const QuicStreamId kMaxStreams = 5;
+ QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+
+ // Create kMaxStreams + 1 data streams, and mark them draining.
+ const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
+ const QuicStreamId kFinalStreamId =
+ GetNthClientInitiatedBidirectionalId(kMaxStreams + 1);
+ for (QuicStreamId i = kFirstStreamId; i < kFinalStreamId; i += IdDelta()) {
+ QuicStreamFrame data1(i, true, 0, QuicStringPiece("HT"));
+ session_.OnStreamFrame(data1);
+ EXPECT_EQ(1u, session_.GetNumOpenIncomingStreams());
+ session_.StreamDraining(i);
+ EXPECT_EQ(0u, session_.GetNumOpenIncomingStreams());
+ }
+}
+
+class QuicSpdySessionTestClient : public QuicSpdySessionTestBase {
+ protected:
+ QuicSpdySessionTestClient()
+ : QuicSpdySessionTestBase(Perspective::IS_CLIENT) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSpdySessionTestClient,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdySessionTestClient, AvailableStreamsClient) {
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthServerInitiatedBidirectionalId(2)) != nullptr);
+ // Both server initiated streams with smaller stream IDs should be available.
+ EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+ &session_, GetNthServerInitiatedBidirectionalId(0)));
+ EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+ &session_, GetNthServerInitiatedBidirectionalId(1)));
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthServerInitiatedBidirectionalId(0)) != nullptr);
+ ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+ GetNthServerInitiatedBidirectionalId(1)) != nullptr);
+ // And client initiated stream ID should be not available.
+ EXPECT_FALSE(QuicSessionPeer::IsStreamAvailable(
+ &session_, GetNthClientInitiatedBidirectionalId(0)));
+}
+
+TEST_P(QuicSpdySessionTestClient, RecordFinAfterReadSideClosed) {
+ // Verify that an incoming FIN is recorded in a stream object even if the read
+ // side has been closed. This prevents an entry from being made in
+ // locally_closed_streams_highest_offset_ (which will never be deleted).
+ TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+ QuicStreamId stream_id = stream->id();
+
+ // Close the read side manually.
+ QuicStreamPeer::CloseReadSide(stream);
+
+ // Receive a stream data frame with FIN.
+ QuicStreamFrame frame(stream_id, true, 0, QuicStringPiece());
+ session_.OnStreamFrame(frame);
+ EXPECT_TRUE(stream->fin_received());
+
+ // Reset stream locally.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+ stream->Reset(QUIC_STREAM_CANCELLED);
+ EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream));
+
+ EXPECT_TRUE(connection_->connected());
+ EXPECT_TRUE(QuicSessionPeer::IsStreamClosed(&session_, stream_id));
+ EXPECT_FALSE(QuicSessionPeer::IsStreamCreated(&session_, stream_id));
+
+ // The stream is not waiting for the arrival of the peer's final offset as it
+ // was received with the FIN earlier.
+ EXPECT_EQ(
+ 0u,
+ QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(&session_).size());
+}
+
+TEST_P(QuicSpdySessionTestClient, WritePriority) {
+ QuicSpdySessionPeer::SetHeadersStream(&session_, nullptr);
+ TestHeadersStream* headers_stream = new TestHeadersStream(&session_);
+ QuicSpdySessionPeer::SetHeadersStream(&session_, headers_stream);
+
+ // Make packet writer blocked so |headers_stream| will buffer its write data.
+ MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(session_.connection()));
+ EXPECT_CALL(*writer, IsWriteBlocked()).WillRepeatedly(Return(true));
+
+ const QuicStreamId id = 4;
+ const QuicStreamId parent_stream_id = 9;
+ const SpdyPriority priority = kV3HighestPriority;
+ const bool exclusive = true;
+ session_.WritePriority(id, parent_stream_id,
+ Spdy3PriorityToHttp2Weight(priority), exclusive);
+
+ QuicStreamSendBuffer& send_buffer =
+ QuicStreamPeer::SendBuffer(headers_stream);
+ if (transport_version() > QUIC_VERSION_39) {
+ ASSERT_EQ(1u, send_buffer.size());
+
+ SpdyPriorityIR priority_frame(
+ id, parent_stream_id, Spdy3PriorityToHttp2Weight(priority), exclusive);
+ SpdyFramer spdy_framer(SpdyFramer::ENABLE_COMPRESSION);
+ SpdySerializedFrame frame = spdy_framer.SerializeFrame(priority_frame);
+
+ const QuicMemSlice& slice =
+ QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer)->slice;
+ EXPECT_EQ(QuicStringPiece(frame.data(), frame.size()),
+ QuicStringPiece(slice.data(), slice.length()));
+ } else {
+ EXPECT_EQ(0u, send_buffer.size());
+ }
+}
+
+TEST_P(QuicSpdySessionTestServer, ZombieStreams) {
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ QuicStreamPeer::SetStreamBytesWritten(3, stream2);
+ EXPECT_TRUE(stream2->IsWaitingForAcks());
+
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream2->id(), _));
+ session_.CloseStream(stream2->id());
+ EXPECT_FALSE(QuicContainsKey(session_.zombie_streams(), stream2->id()));
+ ASSERT_EQ(1u, session_.closed_streams()->size());
+ EXPECT_EQ(stream2->id(), session_.closed_streams()->front()->id());
+ session_.OnStreamDoneWaitingForAcks(2);
+ EXPECT_FALSE(QuicContainsKey(session_.zombie_streams(), stream2->id()));
+ EXPECT_EQ(1u, session_.closed_streams()->size());
+ EXPECT_EQ(stream2->id(), session_.closed_streams()->front()->id());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameLost) {
+ QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+ InSequence s;
+
+ // Drive congestion control manually.
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+ TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+
+ QuicStreamFrame frame2(stream2->id(), false, 0, 9);
+ QuicStreamFrame frame3(stream4->id(), false, 0, 9);
+
+ // Lost data on cryption stream, streams 2 and 4.
+ EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(true));
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+ .WillOnce(Return(true));
+ }
+ EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(true));
+ session_.OnFrameLost(QuicFrame(frame3));
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ QuicStreamFrame frame1(
+ QuicUtils::GetCryptoStreamId(connection_->transport_version()), false,
+ 0, 1300);
+ session_.OnFrameLost(QuicFrame(frame1));
+ } else {
+ QuicCryptoFrame crypto_frame(ENCRYPTION_INITIAL, 0, 1300);
+ session_.OnFrameLost(QuicFrame(&crypto_frame));
+ }
+ session_.OnFrameLost(QuicFrame(frame2));
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+ // Mark streams 2 and 4 write blocked.
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+ // Lost data is retransmitted before new data, and retransmissions for crypto
+ // stream go first.
+ // Do not check congestion window when crypto stream has lost data.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).Times(0);
+ if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+ EXPECT_CALL(*crypto_stream, OnCanWrite());
+ EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+ .WillOnce(Return(false));
+ }
+ // Check congestion window for non crypto streams.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream4, OnCanWrite());
+ EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(false));
+ // Connection is blocked.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(false));
+
+ session_.OnCanWrite();
+ EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+ // Unblock connection.
+ // Stream 2 retransmits lost data.
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream2, OnCanWrite());
+ EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(false));
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ // Stream 2 sends new data.
+ EXPECT_CALL(*stream2, OnCanWrite());
+ EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+ EXPECT_CALL(*stream4, OnCanWrite());
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+
+ session_.OnCanWrite();
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, DonotRetransmitDataOfClosedStreams) {
+ QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+ InSequence s;
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+ QuicStreamFrame frame1(stream2->id(), false, 0, 9);
+ QuicStreamFrame frame2(stream4->id(), false, 0, 9);
+ QuicStreamFrame frame3(stream6->id(), false, 0, 9);
+
+ EXPECT_CALL(*stream6, HasPendingRetransmission()).WillOnce(Return(true));
+ EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(true));
+ EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(true));
+ session_.OnFrameLost(QuicFrame(frame3));
+ session_.OnFrameLost(QuicFrame(frame2));
+ session_.OnFrameLost(QuicFrame(frame1));
+
+ session_.MarkConnectionLevelWriteBlocked(stream2->id());
+ session_.MarkConnectionLevelWriteBlocked(stream4->id());
+ session_.MarkConnectionLevelWriteBlocked(stream6->id());
+
+ // Reset stream 4 locally.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ EXPECT_CALL(*connection_, OnStreamReset(stream4->id(), _));
+ stream4->Reset(QUIC_STREAM_CANCELLED);
+
+ // Verify stream 4 is removed from streams with lost data list.
+ EXPECT_CALL(*stream6, OnCanWrite());
+ EXPECT_CALL(*stream6, HasPendingRetransmission()).WillOnce(Return(false));
+ EXPECT_CALL(*stream2, OnCanWrite());
+ EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(false));
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+ EXPECT_CALL(*stream2, OnCanWrite());
+ EXPECT_CALL(*stream6, OnCanWrite());
+ session_.OnCanWrite();
+}
+
+TEST_P(QuicSpdySessionTestServer, RetransmitFrames) {
+ QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+ InSequence s;
+
+ TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+ TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ session_.SendWindowUpdate(stream2->id(), 9);
+
+ QuicStreamFrame frame1(stream2->id(), false, 0, 9);
+ QuicStreamFrame frame2(stream4->id(), false, 0, 9);
+ QuicStreamFrame frame3(stream6->id(), false, 0, 9);
+ QuicWindowUpdateFrame window_update(1, stream2->id(), 9);
+ QuicFrames frames;
+ frames.push_back(QuicFrame(frame1));
+ frames.push_back(QuicFrame(&window_update));
+ frames.push_back(QuicFrame(frame2));
+ frames.push_back(QuicFrame(frame3));
+ EXPECT_FALSE(session_.WillingAndAbleToWrite());
+
+ EXPECT_CALL(*stream2, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+ EXPECT_CALL(*stream4, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+ EXPECT_CALL(*stream6, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+ EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+ session_.RetransmitFrames(frames, TLP_RETRANSMISSION);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnPriorityFrame) {
+ QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+ TestStream* stream = session_.CreateIncomingStream(stream_id);
+ session_.OnPriorityFrame(stream_id, kV3HighestPriority);
+ EXPECT_EQ(kV3HighestPriority, stream->priority());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc
new file mode 100644
index 00000000000..674b0760d23
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc
@@ -0,0 +1,661 @@
+// 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_spdy_stream.h"
+
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.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_mem_slice_storage.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+
+namespace quic {
+
+// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
+// the connection on unexpected frames.
+class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor {
+ public:
+ explicit HttpDecoderVisitor(QuicSpdyStream* stream) : stream_(stream) {}
+ HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
+ HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
+
+ void OnError(HttpDecoder* decoder) override {
+ stream_->session()->connection()->CloseConnection(
+ QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ }
+
+ void OnPriorityFrame(const PriorityFrame& frame) override {
+ CloseConnectionOnWrongFrame("Priority");
+ }
+
+ void OnCancelPushFrame(const CancelPushFrame& frame) override {
+ CloseConnectionOnWrongFrame("Cancel Push");
+ }
+
+ void OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
+ CloseConnectionOnWrongFrame("Max Push Id");
+ }
+
+ void OnGoAwayFrame(const GoAwayFrame& frame) override {
+ CloseConnectionOnWrongFrame("Goaway");
+ }
+
+ void OnSettingsFrameStart(Http3FrameLengths frame_lengths) override {
+ CloseConnectionOnWrongFrame("Settings");
+ }
+
+ void OnSettingsFrame(const SettingsFrame& frame) override {
+ CloseConnectionOnWrongFrame("Settings");
+ }
+
+ void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
+ CloseConnectionOnWrongFrame("Duplicate Push");
+ }
+
+ void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
+ stream_->OnDataFrameStart(frame_lengths);
+ }
+
+ void OnDataFramePayload(QuicStringPiece payload) override {
+ DCHECK(!payload.empty());
+ stream_->OnDataFramePayload(payload);
+ }
+
+ void OnDataFrameEnd() override { stream_->OnDataFrameEnd(); }
+
+ void OnHeadersFrameStart(Http3FrameLengths frame_length) override {
+ if (!VersionUsesQpack(
+ stream_->session()->connection()->transport_version())) {
+ CloseConnectionOnWrongFrame("Headers");
+ return;
+ }
+ stream_->OnHeadersFrameStart(frame_length);
+ }
+
+ void OnHeadersFramePayload(QuicStringPiece payload) override {
+ DCHECK(!payload.empty());
+ if (!VersionUsesQpack(
+ stream_->session()->connection()->transport_version())) {
+ CloseConnectionOnWrongFrame("Headers");
+ return;
+ }
+ stream_->OnHeadersFramePayload(payload);
+ }
+
+ void OnHeadersFrameEnd() override {
+ if (!VersionUsesQpack(
+ stream_->session()->connection()->transport_version())) {
+ CloseConnectionOnWrongFrame("Headers");
+ return;
+ }
+ stream_->OnHeadersFrameEnd();
+ }
+
+ void OnPushPromiseFrameStart(PushId push_id) override {
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ void OnPushPromiseFramePayload(QuicStringPiece payload) override {
+ DCHECK(!payload.empty());
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ void OnPushPromiseFrameEnd() override {
+ CloseConnectionOnWrongFrame("Push Promise");
+ }
+
+ private:
+ void CloseConnectionOnWrongFrame(std::string frame_type) {
+ stream_->session()->connection()->CloseConnection(
+ QUIC_HTTP_DECODER_ERROR, frame_type + " frame received on data stream",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ }
+
+ QuicSpdyStream* stream_;
+};
+
+#define ENDPOINT \
+ (session()->perspective() == Perspective::IS_SERVER ? "Server: " \
+ : "Client:" \
+ " ")
+
+QuicSpdyStream::QuicSpdyStream(QuicStreamId id,
+ QuicSpdySession* spdy_session,
+ StreamType type)
+ : QuicStream(id, spdy_session, /*is_static=*/false, type),
+ spdy_session_(spdy_session),
+ visitor_(nullptr),
+ headers_decompressed_(false),
+ trailers_decompressed_(false),
+ trailers_consumed_(false),
+ http_decoder_visitor_(new HttpDecoderVisitor(this)),
+ body_buffer_(sequencer()),
+ ack_listener_(nullptr) {
+ DCHECK_NE(QuicUtils::GetCryptoStreamId(
+ spdy_session->connection()->transport_version()),
+ id);
+ // Don't receive any callbacks from the sequencer until headers
+ // are complete.
+ sequencer()->SetBlockedUntilFlush();
+
+ if (VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ sequencer()->set_level_triggered(true);
+ }
+ decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::QuicSpdyStream(PendingStream pending,
+ QuicSpdySession* spdy_session,
+ StreamType type)
+ : QuicStream(std::move(pending), type),
+ spdy_session_(spdy_session),
+ visitor_(nullptr),
+ headers_decompressed_(false),
+ trailers_decompressed_(false),
+ trailers_consumed_(false),
+ http_decoder_visitor_(new HttpDecoderVisitor(this)),
+ body_buffer_(sequencer()),
+ ack_listener_(nullptr) {
+ DCHECK_NE(QuicUtils::GetCryptoStreamId(
+ spdy_session->connection()->transport_version()),
+ id());
+ // Don't receive any callbacks from the sequencer until headers
+ // are complete.
+ sequencer()->SetBlockedUntilFlush();
+
+ if (VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ sequencer()->set_level_triggered(true);
+ }
+ decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::~QuicSpdyStream() {}
+
+size_t QuicSpdyStream::WriteHeaders(
+ SpdyHeaderBlock header_block,
+ bool fin,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ size_t bytes_written =
+ WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
+ if (fin) {
+ // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
+ set_fin_sent(true);
+ CloseWriteSide();
+ }
+ return bytes_written;
+}
+
+void QuicSpdyStream::WriteOrBufferBody(QuicStringPiece data, bool fin) {
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version()) ||
+ data.length() == 0) {
+ WriteOrBufferData(data, fin, nullptr);
+ return;
+ }
+ QuicConnection::ScopedPacketFlusher flusher(
+ spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+ // Write frame header.
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(data.length(), &buffer);
+ unacked_frame_headers_offsets_.Add(
+ send_buffer().stream_offset(),
+ send_buffer().stream_offset() + header_length);
+ QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+ << header_length;
+ WriteOrBufferData(QuicStringPiece(buffer.get(), header_length), false,
+ nullptr);
+
+ // Write body.
+ QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+ << data.length();
+ WriteOrBufferData(data, fin, nullptr);
+}
+
+size_t QuicSpdyStream::WriteTrailers(
+ SpdyHeaderBlock trailer_block,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ if (fin_sent()) {
+ QUIC_BUG << "Trailers cannot be sent after a FIN, on stream " << id();
+ return 0;
+ }
+
+ // The header block must contain the final offset for this stream, as the
+ // trailers may be processed out of order at the peer.
+ QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
+ << stream_bytes_written() + BufferedDataBytes() << ")";
+ trailer_block.insert(
+ std::make_pair(kFinalOffsetHeaderKey,
+ QuicTextUtils::Uint64ToString(stream_bytes_written() +
+ BufferedDataBytes())));
+
+ // Write the trailing headers with a FIN, and close stream for writing:
+ // trailers are the last thing to be sent on a stream.
+ const bool kFin = true;
+ size_t bytes_written =
+ WriteHeadersImpl(std::move(trailer_block), kFin, std::move(ack_listener));
+ set_fin_sent(kFin);
+
+ // Trailers are the last thing to be sent on a stream, but if there is still
+ // queued data then CloseWriteSide() will cause it never to be sent.
+ if (BufferedDataBytes() == 0) {
+ CloseWriteSide();
+ }
+
+ return bytes_written;
+}
+
+QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov,
+ int count,
+ bool fin) {
+ QuicMemSliceStorage storage(
+ iov, count,
+ session()->connection()->helper()->GetStreamSendBufferAllocator(),
+ GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+ return WriteBodySlices(storage.ToSpan(), fin);
+}
+
+QuicConsumedData QuicSpdyStream::WriteBodySlices(QuicMemSliceSpan slices,
+ bool fin) {
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version()) ||
+ slices.empty()) {
+ return WriteMemSlices(slices, fin);
+ }
+
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(slices.total_length(), &buffer);
+ if (!CanWriteNewDataAfterData(header_length)) {
+ return {0, false};
+ }
+
+ QuicConnection::ScopedPacketFlusher flusher(
+ spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+ // Write frame header.
+ struct iovec header_iov = {static_cast<void*>(buffer.get()), header_length};
+ QuicMemSliceStorage storage(
+ &header_iov, 1,
+ spdy_session_->connection()->helper()->GetStreamSendBufferAllocator(),
+ GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+ unacked_frame_headers_offsets_.Add(
+ send_buffer().stream_offset(),
+ send_buffer().stream_offset() + header_length);
+ QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+ << header_length;
+ WriteMemSlices(storage.ToSpan(), false);
+
+ // Write body.
+ QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+ << slices.total_length();
+ return WriteMemSlices(slices, fin);
+}
+
+size_t QuicSpdyStream::Readv(const struct iovec* iov, size_t iov_len) {
+ DCHECK(FinishedReadingHeaders());
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ return sequencer()->Readv(iov, iov_len);
+ }
+ return body_buffer_.ReadBody(iov, iov_len);
+}
+
+int QuicSpdyStream::GetReadableRegions(iovec* iov, size_t iov_len) const {
+ DCHECK(FinishedReadingHeaders());
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ return sequencer()->GetReadableRegions(iov, iov_len);
+ }
+ return body_buffer_.PeekBody(iov, iov_len);
+}
+
+void QuicSpdyStream::MarkConsumed(size_t num_bytes) {
+ DCHECK(FinishedReadingHeaders());
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ sequencer()->MarkConsumed(num_bytes);
+ return;
+ }
+ body_buffer_.MarkBodyConsumed(num_bytes);
+}
+
+bool QuicSpdyStream::IsDoneReading() const {
+ bool done_reading_headers = FinishedReadingHeaders();
+ bool done_reading_body = sequencer()->IsClosed();
+ bool done_reading_trailers = FinishedReadingTrailers();
+ return done_reading_headers && done_reading_body && done_reading_trailers;
+}
+
+bool QuicSpdyStream::HasBytesToRead() const {
+ if (!VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ return sequencer()->HasBytesToRead();
+ }
+ return body_buffer_.HasBytesToRead();
+}
+
+void QuicSpdyStream::MarkTrailersConsumed() {
+ trailers_consumed_ = true;
+}
+
+uint64_t QuicSpdyStream::total_body_bytes_read() const {
+ if (VersionHasDataFrameHeader(
+ spdy_session_->connection()->transport_version())) {
+ return body_buffer_.total_body_bytes_received();
+ }
+ return sequencer()->NumBytesConsumed();
+}
+
+void QuicSpdyStream::ConsumeHeaderList() {
+ header_list_.Clear();
+ if (FinishedReadingHeaders()) {
+ sequencer()->SetUnblocked();
+ }
+}
+
+void QuicSpdyStream::OnStreamHeadersPriority(SpdyPriority priority) {
+ DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+ SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamHeaderList(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ // The headers list avoid infinite buffering by clearing the headers list
+ // if the current headers are too large. So if the list is empty here
+ // then the headers list must have been too large, and the stream should
+ // be reset.
+ // TODO(rch): Use an explicit "headers too large" signal. An empty header list
+ // might be acceptable if it corresponds to a trailing header frame.
+ if (header_list.empty()) {
+ OnHeadersTooLarge();
+ if (IsDoneReading()) {
+ return;
+ }
+ }
+ if (!headers_decompressed_) {
+ OnInitialHeadersComplete(fin, frame_len, header_list);
+ } else {
+ OnTrailingHeadersComplete(fin, frame_len, header_list);
+ }
+}
+
+void QuicSpdyStream::OnHeadersTooLarge() {
+ Reset(QUIC_HEADERS_TOO_LARGE);
+}
+
+void QuicSpdyStream::OnInitialHeadersComplete(
+ bool fin,
+ size_t /*frame_len*/,
+ const QuicHeaderList& header_list) {
+ headers_decompressed_ = true;
+ header_list_ = header_list;
+ if (fin) {
+ OnStreamFrame(QuicStreamFrame(id(), fin, 0, QuicStringPiece()));
+ }
+ if (FinishedReadingHeaders()) {
+ sequencer()->SetUnblocked();
+ }
+}
+
+void QuicSpdyStream::OnPromiseHeaderList(
+ QuicStreamId /* promised_id */,
+ size_t /* frame_len */,
+ const QuicHeaderList& /*header_list */) {
+ // To be overridden in QuicSpdyClientStream. Not supported on
+ // server side.
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "Promise headers received by server",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicSpdyStream::OnTrailingHeadersComplete(
+ bool fin,
+ size_t /*frame_len*/,
+ const QuicHeaderList& header_list) {
+ DCHECK(!trailers_decompressed_);
+ if (fin_received()) {
+ QUIC_DLOG(ERROR) << "Received Trailers after FIN, on stream: " << id();
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers after fin",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ if (!fin) {
+ QUIC_DLOG(ERROR) << "Trailers must have FIN set, on stream: " << id();
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "Fin missing from trailers",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+
+ size_t final_byte_offset = 0;
+ if (!SpdyUtils::CopyAndValidateTrailers(header_list,
+ /* expect_final_byte_offset = */ true,
+ &final_byte_offset,
+ &received_trailers_)) {
+ QUIC_DLOG(ERROR) << "Trailers for stream " << id() << " are malformed.";
+ session()->connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers are malformed",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ trailers_decompressed_ = true;
+ OnStreamFrame(
+ QuicStreamFrame(id(), fin, final_byte_offset, QuicStringPiece()));
+}
+
+size_t QuicSpdyStream::WriteHeadersImpl(
+ spdy::SpdyHeaderBlock header_block,
+ bool fin,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ return spdy_session_->WriteHeadersOnHeadersStream(
+ id(), std::move(header_block), fin, priority(), std::move(ack_listener));
+}
+
+void QuicSpdyStream::OnPriorityFrame(SpdyPriority priority) {
+ DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+ SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+ if (frame.error_code != QUIC_STREAM_NO_ERROR) {
+ QuicStream::OnStreamReset(frame);
+ return;
+ }
+ QUIC_DVLOG(1) << "Received QUIC_STREAM_NO_ERROR, not discarding response";
+ set_rst_received(true);
+ MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
+ set_stream_error(frame.error_code);
+ CloseWriteSide();
+}
+
+void QuicSpdyStream::OnDataAvailable() {
+ DCHECK(FinishedReadingHeaders());
+
+ if (!VersionHasDataFrameHeader(
+ session()->connection()->transport_version())) {
+ OnBodyAvailable();
+ return;
+ }
+
+ iovec iov;
+ while (!reading_stopped() && sequencer()->PrefetchNextRegion(&iov)) {
+ decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
+ iov.iov_len);
+ }
+
+ if (body_buffer_.HasBytesToRead()) {
+ OnBodyAvailable();
+ return;
+ }
+
+ if (sequencer()->IsClosed()) {
+ OnBodyAvailable();
+ return;
+ }
+}
+
+void QuicSpdyStream::OnClose() {
+ QuicStream::OnClose();
+
+ if (visitor_) {
+ Visitor* visitor = visitor_;
+ // Calling Visitor::OnClose() may result the destruction of the visitor,
+ // so we need to ensure we don't call it again.
+ visitor_ = nullptr;
+ visitor->OnClose(this);
+ }
+}
+
+void QuicSpdyStream::OnCanWrite() {
+ QuicStream::OnCanWrite();
+
+ // Trailers (and hence a FIN) may have been sent ahead of queued body bytes.
+ if (!HasBufferedData() && fin_sent()) {
+ CloseWriteSide();
+ }
+}
+
+bool QuicSpdyStream::FinishedReadingHeaders() const {
+ return headers_decompressed_ && header_list_.empty();
+}
+
+bool QuicSpdyStream::ParseHeaderStatusCode(const SpdyHeaderBlock& header,
+ int* status_code) const {
+ SpdyHeaderBlock::const_iterator it = header.find(spdy::kHttp2StatusHeader);
+ if (it == header.end()) {
+ return false;
+ }
+ const QuicStringPiece status(it->second);
+ if (status.size() != 3) {
+ return false;
+ }
+ // First character must be an integer in range [1,5].
+ if (status[0] < '1' || status[0] > '5') {
+ return false;
+ }
+ // The remaining two characters must be integers.
+ if (!isdigit(status[1]) || !isdigit(status[2])) {
+ return false;
+ }
+ return QuicTextUtils::StringToInt(status, status_code);
+}
+
+bool QuicSpdyStream::FinishedReadingTrailers() const {
+ // If no further trailing headers are expected, and the decompressed trailers
+ // (if any) have been consumed, then reading of trailers is finished.
+ if (!fin_received()) {
+ return false;
+ } else if (!trailers_decompressed_) {
+ return true;
+ } else {
+ return trailers_consumed_;
+ }
+}
+
+void QuicSpdyStream::ClearSession() {
+ spdy_session_ = nullptr;
+}
+
+void QuicSpdyStream::OnDataFrameStart(Http3FrameLengths frame_lengths) {
+ DCHECK(
+ VersionHasDataFrameHeader(session()->connection()->transport_version()));
+
+ body_buffer_.OnDataHeader(frame_lengths);
+}
+
+void QuicSpdyStream::OnDataFramePayload(QuicStringPiece payload) {
+ DCHECK(
+ VersionHasDataFrameHeader(session()->connection()->transport_version()));
+
+ body_buffer_.OnDataPayload(payload);
+}
+
+void QuicSpdyStream::OnDataFrameEnd() {
+ DCHECK(
+ VersionHasDataFrameHeader(session()->connection()->transport_version()));
+ DVLOG(1) << "Reaches the end of a data frame. Total bytes received are "
+ << body_buffer_.total_body_bytes_received();
+}
+
+bool QuicSpdyStream::OnStreamFrameAcked(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_acked,
+ QuicTime::Delta ack_delay_time,
+ QuicByteCount* newly_acked_length) {
+ const bool new_data_acked = QuicStream::OnStreamFrameAcked(
+ offset, data_length, fin_acked, ack_delay_time, newly_acked_length);
+
+ const QuicByteCount newly_acked_header_length =
+ GetNumFrameHeadersInInterval(offset, data_length);
+ DCHECK_LE(newly_acked_header_length, *newly_acked_length);
+ unacked_frame_headers_offsets_.Difference(offset, offset + data_length);
+ if (ack_listener_ != nullptr && new_data_acked) {
+ ack_listener_->OnPacketAcked(
+ *newly_acked_length - newly_acked_header_length, ack_delay_time);
+ }
+ return new_data_acked;
+}
+
+void QuicSpdyStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_retransmitted) {
+ QuicStream::OnStreamFrameRetransmitted(offset, data_length,
+ fin_retransmitted);
+
+ const QuicByteCount retransmitted_header_length =
+ GetNumFrameHeadersInInterval(offset, data_length);
+ DCHECK_LE(retransmitted_header_length, data_length);
+
+ if (ack_listener_ != nullptr) {
+ ack_listener_->OnPacketRetransmitted(data_length -
+ retransmitted_header_length);
+ }
+}
+
+QuicByteCount QuicSpdyStream::GetNumFrameHeadersInInterval(
+ QuicStreamOffset offset,
+ QuicByteCount data_length) const {
+ QuicByteCount header_acked_length = 0;
+ QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
+ newly_acked.Intersection(unacked_frame_headers_offsets_);
+ for (const auto& interval : newly_acked) {
+ header_acked_length += interval.Length();
+ }
+ return header_acked_length;
+}
+
+void QuicSpdyStream::OnHeadersFrameStart(Http3FrameLengths frame_length) {
+ DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+}
+
+void QuicSpdyStream::OnHeadersFramePayload(QuicStringPiece payload) {
+ DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+}
+
+void QuicSpdyStream::OnHeadersFrameEnd() {
+ DCHECK(VersionUsesQpack(spdy_session_->connection()->transport_version()));
+}
+
+#undef ENDPOINT // undef for jumbo builds
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h
new file mode 100644
index 00000000000..f81a421a44b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h
@@ -0,0 +1,283 @@
+// 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.
+
+// The base class for streams which deliver data to/from an application.
+// In each direction, the data on such a stream first contains compressed
+// headers then body data.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
+
+#include <sys/types.h>
+
+#include <cstddef>
+#include <list>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+namespace test {
+class QuicSpdyStreamPeer;
+class QuicStreamPeer;
+} // namespace test
+
+class QuicSpdySession;
+
+// A QUIC stream that can send and receive HTTP2 (SPDY) headers.
+class QUIC_EXPORT_PRIVATE QuicSpdyStream : public QuicStream {
+ public:
+ // Visitor receives callbacks from the stream.
+ class QUIC_EXPORT_PRIVATE Visitor {
+ public:
+ Visitor() {}
+ Visitor(const Visitor&) = delete;
+ Visitor& operator=(const Visitor&) = delete;
+
+ // Called when the stream is closed.
+ virtual void OnClose(QuicSpdyStream* stream) = 0;
+
+ // Allows subclasses to override and do work.
+ virtual void OnPromiseHeadersComplete(QuicStreamId promised_id,
+ size_t frame_len) {}
+
+ protected:
+ virtual ~Visitor() {}
+ };
+
+ QuicSpdyStream(QuicStreamId id,
+ QuicSpdySession* spdy_session,
+ StreamType type);
+ QuicSpdyStream(PendingStream pending,
+ QuicSpdySession* spdy_session,
+ StreamType type);
+ QuicSpdyStream(const QuicSpdyStream&) = delete;
+ QuicSpdyStream& operator=(const QuicSpdyStream&) = delete;
+ ~QuicSpdyStream() override;
+
+ // QuicStream implementation
+ void OnClose() override;
+
+ // Override to maybe close the write side after writing.
+ void OnCanWrite() override;
+
+ // Called by the session when headers with a priority have been received
+ // for this stream. This method will only be called for server streams.
+ virtual void OnStreamHeadersPriority(spdy::SpdyPriority priority);
+
+ // Called by the session when decompressed headers have been completely
+ // delivered to this stream. If |fin| is true, then this stream
+ // should be closed; no more data will be sent by the peer.
+ virtual void OnStreamHeaderList(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+
+ // Called by the session when decompressed push promise headers have
+ // been completely delivered to this stream.
+ virtual void OnPromiseHeaderList(QuicStreamId promised_id,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+
+ // Called by the session when a PRIORITY frame has been been received for this
+ // stream. This method will only be called for server streams.
+ void OnPriorityFrame(spdy::SpdyPriority priority);
+
+ // Override the base class to not discard response when receiving
+ // QUIC_STREAM_NO_ERROR.
+ void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+ // Called by the sequencer when new data is available. Decodes the data and
+ // calls OnBodyAvailable() to pass to the upper layer.
+ void OnDataAvailable() override;
+
+ // Called in OnDataAvailable() after it finishes the decoding job.
+ virtual void OnBodyAvailable() = 0;
+
+ // Writes the headers contained in |header_block| to the dedicated
+ // headers stream.
+ virtual size_t WriteHeaders(
+ spdy::SpdyHeaderBlock header_block,
+ bool fin,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+ // Sends |data| to the peer, or buffers if it can't be sent immediately.
+ void WriteOrBufferBody(QuicStringPiece data, bool fin);
+
+ // Writes the trailers contained in |trailer_block| to the dedicated
+ // headers stream. Trailers will always have the FIN set.
+ virtual size_t WriteTrailers(
+ spdy::SpdyHeaderBlock trailer_block,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+ // Override to report newly acked bytes via ack_listener_.
+ bool OnStreamFrameAcked(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_acked,
+ QuicTime::Delta ack_delay_time,
+ QuicByteCount* newly_acked_length) override;
+
+ // Override to report bytes retransmitted via ack_listener_.
+ void OnStreamFrameRetransmitted(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin_retransmitted) override;
+
+ // Does the same thing as WriteOrBufferBody except this method takes iovec
+ // as the data input. Right now it only calls WritevData.
+ // TODO(renjietang): Write data frame header before writing body.
+ QuicConsumedData WritevBody(const struct iovec* iov, int count, bool fin);
+
+ // Does the same thing as WriteOrBufferBody except this method takes
+ // memslicespan as the data input. Right now it only calls WriteMemSlices.
+ // TODO(renjietang): Write data frame header before writing body.
+ QuicConsumedData WriteBodySlices(QuicMemSliceSpan slices, bool fin);
+
+ // Marks the trailers as consumed. This applies to the case where this object
+ // receives headers and trailers as QuicHeaderLists via calls to
+ // OnStreamHeaderList().
+ void MarkTrailersConsumed();
+
+ // Clears |header_list_|.
+ void ConsumeHeaderList();
+
+ // This block of functions wraps the sequencer's functions of the same
+ // name. These methods return uncompressed data until that has
+ // been fully processed. Then they simply delegate to the sequencer.
+ virtual size_t Readv(const struct iovec* iov, size_t iov_len);
+ virtual int GetReadableRegions(iovec* iov, size_t iov_len) const;
+ void MarkConsumed(size_t num_bytes);
+
+ // Returns true if header contains a valid 3-digit status and parse the status
+ // code to |status_code|.
+ bool ParseHeaderStatusCode(const spdy::SpdyHeaderBlock& header,
+ int* status_code) const;
+
+ // Returns true when all data has been read from the peer, including the fin.
+ bool IsDoneReading() const;
+ bool HasBytesToRead() const;
+
+ void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+ bool headers_decompressed() const { return headers_decompressed_; }
+
+ // Returns total amount of body bytes that have been read.
+ uint64_t total_body_bytes_read() const;
+
+ const QuicHeaderList& header_list() const { return header_list_; }
+
+ bool trailers_decompressed() const { return trailers_decompressed_; }
+
+ // Returns whatever trailers have been received for this stream.
+ const spdy::SpdyHeaderBlock& received_trailers() const {
+ return received_trailers_;
+ }
+
+ // Returns true if headers have been fully read and consumed.
+ bool FinishedReadingHeaders() const;
+
+ // Returns true if trailers have been fully read and consumed, or FIN has
+ // been received and there are no trailers.
+ bool FinishedReadingTrailers() const;
+
+ // Called when owning session is getting deleted to avoid subsequent
+ // use of the spdy_session_ member.
+ void ClearSession();
+
+ // Returns true if the sequencer has delivered the FIN, and no more body bytes
+ // will be available.
+ bool IsClosed() { return sequencer()->IsClosed(); }
+
+ using QuicStream::CloseWriteSide;
+
+ protected:
+ // HTTP/3
+ void OnDataFrameStart(Http3FrameLengths frame_lengths);
+ void OnDataFramePayload(QuicStringPiece payload);
+ void OnDataFrameEnd();
+ void OnHeadersFrameStart(Http3FrameLengths frame_length);
+ void OnHeadersFramePayload(QuicStringPiece payload);
+ void OnHeadersFrameEnd();
+
+ // Called when the received headers are too large. By default this will
+ // reset the stream.
+ virtual void OnHeadersTooLarge();
+
+ virtual void OnInitialHeadersComplete(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+ virtual void OnTrailingHeadersComplete(bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list);
+ virtual size_t WriteHeadersImpl(
+ spdy::SpdyHeaderBlock header_block,
+ bool fin,
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+ QuicSpdySession* spdy_session() const { return spdy_session_; }
+ Visitor* visitor() { return visitor_; }
+
+ void set_headers_decompressed(bool val) { headers_decompressed_ = val; }
+
+ void set_ack_listener(
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+ ack_listener_ = std::move(ack_listener);
+ }
+
+ private:
+ friend class test::QuicSpdyStreamPeer;
+ friend class test::QuicStreamPeer;
+ friend class QuicStreamUtils;
+ class HttpDecoderVisitor;
+
+ // Given the interval marked by [|offset|, |offset| + |data_length|), return
+ // the number of frame header bytes contained in it.
+ QuicByteCount GetNumFrameHeadersInInterval(QuicStreamOffset offset,
+ QuicByteCount data_length) const;
+
+ QuicSpdySession* spdy_session_;
+
+ Visitor* visitor_;
+ // True if the headers have been completely decompressed.
+ bool headers_decompressed_;
+ // Contains a copy of the decompressed header (name, value) pairs until they
+ // are consumed via Readv.
+ QuicHeaderList header_list_;
+
+ // True if the trailers have been completely decompressed.
+ bool trailers_decompressed_;
+ // True if the trailers have been consumed.
+ bool trailers_consumed_;
+ // The parsed trailers received from the peer.
+ spdy::SpdyHeaderBlock received_trailers_;
+
+ // Http encoder for writing streams.
+ HttpEncoder encoder_;
+ // Http decoder for processing raw incoming stream frames.
+ HttpDecoder decoder_;
+ // Visitor of the HttpDecoder.
+ std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
+ // Buffer that contains decoded data of the stream.
+ QuicSpdyStreamBodyBuffer body_buffer_;
+
+ // Ack listener of this stream, and it is notified when any of written bytes
+ // are acked or retransmitted.
+ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener_;
+
+ // Offset of unacked frame headers.
+ QuicIntervalSet<QuicStreamOffset> unacked_frame_headers_offsets_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.cc
new file mode 100644
index 00000000000..c0a77b29485
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2018 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_spdy_stream_body_buffer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+QuicSpdyStreamBodyBuffer::QuicSpdyStreamBodyBuffer(
+ QuicStreamSequencer* sequencer)
+ : bytes_remaining_(0),
+ total_body_bytes_readable_(0),
+ total_body_bytes_received_(0),
+ total_payload_lengths_(0),
+ sequencer_(sequencer) {}
+
+QuicSpdyStreamBodyBuffer::~QuicSpdyStreamBodyBuffer() {}
+
+void QuicSpdyStreamBodyBuffer::OnDataHeader(Http3FrameLengths frame_lengths) {
+ frame_meta_.push_back(frame_lengths);
+ total_payload_lengths_ += frame_lengths.payload_length;
+}
+
+void QuicSpdyStreamBodyBuffer::OnDataPayload(QuicStringPiece payload) {
+ DCHECK(!payload.empty());
+ bodies_.push_back(payload);
+ total_body_bytes_received_ += payload.length();
+ total_body_bytes_readable_ += payload.length();
+ DCHECK_LE(total_body_bytes_received_, total_payload_lengths_);
+}
+
+void QuicSpdyStreamBodyBuffer::MarkBodyConsumed(size_t num_bytes) {
+ // Check if the stream has enough decoded data.
+ if (num_bytes > total_body_bytes_readable_) {
+ QUIC_BUG << "Invalid argument to MarkBodyConsumed."
+ << " expect to consume: " << num_bytes
+ << ", but not enough bytes available. "
+ << "Total bytes readable are: " << total_body_bytes_readable_;
+ return;
+ }
+ // Discard references in the stream before the sequencer marks them consumed.
+ size_t remaining = num_bytes;
+ while (remaining > 0) {
+ if (bodies_.empty()) {
+ QUIC_BUG << "Failed to consume because body buffer is empty.";
+ return;
+ }
+ auto body = bodies_.front();
+ bodies_.pop_front();
+ if (body.length() <= remaining) {
+ remaining -= body.length();
+ } else {
+ body = body.substr(remaining, body.length() - remaining);
+ bodies_.push_front(body);
+ remaining = 0;
+ }
+ }
+ // Consume headers.
+ while (bytes_remaining_ < num_bytes) {
+ if (frame_meta_.empty()) {
+ QUIC_BUG << "Faild to consume because frame header buffer is empty.";
+ return;
+ }
+ auto meta = frame_meta_.front();
+ frame_meta_.pop_front();
+ bytes_remaining_ += meta.payload_length;
+ sequencer_->MarkConsumed(meta.header_length);
+ }
+ sequencer_->MarkConsumed(num_bytes);
+ // Update accountings.
+ bytes_remaining_ -= num_bytes;
+ total_body_bytes_readable_ -= num_bytes;
+}
+
+int QuicSpdyStreamBodyBuffer::PeekBody(iovec* iov, size_t iov_len) const {
+ DCHECK(iov != nullptr);
+ DCHECK_GT(iov_len, 0u);
+
+ if (bodies_.empty()) {
+ iov[0].iov_base = nullptr;
+ iov[0].iov_len = 0;
+ return 0;
+ }
+ // Fill iovs with references from the stream.
+ size_t iov_filled = 0;
+ while (iov_filled < bodies_.size() && iov_filled < iov_len) {
+ QuicStringPiece body = bodies_[iov_filled];
+ iov[iov_filled].iov_base = const_cast<char*>(body.data());
+ iov[iov_filled].iov_len = body.size();
+ iov_filled++;
+ }
+ return iov_filled;
+}
+
+size_t QuicSpdyStreamBodyBuffer::ReadBody(const struct iovec* iov,
+ size_t iov_len) {
+ size_t total_data_read = 0;
+ QuicByteCount total_remaining = total_body_bytes_readable_;
+ size_t index = 0;
+ size_t src_offset = 0;
+ for (size_t i = 0; i < iov_len && total_remaining > 0; ++i) {
+ char* dest = reinterpret_cast<char*>(iov[i].iov_base);
+ size_t dest_remaining = iov[i].iov_len;
+ while (dest_remaining > 0 && total_remaining > 0) {
+ auto body = bodies_[index];
+ size_t bytes_to_copy =
+ std::min<size_t>(body.length() - src_offset, dest_remaining);
+ memcpy(dest, body.substr(src_offset, bytes_to_copy).data(),
+ bytes_to_copy);
+ dest += bytes_to_copy;
+ dest_remaining -= bytes_to_copy;
+ total_data_read += bytes_to_copy;
+ total_remaining -= bytes_to_copy;
+ if (bytes_to_copy < body.length() - src_offset) {
+ src_offset += bytes_to_copy;
+ } else {
+ index++;
+ src_offset = 0;
+ }
+ }
+ }
+
+ MarkBodyConsumed(total_data_read);
+ return total_data_read;
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h
new file mode 100644
index 00000000000..2485aacb446
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+
+namespace quic {
+
+// Buffer decoded body for QuicSpdyStream. It also talks to the sequencer to
+// consume data.
+class QUIC_EXPORT_PRIVATE QuicSpdyStreamBodyBuffer {
+ public:
+ // QuicSpdyStreamBodyBuffer doesn't own the sequencer and the sequencer can
+ // outlive the buffer.
+ explicit QuicSpdyStreamBodyBuffer(QuicStreamSequencer* sequencer);
+
+ ~QuicSpdyStreamBodyBuffer();
+
+ // Add metadata of the frame to accountings.
+ // Called when QuicSpdyStream receives data frame header.
+ void OnDataHeader(Http3FrameLengths frame_lengths);
+
+ // Add new data payload to buffer.
+ // Called when QuicSpdyStream received data payload.
+ // Data pointed by payload must be alive until consumed by
+ // QuicStreamSequencer::MarkConsumed().
+ void OnDataPayload(QuicStringPiece payload);
+
+ // Take |num_bytes| as the body size, calculate header sizes accordingly, and
+ // consume the right amount of data in the stream sequencer.
+ void MarkBodyConsumed(size_t num_bytes);
+
+ // Fill up to |iov_len| with bodies available in buffer. No data is consumed.
+ // |iov|.iov_base will point to data in the buffer, and |iov|.iov_len will
+ // be set to the underlying data length accordingly.
+ // Returns the number of iov used.
+ int PeekBody(iovec* iov, size_t iov_len) const;
+
+ // Copies from buffer into |iov| up to |iov_len|, and consume data in
+ // sequencer. |iov.iov_base| and |iov.iov_len| are preassigned and will not be
+ // changed.
+ // Returns the number of bytes read.
+ size_t ReadBody(const struct iovec* iov, size_t iov_len);
+
+ bool HasBytesToRead() const { return !bodies_.empty(); }
+
+ uint64_t total_body_bytes_received() const {
+ return total_body_bytes_received_;
+ }
+
+ private:
+ // Storage for decoded data.
+ QuicDeque<QuicStringPiece> bodies_;
+ // Storage for header lengths.
+ QuicDeque<Http3FrameLengths> frame_meta_;
+ // Bytes in the first available data frame that are not consumed yet.
+ QuicByteCount bytes_remaining_;
+ // Total available body data in the stream.
+ QuicByteCount total_body_bytes_readable_;
+ // Total bytes read from the stream excluding headers.
+ QuicByteCount total_body_bytes_received_;
+ // Total length of payloads tracked by frame_meta_.
+ QuicByteCount total_payload_lengths_;
+ // Stream sequencer that directly manages data in the stream.
+ QuicStreamSequencer* sequencer_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer_test.cc
new file mode 100644
index 00000000000..ba2dbca22e3
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer_test.cc
@@ -0,0 +1,241 @@
+// Copyright (c) 2018 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_spdy_stream_body_buffer.h"
+
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.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_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+
+namespace test {
+
+namespace {
+
+class MockStream : public QuicStreamSequencer::StreamInterface {
+ public:
+ MOCK_METHOD0(OnFinRead, void());
+ MOCK_METHOD0(OnDataAvailable, void());
+ MOCK_METHOD2(CloseConnectionWithDetails,
+ void(QuicErrorCode error, const std::string& details));
+ MOCK_METHOD1(Reset, void(QuicRstStreamErrorCode error));
+ MOCK_METHOD0(OnCanWrite, void());
+ MOCK_METHOD1(AddBytesConsumed, void(QuicByteCount bytes));
+
+ QuicStreamId id() const override { return 1; }
+
+ const QuicSocketAddress& PeerAddressOfLatestPacket() const override {
+ return peer_address_;
+ }
+
+ protected:
+ QuicSocketAddress peer_address_ =
+ QuicSocketAddress(QuicIpAddress::Any4(), 65535);
+};
+
+class MockSequencer : public QuicStreamSequencer {
+ public:
+ explicit MockSequencer(MockStream* stream) : QuicStreamSequencer(stream) {}
+ virtual ~MockSequencer() = default;
+ MOCK_METHOD1(MarkConsumed, void(size_t num_bytes_consumed));
+};
+
+class QuicSpdyStreamBodyBufferTest : public QuicTest {
+ public:
+ QuicSpdyStreamBodyBufferTest()
+ : sequencer_(&stream_), body_buffer_(&sequencer_) {}
+
+ protected:
+ MockStream stream_;
+ MockSequencer sequencer_;
+ QuicSpdyStreamBodyBuffer body_buffer_;
+ HttpEncoder encoder_;
+};
+
+TEST_F(QuicSpdyStreamBodyBufferTest, ReceiveBodies) {
+ std::string body(1024, 'a');
+ EXPECT_FALSE(body_buffer_.HasBytesToRead());
+ body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
+ body_buffer_.OnDataPayload(QuicStringPiece(body));
+ EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
+ EXPECT_TRUE(body_buffer_.HasBytesToRead());
+}
+
+TEST_F(QuicSpdyStreamBodyBufferTest, PeekBody) {
+ std::string body(1024, 'a');
+ body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
+ body_buffer_.OnDataPayload(QuicStringPiece(body));
+ EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
+ iovec vec;
+ EXPECT_EQ(1, body_buffer_.PeekBody(&vec, 1));
+ EXPECT_EQ(1024u, vec.iov_len);
+ EXPECT_EQ(body,
+ QuicStringPiece(static_cast<const char*>(vec.iov_base), 1024));
+}
+
+// Buffer only receives 1 frame. Stream consumes less or equal than a frame.
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedPartialSingleFrame) {
+ testing::InSequence seq;
+ std::string body(1024, 'a');
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ Http3FrameLengths lengths(header_length, 1024);
+ std::string data = header + body;
+ QuicStreamFrame frame(1, false, 0, data);
+ sequencer_.OnStreamFrame(frame);
+ body_buffer_.OnDataHeader(lengths);
+ body_buffer_.OnDataPayload(QuicStringPiece(body));
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length));
+ EXPECT_CALL(stream_, AddBytesConsumed(1024));
+ body_buffer_.MarkBodyConsumed(1024);
+}
+
+// Buffer received 2 frames. Stream consumes multiple times.
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMultipleFrames) {
+ testing::InSequence seq;
+ // 1st frame.
+ std::string body1(1024, 'a');
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length1 =
+ encoder_.SerializeDataFrameHeader(body1.length(), &buffer);
+ std::string header1 = std::string(buffer.get(), header_length1);
+ Http3FrameLengths lengths1(header_length1, 1024);
+ std::string data1 = header1 + body1;
+ QuicStreamFrame frame1(1, false, 0, data1);
+ sequencer_.OnStreamFrame(frame1);
+ body_buffer_.OnDataHeader(lengths1);
+ body_buffer_.OnDataPayload(QuicStringPiece(body1));
+
+ // 2nd frame.
+ std::string body2(2048, 'b');
+ QuicByteCount header_length2 =
+ encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+ std::string header2 = std::string(buffer.get(), header_length2);
+ Http3FrameLengths lengths2(header_length2, 2048);
+ std::string data2 = header2 + body2;
+ QuicStreamFrame frame2(1, false, data1.length(), data2);
+ sequencer_.OnStreamFrame(frame2);
+ body_buffer_.OnDataHeader(lengths2);
+ body_buffer_.OnDataPayload(QuicStringPiece(body2));
+
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length1));
+ EXPECT_CALL(stream_, AddBytesConsumed(512));
+ body_buffer_.MarkBodyConsumed(512);
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length2));
+ EXPECT_CALL(stream_, AddBytesConsumed(2048));
+ body_buffer_.MarkBodyConsumed(2048);
+ EXPECT_CALL(stream_, AddBytesConsumed(512));
+ body_buffer_.MarkBodyConsumed(512);
+}
+
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMoreThanBuffered) {
+ std::string body(1024, 'a');
+ Http3FrameLengths lengths(3, 1024);
+ body_buffer_.OnDataHeader(lengths);
+ body_buffer_.OnDataPayload(body);
+ EXPECT_QUIC_BUG(
+ body_buffer_.MarkBodyConsumed(2048),
+ "Invalid argument to MarkBodyConsumed. expect to consume: 2048, but not "
+ "enough bytes available. Total bytes readable are: 1024");
+}
+
+// Buffer receives 1 frame. Stream read from the buffer.
+TEST_F(QuicSpdyStreamBodyBufferTest, ReadSingleBody) {
+ testing::InSequence seq;
+ std::string body(1024, 'a');
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ Http3FrameLengths lengths(header_length, 1024);
+ std::string data = header + body;
+ QuicStreamFrame frame(1, false, 0, data);
+ sequencer_.OnStreamFrame(frame);
+ body_buffer_.OnDataHeader(lengths);
+ body_buffer_.OnDataPayload(QuicStringPiece(body));
+
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length));
+ EXPECT_CALL(stream_, AddBytesConsumed(1024));
+
+ char base[1024];
+ iovec iov = {&base[0], 1024};
+ EXPECT_EQ(1024u, body_buffer_.ReadBody(&iov, 1));
+ EXPECT_EQ(1024u, iov.iov_len);
+ EXPECT_EQ(body,
+ QuicStringPiece(static_cast<const char*>(iov.iov_base), 1024));
+}
+
+// Buffer receives 2 frames, stream read from the buffer multiple times.
+TEST_F(QuicSpdyStreamBodyBufferTest, ReadMultipleBody) {
+ testing::InSequence seq;
+ // 1st frame.
+ std::string body1(1024, 'a');
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length1 =
+ encoder_.SerializeDataFrameHeader(body1.length(), &buffer);
+ std::string header1 = std::string(buffer.get(), header_length1);
+ Http3FrameLengths lengths1(header_length1, 1024);
+ std::string data1 = header1 + body1;
+ QuicStreamFrame frame1(1, false, 0, data1);
+ sequencer_.OnStreamFrame(frame1);
+ body_buffer_.OnDataHeader(lengths1);
+ body_buffer_.OnDataPayload(QuicStringPiece(body1));
+
+ // 2nd frame.
+ std::string body2(2048, 'b');
+ QuicByteCount header_length2 =
+ encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+ std::string header2 = std::string(buffer.get(), header_length2);
+ Http3FrameLengths lengths2(header_length2, 2048);
+ std::string data2 = header2 + body2;
+ QuicStreamFrame frame2(1, false, data1.length(), data2);
+ sequencer_.OnStreamFrame(frame2);
+ body_buffer_.OnDataHeader(lengths2);
+ body_buffer_.OnDataPayload(QuicStringPiece(body2));
+
+ // First read of 512 bytes.
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length1));
+ EXPECT_CALL(stream_, AddBytesConsumed(512));
+ char base[512];
+ iovec iov = {&base[0], 512};
+ EXPECT_EQ(512u, body_buffer_.ReadBody(&iov, 1));
+ EXPECT_EQ(512u, iov.iov_len);
+ EXPECT_EQ(body1.substr(0, 512),
+ QuicStringPiece(static_cast<const char*>(iov.iov_base), 512));
+
+ // Second read of 2048 bytes.
+ EXPECT_CALL(stream_, AddBytesConsumed(header_length2));
+ EXPECT_CALL(stream_, AddBytesConsumed(2048));
+ char base2[2048];
+ iovec iov2 = {&base2[0], 2048};
+ EXPECT_EQ(2048u, body_buffer_.ReadBody(&iov2, 1));
+ EXPECT_EQ(2048u, iov2.iov_len);
+ EXPECT_EQ(body1.substr(512, 512) + body2.substr(0, 1536),
+ QuicStringPiece(static_cast<const char*>(iov2.iov_base), 2048));
+
+ // Third read of the rest 512 bytes.
+ EXPECT_CALL(stream_, AddBytesConsumed(512));
+ char base3[512];
+ iovec iov3 = {&base3[0], 512};
+ EXPECT_EQ(512u, body_buffer_.ReadBody(&iov3, 1));
+ EXPECT_EQ(512u, iov3.iov_len);
+ EXPECT_EQ(body2.substr(1536, 512),
+ QuicStringPiece(static_cast<const char*>(iov3.iov_base), 512));
+}
+
+} // anonymous namespace
+
+} // namespace test
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc
new file mode 100644
index 00000000000..1451533e8e0
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc
@@ -0,0 +1,1559 @@
+// 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_spdy_stream.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.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/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_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_spdy_stream_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"
+
+using spdy::kV3HighestPriority;
+using spdy::kV3LowestPriority;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+using testing::_;
+using testing::AtLeast;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+const bool kShouldProcessData = true;
+
+class TestStream : public QuicSpdyStream {
+ public:
+ TestStream(QuicStreamId id,
+ QuicSpdySession* session,
+ bool should_process_data)
+ : QuicSpdyStream(id, session, BIDIRECTIONAL),
+ should_process_data_(should_process_data) {}
+ ~TestStream() override = default;
+
+ using QuicSpdyStream::set_ack_listener;
+ using QuicStream::CloseWriteSide;
+ using QuicStream::WriteOrBufferData;
+
+ void OnBodyAvailable() override {
+ if (!should_process_data_) {
+ return;
+ }
+ char buffer[2048];
+ struct iovec vec;
+ vec.iov_base = buffer;
+ vec.iov_len = QUIC_ARRAYSIZE(buffer);
+ size_t bytes_read = Readv(&vec, 1);
+ data_ += std::string(buffer, bytes_read);
+ }
+
+ MOCK_METHOD1(WriteHeadersMock, void(bool fin));
+
+ size_t WriteHeadersImpl(spdy::SpdyHeaderBlock header_block,
+ bool fin,
+ QuicReferenceCountedPointer<QuicAckListenerInterface>
+ ack_listener) override {
+ saved_headers_ = std::move(header_block);
+ WriteHeadersMock(fin);
+ return 0;
+ }
+
+ const std::string& data() const { return data_; }
+ const spdy::SpdyHeaderBlock& saved_headers() const { return saved_headers_; }
+
+ private:
+ bool should_process_data_;
+ spdy::SpdyHeaderBlock saved_headers_;
+ std::string data_;
+};
+
+class TestMockUpdateStreamSession : public MockQuicSpdySession {
+ public:
+ explicit TestMockUpdateStreamSession(QuicConnection* connection)
+ : MockQuicSpdySession(connection) {}
+
+ void UpdateStreamPriority(QuicStreamId id, SpdyPriority priority) override {
+ EXPECT_EQ(id, expected_stream_->id());
+ EXPECT_EQ(expected_priority_, priority);
+ EXPECT_EQ(expected_priority_, expected_stream_->priority());
+ }
+
+ void SetExpectedStream(QuicSpdyStream* stream) { expected_stream_ = stream; }
+ void SetExpectedPriority(SpdyPriority priority) {
+ expected_priority_ = priority;
+ }
+
+ private:
+ QuicSpdyStream* expected_stream_;
+ SpdyPriority expected_priority_;
+};
+
+class QuicSpdyStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+ QuicSpdyStreamTest() {
+ headers_[":host"] = "www.google.com";
+ headers_[":path"] = "/index.hml";
+ headers_[":scheme"] = "https";
+ headers_["cookie"] =
+ "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+ "__utmc=160408618; "
+ "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+ "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+ "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+ "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+ "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+ "1zFMi5vzcns38-8_Sns; "
+ "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+ "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+ "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+ "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+ "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+ "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+ "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+ "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+ "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+ "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+ "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+ "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+ "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+ "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+ "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+ }
+
+ void Initialize(bool stream_should_process_data) {
+ connection_ = new StrictMock<MockQuicConnection>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER,
+ SupportedVersions(GetParam()));
+ session_ = QuicMakeUnique<StrictMock<MockQuicSpdySession>>(connection_);
+ session_->Initialize();
+ ON_CALL(*session_, WritevData(_, _, _, _, _))
+ .WillByDefault(Invoke(MockQuicSession::ConsumeData));
+
+ stream_ =
+ new StrictMock<TestStream>(GetNthClientInitiatedBidirectionalId(0),
+ session_.get(), stream_should_process_data);
+ session_->ActivateStream(QuicWrapUnique(stream_));
+ stream2_ =
+ new StrictMock<TestStream>(GetNthClientInitiatedBidirectionalId(1),
+ session_.get(), stream_should_process_data);
+ session_->ActivateStream(QuicWrapUnique(stream2_));
+ }
+
+ QuicHeaderList ProcessHeaders(bool fin, const SpdyHeaderBlock& headers) {
+ QuicHeaderList h = AsHeaderList(headers);
+ stream_->OnStreamHeaderList(fin, h.uncompressed_header_bytes(), h);
+ return h;
+ }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ bool HasFrameHeader() const {
+ return VersionHasDataFrameHeader(connection_->transport_version());
+ }
+
+ protected:
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ MockQuicConnection* connection_;
+ std::unique_ptr<MockQuicSpdySession> session_;
+
+ // Owned by the |session_|.
+ TestStream* stream_;
+ TestStream* stream2_;
+
+ SpdyHeaderBlock headers_;
+
+ HttpEncoder encoder_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSpdyStreamTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyStreamTest, ProcessHeaderList) {
+ Initialize(kShouldProcessData);
+
+ stream_->OnStreamHeadersPriority(kV3HighestPriority);
+ ProcessHeaders(false, headers_);
+ EXPECT_EQ("", stream_->data());
+ EXPECT_FALSE(stream_->header_list().empty());
+ EXPECT_FALSE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessTooLargeHeaderList) {
+ Initialize(kShouldProcessData);
+
+ QuicHeaderList headers;
+ stream_->OnStreamHeadersPriority(kV3HighestPriority);
+
+ EXPECT_CALL(*session_,
+ SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
+ stream_->OnStreamHeaderList(false, 1 << 20, headers);
+ EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeaderListWithFin) {
+ Initialize(kShouldProcessData);
+
+ size_t total_bytes = 0;
+ QuicHeaderList headers;
+ for (auto p : headers_) {
+ headers.OnHeader(p.first, p.second);
+ total_bytes += p.first.size() + p.second.size();
+ }
+ stream_->OnStreamHeadersPriority(kV3HighestPriority);
+ stream_->OnStreamHeaderList(true, total_bytes, headers);
+ EXPECT_EQ("", stream_->data());
+ EXPECT_FALSE(stream_->header_list().empty());
+ EXPECT_FALSE(stream_->IsDoneReading());
+ EXPECT_TRUE(stream_->HasFinalReceivedByteOffset());
+}
+
+TEST_P(QuicSpdyStreamTest, ParseHeaderStatusCode) {
+ // A valid status code should be 3-digit integer. The first digit should be in
+ // the range of [1, 5]. All the others are invalid.
+ Initialize(kShouldProcessData);
+ int status_code = 0;
+
+ // Valid status codes.
+ headers_[":status"] = "404";
+ EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+ EXPECT_EQ(404, status_code);
+
+ headers_[":status"] = "100";
+ EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+ EXPECT_EQ(100, status_code);
+
+ headers_[":status"] = "599";
+ EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+ EXPECT_EQ(599, status_code);
+
+ // Invalid status codes.
+ headers_[":status"] = "010";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "600";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "200 ok";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "2000";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "+200";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "+20";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "-10";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "-100";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ // Leading or trailing spaces are also invalid.
+ headers_[":status"] = " 200";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = "200 ";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = " 200 ";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+ headers_[":status"] = " ";
+ EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+}
+
+TEST_P(QuicSpdyStreamTest, MarkHeadersConsumed) {
+ Initialize(kShouldProcessData);
+
+ std::string body = "this is the body";
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ EXPECT_EQ(headers, stream_->header_list());
+
+ stream_->ConsumeHeaderList();
+ EXPECT_EQ(QuicHeaderList(), stream_->header_list());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessWrongFramesOnSpdyStream) {
+ testing::InSequence s;
+ Initialize(kShouldProcessData);
+ if (!HasFrameHeader()) {
+ return;
+ }
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ GoAwayFrame goaway;
+ goaway.stream_id = 0x1;
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length = encoder_.SerializeGoAwayFrame(goaway, &buffer);
+ std::string data = std::string(buffer.get(), header_length);
+
+ EXPECT_EQ("", stream_->data());
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ EXPECT_EQ(headers, stream_->header_list());
+ stream_->ConsumeHeaderList();
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _))
+ .WillOnce(
+ (Invoke([this](QuicErrorCode error, const std::string& error_details,
+ ConnectionCloseBehavior connection_close_behavior) {
+ connection_->ReallyCloseConnection(error, error_details,
+ connection_close_behavior);
+ })));
+ EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _, _));
+ EXPECT_CALL(*session_, OnConnectionClosed(_, _, _))
+ .WillOnce(
+ Invoke([this](QuicErrorCode error, const std::string& error_details,
+ ConnectionCloseSource source) {
+ session_->ReallyOnConnectionClosed(error, error_details, source);
+ }));
+ EXPECT_CALL(*session_, SendRstStream(_, _, _));
+ EXPECT_CALL(*session_, SendRstStream(_, _, _));
+
+ stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBody) {
+ Initialize(kShouldProcessData);
+
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ EXPECT_EQ("", stream_->data());
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ EXPECT_EQ(headers, stream_->header_list());
+ stream_->ConsumeHeaderList();
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(QuicHeaderList(), stream_->header_list());
+ EXPECT_EQ(body, stream_->data());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragments) {
+ Initialize(kShouldProcessData);
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ for (size_t fragment_size = 1; fragment_size < data.size(); ++fragment_size) {
+ Initialize(kShouldProcessData);
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ ASSERT_EQ(headers, stream_->header_list());
+ stream_->ConsumeHeaderList();
+ for (size_t offset = 0; offset < data.size(); offset += fragment_size) {
+ size_t remaining_data = data.size() - offset;
+ QuicStringPiece fragment(data.data() + offset,
+ std::min(fragment_size, remaining_data));
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false,
+ offset, QuicStringPiece(fragment));
+ stream_->OnStreamFrame(frame);
+ }
+ ASSERT_EQ(body, stream_->data()) << "fragment_size: " << fragment_size;
+ }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragmentsSplit) {
+ Initialize(kShouldProcessData);
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ for (size_t split_point = 1; split_point < data.size() - 1; ++split_point) {
+ Initialize(kShouldProcessData);
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ ASSERT_EQ(headers, stream_->header_list());
+ stream_->ConsumeHeaderList();
+
+ QuicStringPiece fragment1(data.data(), split_point);
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(fragment1));
+ stream_->OnStreamFrame(frame1);
+
+ QuicStringPiece fragment2(data.data() + split_point,
+ data.size() - split_point);
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+ split_point, QuicStringPiece(fragment2));
+ stream_->OnStreamFrame(frame2);
+
+ ASSERT_EQ(body, stream_->data()) << "split_point: " << split_point;
+ }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyReadv) {
+ Initialize(!kShouldProcessData);
+
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ stream_->ConsumeHeaderList();
+
+ char buffer[2048];
+ ASSERT_LT(data.length(), QUIC_ARRAYSIZE(buffer));
+ struct iovec vec;
+ vec.iov_base = buffer;
+ vec.iov_len = QUIC_ARRAYSIZE(buffer);
+
+ size_t bytes_read = stream_->Readv(&vec, 1);
+ QuicStreamPeer::CloseReadSide(stream_);
+ EXPECT_EQ(body.length(), bytes_read);
+ EXPECT_EQ(body, std::string(buffer, bytes_read));
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndLargeBodySmallReadv) {
+ Initialize(kShouldProcessData);
+ std::string body(12 * 1024, 'a');
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ stream_->ConsumeHeaderList();
+ char buffer[2048];
+ char buffer2[2048];
+ struct iovec vec[2];
+ vec[0].iov_base = buffer;
+ vec[0].iov_len = QUIC_ARRAYSIZE(buffer);
+ vec[1].iov_base = buffer2;
+ vec[1].iov_len = QUIC_ARRAYSIZE(buffer2);
+ size_t bytes_read = stream_->Readv(vec, 2);
+ EXPECT_EQ(2048u * 2, bytes_read);
+ EXPECT_EQ(body.substr(0, 2048), std::string(buffer, 2048));
+ EXPECT_EQ(body.substr(2048, 2048), std::string(buffer2, 2048));
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyMarkConsumed) {
+ Initialize(!kShouldProcessData);
+
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ stream_->ConsumeHeaderList();
+
+ struct iovec vec;
+
+ EXPECT_EQ(1, stream_->GetReadableRegions(&vec, 1));
+ EXPECT_EQ(body.length(), vec.iov_len);
+ EXPECT_EQ(body, std::string(static_cast<char*>(vec.iov_base), vec.iov_len));
+
+ stream_->MarkConsumed(body.length());
+ EXPECT_EQ(data.length(), stream_->flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndConsumeMultipleBody) {
+ Initialize(!kShouldProcessData);
+ std::string body1 = "this is body 1";
+ std::string body2 = "body 2";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body1.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data1 = HasFrameHeader() ? header + body1 : body1;
+ header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buf);
+ std::string data2 = HasFrameHeader() ? header + body2 : body2;
+
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data1));
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+ data1.length(), QuicStringPiece(data2));
+ stream_->OnStreamFrame(frame1);
+ stream_->OnStreamFrame(frame2);
+ stream_->ConsumeHeaderList();
+
+ stream_->MarkConsumed(body1.length() + body2.length());
+ EXPECT_EQ(data1.length() + data2.length(),
+ stream_->flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
+ Initialize(!kShouldProcessData);
+
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ stream_->ConsumeHeaderList();
+
+ char buffer[1];
+ struct iovec vec;
+ vec.iov_base = buffer;
+ vec.iov_len = QUIC_ARRAYSIZE(buffer);
+
+ for (size_t i = 0; i < body.length(); ++i) {
+ size_t bytes_read = stream_->Readv(&vec, 1);
+ ASSERT_EQ(1u, bytes_read);
+ EXPECT_EQ(body.data()[i], buffer[0]);
+ }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
+ Initialize(!kShouldProcessData);
+
+ std::string body = "this is the body";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ ProcessHeaders(false, headers_);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame);
+ stream_->ConsumeHeaderList();
+
+ char buffer1[1];
+ char buffer2[1];
+ struct iovec vec[2];
+ vec[0].iov_base = buffer1;
+ vec[0].iov_len = QUIC_ARRAYSIZE(buffer1);
+ vec[1].iov_base = buffer2;
+ vec[1].iov_len = QUIC_ARRAYSIZE(buffer2);
+
+ for (size_t i = 0; i < body.length(); i += 2) {
+ size_t bytes_read = stream_->Readv(vec, 2);
+ ASSERT_EQ(2u, bytes_read) << i;
+ ASSERT_EQ(body.data()[i], buffer1[0]) << i;
+ ASSERT_EQ(body.data()[i + 1], buffer2[0]) << i;
+ }
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlBlocked) {
+ testing::InSequence seq;
+ // Tests that we send a BLOCKED frame to the peer when we attempt to write,
+ // but are flow control blocked.
+ Initialize(kShouldProcessData);
+
+ // Set a small flow control limit.
+ const uint64_t kWindow = 36;
+ QuicFlowControllerPeer::SetSendWindowOffset(stream_->flow_controller(),
+ kWindow);
+ EXPECT_EQ(kWindow, QuicFlowControllerPeer::SendWindowOffset(
+ stream_->flow_controller()));
+
+ // Try to send more data than the flow control limit allows.
+ const uint64_t kOverflow = 15;
+ std::string body(kWindow + kOverflow, 'a');
+
+ const uint64_t kHeaderLength = HasFrameHeader() ? 2 : 0;
+ if (HasFrameHeader()) {
+ EXPECT_CALL(*session_, WritevData(_, _, kHeaderLength, _, NO_FIN));
+ }
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _))
+ .WillOnce(Return(QuicConsumedData(kWindow - kHeaderLength, true)));
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ stream_->WriteOrBufferBody(body, false);
+
+ // Should have sent as much as possible, resulting in no send window left.
+ EXPECT_EQ(0u,
+ QuicFlowControllerPeer::SendWindowSize(stream_->flow_controller()));
+
+ // And we should have queued the overflowed data.
+ EXPECT_EQ(kOverflow + kHeaderLength, stream_->BufferedDataBytes());
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) {
+ // The flow control receive window decreases whenever we add new bytes to the
+ // sequencer, whether they are consumed immediately or buffered. However we
+ // only send WINDOW_UPDATE frames based on increasing number of bytes
+ // consumed.
+
+ // Don't process data - it will be buffered instead.
+ Initialize(!kShouldProcessData);
+
+ // Expect no WINDOW_UPDATE frames to be sent.
+ EXPECT_CALL(*connection_, SendWindowUpdate(_, _)).Times(0);
+
+ // Set a small flow control receive window.
+ const uint64_t kWindow = 36;
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+ kWindow);
+ EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+ stream_->flow_controller()));
+
+ // Stream receives enough data to fill a fraction of the receive window.
+ std::string body(kWindow / 3, 'a');
+ QuicByteCount header_length = 0;
+ std::string data;
+
+ if (HasFrameHeader()) {
+ std::unique_ptr<char[]> buffer;
+ header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ data = header + body;
+ } else {
+ data = body;
+ }
+
+ ProcessHeaders(false, headers_);
+
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(
+ kWindow - (kWindow / 3) - header_length,
+ QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+
+ // Now receive another frame which results in the receive window being over
+ // half full. This should all be buffered, decreasing the receive window but
+ // not sending WINDOW_UPDATE.
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+ kWindow / 3 + header_length, QuicStringPiece(data));
+ stream_->OnStreamFrame(frame2);
+ EXPECT_EQ(
+ kWindow - (2 * kWindow / 3) - 2 * header_length,
+ QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) {
+ // Tests that on receipt of data, the stream updates its receive window offset
+ // appropriately, and sends WINDOW_UPDATE frames when its receive window drops
+ // too low.
+ Initialize(kShouldProcessData);
+
+ // Set a small flow control limit.
+ const uint64_t kWindow = 36;
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+ kWindow);
+ EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+ stream_->flow_controller()));
+
+ // Stream receives enough data to fill a fraction of the receive window.
+ std::string body(kWindow / 3, 'a');
+ QuicByteCount header_length = 0;
+ std::string data;
+
+ if (HasFrameHeader()) {
+ std::unique_ptr<char[]> buffer;
+ header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ data = header + body;
+ } else {
+ data = body;
+ }
+
+ ProcessHeaders(false, headers_);
+ stream_->ConsumeHeaderList();
+
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(
+ kWindow - (kWindow / 3) - header_length,
+ QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+
+ // Now receive another frame which results in the receive window being over
+ // half full. This will trigger the stream to increase its receive window
+ // offset and send a WINDOW_UPDATE. The result will be again an available
+ // window of kWindow bytes.
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+ kWindow / 3 + header_length, QuicStringPiece(data));
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ stream_->OnStreamFrame(frame2);
+ EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowSize(
+ stream_->flow_controller()));
+}
+
+TEST_P(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) {
+ // Tests that on receipt of data, the connection updates its receive window
+ // offset appropriately, and sends WINDOW_UPDATE frames when its receive
+ // window drops too low.
+ Initialize(kShouldProcessData);
+
+ // Set a small flow control limit for streams and connection.
+ const uint64_t kWindow = 36;
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream2_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetMaxReceiveWindow(stream2_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+ kWindow);
+ QuicFlowControllerPeer::SetMaxReceiveWindow(session_->flow_controller(),
+ kWindow);
+
+ // Supply headers to both streams so that they are happy to receive data.
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ stream_->ConsumeHeaderList();
+ stream2_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+ headers);
+ stream2_->ConsumeHeaderList();
+
+ // Each stream gets a quarter window of data. This should not trigger a
+ // WINDOW_UPDATE for either stream, nor for the connection.
+ QuicByteCount header_length = 0;
+ std::string body;
+ std::string data;
+ std::string data2;
+ std::string body2(1, 'a');
+
+ if (HasFrameHeader()) {
+ body = std::string(kWindow / 4 - 2, 'a');
+ std::unique_ptr<char[]> buffer;
+ header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+ data = header + body;
+ std::unique_ptr<char[]> buffer2;
+ QuicByteCount header_length2 =
+ encoder_.SerializeDataFrameHeader(body2.length(), &buffer2);
+ std::string header2 = std::string(buffer2.get(), header_length2);
+ data2 = header2 + body2;
+ } else {
+ body = std::string(kWindow / 4, 'a');
+ data = body;
+ data2 = body2;
+ }
+
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ stream_->OnStreamFrame(frame1);
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ QuicStringPiece(data));
+ stream2_->OnStreamFrame(frame2);
+
+ // Now receive a further single byte on one stream - again this does not
+ // trigger a stream WINDOW_UPDATE, but now the connection flow control window
+ // is over half full and thus a connection WINDOW_UPDATE is sent.
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false,
+ body.length() + header_length, QuicStringPiece(data2));
+ stream_->OnStreamFrame(frame3);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlViolation) {
+ // Tests that on if the peer sends too much data (i.e. violates the flow
+ // control protocol), then we terminate the connection.
+
+ // Stream should not process data, so that data gets buffered in the
+ // sequencer, triggering flow control limits.
+ Initialize(!kShouldProcessData);
+
+ // Set a small flow control limit.
+ const uint64_t kWindow = 50;
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+ kWindow);
+
+ ProcessHeaders(false, headers_);
+
+ // Receive data to overflow the window, violating flow control.
+ std::string body(kWindow + 1, 'a');
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _));
+ stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSpdyStreamTest, TestHandlingQuicRstStreamNoError) {
+ Initialize(kShouldProcessData);
+ ProcessHeaders(false, headers_);
+
+ stream_->OnStreamReset(QuicRstStreamFrame(
+ kInvalidControlFrameId, stream_->id(), QUIC_STREAM_NO_ERROR, 0));
+ EXPECT_TRUE(stream_->write_side_closed());
+ EXPECT_FALSE(stream_->reading_stopped());
+}
+
+TEST_P(QuicSpdyStreamTest, ConnectionFlowControlViolation) {
+ // Tests that on if the peer sends too much data (i.e. violates the flow
+ // control protocol), at the connection level (rather than the stream level)
+ // then we terminate the connection.
+
+ // Stream should not process data, so that data gets buffered in the
+ // sequencer, triggering flow control limits.
+ Initialize(!kShouldProcessData);
+
+ // Set a small flow control window on streams, and connection.
+ const uint64_t kStreamWindow = 50;
+ const uint64_t kConnectionWindow = 10;
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+ kStreamWindow);
+ QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+ kConnectionWindow);
+
+ ProcessHeaders(false, headers_);
+
+ // Send enough data to overflow the connection level flow control window.
+ std::string body(kConnectionWindow + 1, 'a');
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ EXPECT_LT(data.size(), kStreamWindow);
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece(data));
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _));
+ stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlFinNotBlocked) {
+ // An attempt to write a FIN with no data should not be flow control blocked,
+ // even if the send window is 0.
+
+ Initialize(kShouldProcessData);
+
+ // Set a flow control limit of zero.
+ QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(), 0);
+ EXPECT_EQ(0u, QuicFlowControllerPeer::ReceiveWindowOffset(
+ stream_->flow_controller()));
+
+ // Send a frame with a FIN but no data. This should not be blocked.
+ std::string body = "";
+ bool fin = true;
+
+ EXPECT_CALL(*connection_,
+ SendBlocked(GetNthClientInitiatedBidirectionalId(0)))
+ .Times(0);
+ EXPECT_CALL(*session_, WritevData(_, _, 0, _, FIN));
+
+ stream_->WriteOrBufferBody(body, fin);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersViaHeaderList) {
+ // Test that receiving trailing headers from the peer via
+ // OnStreamHeaderList() works, and can be read from the stream and consumed.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers.
+ size_t total_bytes = 0;
+ QuicHeaderList headers;
+ for (const auto& p : headers_) {
+ headers.OnHeader(p.first, p.second);
+ total_bytes += p.first.size() + p.second.size();
+ }
+
+ stream_->OnStreamHeadersPriority(kV3HighestPriority);
+ stream_->OnStreamHeaderList(/*fin=*/false, total_bytes, headers);
+ stream_->ConsumeHeaderList();
+
+ // Receive trailing headers.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["key1"] = "value1";
+ trailers_block["key2"] = "value2";
+ trailers_block["key3"] = "value3";
+ SpdyHeaderBlock trailers_block_with_final_offset = trailers_block.Clone();
+ trailers_block_with_final_offset[kFinalOffsetHeaderKey] = "0";
+ total_bytes = 0;
+ QuicHeaderList trailers;
+ for (const auto& p : trailers_block_with_final_offset) {
+ trailers.OnHeader(p.first, p.second);
+ total_bytes += p.first.size() + p.second.size();
+ }
+ stream_->OnStreamHeaderList(/*fin=*/true, total_bytes, trailers);
+
+ // The trailers should be decompressed, and readable from the stream.
+ EXPECT_TRUE(stream_->trailers_decompressed());
+ EXPECT_EQ(trailers_block, stream_->received_trailers());
+
+ // IsDoneReading() returns false until trailers marked consumed.
+ EXPECT_FALSE(stream_->IsDoneReading());
+ stream_->MarkTrailersConsumed();
+ EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithOffset) {
+ // Test that when receiving trailing headers with an offset before response
+ // body, stream is closed at the right offset.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers.
+ QuicHeaderList headers = ProcessHeaders(false, headers_);
+ stream_->ConsumeHeaderList();
+
+ const std::string body = "this is the body";
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ // Receive trailing headers.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["key1"] = "value1";
+ trailers_block["key2"] = "value2";
+ trailers_block["key3"] = "value3";
+ trailers_block[kFinalOffsetHeaderKey] =
+ QuicTextUtils::Uint64ToString(data.size());
+
+ QuicHeaderList trailers = ProcessHeaders(true, trailers_block);
+
+ // The trailers should be decompressed, and readable from the stream.
+ EXPECT_TRUE(stream_->trailers_decompressed());
+
+ // The final offset trailer will be consumed by QUIC.
+ trailers_block.erase(kFinalOffsetHeaderKey);
+ EXPECT_EQ(trailers_block, stream_->received_trailers());
+
+ // Consuming the trailers erases them from the stream.
+ stream_->MarkTrailersConsumed();
+ EXPECT_TRUE(stream_->FinishedReadingTrailers());
+
+ EXPECT_FALSE(stream_->IsDoneReading());
+ // Receive and consume body.
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/false,
+ 0, data);
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(body, stream_->data());
+ EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithoutOffset) {
+ // Test that receiving trailers without a final offset field is an error.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers.
+ ProcessHeaders(false, headers_);
+ stream_->ConsumeHeaderList();
+
+ // Receive trailing headers, without kFinalOffsetHeaderKey.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["key1"] = "value1";
+ trailers_block["key2"] = "value2";
+ trailers_block["key3"] = "value3";
+ auto trailers = AsHeaderList(trailers_block);
+
+ // Verify that the trailers block didn't contain a final offset.
+ EXPECT_EQ("", trailers_block[kFinalOffsetHeaderKey].as_string());
+
+ // Receipt of the malformed trailers will close the connection.
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+ .Times(1);
+ stream_->OnStreamHeaderList(/*fin=*/true,
+ trailers.uncompressed_header_bytes(), trailers);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithoutFin) {
+ // Test that received Trailers must always have the FIN set.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers.
+ auto headers = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(/*fin=*/false,
+ headers.uncompressed_header_bytes(), headers);
+ stream_->ConsumeHeaderList();
+
+ // Receive trailing headers with FIN deliberately set to false.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["foo"] = "bar";
+ auto trailers = AsHeaderList(trailers_block);
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+ .Times(1);
+ stream_->OnStreamHeaderList(/*fin=*/false,
+ trailers.uncompressed_header_bytes(), trailers);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersAfterHeadersWithFin) {
+ // If headers are received with a FIN, no trailers should then arrive.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers with FIN set.
+ ProcessHeaders(true, headers_);
+ stream_->ConsumeHeaderList();
+
+ // Receive trailing headers after FIN already received.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["foo"] = "bar";
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+ .Times(1);
+ ProcessHeaders(true, trailers_block);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersAfterBodyWithFin) {
+ // If body data are received with a FIN, no trailers should then arrive.
+ Initialize(kShouldProcessData);
+
+ // Receive initial headers without FIN set.
+ ProcessHeaders(false, headers_);
+ stream_->ConsumeHeaderList();
+
+ // Receive body data, with FIN.
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/true,
+ 0, "body");
+ stream_->OnStreamFrame(frame);
+
+ // Receive trailing headers after FIN already received.
+ SpdyHeaderBlock trailers_block;
+ trailers_block["foo"] = "bar";
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+ .Times(1);
+ ProcessHeaders(true, trailers_block);
+}
+
+TEST_P(QuicSpdyStreamTest, ClosingStreamWithNoTrailers) {
+ // Verify that a stream receiving headers, body, and no trailers is correctly
+ // marked as done reading on consumption of headers and body.
+ Initialize(kShouldProcessData);
+
+ // Receive and consume initial headers with FIN not set.
+ auto h = AsHeaderList(headers_);
+ stream_->OnStreamHeaderList(/*fin=*/false, h.uncompressed_header_bytes(), h);
+ stream_->ConsumeHeaderList();
+
+ // Receive and consume body with FIN set, and no trailers.
+ std::string body(1024, 'x');
+ std::unique_ptr<char[]> buf;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ std::string header = std::string(buf.get(), header_length);
+ std::string data = HasFrameHeader() ? header + body : body;
+
+ QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/true,
+ 0, data);
+ stream_->OnStreamFrame(frame);
+
+ EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersSendsAFin) {
+ // Test that writing trailers will send a FIN, as Trailers are the last thing
+ // to be sent on a stream.
+ Initialize(kShouldProcessData);
+
+ // Write the initial headers, without a FIN.
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+ // Writing trailers implicitly sends a FIN.
+ SpdyHeaderBlock trailers;
+ trailers["trailer key"] = "trailer value";
+ EXPECT_CALL(*stream_, WriteHeadersMock(true));
+ stream_->WriteTrailers(std::move(trailers), nullptr);
+ EXPECT_TRUE(stream_->fin_sent());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersFinalOffset) {
+ // Test that when writing trailers, the trailers that are actually sent to the
+ // peer contain the final offset field indicating last byte of data.
+ Initialize(kShouldProcessData);
+
+ // Write the initial headers.
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+ // Write non-zero body data to force a non-zero final offset.
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ std::string body(1024, 'x'); // 1 kB
+ QuicByteCount header_length = 0;
+ if (HasFrameHeader()) {
+ std::unique_ptr<char[]> buf;
+ header_length = encoder_.SerializeDataFrameHeader(body.length(), &buf);
+ }
+
+ stream_->WriteOrBufferBody(body, false);
+
+ // The final offset field in the trailing headers is populated with the
+ // number of body bytes written (including queued bytes).
+ SpdyHeaderBlock trailers;
+ trailers["trailer key"] = "trailer value";
+ SpdyHeaderBlock trailers_with_offset(trailers.Clone());
+ trailers_with_offset[kFinalOffsetHeaderKey] =
+ QuicTextUtils::Uint64ToString(body.length() + header_length);
+ EXPECT_CALL(*stream_, WriteHeadersMock(true));
+ stream_->WriteTrailers(std::move(trailers), nullptr);
+ EXPECT_EQ(trailers_with_offset, stream_->saved_headers());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersClosesWriteSide) {
+ // Test that if trailers are written after all other data has been written
+ // (headers and body), that this closes the stream for writing.
+ Initialize(kShouldProcessData);
+
+ // Write the initial headers.
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+ // Write non-zero body data.
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ const int kBodySize = 1 * 1024; // 1 kB
+ stream_->WriteOrBufferBody(std::string(kBodySize, 'x'), false);
+ EXPECT_EQ(0u, stream_->BufferedDataBytes());
+
+ // Headers and body have been fully written, there is no queued data. Writing
+ // trailers marks the end of this stream, and thus the write side is closed.
+ EXPECT_CALL(*stream_, WriteHeadersMock(true));
+ stream_->WriteTrailers(SpdyHeaderBlock(), nullptr);
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersWithQueuedBytes) {
+ // Test that the stream is not closed for writing when trailers are sent
+ // while there are still body bytes queued.
+ testing::InSequence seq;
+ Initialize(kShouldProcessData);
+
+ // Write the initial headers.
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+ // Write non-zero body data, but only consume partially, ensuring queueing.
+ const int kBodySize = 1 * 1024; // 1 kB
+ if (HasFrameHeader()) {
+ EXPECT_CALL(*session_, WritevData(_, _, 3, _, NO_FIN));
+ }
+ EXPECT_CALL(*session_, WritevData(_, _, kBodySize, _, NO_FIN))
+ .WillOnce(Return(QuicConsumedData(kBodySize - 1, false)));
+ stream_->WriteOrBufferBody(std::string(kBodySize, 'x'), false);
+ EXPECT_EQ(1u, stream_->BufferedDataBytes());
+
+ // Writing trailers will send a FIN, but not close the write side of the
+ // stream as there are queued bytes.
+ EXPECT_CALL(*stream_, WriteHeadersMock(true));
+ stream_->WriteTrailers(SpdyHeaderBlock(), nullptr);
+ EXPECT_TRUE(stream_->fin_sent());
+ EXPECT_FALSE(stream_->write_side_closed());
+
+ // Writing the queued bytes will close the write side of the stream.
+ EXPECT_CALL(*session_, WritevData(_, _, 1, _, NO_FIN));
+ stream_->OnCanWrite();
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersAfterFIN) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Test that it is not possible to write Trailers after a FIN has been sent.
+ Initialize(kShouldProcessData);
+
+ // Write the initial headers, with a FIN.
+ EXPECT_CALL(*stream_, WriteHeadersMock(true));
+ stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/true, nullptr);
+ EXPECT_TRUE(stream_->fin_sent());
+
+ // Writing Trailers should fail, as the FIN has already been sent.
+ // populated with the number of body bytes written.
+ EXPECT_QUIC_BUG(stream_->WriteTrailers(SpdyHeaderBlock(), nullptr),
+ "Trailers cannot be sent after a FIN");
+}
+
+TEST_P(QuicSpdyStreamTest, HeaderStreamNotiferCorrespondingSpdyStream) {
+ Initialize(kShouldProcessData);
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ testing::InSequence s;
+ QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+ new MockAckListener());
+ QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+ new MockAckListener());
+ stream_->set_ack_listener(ack_listener1);
+ stream2_->set_ack_listener(ack_listener2);
+
+ session_->headers_stream()->WriteOrBufferData("Header1", false,
+ ack_listener1);
+ stream_->WriteOrBufferBody("Test1", true);
+
+ session_->headers_stream()->WriteOrBufferData("Header2", false,
+ ack_listener2);
+ stream2_->WriteOrBufferBody("Test2", false);
+
+ QuicStreamFrame frame1(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()), false, 0,
+ "Header1");
+ std::string header = "";
+ if (HasFrameHeader()) {
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length = encoder_.SerializeDataFrameHeader(5, &buffer);
+ header = std::string(buffer.get(), header_length);
+ }
+ QuicStreamFrame frame2(stream_->id(), true, 0, header + "Test1");
+ QuicStreamFrame frame3(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()), false, 7,
+ "Header2");
+ QuicStreamFrame frame4(stream2_->id(), false, 0, header + "Test2");
+
+ EXPECT_CALL(*ack_listener1, OnPacketRetransmitted(7));
+ session_->OnStreamFrameRetransmitted(frame1);
+
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame1), QuicTime::Delta::Zero()));
+ EXPECT_CALL(*ack_listener1, OnPacketAcked(5, _));
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame2), QuicTime::Delta::Zero()));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame3), QuicTime::Delta::Zero()));
+ EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame4), QuicTime::Delta::Zero()));
+}
+
+TEST_P(QuicSpdyStreamTest, StreamBecomesZombieWithWriteThatCloses) {
+ Initialize(kShouldProcessData);
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ QuicStreamPeer::CloseReadSide(stream_);
+ // This write causes stream to be closed.
+ stream_->WriteOrBufferBody("Test1", true);
+ // stream_ has unacked data and should become zombie.
+ EXPECT_TRUE(QuicContainsKey(QuicSessionPeer::zombie_streams(session_.get()),
+ stream_->id()));
+ EXPECT_TRUE(QuicSessionPeer::closed_streams(session_.get()).empty());
+}
+
+TEST_P(QuicSpdyStreamTest, OnPriorityFrame) {
+ Initialize(kShouldProcessData);
+ stream_->OnPriorityFrame(kV3HighestPriority);
+ EXPECT_EQ(kV3HighestPriority, stream_->priority());
+}
+
+TEST_P(QuicSpdyStreamTest, OnPriorityFrameAfterSendingData) {
+ testing::InSequence seq;
+ Initialize(kShouldProcessData);
+
+ if (HasFrameHeader()) {
+ EXPECT_CALL(*session_, WritevData(_, _, 2, _, NO_FIN));
+ }
+ EXPECT_CALL(*session_, WritevData(_, _, 4, _, FIN));
+ stream_->WriteOrBufferBody("data", true);
+ stream_->OnPriorityFrame(kV3HighestPriority);
+ EXPECT_EQ(kV3HighestPriority, stream_->priority());
+}
+
+TEST_P(QuicSpdyStreamTest, SetPriorityBeforeUpdateStreamPriority) {
+ MockQuicConnection* connection = new StrictMock<MockQuicConnection>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER,
+ SupportedVersions(GetParam()));
+ std::unique_ptr<TestMockUpdateStreamSession> session(
+ new StrictMock<TestMockUpdateStreamSession>(connection));
+ auto stream = new StrictMock<TestStream>(
+ GetNthClientInitiatedBidirectionalStreamId(
+ session->connection()->transport_version(), 0),
+ session.get(),
+ /*should_process_data=*/true);
+ session->ActivateStream(QuicWrapUnique(stream));
+
+ // QuicSpdyStream::SetPriority() should eventually call UpdateStreamPriority()
+ // on the session. Make sure stream->priority() returns the updated priority
+ // if called within UpdateStreamPriority(). This expectation is enforced in
+ // TestMockUpdateStreamSession::UpdateStreamPriority().
+ session->SetExpectedStream(stream);
+ session->SetExpectedPriority(kV3HighestPriority);
+ stream->SetPriority(kV3HighestPriority);
+
+ session->SetExpectedPriority(kV3LowestPriority);
+ stream->SetPriority(kV3LowestPriority);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamWaitsForAcks) {
+ Initialize(kShouldProcessData);
+ QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+ new StrictMock<MockAckListener>);
+ stream_->set_ack_listener(mock_ack_listener);
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ // Stream is not waiting for acks initially.
+ EXPECT_FALSE(stream_->IsWaitingForAcks());
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+ // Send kData1.
+ stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+ EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+ EXPECT_TRUE(stream_->IsWaitingForAcks());
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+ QuicByteCount newly_acked_length = 0;
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(0, 9, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ // Stream is not waiting for acks as all sent data is acked.
+ EXPECT_FALSE(stream_->IsWaitingForAcks());
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+ // Send kData2.
+ stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+ EXPECT_TRUE(stream_->IsWaitingForAcks());
+ EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+ // Send FIN.
+ stream_->WriteOrBufferData("", true, nullptr);
+ // Fin only frame is not stored in send buffer.
+ EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+
+ // kData2 is retransmitted.
+ EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(9));
+ stream_->OnStreamFrameRetransmitted(9, 9, false);
+
+ // kData2 is acked.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(9, 9, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ // Stream is waiting for acks as FIN is not acked.
+ EXPECT_TRUE(stream_->IsWaitingForAcks());
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+ // FIN is acked.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(18, 0, true, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_FALSE(stream_->IsWaitingForAcks());
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+}
+
+TEST_P(QuicSpdyStreamTest, StreamDataGetAckedMultipleTimes) {
+ Initialize(kShouldProcessData);
+ QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+ new StrictMock<MockAckListener>);
+ stream_->set_ack_listener(mock_ack_listener);
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ // Send [0, 27) and fin.
+ stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+ stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+ stream_->WriteOrBufferData("FooAndBar", true, nullptr);
+
+ // Ack [0, 9), [5, 22) and [18, 26)
+ // Verify [0, 9) 9 bytes are acked.
+ QuicByteCount newly_acked_length = 0;
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(0, 9, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_EQ(2u, QuicStreamPeer::SendBuffer(stream_).size());
+ // Verify [9, 22) 13 bytes are acked.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(13, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(5, 17, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+ // Verify [22, 26) 4 bytes are acked.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(4, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(18, 8, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+ EXPECT_TRUE(stream_->IsWaitingForAcks());
+
+ // Ack [0, 27).
+ // Verify [26, 27) 1 byte is acked.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(1, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(26, 1, false, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+ EXPECT_TRUE(stream_->IsWaitingForAcks());
+
+ // Ack Fin. Verify OnPacketAcked is called.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+ EXPECT_TRUE(stream_->OnStreamFrameAcked(27, 0, true, QuicTime::Delta::Zero(),
+ &newly_acked_length));
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+ EXPECT_FALSE(stream_->IsWaitingForAcks());
+
+ // Ack [10, 27) and fin.
+ // No new data is acked, verify OnPacketAcked is not called.
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(_, _)).Times(0);
+ EXPECT_FALSE(stream_->OnStreamFrameAcked(
+ 10, 17, true, QuicTime::Delta::Zero(), &newly_acked_length));
+ EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+ EXPECT_FALSE(stream_->IsWaitingForAcks());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeadersAckNotReportedWriteOrBufferBody) {
+ Initialize(kShouldProcessData);
+ if (!HasFrameHeader()) {
+ return;
+ }
+ QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+ new StrictMock<MockAckListener>);
+ stream_->set_ack_listener(mock_ack_listener);
+ std::string body = "Test1";
+ std::string body2(100, 'x');
+
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ stream_->WriteOrBufferBody(body, false);
+ stream_->WriteOrBufferBody(body2, true);
+
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+
+ header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+ std::string header2 = std::string(buffer.get(), header_length);
+
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body.length(), _));
+ QuicStreamFrame frame(stream_->id(), false, 0, header + body);
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame), QuicTime::Delta::Zero()));
+
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+ QuicStreamFrame frame2(stream_->id(), false, (header + body).length(),
+ header2);
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame2), QuicTime::Delta::Zero()));
+
+ EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body2.length(), _));
+ QuicStreamFrame frame3(stream_->id(), true,
+ (header + body).length() + header2.length(), body2);
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame3), QuicTime::Delta::Zero()));
+
+ EXPECT_TRUE(
+ QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeadersAckNotReportedWriteBodySlices) {
+ Initialize(kShouldProcessData);
+ if (!HasFrameHeader()) {
+ return;
+ }
+ QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+ new StrictMock<MockAckListener>);
+ stream_->set_ack_listener(mock_ack_listener);
+ std::string body = "Test1";
+ std::string body2(100, 'x');
+ struct iovec body1_iov = {const_cast<char*>(body.data()), body.length()};
+ struct iovec body2_iov = {const_cast<char*>(body2.data()), body2.length()};
+ QuicMemSliceStorage storage(&body1_iov, 1,
+ helper_.GetStreamSendBufferAllocator(), 1024);
+ QuicMemSliceStorage storage2(&body2_iov, 1,
+ helper_.GetStreamSendBufferAllocator(), 1024);
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ stream_->WriteBodySlices(storage.ToSpan(), false);
+ stream_->WriteBodySlices(storage2.ToSpan(), true);
+
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+
+ header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+ std::string header2 = std::string(buffer.get(), header_length);
+
+ EXPECT_CALL(*mock_ack_listener,
+ OnPacketAcked(body.length() + body2.length(), _));
+ QuicStreamFrame frame(stream_->id(), true, 0,
+ header + body + header2 + body2);
+ EXPECT_TRUE(
+ session_->OnFrameAcked(QuicFrame(frame), QuicTime::Delta::Zero()));
+
+ EXPECT_TRUE(
+ QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeaderBytesNotReportedOnRetransmission) {
+ Initialize(kShouldProcessData);
+ if (!HasFrameHeader()) {
+ return;
+ }
+ QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+ new StrictMock<MockAckListener>);
+ stream_->set_ack_listener(mock_ack_listener);
+ std::string body = "Test1";
+ std::string body2(100, 'x');
+
+ EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+ stream_->WriteOrBufferBody(body, false);
+ stream_->WriteOrBufferBody(body2, true);
+
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount header_length =
+ encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+ std::string header = std::string(buffer.get(), header_length);
+
+ header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+ std::string header2 = std::string(buffer.get(), header_length);
+
+ EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(body.length()));
+ QuicStreamFrame frame(stream_->id(), false, 0, header + body);
+ session_->OnStreamFrameRetransmitted(frame);
+
+ EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(body2.length()));
+ QuicStreamFrame frame2(stream_->id(), true, (header + body).length(),
+ header2 + body2);
+ session_->OnStreamFrameRetransmitted(frame2);
+
+ EXPECT_FALSE(
+ QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc
new file mode 100644
index 00000000000..721ae33face
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 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/spdy_utils.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "url/gurl.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.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_map_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+// static
+bool SpdyUtils::ExtractContentLengthFromHeaders(int64_t* content_length,
+ SpdyHeaderBlock* headers) {
+ auto it = headers->find("content-length");
+ if (it == headers->end()) {
+ return false;
+ } else {
+ // Check whether multiple values are consistent.
+ QuicStringPiece content_length_header = it->second;
+ std::vector<QuicStringPiece> values =
+ QuicTextUtils::Split(content_length_header, '\0');
+ for (const QuicStringPiece& value : values) {
+ uint64_t new_value;
+ if (!QuicTextUtils::StringToUint64(value, &new_value)) {
+ QUIC_DLOG(ERROR)
+ << "Content length was either unparseable or negative.";
+ return false;
+ }
+ if (*content_length < 0) {
+ *content_length = new_value;
+ continue;
+ }
+ if (new_value != static_cast<uint64_t>(*content_length)) {
+ QUIC_DLOG(ERROR)
+ << "Parsed content length " << new_value << " is "
+ << "inconsistent with previously detected content length "
+ << *content_length;
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+bool SpdyUtils::CopyAndValidateHeaders(const QuicHeaderList& header_list,
+ int64_t* content_length,
+ SpdyHeaderBlock* headers) {
+ for (const auto& p : header_list) {
+ const std::string& name = p.first;
+ if (name.empty()) {
+ QUIC_DLOG(ERROR) << "Header name must not be empty.";
+ return false;
+ }
+
+ if (QuicTextUtils::ContainsUpperCase(name)) {
+ QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
+ << " contains upper-case characters.";
+ return false;
+ }
+
+ headers->AppendValueOrAddHeader(name, p.second);
+ }
+
+ if (QuicContainsKey(*headers, "content-length") &&
+ !ExtractContentLengthFromHeaders(content_length, headers)) {
+ return false;
+ }
+
+ QUIC_DVLOG(1) << "Successfully parsed headers: " << headers->DebugString();
+ return true;
+}
+
+bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list,
+ bool expect_final_byte_offset,
+ size_t* final_byte_offset,
+ SpdyHeaderBlock* trailers) {
+ bool found_final_byte_offset = false;
+ for (const auto& p : header_list) {
+ const std::string& name = p.first;
+
+ // Pull out the final offset pseudo header which indicates the number of
+ // response body bytes expected.
+ if (expect_final_byte_offset && !found_final_byte_offset &&
+ name == kFinalOffsetHeaderKey &&
+ QuicTextUtils::StringToSizeT(p.second, final_byte_offset)) {
+ found_final_byte_offset = true;
+ continue;
+ }
+
+ if (name.empty() || name[0] == ':') {
+ QUIC_DLOG(ERROR)
+ << "Trailers must not be empty, and must not contain pseudo-"
+ << "headers. Found: '" << name << "'";
+ return false;
+ }
+
+ if (QuicTextUtils::ContainsUpperCase(name)) {
+ QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
+ << " contains upper-case characters.";
+ return false;
+ }
+
+ trailers->AppendValueOrAddHeader(name, p.second);
+ }
+
+ if (expect_final_byte_offset && !found_final_byte_offset) {
+ QUIC_DLOG(ERROR) << "Required key '" << kFinalOffsetHeaderKey
+ << "' not present";
+ return false;
+ }
+
+ // TODO(rjshade): Check for other forbidden keys, following the HTTP/2 spec.
+
+ QUIC_DVLOG(1) << "Successfully parsed Trailers: " << trailers->DebugString();
+ return true;
+}
+
+// static
+std::string SpdyUtils::GetPromisedUrlFromHeaders(
+ const SpdyHeaderBlock& headers) {
+ // RFC 7540, Section 8.1.2.3: All HTTP/2 requests MUST include exactly
+ // one valid value for the ":method", ":scheme", and ":path" pseudo-header
+ // fields, unless it is a CONNECT request.
+
+ // RFC 7540, Section 8.2.1: The header fields in PUSH_PROMISE and any
+ // subsequent CONTINUATION frames MUST be a valid and complete set of request
+ // header fields (Section 8.1.2.3). The server MUST include a method in the
+ // ":method" pseudo-header field that is safe and cacheable.
+ //
+ // RFC 7231, Section 4.2.1: Of the request methods defined by this
+ // specification, the GET, HEAD, OPTIONS, and TRACE methods are defined to be
+ // safe.
+ //
+ // RFC 7231, Section 4.2.1: ... this specification defines GET, HEAD, and
+ // POST as cacheable, ...
+ //
+ // So the only methods allowed in a PUSH_PROMISE are GET and HEAD.
+ SpdyHeaderBlock::const_iterator it = headers.find(":method");
+ if (it == headers.end() || (it->second != "GET" && it->second != "HEAD")) {
+ return std::string();
+ }
+
+ it = headers.find(":scheme");
+ if (it == headers.end() || it->second.empty()) {
+ return std::string();
+ }
+ QuicStringPiece scheme = it->second;
+
+ // RFC 7540, Section 8.2: The server MUST include a value in the
+ // ":authority" pseudo-header field for which the server is authoritative
+ // (see Section 10.1).
+ it = headers.find(":authority");
+ if (it == headers.end() || it->second.empty()) {
+ return std::string();
+ }
+ QuicStringPiece authority = it->second;
+
+ // RFC 7540, Section 8.1.2.3 requires that the ":path" pseudo-header MUST
+ // NOT be empty for "http" or "https" URIs;
+ //
+ // However, to ensure the scheme is consistently canonicalized, that check
+ // is deferred to implementations in QuicUrlUtils::GetPushPromiseUrl().
+ it = headers.find(":path");
+ if (it == headers.end()) {
+ return std::string();
+ }
+ QuicStringPiece path = it->second;
+
+ return GetPushPromiseUrl(scheme, authority, path);
+}
+
+// static
+std::string SpdyUtils::GetPromisedHostNameFromHeaders(
+ const SpdyHeaderBlock& headers) {
+ // TODO(fayang): Consider just checking out the value of the ":authority" key
+ // in headers.
+ return GURL(GetPromisedUrlFromHeaders(headers)).host();
+}
+
+// static
+bool SpdyUtils::PromisedUrlIsValid(const SpdyHeaderBlock& headers) {
+ std::string url(GetPromisedUrlFromHeaders(headers));
+ return !url.empty() && GURL(url).is_valid();
+}
+
+// static
+bool SpdyUtils::PopulateHeaderBlockFromUrl(const std::string url,
+ SpdyHeaderBlock* headers) {
+ (*headers)[":method"] = "GET";
+ size_t pos = url.find("://");
+ if (pos == std::string::npos) {
+ return false;
+ }
+ (*headers)[":scheme"] = url.substr(0, pos);
+ size_t start = pos + 3;
+ pos = url.find("/", start);
+ if (pos == std::string::npos) {
+ (*headers)[":authority"] = url.substr(start);
+ (*headers)[":path"] = "/";
+ return true;
+ }
+ (*headers)[":authority"] = url.substr(start, pos - start);
+ (*headers)[":path"] = url.substr(pos);
+ return true;
+}
+
+// static
+std::string SpdyUtils::GetPushPromiseUrl(QuicStringPiece scheme,
+ QuicStringPiece authority,
+ QuicStringPiece path) {
+ // RFC 7540, Section 8.1.2.3: The ":path" pseudo-header field includes the
+ // path and query parts of the target URI (the "path-absolute" production
+ // and optionally a '?' character followed by the "query" production (see
+ // Sections 3.3 and 3.4 of RFC3986). A request in asterisk form includes the
+ // value '*' for the ":path" pseudo-header field.
+ //
+ // This pseudo-header field MUST NOT be empty for "http" or "https" URIs;
+ // "http" or "https" URIs that do not contain a path MUST include a value of
+ // '/'. The exception to this rule is an OPTIONS request for an "http" or
+ // "https" URI that does not include a path component; these MUST include a
+ // ":path" pseudo-header with a value of '*' (see RFC7230, Section 5.3.4).
+ //
+ // In addition to the above restriction from RFC 7540, note that RFC3986
+ // defines the "path-absolute" construction as starting with "/" but not "//".
+ //
+ // RFC 7540, Section 8.2.1: The header fields in PUSH_PROMISE and any
+ // subsequent CONTINUATION frames MUST be a valid and complete set of request
+ // header fields (Section 8.1.2.3). The server MUST include a method in the
+ // ":method" pseudo-header field that is safe and cacheable.
+ //
+ // RFC 7231, Section 4.2.1:
+ // ... this specification defines GET, HEAD, and POST as cacheable, ...
+ //
+ // Since the OPTIONS method is not cacheable, it cannot be the method of a
+ // PUSH_PROMISE. Therefore, the exception mentioned in RFC 7540, Section
+ // 8.1.2.3 about OPTIONS requests does not apply here (i.e. ":path" cannot be
+ // "*").
+ if (path.empty() || path[0] != '/' || (path.size() >= 2 && path[1] == '/')) {
+ return std::string();
+ }
+
+ // Validate the scheme; this is to ensure a scheme of "foo://bar" is not
+ // parsed as a URL of "foo://bar://baz" when combined with a host of "baz".
+ std::string canonical_scheme;
+ url::StdStringCanonOutput canon_scheme_output(&canonical_scheme);
+ url::Component canon_component;
+ url::Component scheme_component(0, scheme.size());
+
+ if (!url::CanonicalizeScheme(scheme.data(), scheme_component,
+ &canon_scheme_output, &canon_component) ||
+ !canon_component.is_nonempty() || canon_component.begin != 0) {
+ return std::string();
+ }
+ canonical_scheme.resize(canon_component.len + 1);
+
+ // Validate the authority; this is to ensure an authority such as
+ // "host/path" is not accepted, as when combined with a scheme like
+ // "http://", could result in a URL of "http://host/path".
+ url::Component auth_component(0, authority.size());
+ url::Component username_component;
+ url::Component password_component;
+ url::Component host_component;
+ url::Component port_component;
+
+ url::ParseAuthority(authority.data(), auth_component, &username_component,
+ &password_component, &host_component, &port_component);
+
+ // RFC 7540, Section 8.1.2.3: The authority MUST NOT include the deprecated
+ // "userinfo" subcomponent for "http" or "https" schemed URIs.
+ //
+ // Note: Although |canonical_scheme| has not yet been checked for that, as
+ // it is performed later in processing, only "http" and "https" schemed
+ // URIs are supported for PUSH.
+ if (username_component.is_valid() || password_component.is_valid()) {
+ return std::string();
+ }
+
+ // Failed parsing or no host present. ParseAuthority() will ensure that
+ // host_component + port_component cover the entire string, if
+ // username_component and password_component are not present.
+ if (!host_component.is_nonempty()) {
+ return std::string();
+ }
+
+ // Validate the port (if present; it's optional).
+ int parsed_port_number = url::PORT_INVALID;
+ if (port_component.is_nonempty()) {
+ parsed_port_number = url::ParsePort(authority.data(), port_component);
+ if (parsed_port_number < 0 && parsed_port_number != url::PORT_UNSPECIFIED) {
+ return std::string();
+ }
+ }
+
+ // Validate the host by attempting to canonicalize it. Invalid characters
+ // will result in a canonicalization failure (e.g. '/')
+ std::string canon_host;
+ url::StdStringCanonOutput canon_host_output(&canon_host);
+ canon_component.reset();
+ if (!url::CanonicalizeHost(authority.data(), host_component,
+ &canon_host_output, &canon_component) ||
+ !canon_component.is_nonempty() || canon_component.begin != 0) {
+ return std::string();
+ }
+
+ // At this point, "authority" has been validated to either be of the form
+ // 'host:port' or 'host', with 'host' being a valid domain or IP address,
+ // and 'port' (if present), being a valid port. Attempt to construct a
+ // URL of just the (scheme, host, port), which should be safe and will not
+ // result in ambiguous parsing.
+ //
+ // This also enforces that all PUSHed URLs are either HTTP or HTTPS-schemed
+ // URIs, consistent with the other restrictions enforced above.
+ //
+ // Note: url::CanonicalizeScheme() will have added the ':' to
+ // |canonical_scheme|.
+ GURL origin_url(canonical_scheme + "//" + std::string(authority));
+ if (!origin_url.is_valid() || !origin_url.SchemeIsHTTPOrHTTPS() ||
+ // The following checks are merely defense in depth.
+ origin_url.has_username() || origin_url.has_password() ||
+ (origin_url.has_path() && origin_url.path_piece() != "/") ||
+ origin_url.has_query() || origin_url.has_ref()) {
+ return std::string();
+ }
+
+ // Attempt to parse the path.
+ std::string spec = origin_url.GetWithEmptyPath().spec();
+ spec.pop_back(); // Remove the '/', as ":path" must contain it.
+ spec.append(std::string(path));
+
+ // Attempt to parse the full URL, with the path as well. Ensure there is no
+ // fragment to the query.
+ GURL full_url(spec);
+ if (!full_url.is_valid() || full_url.has_ref()) {
+ return std::string();
+ }
+
+ return full_url.spec();
+}
+
+} // namespace quic
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h
new file mode 100644
index 00000000000..dc3fabca33b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
+#define QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QUIC_EXPORT_PRIVATE SpdyUtils {
+ public:
+ SpdyUtils() = delete;
+
+ // Populate |content length| with the value of the content-length header.
+ // Returns true on success, false if parsing fails or content-length header is
+ // missing.
+ static bool ExtractContentLengthFromHeaders(int64_t* content_length,
+ spdy::SpdyHeaderBlock* headers);
+
+ // Copies a list of headers to a SpdyHeaderBlock.
+ static bool CopyAndValidateHeaders(const QuicHeaderList& header_list,
+ int64_t* content_length,
+ spdy::SpdyHeaderBlock* headers);
+
+ // Copies a list of headers to a SpdyHeaderBlock.
+ // If |expect_final_byte_offset| is true, requires exactly one header field
+ // with key kFinalOffsetHeaderKey and an integer value.
+ // If |expect_final_byte_offset| is false, no kFinalOffsetHeaderKey may be
+ // present.
+ // Returns true if parsing is successful. Returns false if the presence of
+ // kFinalOffsetHeaderKey does not match the value of
+ // |expect_final_byte_offset|, the kFinalOffsetHeaderKey value cannot be
+ // parsed, any other pseudo-header is present, an empty header key is present,
+ // or a header key contains an uppercase character.
+ static bool CopyAndValidateTrailers(const QuicHeaderList& header_list,
+ bool expect_final_byte_offset,
+ size_t* final_byte_offset,
+ spdy::SpdyHeaderBlock* trailers);
+
+ // Returns a canonicalized URL composed from the :scheme, :authority, and
+ // :path headers of a PUSH_PROMISE. Returns empty string if the headers do not
+ // conform to HTTP/2 spec or if the ":method" header contains a forbidden
+ // method for PUSH_PROMISE.
+ static std::string GetPromisedUrlFromHeaders(
+ const spdy::SpdyHeaderBlock& headers);
+
+ // Returns hostname, or empty string if missing.
+ static std::string GetPromisedHostNameFromHeaders(
+ const spdy::SpdyHeaderBlock& headers);
+
+ // Returns true if result of |GetPromisedUrlFromHeaders()| is non-empty
+ // and is a well-formed URL.
+ static bool PromisedUrlIsValid(const spdy::SpdyHeaderBlock& headers);
+
+ // Populates the fields of |headers| to make a GET request of |url|,
+ // which must be fully-qualified.
+ static bool PopulateHeaderBlockFromUrl(const std::string url,
+ spdy::SpdyHeaderBlock* headers);
+
+ // Returns a canonical, valid URL for a PUSH_PROMISE with the specified
+ // ":scheme", ":authority", and ":path" header fields, or an empty
+ // string if the resulting URL is not valid or supported.
+ static std::string GetPushPromiseUrl(QuicStringPiece scheme,
+ QuicStringPiece authority,
+ QuicStringPiece path);
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc
new file mode 100644
index 00000000000..d5c96fb9375
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc
@@ -0,0 +1,557 @@
+// 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 <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.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/platform/api/quic_text_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::Pair;
+using testing::UnorderedElementsAre;
+
+namespace quic {
+namespace test {
+namespace {
+
+const bool kExpectFinalByteOffset = true;
+const bool kDoNotExpectFinalByteOffset = false;
+
+static std::unique_ptr<QuicHeaderList> FromList(
+ const QuicHeaderList::ListType& src) {
+ std::unique_ptr<QuicHeaderList> headers(new QuicHeaderList);
+ headers->OnHeaderBlockStart();
+ for (const auto& p : src) {
+ headers->OnHeader(p.first, p.second);
+ }
+ headers->OnHeaderBlockEnd(0, 0);
+ return headers;
+}
+
+} // anonymous namespace
+
+using CopyAndValidateHeaders = QuicTest;
+
+TEST_F(CopyAndValidateHeaders, NormalUsage) {
+ auto headers = FromList({// All cookie crumbs are joined.
+ {"cookie", " part 1"},
+ {"cookie", "part 2 "},
+ {"cookie", "part3"},
+
+ // Already-delimited headers are passed through.
+ {"passed-through", std::string("foo\0baz", 7)},
+
+ // Other headers are joined on \0.
+ {"joined", "value 1"},
+ {"joined", "value 2"},
+
+ // Empty headers remain empty.
+ {"empty", ""},
+
+ // Joined empty headers work as expected.
+ {"empty-joined", ""},
+ {"empty-joined", "foo"},
+ {"empty-joined", ""},
+ {"empty-joined", ""},
+
+ // Non-continguous cookie crumb.
+ {"cookie", " fin!"}});
+
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block,
+ UnorderedElementsAre(
+ Pair("cookie", " part 1; part 2 ; part3; fin!"),
+ Pair("passed-through", QuicStringPiece("foo\0baz", 7)),
+ Pair("joined", QuicStringPiece("value 1\0value 2", 15)),
+ Pair("empty", ""),
+ Pair("empty-joined", QuicStringPiece("\0foo\0\0", 6))));
+ EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, EmptyName) {
+ auto headers = FromList({{"foo", "foovalue"}, {"", "barvalue"}, {"baz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_FALSE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, UpperCaseName) {
+ auto headers =
+ FromList({{"foo", "foovalue"}, {"bar", "barvalue"}, {"bAz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_FALSE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleContentLengths) {
+ auto headers = FromList({{"content-length", "9"},
+ {"foo", "foovalue"},
+ {"content-length", "9"},
+ {"bar", "barvalue"},
+ {"baz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(
+ Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+ Pair("content-length", QuicStringPiece("9\09", 3)),
+ Pair("baz", "")));
+ EXPECT_EQ(9, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, InconsistentContentLengths) {
+ auto headers = FromList({{"content-length", "9"},
+ {"foo", "foovalue"},
+ {"content-length", "8"},
+ {"bar", "barvalue"},
+ {"baz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_FALSE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, LargeContentLength) {
+ auto headers = FromList({{"content-length", "9000000000"},
+ {"foo", "foovalue"},
+ {"bar", "barvalue"},
+ {"baz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(
+ Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+ Pair("content-length", QuicStringPiece("9000000000")),
+ Pair("baz", "")));
+ EXPECT_EQ(9000000000, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleValues) {
+ auto headers = FromList({{"foo", "foovalue"},
+ {"bar", "barvalue"},
+ {"baz", ""},
+ {"foo", "boo"},
+ {"baz", "buzz"}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(
+ Pair("foo", QuicStringPiece("foovalue\0boo", 12)),
+ Pair("bar", "barvalue"),
+ Pair("baz", QuicStringPiece("\0buzz", 5))));
+ EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MoreThanTwoValues) {
+ auto headers = FromList({{"set-cookie", "value1"},
+ {"set-cookie", "value2"},
+ {"set-cookie", "value3"}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(
+ block, UnorderedElementsAre(Pair(
+ "set-cookie", QuicStringPiece("value1\0value2\0value3", 20))));
+ EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, Cookie) {
+ auto headers = FromList({{"foo", "foovalue"},
+ {"bar", "barvalue"},
+ {"cookie", "value1"},
+ {"baz", ""}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(
+ Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+ Pair("cookie", "value1"), Pair("baz", "")));
+ EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleCookies) {
+ auto headers = FromList({{"foo", "foovalue"},
+ {"bar", "barvalue"},
+ {"cookie", "value1"},
+ {"baz", ""},
+ {"cookie", "value2"}});
+ int64_t content_length = -1;
+ SpdyHeaderBlock block;
+ ASSERT_TRUE(
+ SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(
+ Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+ Pair("cookie", "value1; value2"), Pair("baz", "")));
+ EXPECT_EQ(-1, content_length);
+}
+
+using CopyAndValidateTrailers = QuicTest;
+
+TEST_F(CopyAndValidateTrailers, SimplestValidList) {
+ // Verify that the simplest trailers are valid: just a final byte offset that
+ // gets parsed successfully.
+ auto trailers = FromList({{kFinalOffsetHeaderKey, "1234"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+ EXPECT_EQ(1234u, final_byte_offset);
+}
+
+TEST_F(CopyAndValidateTrailers, EmptyTrailerListWithFinalByteOffsetExpected) {
+ // An empty trailer list will fail as expected key kFinalOffsetHeaderKey is
+ // not present.
+ QuicHeaderList trailers;
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
+ trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers,
+ EmptyTrailerListWithFinalByteOffsetNotExpected) {
+ // An empty trailer list will pass successfully if kFinalOffsetHeaderKey is
+ // not expected.
+ QuicHeaderList trailers;
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
+ trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
+ EXPECT_TRUE(block.empty());
+}
+
+TEST_F(CopyAndValidateTrailers, FinalByteOffsetExpectedButNotPresent) {
+ // Validation fails if expected kFinalOffsetHeaderKey is not present, even if
+ // the rest of the header block is valid.
+ auto trailers = FromList({{"key", "value"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers, FinalByteOffsetNotExpectedButPresent) {
+ // Validation fails if kFinalOffsetHeaderKey is present but should not be,
+ // even if the rest of the header block is valid.
+ auto trailers = FromList({{"key", "value"}, {kFinalOffsetHeaderKey, "1234"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers, FinalByteOffsetNotExpectedAndNotPresent) {
+ // Validation succeeds if kFinalOffsetHeaderKey is not expected and not
+ // present.
+ auto trailers = FromList({{"key", "value"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
+ EXPECT_THAT(block, UnorderedElementsAre(Pair("key", "value")));
+}
+
+TEST_F(CopyAndValidateTrailers, EmptyName) {
+ // Trailer validation will fail with an empty header key, in an otherwise
+ // valid block of trailers.
+ auto trailers = FromList({{"", "value"}, {kFinalOffsetHeaderKey, "1234"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers, PseudoHeaderInTrailers) {
+ // Pseudo headers are illegal in trailers.
+ auto trailers =
+ FromList({{":pseudo_key", "value"}, {kFinalOffsetHeaderKey, "1234"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers, DuplicateTrailers) {
+ // Duplicate trailers are allowed, and their values are concatenated into a
+ // single string delimted with '\0'. Some of the duplicate headers
+ // deliberately have an empty value.
+ auto trailers = FromList({{"key", "value0"},
+ {"key", "value1"},
+ {"key", ""},
+ {"key", ""},
+ {"key", "value2"},
+ {"key", ""},
+ {kFinalOffsetHeaderKey, "1234"},
+ {"other_key", "value"},
+ {"key", "non_contiguous_duplicate"}});
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
+ *trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
+ EXPECT_THAT(
+ block,
+ UnorderedElementsAre(
+ Pair("key",
+ QuicStringPiece(
+ "value0\0value1\0\0\0value2\0\0non_contiguous_duplicate",
+ 48)),
+ Pair("other_key", "value")));
+}
+
+TEST_F(CopyAndValidateTrailers, DuplicateCookies) {
+ // Duplicate cookie headers in trailers should be concatenated into a single
+ // "; " delimted string.
+ auto headers = FromList({{"cookie", " part 1"},
+ {"cookie", "part 2 "},
+ {"cookie", "part3"},
+ {"key", "value"},
+ {kFinalOffsetHeaderKey, "1234"},
+ {"cookie", " non_contiguous_cookie!"}});
+
+ size_t final_byte_offset = 0;
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
+ *headers, kExpectFinalByteOffset, &final_byte_offset, &block));
+ EXPECT_THAT(
+ block,
+ UnorderedElementsAre(
+ Pair("cookie", " part 1; part 2 ; part3; non_contiguous_cookie!"),
+ Pair("key", "value")));
+}
+
+using GetPromisedUrlFromHeaders = QuicTest;
+
+TEST_F(GetPromisedUrlFromHeaders, Basic) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = "GET";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":scheme"] = "https";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":authority"] = "www.google.com";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":path"] = "/index.html";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers),
+ "https://www.google.com/index.html");
+ headers["key1"] = "value1";
+ headers["key2"] = "value2";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers),
+ "https://www.google.com/index.html");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, Connect) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = "CONNECT";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":authority"] = "www.google.com";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":scheme"] = "https";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+ headers[":path"] = "https";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, InvalidUserinfo) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = "GET";
+ headers[":authority"] = "user@www.google.com";
+ headers[":scheme"] = "https";
+ headers[":path"] = "/";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, InvalidPath) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = "GET";
+ headers[":authority"] = "www.google.com";
+ headers[":scheme"] = "https";
+ headers[":path"] = "";
+ EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+using GetPromisedHostNameFromHeaders = QuicTest;
+
+TEST_F(GetPromisedHostNameFromHeaders, NormalUsage) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = "GET";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+ headers[":scheme"] = "https";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+ headers[":authority"] = "www.google.com";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+ headers[":path"] = "/index.html";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+ "www.google.com");
+ headers["key1"] = "value1";
+ headers["key2"] = "value2";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+ "www.google.com");
+ headers[":authority"] = "www.google.com:6666";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+ "www.google.com");
+ headers[":authority"] = "192.168.1.1";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "192.168.1.1");
+ headers[":authority"] = "192.168.1.1:6666";
+ EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "192.168.1.1");
+}
+
+using PopulateHeaderBlockFromUrl = QuicTest;
+
+TEST_F(PopulateHeaderBlockFromUrl, NormalUsage) {
+ std::string url = "https://www.google.com/index.html";
+ SpdyHeaderBlock headers;
+ EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
+ EXPECT_EQ("https", headers[":scheme"].as_string());
+ EXPECT_EQ("www.google.com", headers[":authority"].as_string());
+ EXPECT_EQ("/index.html", headers[":path"].as_string());
+}
+
+TEST_F(PopulateHeaderBlockFromUrl, UrlWithNoPath) {
+ std::string url = "https://www.google.com";
+ SpdyHeaderBlock headers;
+ EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
+ EXPECT_EQ("https", headers[":scheme"].as_string());
+ EXPECT_EQ("www.google.com", headers[":authority"].as_string());
+ EXPECT_EQ("/", headers[":path"].as_string());
+}
+
+TEST_F(PopulateHeaderBlockFromUrl, Failure) {
+ SpdyHeaderBlock headers;
+ EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/", &headers));
+ EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/index.html", &headers));
+ EXPECT_FALSE(
+ SpdyUtils::PopulateHeaderBlockFromUrl("www.google.com/", &headers));
+}
+
+using PushPromiseUrlTest = QuicTest;
+
+TEST_F(PushPromiseUrlTest, GetPushPromiseUrl) {
+ // Test rejection of various inputs.
+ EXPECT_EQ("",
+ SpdyUtils::GetPushPromiseUrl("file", "localhost", "/etc/password"));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("file", "",
+ "/C:/Windows/System32/Config/"));
+ EXPECT_EQ("",
+ SpdyUtils::GetPushPromiseUrl("", "https://www.google.com", "/"));
+
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https://www.google.com",
+ "www.google.com", "/"));
+ EXPECT_EQ("",
+ SpdyUtils::GetPushPromiseUrl("https://", "www.google.com", "/"));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "", "/"));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "", "www.google.com/"));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google.com/", "/"));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google.com", ""));
+ EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google", ".com/"));
+
+ // Test acception/rejection of various input combinations.
+ // |input_headers| is an array of pairs. The first value of each pair is a
+ // string that will be used as one of the inputs of GetPushPromiseUrl(). The
+ // second value of each pair is a bitfield where the lowest 3 bits indicate
+ // for which headers that string is valid (in a PUSH_PROMISE). For example,
+ // the string "http" would be valid for both the ":scheme" and ":authority"
+ // headers, so the bitfield paired with it is set to SCHEME | AUTH.
+ const unsigned char SCHEME = (1u << 0);
+ const unsigned char AUTH = (1u << 1);
+ const unsigned char PATH = (1u << 2);
+ const std::pair<const char*, unsigned char> input_headers[] = {
+ {"http", SCHEME | AUTH},
+ {"https", SCHEME | AUTH},
+ {"hTtP", SCHEME | AUTH},
+ {"HTTPS", SCHEME | AUTH},
+ {"www.google.com", AUTH},
+ {"90af90e0", AUTH},
+ {"12foo%20-bar:00001233", AUTH},
+ {"GOO\u200b\u2060\ufeffgoo", AUTH},
+ {"192.168.0.5", AUTH},
+ {"[::ffff:192.168.0.1.]", AUTH},
+ {"http:", AUTH},
+ {"bife l", AUTH},
+ {"/", PATH},
+ {"/foo/bar/baz", PATH},
+ {"/%20-2DVdkj.cie/foe_.iif/", PATH},
+ {"http://", 0},
+ {":443", 0},
+ {":80/eddd", 0},
+ {"google.com:-0", 0},
+ {"google.com:65536", 0},
+ {"http://google.com", 0},
+ {"http://google.com:39", 0},
+ {"//google.com/foo", 0},
+ {".com/", 0},
+ {"http://www.google.com/", 0},
+ {"http://foo:439", 0},
+ {"[::ffff:192.168", 0},
+ {"]/", 0},
+ {"//", 0}};
+ for (size_t i = 0; i < QUIC_ARRAYSIZE(input_headers); ++i) {
+ bool should_accept = (input_headers[i].second & SCHEME);
+ for (size_t j = 0; j < QUIC_ARRAYSIZE(input_headers); ++j) {
+ bool should_accept_2 = should_accept && (input_headers[j].second & AUTH);
+ for (size_t k = 0; k < QUIC_ARRAYSIZE(input_headers); ++k) {
+ // |should_accept_3| indicates whether or not GetPushPromiseUrl() is
+ // expected to accept this input combination.
+ bool should_accept_3 =
+ should_accept_2 && (input_headers[k].second & PATH);
+
+ std::string url = SpdyUtils::GetPushPromiseUrl(input_headers[i].first,
+ input_headers[j].first,
+ input_headers[k].first);
+
+ ::testing::AssertionResult result = ::testing::AssertionSuccess();
+ if (url.empty() == should_accept_3) {
+ result = ::testing::AssertionFailure()
+ << "GetPushPromiseUrl() accepted/rejected the inputs when "
+ "it shouldn't have."
+ << std::endl
+ << " scheme: " << input_headers[i].first << std::endl
+ << " authority: " << input_headers[j].first << std::endl
+ << " path: " << input_headers[k].first << std::endl
+ << "Output: " << url << std::endl;
+ }
+ ASSERT_TRUE(result);
+ }
+ }
+ }
+
+ // Test canonicalization of various valid inputs.
+ EXPECT_EQ("http://www.google.com/",
+ SpdyUtils::GetPushPromiseUrl("http", "www.google.com", "/"));
+ EXPECT_EQ(
+ "https://www.goo-gle.com/fOOo/baRR",
+ SpdyUtils::GetPushPromiseUrl("hTtPs", "wWw.gOo-gLE.cOm", "/fOOo/baRR"));
+ EXPECT_EQ("https://www.goo-gle.com:3278/pAth/To/reSOurce",
+ SpdyUtils::GetPushPromiseUrl("hTtPs", "Www.gOo-Gle.Com:000003278",
+ "/pAth/To/reSOurce"));
+ EXPECT_EQ("https://foo%20bar/foo/bar/baz",
+ SpdyUtils::GetPushPromiseUrl("https", "foo bar", "/foo/bar/baz"));
+ EXPECT_EQ("http://foo.com:70/e/",
+ SpdyUtils::GetPushPromiseUrl("http", "foo.com:0000070", "/e/"));
+ EXPECT_EQ(
+ "http://192.168.0.1:70/e/",
+ SpdyUtils::GetPushPromiseUrl("http", "0300.0250.00.01:0070", "/e/"));
+ EXPECT_EQ("http://192.168.0.1/e/",
+ SpdyUtils::GetPushPromiseUrl("http", "0xC0a80001", "/e/"));
+ EXPECT_EQ("http://[::c0a8:1]/",
+ SpdyUtils::GetPushPromiseUrl("http", "[::192.168.0.1]", "/"));
+ EXPECT_EQ(
+ "https://[::ffff:c0a8:1]/",
+ SpdyUtils::GetPushPromiseUrl("https", "[::ffff:0xC0.0Xa8.0x0.0x1]", "/"));
+}
+
+} // namespace test
+} // namespace quic