diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 11:40:17 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 12:42:11 +0000 |
commit | 5d87695f37678f96492b258bbab36486c59866b4 (patch) | |
tree | be9783bbaf04fb930c4d74ca9c00b5e7954c8bc6 /chromium/net/third_party/quiche/src/quic/core/http | |
parent | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (diff) | |
download | qtwebengine-chromium-5d87695f37678f96492b258bbab36486c59866b4.tar.gz |
BASELINE: Update Chromium to 75.0.3770.56
Change-Id: I86d2007fd27a45d5797eee06f4c9369b8b50ac4f
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche/src/quic/core/http')
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(¤t_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(¤t_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 |