diff options
Diffstat (limited to 'chromium/net/third_party/quiche/src/quic/tools')
43 files changed, 7516 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.cc new file mode 100644 index 00000000000..7900683913d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.cc @@ -0,0 +1,29 @@ +// 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/tools/quic_backend_response.h" + +namespace quic { + +QuicBackendResponse::ServerPushInfo::ServerPushInfo( + QuicUrl request_url, + spdy::SpdyHeaderBlock headers, + spdy::SpdyPriority priority, + std::string body) + : request_url(request_url), + headers(std::move(headers)), + priority(priority), + body(body) {} + +QuicBackendResponse::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other) + : request_url(other.request_url), + headers(other.headers.Clone()), + priority(other.priority), + body(other.body) {} + +QuicBackendResponse::QuicBackendResponse() : response_type_(REGULAR_RESPONSE) {} + +QuicBackendResponse::~QuicBackendResponse() = default; + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.h b/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.h new file mode 100644 index 00000000000..4ef0fdc1bad --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_backend_response.h @@ -0,0 +1,84 @@ +// Copyright 2017 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_TOOLS_QUIC_BACKEND_RESPONSE_H_ +#define QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_ + +#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_url.h" + +namespace quic { + +// Container for HTTP response header/body pairs +// fetched by the QuicSimpleServerBackend +class QuicBackendResponse { + public: + // A ServerPushInfo contains path of the push request and everything needed in + // comprising a response for the push request. + struct ServerPushInfo { + ServerPushInfo(QuicUrl request_url, + spdy::SpdyHeaderBlock headers, + spdy::SpdyPriority priority, + std::string body); + ServerPushInfo(const ServerPushInfo& other); + + QuicUrl request_url; + spdy::SpdyHeaderBlock headers; + spdy::SpdyPriority priority; + std::string body; + }; + + enum SpecialResponseType { + REGULAR_RESPONSE, // Send the headers and body like a server should. + CLOSE_CONNECTION, // Close the connection (sending the close packet). + IGNORE_REQUEST, // Do nothing, expect the client to time out. + BACKEND_ERR_RESPONSE, // There was an error fetching the response from + // the backend, for example as a TCP connection + // error. + INCOMPLETE_RESPONSE, // The server will act as if there is a non-empty + // trailer but it will not be sent, as a result, FIN + // will not be sent too. + STOP_SENDING, // Acts like INCOMPLETE_RESPONSE in that the entire + // response is not sent. After sending what is sent, + // the server will send a STOP_SENDING. + }; + QuicBackendResponse(); + + QuicBackendResponse(const QuicBackendResponse& other) = delete; + QuicBackendResponse& operator=(const QuicBackendResponse& other) = delete; + + ~QuicBackendResponse(); + + SpecialResponseType response_type() const { return response_type_; } + const spdy::SpdyHeaderBlock& headers() const { return headers_; } + const spdy::SpdyHeaderBlock& trailers() const { return trailers_; } + const QuicStringPiece body() const { return QuicStringPiece(body_); } + + void set_response_type(SpecialResponseType response_type) { + response_type_ = response_type; + } + + void set_headers(spdy::SpdyHeaderBlock headers) { + headers_ = std::move(headers); + } + void set_trailers(spdy::SpdyHeaderBlock trailers) { + trailers_ = std::move(trailers); + } + void set_body(QuicStringPiece body) { + body_.assign(body.data(), body.size()); + } + uint16_t stop_sending_code() const { return stop_sending_code_; } + void set_stop_sending_code(uint16_t code) { stop_sending_code_ = code; } + + private: + SpecialResponseType response_type_; + spdy::SpdyHeaderBlock headers_; + spdy::SpdyHeaderBlock trailers_; + std::string body_; + uint16_t stop_sending_code_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client.cc new file mode 100644 index 00000000000..b736c71aef4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client.cc @@ -0,0 +1,99 @@ +// 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/tools/quic_client.h" + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.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_data_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h" +#include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_server_id.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.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/quic/platform/impl/quic_socket_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_client_session.h" + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +namespace quic { + +QuicClient::QuicClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier) + : QuicClient( + server_address, + server_id, + supported_versions, + QuicConfig(), + epoll_server, + QuicWrapUnique(new QuicClientEpollNetworkHelper(epoll_server, this)), + std::move(proof_verifier)) {} + +QuicClient::QuicClient( + QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + QuicEpollServer* epoll_server, + std::unique_ptr<QuicClientEpollNetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier) + : QuicClient(server_address, + server_id, + supported_versions, + QuicConfig(), + epoll_server, + std::move(network_helper), + std::move(proof_verifier)) {} + +QuicClient::QuicClient( + QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicEpollServer* epoll_server, + std::unique_ptr<QuicClientEpollNetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier) + : QuicSpdyClientBase( + server_id, + supported_versions, + config, + new QuicEpollConnectionHelper(epoll_server, QuicAllocator::SIMPLE), + new QuicEpollAlarmFactory(epoll_server), + std::move(network_helper), + std::move(proof_verifier)) { + set_server_address(server_address); +} + +QuicClient::~QuicClient() = default; + +std::unique_ptr<QuicSession> QuicClient::CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) { + return QuicMakeUnique<QuicSimpleClientSession>( + *config(), supported_versions, connection, server_id(), crypto_config(), + push_promise_index(), drop_response_body_); +} + +QuicClientEpollNetworkHelper* QuicClient::epoll_network_helper() { + return static_cast<QuicClientEpollNetworkHelper*>(network_helper()); +} + +const QuicClientEpollNetworkHelper* QuicClient::epoll_network_helper() const { + return static_cast<const QuicClientEpollNetworkHelper*>(network_helper()); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client.h b/chromium/net/third_party/quiche/src/quic/tools/quic_client.h new file mode 100644 index 00000000000..b84c597969a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client.h @@ -0,0 +1,81 @@ +// 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 toy client, which connects to a specified port and sends QUIC +// request to that endpoint. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_ +#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_ + +#include <cstdint> +#include <memory> +#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.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_process_packet_interface.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" +#include "net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h" +#include "net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h" + +namespace quic { + +class QuicServerId; + +namespace test { +class QuicClientPeer; +} // namespace test + +class QuicClient : public QuicSpdyClientBase { + public: + // This will create its own QuicClientEpollNetworkHelper. + QuicClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier); + // This will take ownership of a passed in network primitive. + QuicClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + QuicEpollServer* epoll_server, + std::unique_ptr<QuicClientEpollNetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier); + QuicClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicEpollServer* epoll_server, + std::unique_ptr<QuicClientEpollNetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier); + QuicClient(const QuicClient&) = delete; + QuicClient& operator=(const QuicClient&) = delete; + + ~QuicClient() override; + + std::unique_ptr<QuicSession> CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) override; + + // Exposed for the quic client test. + int GetLatestFD() const { return epoll_network_helper()->GetLatestFD(); } + + QuicClientEpollNetworkHelper* epoll_network_helper(); + const QuicClientEpollNetworkHelper* epoll_network_helper() const; + + void set_drop_response_body(bool drop_response_body) { + drop_response_body_ = drop_response_body; + } + + private: + friend class test::QuicClientPeer; + bool drop_response_body_ = false; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc new file mode 100644 index 00000000000..db0503c5f9b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc @@ -0,0 +1,353 @@ +// 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/tools/quic_client_base.h" + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.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/core/tls_client_handshaker.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_text_utils.h" + +namespace quic { + +QuicClientBase::NetworkHelper::~NetworkHelper() = default; + +QuicClientBase::QuicClientBase( + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, + std::unique_ptr<NetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier) + : server_id_(server_id), + initialized_(false), + local_port_(0), + config_(config), + crypto_config_(std::move(proof_verifier), + TlsClientHandshaker::CreateSslCtx()), + helper_(helper), + alarm_factory_(alarm_factory), + supported_versions_(supported_versions), + initial_max_packet_length_(0), + num_stateless_rejects_received_(0), + num_sent_client_hellos_(0), + connection_error_(QUIC_NO_ERROR), + connected_or_attempting_connect_(false), + network_helper_(std::move(network_helper)) {} + +QuicClientBase::~QuicClientBase() = default; + +bool QuicClientBase::Initialize() { + num_sent_client_hellos_ = 0; + num_stateless_rejects_received_ = 0; + connection_error_ = QUIC_NO_ERROR; + connected_or_attempting_connect_ = false; + + // If an initial flow control window has not explicitly been set, then use the + // same values that Chrome uses. + const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024; // 15 MB + const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024; // 6 MB + if (config()->GetInitialStreamFlowControlWindowToSend() == + kMinimumFlowControlSendWindow) { + config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize); + } + if (config()->GetInitialSessionFlowControlWindowToSend() == + kMinimumFlowControlSendWindow) { + config()->SetInitialSessionFlowControlWindowToSend( + kSessionMaxRecvWindowSize); + } + + if (!network_helper_->CreateUDPSocketAndBind(server_address_, + bind_to_address_, local_port_)) { + return false; + } + + initialized_ = true; + return true; +} + +bool QuicClientBase::Connect() { + // Attempt multiple connects until the maximum number of client hellos have + // been sent. + while (!connected() && + GetNumSentClientHellos() <= QuicCryptoClientStream::kMaxClientHellos) { + StartConnect(); + while (EncryptionBeingEstablished()) { + WaitForEvents(); + } + if (GetQuicReloadableFlag(enable_quic_stateless_reject_support) && + connected()) { + // Resend any previously queued data. + ResendSavedData(); + } + ParsedQuicVersion version = UnsupportedQuicVersion(); + if (session() != nullptr && + session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT && + !CanReconnectWithDifferentVersion(&version)) { + // We've successfully created a session but we're not connected, and there + // is no stateless reject to recover from and cannot try to reconnect with + // different version. Give up trying. + break; + } + } + if (!connected() && + GetNumSentClientHellos() > QuicCryptoClientStream::kMaxClientHellos && + session() != nullptr && + session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { + // The overall connection failed due too many stateless rejects. + set_connection_error(QUIC_CRYPTO_TOO_MANY_REJECTS); + } + return session()->connection()->connected(); +} + +void QuicClientBase::StartConnect() { + DCHECK(initialized_); + DCHECK(!connected()); + QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); + ParsedQuicVersion mutual_version = UnsupportedQuicVersion(); + const bool can_reconnect_with_different_version = + CanReconnectWithDifferentVersion(&mutual_version); + if (connected_or_attempting_connect()) { + // If the last error was not a stateless reject, then the queued up data + // does not need to be resent. + // Keep queued up data if client can try to connect with a different + // version. + if (session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT && + !can_reconnect_with_different_version) { + ClearDataToResend(); + } + // Before we destroy the last session and create a new one, gather its stats + // and update the stats for the overall connection. + UpdateStats(); + } + + session_ = CreateQuicClientSession( + supported_versions(), + new QuicConnection(GetNextConnectionId(), server_address(), helper(), + alarm_factory(), writer, + /* owns_writer= */ false, Perspective::IS_CLIENT, + can_reconnect_with_different_version + ? ParsedQuicVersionVector{mutual_version} + : supported_versions())); + if (initial_max_packet_length_ != 0) { + session()->connection()->SetMaxPacketLength(initial_max_packet_length_); + } + // Reset |writer()| after |session()| so that the old writer outlives the old + // session. + set_writer(writer); + InitializeSession(); + set_connected_or_attempting_connect(true); +} + +void QuicClientBase::InitializeSession() { + session()->Initialize(); +} + +void QuicClientBase::Disconnect() { + DCHECK(initialized_); + + initialized_ = false; + if (connected()) { + session()->connection()->CloseConnection( + QUIC_PEER_GOING_AWAY, "Client disconnecting", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + } + + ClearDataToResend(); + + network_helper_->CleanUpAllUDPSockets(); +} + +ProofVerifier* QuicClientBase::proof_verifier() const { + return crypto_config_.proof_verifier(); +} + +bool QuicClientBase::EncryptionBeingEstablished() { + return !session_->IsEncryptionEstablished() && + session_->connection()->connected(); +} + +bool QuicClientBase::WaitForEvents() { + DCHECK(connected()); + + network_helper_->RunEventLoop(); + + DCHECK(session() != nullptr); + ParsedQuicVersion version = UnsupportedQuicVersion(); + if (!connected() && + (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT || + CanReconnectWithDifferentVersion(&version))) { + if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { + DCHECK(GetQuicReloadableFlag(enable_quic_stateless_reject_support)); + QUIC_DLOG(INFO) << "Detected stateless reject while waiting for events. " + << "Attempting to reconnect."; + } else { + QUIC_DLOG(INFO) << "Can reconnect with version: " << version + << ", attempting to reconnect."; + } + Connect(); + } + + return HasActiveRequests(); +} + +bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) { + return MigrateSocketWithSpecifiedPort(new_host, local_port_); +} + +bool QuicClientBase::MigrateSocketWithSpecifiedPort( + const QuicIpAddress& new_host, + int port) { + if (!connected()) { + return false; + } + + network_helper_->CleanUpAllUDPSockets(); + + set_bind_to_address(new_host); + if (!network_helper_->CreateUDPSocketAndBind(server_address_, + bind_to_address_, port)) { + return false; + } + + session()->connection()->SetSelfAddress( + network_helper_->GetLatestClientAddress()); + + QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); + set_writer(writer); + session()->connection()->SetQuicPacketWriter(writer, false); + + return true; +} + +bool QuicClientBase::ChangeEphemeralPort() { + auto current_host = network_helper_->GetLatestClientAddress().host(); + return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/); +} + +QuicSession* QuicClientBase::session() { + return session_.get(); +} + +QuicClientBase::NetworkHelper* QuicClientBase::network_helper() { + return network_helper_.get(); +} + +const QuicClientBase::NetworkHelper* QuicClientBase::network_helper() const { + return network_helper_.get(); +} + +void QuicClientBase::WaitForStreamToClose(QuicStreamId id) { + DCHECK(connected()); + + while (connected() && !session_->IsClosedStream(id)) { + WaitForEvents(); + } +} + +bool QuicClientBase::WaitForCryptoHandshakeConfirmed() { + DCHECK(connected()); + + while (connected() && !session_->IsCryptoHandshakeConfirmed()) { + WaitForEvents(); + } + + // If the handshake fails due to a timeout, the connection will be closed. + QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed."; + return connected(); +} + +bool QuicClientBase::connected() const { + return session_.get() && session_->connection() && + session_->connection()->connected(); +} + +bool QuicClientBase::goaway_received() const { + return session_ != nullptr && session_->goaway_received(); +} + +int QuicClientBase::GetNumSentClientHellos() { + // If we are not actively attempting to connect, the session object + // corresponds to the previous connection and should not be used. + const int current_session_hellos = !connected_or_attempting_connect_ + ? 0 + : GetNumSentClientHellosFromSession(); + return num_sent_client_hellos_ + current_session_hellos; +} + +void QuicClientBase::UpdateStats() { + num_sent_client_hellos_ += GetNumSentClientHellosFromSession(); + if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { + ++num_stateless_rejects_received_; + } +} + +int QuicClientBase::GetNumReceivedServerConfigUpdates() { + // If we are not actively attempting to connect, the session object + // corresponds to the previous connection and should not be used. + // We do not need to take stateless rejects into account, since we + // don't expect any scup messages to be sent during a + // statelessly-rejected connection. + return !connected_or_attempting_connect_ + ? 0 + : GetNumReceivedServerConfigUpdatesFromSession(); +} + +QuicErrorCode QuicClientBase::connection_error() const { + // Return the high-level error if there was one. Otherwise, return the + // connection error from the last session. + if (connection_error_ != QUIC_NO_ERROR) { + return connection_error_; + } + if (session_ == nullptr) { + return QUIC_NO_ERROR; + } + return session_->error(); +} + +QuicConnectionId QuicClientBase::GetNextConnectionId() { + QuicConnectionId server_designated_id = GetNextServerDesignatedConnectionId(); + return !server_designated_id.IsEmpty() ? server_designated_id + : GenerateNewConnectionId(); +} + +QuicConnectionId QuicClientBase::GetNextServerDesignatedConnectionId() { + QuicCryptoClientConfig::CachedState* cached = + crypto_config_.LookupOrCreate(server_id_); + // If the cached state indicates that we should use a server-designated + // connection ID, then return that connection ID. + CHECK(cached != nullptr) << "QuicClientCryptoConfig::LookupOrCreate returned " + << "unexpected nullptr."; + return cached->has_server_designated_connection_id() + ? cached->GetNextServerDesignatedConnectionId() + : EmptyQuicConnectionId(); +} + +QuicConnectionId QuicClientBase::GenerateNewConnectionId() { + return QuicUtils::CreateRandomConnectionId(); +} + +bool QuicClientBase::CanReconnectWithDifferentVersion( + ParsedQuicVersion* version) const { + if (session_ == nullptr || session_->connection() == nullptr || + session_->error() != QUIC_INVALID_VERSION || + session_->connection()->server_supported_versions().empty()) { + return false; + } + for (const auto& client_version : supported_versions_) { + if (QuicContainsValue(session_->connection()->server_supported_versions(), + client_version)) { + *version = client_version; + return true; + } + } + return false; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h new file mode 100644 index 00000000000..6ad326eb4dc --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h @@ -0,0 +1,364 @@ +// 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. + +// A base class for the toy client, which connects to a specified port and sends +// QUIC request to that endpoint. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_ +#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_ + +#include <string> + +#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h" +#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.h" +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_macros.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" + +namespace quic { + +class ProofVerifier; +class QuicServerId; + +// QuicClientBase handles establishing a connection to the passed in +// server id, including ensuring that it supports the passed in versions +// and config. +// Subclasses derived from this class are responsible for creating the +// actual QuicSession instance, as well as defining functions that +// create and run the underlying network transport. +class QuicClientBase { + public: + // An interface to various network events that the QuicClient will need to + // interact with. + class NetworkHelper { + public: + virtual ~NetworkHelper(); + + // Runs one iteration of the event loop. + virtual void RunEventLoop() = 0; + + // Used during initialization: creates the UDP socket FD, sets socket + // options, and binds the socket to our address. + virtual bool CreateUDPSocketAndBind(QuicSocketAddress server_address, + QuicIpAddress bind_to_address, + int bind_to_port) = 0; + + // Unregister and close all open UDP sockets. + virtual void CleanUpAllUDPSockets() = 0; + + // If the client has at least one UDP socket, return address of the latest + // created one. Otherwise, return an empty socket address. + virtual QuicSocketAddress GetLatestClientAddress() const = 0; + + // Creates a packet writer to be used for the next connection. + virtual QuicPacketWriter* CreateQuicPacketWriter() = 0; + }; + + QuicClientBase(const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, + std::unique_ptr<NetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier); + QuicClientBase(const QuicClientBase&) = delete; + QuicClientBase& operator=(const QuicClientBase&) = delete; + + virtual ~QuicClientBase(); + + // Initializes the client to create a connection. Should be called exactly + // once before calling StartConnect or Connect. Returns true if the + // initialization succeeds, false otherwise. + virtual bool Initialize(); + + // "Connect" to the QUIC server, including performing synchronous crypto + // handshake. + bool Connect(); + + // Start the crypto handshake. This can be done in place of the synchronous + // Connect(), but callers are responsible for making sure the crypto handshake + // completes. + void StartConnect(); + + // Calls session()->Initialize(). Subclasses may override this if any extra + // initialization needs to be done. Subclasses should expect that session() + // is non-null and valid. + virtual void InitializeSession(); + + // Disconnects from the QUIC server. + void Disconnect(); + + // Returns true if the crypto handshake has yet to establish encryption. + // Returns false if encryption is active (even if the server hasn't confirmed + // the handshake) or if the connection has been closed. + bool EncryptionBeingEstablished(); + + // Wait for events until the stream with the given ID is closed. + void WaitForStreamToClose(QuicStreamId id); + + // Wait for events until the handshake is confirmed. + // Returns true if the crypto handshake succeeds, false otherwise. + bool WaitForCryptoHandshakeConfirmed() QUIC_MUST_USE_RESULT; + + // Wait up to 50ms, and handle any events which occur. + // Returns true if there are any outstanding requests. + bool WaitForEvents(); + + // Migrate to a new socket (new_host) during an active connection. + bool MigrateSocket(const QuicIpAddress& new_host); + + // Migrate to a new socket (new_host, port) during an active connection. + bool MigrateSocketWithSpecifiedPort(const QuicIpAddress& new_host, int port); + + // Open a new socket to change to a new ephemeral port. + bool ChangeEphemeralPort(); + + QuicSession* session(); + + bool connected() const; + bool goaway_received() const; + + const QuicServerId& server_id() const { return server_id_; } + + // This should only be set before the initial Connect() + void set_server_id(const QuicServerId& server_id) { server_id_ = server_id; } + + void SetUserAgentID(const std::string& user_agent_id) { + crypto_config_.set_user_agent_id(user_agent_id); + } + + // SetChannelIDSource sets a ChannelIDSource that will be called, when the + // server supports channel IDs, to obtain a channel ID for signing a message + // proving possession of the channel ID. + void SetChannelIDSource(std::unique_ptr<ChannelIDSource> source) { + crypto_config_.SetChannelIDSource(std::move(source)); + } + + const ParsedQuicVersionVector& supported_versions() const { + return supported_versions_; + } + + void SetSupportedVersions(const ParsedQuicVersionVector& versions) { + supported_versions_ = versions; + } + + QuicConfig* config() { return &config_; } + + QuicCryptoClientConfig* crypto_config() { return &crypto_config_; } + + // Change the initial maximum packet size of the connection. Has to be called + // before Connect()/StartConnect() in order to have any effect. + void set_initial_max_packet_length(QuicByteCount initial_max_packet_length) { + initial_max_packet_length_ = initial_max_packet_length; + } + + int num_stateless_rejects_received() const { + return num_stateless_rejects_received_; + } + + // The number of client hellos sent, taking stateless rejects into + // account. In the case of a stateless reject, the initial + // connection object may be torn down and a new one created. The + // user cannot rely upon the latest connection object to get the + // total number of client hellos sent, and should use this function + // instead. + int GetNumSentClientHellos(); + + // Gather the stats for the last session and update the stats for the overall + // connection. + void UpdateStats(); + + // The number of server config updates received. We assume no + // updates can be sent during a previously, statelessly rejected + // connection, so only the latest session is taken into account. + int GetNumReceivedServerConfigUpdates(); + + // Returns any errors that occurred at the connection-level (as + // opposed to the session-level). When a stateless reject occurs, + // the error of the last session may not reflect the overall state + // of the connection. + QuicErrorCode connection_error() const; + void set_connection_error(QuicErrorCode connection_error) { + connection_error_ = connection_error; + } + + bool connected_or_attempting_connect() const { + return connected_or_attempting_connect_; + } + void set_connected_or_attempting_connect( + bool connected_or_attempting_connect) { + connected_or_attempting_connect_ = connected_or_attempting_connect; + } + + QuicPacketWriter* writer() { return writer_.get(); } + void set_writer(QuicPacketWriter* writer) { + if (writer_.get() != writer) { + writer_.reset(writer); + } + } + + void reset_writer() { writer_.reset(); } + + ProofVerifier* proof_verifier() const; + + void set_bind_to_address(QuicIpAddress address) { + bind_to_address_ = address; + } + + QuicIpAddress bind_to_address() const { return bind_to_address_; } + + void set_local_port(int local_port) { local_port_ = local_port; } + + int local_port() const { return local_port_; } + + const QuicSocketAddress& server_address() const { return server_address_; } + + void set_server_address(const QuicSocketAddress& server_address) { + server_address_ = server_address; + } + + QuicConnectionHelperInterface* helper() { return helper_.get(); } + + NetworkHelper* network_helper(); + const NetworkHelper* network_helper() const; + + bool initialized() const { return initialized_; } + + void SetPreSharedKey(QuicStringPiece key) { + crypto_config_.set_pre_shared_key(key); + } + + protected: + // TODO(rch): Move GetNumSentClientHellosFromSession and + // GetNumReceivedServerConfigUpdatesFromSession into a new/better + // QuicSpdyClientSession class. The current inherits dependencies from + // Spdy. When that happens this class and all its subclasses should + // work with QuicSpdyClientSession instead of QuicSession. + // That will obviate the need for the pure virtual functions below. + + // Extract the number of sent client hellos from the session. + virtual int GetNumSentClientHellosFromSession() = 0; + + // The number of server config updates received. We assume no + // updates can be sent during a previously, statelessly rejected + // connection, so only the latest session is taken into account. + virtual int GetNumReceivedServerConfigUpdatesFromSession() = 0; + + // If this client supports buffering data, resend it. + virtual void ResendSavedData() = 0; + + // If this client supports buffering data, clear it. + virtual void ClearDataToResend() = 0; + + // Takes ownership of |connection|. If you override this function, + // you probably want to call ResetSession() in your destructor. + // TODO(rch): Change the connection parameter to take in a + // std::unique_ptr<QuicConnection> instead. + virtual std::unique_ptr<QuicSession> CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) = 0; + + // Generates the next ConnectionId for |server_id_|. By default, if the + // cached server config contains a server-designated ID, that ID will be + // returned. Otherwise, the next random ID will be returned. + QuicConnectionId GetNextConnectionId(); + + // Returns the next server-designated ConnectionId from the cached config for + // |server_id_|, if it exists. Otherwise, returns 0. + QuicConnectionId GetNextServerDesignatedConnectionId(); + + // Generates a new, random connection ID (as opposed to a server-designated + // connection ID). + virtual QuicConnectionId GenerateNewConnectionId(); + + QuicAlarmFactory* alarm_factory() { return alarm_factory_.get(); } + + // Subclasses may need to explicitly clear the session on destruction + // if they create it with objects that will be destroyed before this is. + // You probably want to call this if you override CreateQuicSpdyClientSession. + void ResetSession() { session_.reset(); } + + // Returns true if the corresponding of this client has active requests. + virtual bool HasActiveRequests() = 0; + + private: + // Returns true and set |version| if client can reconnect with a different + // version. + bool CanReconnectWithDifferentVersion(ParsedQuicVersion* version) const; + + // |server_id_| is a tuple (hostname, port, is_https) of the server. + QuicServerId server_id_; + + // Tracks if the client is initialized to connect. + bool initialized_; + + // Address of the server. + QuicSocketAddress server_address_; + + // If initialized, the address to bind to. + QuicIpAddress bind_to_address_; + + // Local port to bind to. Initialize to 0. + int local_port_; + + // config_ and crypto_config_ contain configuration and cached state about + // servers. + QuicConfig config_; + QuicCryptoClientConfig crypto_config_; + + // Helper to be used by created connections. Must outlive |session_|. + std::unique_ptr<QuicConnectionHelperInterface> helper_; + + // Alarm factory to be used by created connections. Must outlive |session_|. + std::unique_ptr<QuicAlarmFactory> alarm_factory_; + + // Writer used to actually send packets to the wire. Must outlive |session_|. + std::unique_ptr<QuicPacketWriter> writer_; + + // Session which manages streams. + std::unique_ptr<QuicSession> session_; + + // This vector contains QUIC versions which we currently support. + // This should be ordered such that the highest supported version is the first + // element, with subsequent elements in descending order (versions can be + // skipped as necessary). We will always pick supported_versions_[0] as the + // initial version to use. + ParsedQuicVersionVector supported_versions_; + + // The initial value of maximum packet size of the connection. If set to + // zero, the default is used. + QuicByteCount initial_max_packet_length_; + + // The number of stateless rejects received during the current/latest + // connection. + // TODO(jokulik): Consider some consistent naming scheme (or other) for member + // variables that are kept per-request, per-connection, and over the client's + // lifetime. + int num_stateless_rejects_received_; + + // The number of hellos sent during the current/latest connection. + int num_sent_client_hellos_; + + // Used to store any errors that occurred with the overall connection (as + // opposed to that associated with the last session object). + QuicErrorCode connection_error_; + + // True when the client is attempting to connect or re-connect the session (in + // the case of a stateless reject). Set to false between a call to + // Disconnect() and the subsequent call to StartConnect(). When + // connected_or_attempting_connect_ is false, the session object corresponds + // to the previous client-level connection. + bool connected_or_attempting_connect_; + + // The network helper used to create sockets and manage the event loop. + // Not owned by this class. + std::unique_ptr<NetworkHelper> network_helper_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc new file mode 100644 index 00000000000..ee8262f751f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc @@ -0,0 +1,403 @@ +// 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 binary wrapper for QuicClient. +// Connects to a host using QUIC, sends a request to the provided URL, and +// displays the response. +// +// Some usage examples: +// +// Standard request/response: +// quic_client www.google.com +// quic_client www.google.com --quiet +// quic_client www.google.com --port=443 +// +// Use a specific version: +// quic_client www.google.com --quic_version=23 +// +// Send a POST instead of a GET: +// quic_client www.google.com --body="this is a POST body" +// +// Append additional headers to the request: +// quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678" +// +// Connect to a host different to the URL being requested: +// quic_client mail.google.com --host=www.google.com +// +// Connect to a specific IP: +// IP=`dig www.google.com +short | head -1` +// quic_client www.google.com --host=${IP} +// +// Send repeated requests and change ephemeral port between requests +// quic_client www.google.com --num_requests=10 +// +// Try to connect to a host which does not speak QUIC: +// quic_client www.example.com + +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include "net/third_party/quiche/src/quic/core/quic_packets.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/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_default_proof_providers.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_string_piece.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_client.h" +#include "net/third_party/quiche/src/quic/tools/quic_url.h" + +namespace { + +using quic::QuicSocketAddress; +using quic::QuicStringPiece; +using quic::QuicTextUtils; +using quic::QuicUrl; + +class FakeProofVerifier : public quic::ProofVerifier { + public: + ~FakeProofVerifier() override {} + quic::QuicAsyncStatus VerifyProof( + const std::string& /*hostname*/, + const uint16_t /*port*/, + const std::string& /*server_config*/, + quic::QuicTransportVersion /*quic_version*/, + quic::QuicStringPiece /*chlo_hash*/, + const std::vector<std::string>& /*certs*/, + const std::string& /*cert_sct*/, + const std::string& /*signature*/, + const quic::ProofVerifyContext* /*context*/, + std::string* /*error_details*/, + std::unique_ptr<quic::ProofVerifyDetails>* /*details*/, + std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override { + return quic::QUIC_SUCCESS; + } + quic::QuicAsyncStatus VerifyCertChain( + const std::string& /*hostname*/, + const std::vector<std::string>& /*certs*/, + const quic::ProofVerifyContext* /*context*/, + std::string* /*error_details*/, + std::unique_ptr<quic::ProofVerifyDetails>* /*details*/, + std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override { + return quic::QUIC_SUCCESS; + } + std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override { + return nullptr; + } +}; + +QuicSocketAddress LookupAddress(std::string host, std::string port) { + addrinfo hint; + memset(&hint, 0, sizeof(hint)); + hint.ai_protocol = IPPROTO_UDP; + + addrinfo* info_list = nullptr; + int result = getaddrinfo(host.c_str(), port.c_str(), &hint, &info_list); + if (result != 0) { + QUIC_LOG(ERROR) << "Failed to look up " << host << ": " + << gai_strerror(result); + return QuicSocketAddress(); + } + + CHECK(info_list != nullptr); + std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list, + freeaddrinfo); + return QuicSocketAddress(*info_list->ai_addr); +} + +} // namespace + +DEFINE_QUIC_COMMAND_LINE_FLAG( + std::string, + host, + "", + "The IP or hostname to connect to. If not provided, the host " + "will be derived from the provided URL."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, + body, + "", + "If set, send a POST with this body."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + std::string, + body_hex, + "", + "If set, contents are converted from hex to ascii, before " + "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\""); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + std::string, + headers, + "", + "A semicolon separated list of key:value pairs to " + "add to request headers."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(bool, + quiet, + false, + "Set to true for a quieter output experience."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + std::string, + quic_version, + "", + "QUIC version to speak, e.g. 21. If not set, then all available " + "versions are offered in the handshake. Also supports wire versions " + "such as Q043 or T099."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + int32_t, + quic_ietf_draft, + 0, + "QUIC IETF draft number to use over the wire, e.g. 18. " + "By default this sets quic_version to T099. " + "This also enables required internal QUIC flags."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + bool, + version_mismatch_ok, + false, + "If true, a version mismatch in the handshake is not considered a " + "failure. Useful for probing a server to determine if it speaks " + "any version of QUIC."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + bool, + redirect_is_success, + true, + "If true, an HTTP response code of 3xx is considered to be a " + "successful response, otherwise a failure."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, + initial_mtu, + 0, + "Initial MTU of the connection."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + int32_t, + num_requests, + 1, + "How many sequential requests to make on a single connection."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(bool, + disable_certificate_verification, + false, + "If true, don't verify the server certificate."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + bool, + drop_response_body, + false, + "If true, drop response body immediately after it is received."); + +int main(int argc, char* argv[]) { + QuicSystemEventLoop event_loop("quic_client"); + const char* usage = "Usage: quic_client [options] <url>"; + + // All non-flag arguments should be interpreted as URLs to fetch. + std::vector<std::string> urls = + quic::QuicParseCommandLineFlags(usage, argc, argv); + if (urls.size() != 1) { + quic::QuicPrintCommandLineFlagHelp(usage); + exit(0); + } + + QuicUrl url(urls[0], "https"); + std::string host = GetQuicFlag(FLAGS_host); + if (host.empty()) { + host = url.host(); + } + int port = GetQuicFlag(FLAGS_port); + if (port == 0) { + port = url.port(); + } + + // Determine IP address to connect to from supplied hostname. + QuicSocketAddress addr = LookupAddress(host, quic::QuicStrCat(port)); + if (!addr.IsInitialized()) { + return 1; + } + std::cerr << "Resolved " << url.ToString() << " to " << addr.ToString() + << std::endl; + + // Build the client, and try to connect. + quic::QuicEpollServer epoll_server; + quic::QuicServerId server_id(url.host(), port, false); + quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions(); + + std::string quic_version_string = GetQuicFlag(FLAGS_quic_version); + const int32_t quic_ietf_draft = GetQuicFlag(FLAGS_quic_ietf_draft); + if (quic_ietf_draft > 0) { + quic::QuicVersionInitializeSupportForIetfDraft(quic_ietf_draft); + if (quic_version_string.length() == 0) { + quic_version_string = "T099"; + } + } + if (quic_version_string.length() > 0) { + if (quic_version_string[0] == 'T') { + // ParseQuicVersionString checks quic_supports_tls_handshake. + SetQuicFlag(&FLAGS_quic_supports_tls_handshake, true); + } + quic::ParsedQuicVersion parsed_quic_version = + quic::ParseQuicVersionString(quic_version_string); + if (parsed_quic_version.transport_version == + quic::QUIC_VERSION_UNSUPPORTED) { + return 1; + } + versions.clear(); + versions.push_back(parsed_quic_version); + quic::QuicEnableVersion(parsed_quic_version); + } + + const int32_t num_requests(GetQuicFlag(FLAGS_num_requests)); + std::unique_ptr<quic::ProofVerifier> proof_verifier; + if (GetQuicFlag(FLAGS_disable_certificate_verification)) { + proof_verifier = quic::QuicMakeUnique<FakeProofVerifier>(); + } else { + proof_verifier = quic::CreateDefaultProofVerifier(); + } + quic::QuicClient client(addr, server_id, versions, &epoll_server, + std::move(proof_verifier)); + int32_t initial_mtu = GetQuicFlag(FLAGS_initial_mtu); + client.set_initial_max_packet_length( + initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize); + client.set_drop_response_body(GetQuicFlag(FLAGS_drop_response_body)); + if (!client.Initialize()) { + std::cerr << "Failed to initialize client." << std::endl; + return 1; + } + if (!client.Connect()) { + quic::QuicErrorCode error = client.session()->error(); + if (error == quic::QUIC_INVALID_VERSION) { + std::cerr << "Server talks QUIC, but none of the versions supported by " + << "this client: " << ParsedQuicVersionVectorToString(versions) + << std::endl; + // 0: No error. + // 20: Failed to connect due to QUIC_INVALID_VERSION. + return GetQuicFlag(FLAGS_version_mismatch_ok) ? 0 : 20; + } + std::cerr << "Failed to connect to " << addr.ToString() + << ". Error: " << quic::QuicErrorCodeToString(error) << std::endl; + return 1; + } + std::cerr << "Connected to " << addr.ToString() << std::endl; + + // Construct the string body from flags, if provided. + std::string body = GetQuicFlag(FLAGS_body); + if (!GetQuicFlag(FLAGS_body_hex).empty()) { + DCHECK(GetQuicFlag(FLAGS_body).empty()) + << "Only set one of --body and --body_hex."; + body = QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex)); + } + + // Construct a GET or POST request for supplied URL. + spdy::SpdyHeaderBlock header_block; + header_block[":method"] = body.empty() ? "GET" : "POST"; + header_block[":scheme"] = url.scheme(); + header_block[":authority"] = url.HostPort(); + header_block[":path"] = url.PathParamsQuery(); + + // Append any additional headers supplied on the command line. + for (QuicStringPiece sp : + QuicTextUtils::Split(GetQuicFlag(FLAGS_headers), ';')) { + QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp); + if (sp.empty()) { + continue; + } + std::vector<QuicStringPiece> kv = QuicTextUtils::Split(sp, ':'); + QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]); + QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]); + header_block[kv[0]] = kv[1]; + } + + // Make sure to store the response, for later output. + client.set_store_response(true); + + for (int i = 0; i < num_requests; ++i) { + // Send the request. + client.SendRequestAndWaitForResponse(header_block, body, /*fin=*/true); + + // Print request and response details. + if (!GetQuicFlag(FLAGS_quiet)) { + std::cout << "Request:" << std::endl; + std::cout << "headers:" << header_block.DebugString(); + if (!GetQuicFlag(FLAGS_body_hex).empty()) { + // Print the user provided hex, rather than binary body. + std::cout << "body:\n" + << QuicTextUtils::HexDump( + QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex))) + << std::endl; + } else { + std::cout << "body: " << body << std::endl; + } + std::cout << std::endl; + + if (!client.preliminary_response_headers().empty()) { + std::cout << "Preliminary response headers: " + << client.preliminary_response_headers() << std::endl; + std::cout << std::endl; + } + + std::cout << "Response:" << std::endl; + std::cout << "headers: " << client.latest_response_headers() << std::endl; + std::string response_body = client.latest_response_body(); + if (!GetQuicFlag(FLAGS_body_hex).empty()) { + // Assume response is binary data. + std::cout << "body:\n" + << QuicTextUtils::HexDump(response_body) << std::endl; + } else { + std::cout << "body: " << response_body << std::endl; + } + std::cout << "trailers: " << client.latest_response_trailers() + << std::endl; + } + + if (!client.connected()) { + std::cerr << "Request caused connection failure. Error: " + << quic::QuicErrorCodeToString(client.session()->error()) + << std::endl; + return 1; + } + + size_t response_code = client.latest_response_code(); + if (response_code >= 200 && response_code < 300) { + std::cerr << "Request succeeded (" << response_code << ")." << std::endl; + } else if (response_code >= 300 && response_code < 400) { + if (GetQuicFlag(FLAGS_redirect_is_success)) { + std::cerr << "Request succeeded (redirect " << response_code << ")." + << std::endl; + } else { + std::cerr << "Request failed (redirect " << response_code << ")." + << std::endl; + return 1; + } + } else { + std::cerr << "Request failed (" << response_code << ")." << std::endl; + return 1; + } + + // Change the ephemeral port if there are more requests to do. + if (i + 1 < num_requests) { + if (!client.ChangeEphemeralPort()) { + std::cerr << "Failed to change ephemeral port." << std::endl; + return 1; + } + } + } + + return 0; +} diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.cc new file mode 100644 index 00000000000..e545ee2c62c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.cc @@ -0,0 +1,206 @@ +// 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/tools/quic_client_epoll_network_helper.h" + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.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_data_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h" +#include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_server_id.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.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_system_event_loop.h" +#include "net/quic/platform/impl/quic_socket_utils.h" + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +namespace quic { + +namespace { +const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET; +} // namespace + +QuicClientEpollNetworkHelper::QuicClientEpollNetworkHelper( + QuicEpollServer* epoll_server, + QuicClientBase* client) + : epoll_server_(epoll_server), + packets_dropped_(0), + overflow_supported_(false), + packet_reader_(new QuicPacketReader()), + client_(client), + max_reads_per_epoll_loop_(std::numeric_limits<int>::max()) {} + +QuicClientEpollNetworkHelper::~QuicClientEpollNetworkHelper() { + if (client_->connected()) { + client_->session()->connection()->CloseConnection( + QUIC_PEER_GOING_AWAY, "Client being torn down", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + } + + CleanUpAllUDPSockets(); +} + +std::string QuicClientEpollNetworkHelper::Name() const { + return "QuicClientEpollNetworkHelper"; +} + +bool QuicClientEpollNetworkHelper::CreateUDPSocketAndBind( + QuicSocketAddress server_address, + QuicIpAddress bind_to_address, + int bind_to_port) { + epoll_server_->set_timeout_in_us(50 * 1000); + + int fd = CreateUDPSocket(server_address, &overflow_supported_); + if (fd < 0) { + return false; + } + + QuicSocketAddress client_address; + if (bind_to_address.IsInitialized()) { + client_address = QuicSocketAddress(bind_to_address, client_->local_port()); + } else if (server_address.host().address_family() == IpAddressFamily::IP_V4) { + client_address = QuicSocketAddress(QuicIpAddress::Any4(), bind_to_port); + } else { + client_address = QuicSocketAddress(QuicIpAddress::Any6(), bind_to_port); + } + + sockaddr_storage addr = client_address.generic_address(); + int rc = bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); + if (rc < 0) { + QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno); + return false; + } + + if (client_address.FromSocket(fd) != 0) { + QUIC_LOG(ERROR) << "Unable to get self address. Error: " + << strerror(errno); + } + + fd_address_map_[fd] = client_address; + + epoll_server_->RegisterFD(fd, this, kEpollFlags); + return true; +} + +void QuicClientEpollNetworkHelper::CleanUpUDPSocket(int fd) { + CleanUpUDPSocketImpl(fd); + fd_address_map_.erase(fd); +} + +void QuicClientEpollNetworkHelper::CleanUpAllUDPSockets() { + for (std::pair<int, QuicSocketAddress> fd_address : fd_address_map_) { + CleanUpUDPSocketImpl(fd_address.first); + } + fd_address_map_.clear(); +} + +void QuicClientEpollNetworkHelper::CleanUpUDPSocketImpl(int fd) { + if (fd > -1) { + epoll_server_->UnregisterFD(fd); + int rc = close(fd); + DCHECK_EQ(0, rc); + } +} + +void QuicClientEpollNetworkHelper::RunEventLoop() { + QuicRunSystemEventLoopIteration(); + epoll_server_->WaitForEventsAndExecuteCallbacks(); +} + +void QuicClientEpollNetworkHelper::OnRegistration(QuicEpollServer* eps, + int fd, + int event_mask) {} +void QuicClientEpollNetworkHelper::OnModification(int fd, int event_mask) {} +void QuicClientEpollNetworkHelper::OnUnregistration(int fd, bool replaced) {} +void QuicClientEpollNetworkHelper::OnShutdown(QuicEpollServer* eps, int fd) {} + +void QuicClientEpollNetworkHelper::OnEvent(int fd, QuicEpollEvent* event) { + DCHECK_EQ(fd, GetLatestFD()); + + if (event->in_events & EPOLLIN) { + DVLOG(1) << "Read packets on EPOLLIN"; + int times_to_read = max_reads_per_epoll_loop_; + bool more_to_read = true; + QuicPacketCount packets_dropped = 0; + while (client_->connected() && more_to_read && times_to_read > 0) { + more_to_read = packet_reader_->ReadAndDispatchPackets( + GetLatestFD(), GetLatestClientAddress().port(), + *client_->helper()->GetClock(), this, + overflow_supported_ ? &packets_dropped : nullptr); + --times_to_read; + } + if (packets_dropped_ < packets_dropped) { + QUIC_LOG(ERROR) + << packets_dropped - packets_dropped_ + << " more packets are dropped in the socket receive buffer."; + packets_dropped_ = packets_dropped; + } + if (client_->connected() && more_to_read) { + event->out_ready_mask |= EPOLLIN; + } + } + if (client_->connected() && (event->in_events & EPOLLOUT)) { + client_->writer()->SetWritable(); + client_->session()->connection()->OnCanWrite(); + } + if (event->in_events & EPOLLERR) { + QUIC_DLOG(INFO) << "Epollerr"; + } +} + +QuicPacketWriter* QuicClientEpollNetworkHelper::CreateQuicPacketWriter() { + return new QuicDefaultPacketWriter(GetLatestFD()); +} + +void QuicClientEpollNetworkHelper::SetClientPort(int port) { + fd_address_map_.back().second = + QuicSocketAddress(GetLatestClientAddress().host(), port); +} + +QuicSocketAddress QuicClientEpollNetworkHelper::GetLatestClientAddress() const { + if (fd_address_map_.empty()) { + return QuicSocketAddress(); + } + + return fd_address_map_.back().second; +} + +int QuicClientEpollNetworkHelper::GetLatestFD() const { + if (fd_address_map_.empty()) { + return -1; + } + + return fd_address_map_.back().first; +} + +void QuicClientEpollNetworkHelper::ProcessPacket( + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicReceivedPacket& packet) { + client_->session()->ProcessUdpPacket(self_address, peer_address, packet); +} + +int QuicClientEpollNetworkHelper::CreateUDPSocket( + QuicSocketAddress server_address, + bool* overflow_supported) { + return QuicSocketUtils::CreateUDPSocket( + server_address, + /*receive_buffer_size =*/kDefaultSocketReceiveBuffer, + /*send_buffer_size =*/kDefaultSocketReceiveBuffer, overflow_supported); +} +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h new file mode 100644 index 00000000000..89d12946c7b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h @@ -0,0 +1,135 @@ +// 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. + +// An implementation of the QuicClientBase::NetworkHelper +// that is based off the epoll server. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_ +#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_ + +#include <cstdint> +#include <memory> +#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/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_process_packet_interface.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" +#include "net/third_party/quiche/src/quic/tools/quic_client_base.h" + +namespace quic { + +namespace test { +class QuicClientPeer; +} // namespace test + +// An implementation of the QuicClientBase::NetworkHelper based off +// the epoll server. +class QuicClientEpollNetworkHelper : public QuicClientBase::NetworkHelper, + public QuicEpollCallbackInterface, + public ProcessPacketInterface { + public: + // Create a quic client, which will have events managed by an externally owned + // EpollServer. + QuicClientEpollNetworkHelper(QuicEpollServer* epoll_server, + QuicClientBase* client); + QuicClientEpollNetworkHelper(const QuicClientEpollNetworkHelper&) = delete; + QuicClientEpollNetworkHelper& operator=(const QuicClientEpollNetworkHelper&) = + delete; + + ~QuicClientEpollNetworkHelper() override; + + // Return a name describing the class for use in debug/error reporting. + std::string Name() const override; + + // From EpollCallbackInterface + void OnRegistration(QuicEpollServer* eps, int fd, int event_mask) override; + void OnModification(int fd, int event_mask) override; + void OnEvent(int fd, QuicEpollEvent* event) override; + // |fd_| can be unregistered without the client being disconnected. This + // happens in b3m QuicProber where we unregister |fd_| to feed in events to + // the client from the SelectServer. + void OnUnregistration(int fd, bool replaced) override; + void OnShutdown(QuicEpollServer* eps, int fd) override; + + // From ProcessPacketInterface. This will be called for each received + // packet. + void ProcessPacket(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicReceivedPacket& packet) override; + + // From NetworkHelper. + void RunEventLoop() override; + bool CreateUDPSocketAndBind(QuicSocketAddress server_address, + QuicIpAddress bind_to_address, + int bind_to_port) override; + void CleanUpAllUDPSockets() override; + QuicSocketAddress GetLatestClientAddress() const override; + QuicPacketWriter* CreateQuicPacketWriter() override; + + // Accessors provided for convenience, not part of any interface. + + QuicEpollServer* epoll_server() { return epoll_server_; } + + const QuicLinkedHashMap<int, QuicSocketAddress>& fd_address_map() const { + return fd_address_map_; + } + + // If the client has at least one UDP socket, return the latest created one. + // Otherwise, return -1. + int GetLatestFD() const; + + // Create socket for connection to |server_address| with default socket + // options. + // Return fd index. + virtual int CreateUDPSocket(QuicSocketAddress server_address, + bool* overflow_supported); + + QuicClientBase* client() { return client_; } + + void set_max_reads_per_epoll_loop(int num_reads) { + max_reads_per_epoll_loop_ = num_reads; + } + // If |fd| is an open UDP socket, unregister and close it. Otherwise, do + // nothing. + void CleanUpUDPSocket(int fd); + + private: + friend class test::QuicClientPeer; + + // Used for testing. + void SetClientPort(int port); + + // Actually clean up |fd|. + void CleanUpUDPSocketImpl(int fd); + + // Listens for events on the client socket. + QuicEpollServer* epoll_server_; + + // Map mapping created UDP sockets to their addresses. By using linked hash + // map, the order of socket creation can be recorded. + QuicLinkedHashMap<int, QuicSocketAddress> fd_address_map_; + + // If overflow_supported_ is true, this will be the number of packets dropped + // during the lifetime of the server. + QuicPacketCount packets_dropped_; + + // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped + // because the socket would otherwise overflow. + bool overflow_supported_; + + // Point to a QuicPacketReader object on the heap. The reader allocates more + // space than allowed on the stack. + std::unique_ptr<QuicPacketReader> packet_reader_; + + QuicClientBase* client_; + + int max_reads_per_epoll_loop_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_test.cc new file mode 100644 index 00000000000..12a687c1671 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_test.cc @@ -0,0 +1,120 @@ +// Copyright (c) 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/tools/quic_client.h" + +#include <dirent.h> +#include <sys/types.h> + +#include <memory> + +#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_port_utils.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/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_client_peer.h" + +namespace quic { +namespace test { +namespace { + +const char* kPathToFds = "/proc/self/fd"; + +std::string ReadLink(const std::string& path) { + std::string result(PATH_MAX, '\0'); + ssize_t result_size = readlink(path.c_str(), &result[0], result.size()); + CHECK(result_size > 0 && static_cast<size_t>(result_size) < result.size()); + result.resize(result_size); + return result; +} + +// Counts the number of open sockets for the current process. +size_t NumOpenSocketFDs() { + size_t socket_count = 0; + dirent* file; + std::unique_ptr<DIR, int (*)(DIR*)> fd_directory(opendir(kPathToFds), + closedir); + while ((file = readdir(fd_directory.get())) != nullptr) { + QuicStringPiece name(file->d_name); + if (name == "." || name == "..") { + continue; + } + + std::string fd_path = ReadLink(QuicStrCat(kPathToFds, "/", name)); + if (QuicTextUtils::StartsWith(fd_path, "socket:")) { + socket_count++; + } + } + return socket_count; +} + +// Creates a new QuicClient and Initializes it. Caller is responsible for +// deletion. +std::unique_ptr<QuicClient> CreateAndInitializeQuicClient(QuicEpollServer* eps, + uint16_t port) { + QuicSocketAddress server_address(QuicSocketAddress(TestLoopback(), port)); + QuicServerId server_id("hostname", server_address.port(), false); + ParsedQuicVersionVector versions = AllSupportedVersions(); + auto client = + QuicMakeUnique<QuicClient>(server_address, server_id, versions, eps, + crypto_test_utils::ProofVerifierForTesting()); + EXPECT_TRUE(client->Initialize()); + return client; +} + +class QuicClientTest : public QuicTest {}; + +TEST_F(QuicClientTest, DoNotLeakSocketFDs) { + // Make sure that the QuicClient doesn't leak socket FDs. Doing so could cause + // port exhaustion in long running processes which repeatedly create clients. + + // Record initial number of FDs, after creating EpollServer and creating and + // destroying a single client (the latter is needed since initializing + // platform dependencies like certificate verifier may open a persistent + // socket). + QuicEpollServer eps; + CreateAndInitializeQuicClient(&eps, QuicPickUnusedPortOrDie()); + size_t number_of_open_fds = NumOpenSocketFDs(); + + // Create a number of clients, initialize them, and verify this has resulted + // in additional FDs being opened. + const int kNumClients = 50; + for (int i = 0; i < kNumClients; ++i) { + std::unique_ptr<QuicClient> client( + CreateAndInitializeQuicClient(&eps, QuicPickUnusedPortOrDie())); + + // Initializing the client will create a new FD. + EXPECT_LT(number_of_open_fds, NumOpenSocketFDs()); + } + + // The FDs created by the QuicClients should now be closed. + EXPECT_EQ(number_of_open_fds, NumOpenSocketFDs()); +} + +TEST_F(QuicClientTest, CreateAndCleanUpUDPSockets) { + QuicEpollServer eps; + size_t number_of_open_fds = NumOpenSocketFDs(); + + std::unique_ptr<QuicClient> client( + CreateAndInitializeQuicClient(&eps, QuicPickUnusedPortOrDie())); + EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs()); + // Create more UDP sockets. + EXPECT_TRUE(QuicClientPeer::CreateUDPSocketAndBind(client.get())); + EXPECT_EQ(number_of_open_fds + 2, NumOpenSocketFDs()); + EXPECT_TRUE(QuicClientPeer::CreateUDPSocketAndBind(client.get())); + EXPECT_EQ(number_of_open_fds + 3, NumOpenSocketFDs()); + + // Clean up UDP sockets. + QuicClientPeer::CleanUpUDPSocket(client.get(), client->GetLatestFD()); + EXPECT_EQ(number_of_open_fds + 2, NumOpenSocketFDs()); + QuicClientPeer::CleanUpUDPSocket(client.get(), client->GetLatestFD()); + EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs()); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc new file mode 100644 index 00000000000..76a52c3bf87 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc @@ -0,0 +1,419 @@ +// 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/tools/quic_memory_cache_backend.h" + +#include <utility> + +#include "net/third_party/quiche/src/quic/core/http/spdy_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_file_utils.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_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" + +using spdy::kV3LowestPriority; +using spdy::SpdyHeaderBlock; + +namespace quic { + +QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& file_name) + : file_name_(file_name) {} + +QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default; + +void QuicMemoryCacheBackend::ResourceFile::Read() { + ReadFileContents(file_name_, &file_contents_); + + // First read the headers. + size_t start = 0; + while (start < file_contents_.length()) { + size_t pos = file_contents_.find("\n", start); + if (pos == std::string::npos) { + QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_; + return; + } + size_t len = pos - start; + // Support both dos and unix line endings for convenience. + if (file_contents_[pos - 1] == '\r') { + len -= 1; + } + QuicStringPiece line(file_contents_.data() + start, len); + start = pos + 1; + // Headers end with an empty line. + if (line.empty()) { + break; + } + // Extract the status from the HTTP first line. + if (line.substr(0, 4) == "HTTP") { + pos = line.find(" "); + if (pos == std::string::npos) { + QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " + << file_name_; + return; + } + spdy_headers_[":status"] = line.substr(pos + 1, 3); + continue; + } + // Headers are "key: value". + pos = line.find(": "); + if (pos == std::string::npos) { + QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_; + return; + } + spdy_headers_.AppendValueOrAddHeader( + QuicTextUtils::ToLower(line.substr(0, pos)), line.substr(pos + 2)); + } + + // The connection header is prohibited in HTTP/2. + spdy_headers_.erase("connection"); + + // Override the URL with the X-Original-Url header, if present. + auto it = spdy_headers_.find("x-original-url"); + if (it != spdy_headers_.end()) { + x_original_url_ = it->second; + HandleXOriginalUrl(); + } + + // X-Push-URL header is a relatively quick way to support sever push + // in the toy server. A production server should use link=preload + // stuff as described in https://w3c.github.io/preload/. + it = spdy_headers_.find("x-push-url"); + if (it != spdy_headers_.end()) { + QuicStringPiece push_urls = it->second; + size_t start = 0; + while (start < push_urls.length()) { + size_t pos = push_urls.find('\0', start); + if (pos == std::string::npos) { + push_urls_.push_back(QuicStringPiece(push_urls.data() + start, + push_urls.length() - start)); + break; + } + push_urls_.push_back(QuicStringPiece(push_urls.data() + start, pos)); + start += pos + 1; + } + } + + body_ = QuicStringPiece(file_contents_.data() + start, + file_contents_.size() - start); +} + +void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase( + QuicStringPiece base) { + size_t path_start = base.find_first_of('/'); + DCHECK_LT(0UL, path_start); + host_ = base.substr(0, path_start); + size_t query_start = base.find_first_of(','); + if (query_start > 0) { + path_ = base.substr(path_start, query_start - 1); + } else { + path_ = base.substr(path_start); + } +} + +QuicStringPiece QuicMemoryCacheBackend::ResourceFile::RemoveScheme( + QuicStringPiece url) { + if (QuicTextUtils::StartsWith(url, "https://")) { + url.remove_prefix(8); + } else if (QuicTextUtils::StartsWith(url, "http://")) { + url.remove_prefix(7); + } + return url; +} + +void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() { + QuicStringPiece url(x_original_url_); + // Remove the protocol so we can add it below. + url = RemoveScheme(url); + SetHostPathFromBase(url); +} + +const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse( + QuicStringPiece host, + QuicStringPiece path) const { + QuicWriterMutexLock lock(&response_mutex_); + + auto it = responses_.find(GetKey(host, path)); + if (it == responses_.end()) { + DVLOG(1) << "Get response for resource failed: host " << host << " path " + << path; + if (default_response_) { + return default_response_.get(); + } + return nullptr; + } + return it->second.get(); +} + +typedef QuicBackendResponse::ServerPushInfo ServerPushInfo; +typedef QuicBackendResponse::SpecialResponseType SpecialResponseType; + +void QuicMemoryCacheBackend::AddSimpleResponse(QuicStringPiece host, + QuicStringPiece path, + int response_code, + QuicStringPiece body) { + SpdyHeaderBlock response_headers; + response_headers[":status"] = QuicTextUtils::Uint64ToString(response_code); + response_headers["content-length"] = + QuicTextUtils::Uint64ToString(body.length()); + AddResponse(host, path, std::move(response_headers), body); +} + +void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources( + QuicStringPiece host, + QuicStringPiece path, + int response_code, + QuicStringPiece body, + std::list<ServerPushInfo> push_resources) { + AddSimpleResponse(host, path, response_code, body); + MaybeAddServerPushResources(host, path, push_resources); +} + +void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) { + QuicWriterMutexLock lock(&response_mutex_); + default_response_.reset(response); +} + +void QuicMemoryCacheBackend::AddResponse(QuicStringPiece host, + QuicStringPiece path, + SpdyHeaderBlock response_headers, + QuicStringPiece response_body) { + AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE, + std::move(response_headers), response_body, SpdyHeaderBlock(), + 0); +} + +void QuicMemoryCacheBackend::AddResponse(QuicStringPiece host, + QuicStringPiece path, + SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + SpdyHeaderBlock response_trailers) { + AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE, + std::move(response_headers), response_body, + std::move(response_trailers), 0); +} + +void QuicMemoryCacheBackend::AddSpecialResponse( + QuicStringPiece host, + QuicStringPiece path, + SpecialResponseType response_type) { + AddResponseImpl(host, path, response_type, SpdyHeaderBlock(), "", + SpdyHeaderBlock(), 0); +} + +void QuicMemoryCacheBackend::AddSpecialResponse( + QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + SpecialResponseType response_type) { + AddResponseImpl(host, path, response_type, std::move(response_headers), + response_body, SpdyHeaderBlock(), 0); +} + +void QuicMemoryCacheBackend::AddStopSendingResponse( + QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + uint16_t stop_sending_code) { + AddResponseImpl(host, path, SpecialResponseType::STOP_SENDING, + std::move(response_headers), response_body, SpdyHeaderBlock(), + stop_sending_code); +} + +QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {} + +bool QuicMemoryCacheBackend::InitializeBackend( + const std::string& cache_directory) { + if (cache_directory.empty()) { + QUIC_BUG << "cache_directory must not be empty."; + return false; + } + QUIC_LOG(INFO) + << "Attempting to initialize QuicMemoryCacheBackend from directory: " + << cache_directory; + std::vector<std::string> files = ReadFileContents(cache_directory); + std::list<std::unique_ptr<ResourceFile>> resource_files; + for (const auto& filename : files) { + std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename)); + + // Tease apart filename into host and path. + QuicStringPiece base(resource_file->file_name()); + base.remove_prefix(cache_directory.length()); + if (base[0] == '/') { + base.remove_prefix(1); + } + + resource_file->SetHostPathFromBase(base); + resource_file->Read(); + + AddResponse(resource_file->host(), resource_file->path(), + resource_file->spdy_headers().Clone(), resource_file->body()); + + resource_files.push_back(std::move(resource_file)); + } + + for (const auto& resource_file : resource_files) { + std::list<ServerPushInfo> push_resources; + for (const auto& push_url : resource_file->push_urls()) { + QuicUrl url(push_url); + const QuicBackendResponse* response = GetResponse(url.host(), url.path()); + if (!response) { + QUIC_BUG << "Push URL '" << push_url << "' not found."; + return false; + } + push_resources.push_back(ServerPushInfo(url, response->headers().Clone(), + kV3LowestPriority, + (std::string(response->body())))); + } + MaybeAddServerPushResources(resource_file->host(), resource_file->path(), + push_resources); + } + cache_initialized_ = true; + return true; +} + +bool QuicMemoryCacheBackend::IsBackendInitialized() const { + return cache_initialized_; +} + +void QuicMemoryCacheBackend::FetchResponseFromBackend( + const SpdyHeaderBlock& request_headers, + const std::string& request_body, + QuicSimpleServerBackend::RequestHandler* quic_stream) { + const QuicBackendResponse* quic_response = nullptr; + // Find response in cache. If not found, send error response. + auto authority = request_headers.find(":authority"); + auto path = request_headers.find(":path"); + if (authority != request_headers.end() && path != request_headers.end()) { + quic_response = GetResponse(authority->second, path->second); + } + + std::string request_url = + std::string(authority->second) + std::string(path->second); + std::list<ServerPushInfo> resources = GetServerPushResources(request_url); + QUIC_DVLOG(1) + << "Fetching QUIC response from backend in-memory cache for url " + << request_url; + quic_stream->OnResponseBackendComplete(quic_response, resources); +} + +// The memory cache does not have a per-stream handler +void QuicMemoryCacheBackend::CloseBackendResponseStream( + QuicSimpleServerBackend::RequestHandler* quic_stream) {} + +std::list<ServerPushInfo> QuicMemoryCacheBackend::GetServerPushResources( + std::string request_url) { + QuicWriterMutexLock lock(&response_mutex_); + + std::list<ServerPushInfo> resources; + auto resource_range = server_push_resources_.equal_range(request_url); + for (auto it = resource_range.first; it != resource_range.second; ++it) { + resources.push_back(it->second); + } + QUIC_DVLOG(1) << "Found " << resources.size() << " push resources for " + << request_url; + return resources; +} + +QuicMemoryCacheBackend::~QuicMemoryCacheBackend() { + { + QuicWriterMutexLock lock(&response_mutex_); + responses_.clear(); + } +} + +void QuicMemoryCacheBackend::AddResponseImpl(QuicStringPiece host, + QuicStringPiece path, + SpecialResponseType response_type, + SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + SpdyHeaderBlock response_trailers, + uint16_t stop_sending_code) { + QuicWriterMutexLock lock(&response_mutex_); + + DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; + std::string key = GetKey(host, path); + if (QuicContainsKey(responses_, key)) { + QUIC_BUG << "Response for '" << key << "' already exists!"; + return; + } + auto new_response = QuicMakeUnique<QuicBackendResponse>(); + new_response->set_response_type(response_type); + new_response->set_headers(std::move(response_headers)); + new_response->set_body(response_body); + new_response->set_trailers(std::move(response_trailers)); + new_response->set_stop_sending_code(stop_sending_code); + QUIC_DVLOG(1) << "Add response with key " << key; + responses_[key] = std::move(new_response); +} + +std::string QuicMemoryCacheBackend::GetKey(QuicStringPiece host, + QuicStringPiece path) const { + std::string host_string = std::string(host); + size_t port = host_string.find(':'); + if (port != std::string::npos) + host_string = std::string(host_string.c_str(), port); + return host_string + std::string(path); +} + +void QuicMemoryCacheBackend::MaybeAddServerPushResources( + QuicStringPiece request_host, + QuicStringPiece request_path, + std::list<ServerPushInfo> push_resources) { + std::string request_url = GetKey(request_host, request_path); + + for (const auto& push_resource : push_resources) { + if (PushResourceExistsInCache(request_url, push_resource)) { + continue; + } + + QUIC_DVLOG(1) << "Add request-resource association: request url " + << request_url << " push url " + << push_resource.request_url.ToString() + << " response headers " + << push_resource.headers.DebugString(); + { + QuicWriterMutexLock lock(&response_mutex_); + server_push_resources_.insert(std::make_pair(request_url, push_resource)); + } + std::string host = push_resource.request_url.host(); + if (host.empty()) { + host = std::string(request_host); + } + std::string path = push_resource.request_url.path(); + bool found_existing_response = false; + { + QuicWriterMutexLock lock(&response_mutex_); + found_existing_response = QuicContainsKey(responses_, GetKey(host, path)); + } + if (!found_existing_response) { + // Add a server push response to responses map, if it is not in the map. + QuicStringPiece body = push_resource.body; + QUIC_DVLOG(1) << "Add response for push resource: host " << host + << " path " << path; + AddResponse(host, path, push_resource.headers.Clone(), body); + } + } +} + +bool QuicMemoryCacheBackend::PushResourceExistsInCache( + std::string original_request_url, + ServerPushInfo resource) { + QuicWriterMutexLock lock(&response_mutex_); + auto resource_range = + server_push_resources_.equal_range(original_request_url); + for (auto it = resource_range.first; it != resource_range.second; ++it) { + ServerPushInfo push_resource = it->second; + if (push_resource.request_url.ToString() == + resource.request_url.ToString()) { + return true; + } + } + return false; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h new file mode 100644 index 00000000000..ff76c9ad41b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h @@ -0,0 +1,198 @@ +// 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_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_ +#define QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_ + +#include <list> +#include <map> +#include <memory> +#include <vector> + +#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" +#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" +#include "net/third_party/quiche/src/quic/tools/quic_url.h" +#include "net/third_party/quiche/src/spdy/core/spdy_framer.h" + +namespace quic { + +// In-memory cache for HTTP responses. +// Reads from disk cache generated by: +// `wget -p --save_headers <url>` +class QuicMemoryCacheBackend : public QuicSimpleServerBackend { + public: + // Class to manage loading a resource file into memory. There are + // two uses: called by InitializeBackend to load resources + // from files, and recursively called when said resources specify + // server push associations. + class ResourceFile { + public: + explicit ResourceFile(const std::string& file_name); + ResourceFile(const ResourceFile&) = delete; + ResourceFile& operator=(const ResourceFile&) = delete; + virtual ~ResourceFile(); + + void Read(); + + // |base| is |file_name_| with |cache_directory| prefix stripped. + void SetHostPathFromBase(QuicStringPiece base); + + const std::string& file_name() { return file_name_; } + + QuicStringPiece host() { return host_; } + + QuicStringPiece path() { return path_; } + + const spdy::SpdyHeaderBlock& spdy_headers() { return spdy_headers_; } + + QuicStringPiece body() { return body_; } + + const std::vector<QuicStringPiece>& push_urls() { return push_urls_; } + + protected: + void HandleXOriginalUrl(); + QuicStringPiece RemoveScheme(QuicStringPiece url); + + std::string file_name_; + std::string file_contents_; + QuicStringPiece body_; + spdy::SpdyHeaderBlock spdy_headers_; + QuicStringPiece x_original_url_; + std::vector<QuicStringPiece> push_urls_; + + private: + QuicStringPiece host_; + QuicStringPiece path_; + }; + + QuicMemoryCacheBackend(); + QuicMemoryCacheBackend(const QuicMemoryCacheBackend&) = delete; + QuicMemoryCacheBackend& operator=(const QuicMemoryCacheBackend&) = delete; + ~QuicMemoryCacheBackend() override; + + // Retrieve a response from this cache for a given host and path.. + // If no appropriate response exists, nullptr is returned. + const QuicBackendResponse* GetResponse(QuicStringPiece host, + QuicStringPiece path) const; + + // Adds a simple response to the cache. The response headers will + // only contain the "content-length" header with the length of |body|. + void AddSimpleResponse(QuicStringPiece host, + QuicStringPiece path, + int response_code, + QuicStringPiece body); + + // Add a simple response to the cache as AddSimpleResponse() does, and add + // some server push resources(resource path, corresponding response status and + // path) associated with it. + // Push resource implicitly come from the same host. + void AddSimpleResponseWithServerPushResources( + QuicStringPiece host, + QuicStringPiece path, + int response_code, + QuicStringPiece body, + std::list<QuicBackendResponse::ServerPushInfo> push_resources); + + // Add a response to the cache. + void AddResponse(QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body); + + // Add a response, with trailers, to the cache. + void AddResponse(QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + spdy::SpdyHeaderBlock response_trailers); + + // Simulate a special behavior at a particular path. + void AddSpecialResponse( + QuicStringPiece host, + QuicStringPiece path, + QuicBackendResponse::SpecialResponseType response_type); + + void AddSpecialResponse( + QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + QuicBackendResponse::SpecialResponseType response_type); + + void AddStopSendingResponse(QuicStringPiece host, + QuicStringPiece path, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + uint16_t stop_sending_code); + + // Sets a default response in case of cache misses. Takes ownership of + // 'response'. + void AddDefaultResponse(QuicBackendResponse* response); + + // |cache_cirectory| can be generated using `wget -p --save-headers <url>`. + void InitializeFromDirectory(const std::string& cache_directory); + + // Find all the server push resources associated with |request_url|. + std::list<QuicBackendResponse::ServerPushInfo> GetServerPushResources( + std::string request_url); + + // Implements the functions for interface QuicSimpleServerBackend + // |cache_cirectory| can be generated using `wget -p --save-headers <url>`. + bool InitializeBackend(const std::string& cache_directory) override; + bool IsBackendInitialized() const override; + void FetchResponseFromBackend( + const spdy::SpdyHeaderBlock& request_headers, + const std::string& request_body, + QuicSimpleServerBackend::RequestHandler* quic_server_stream) override; + void CloseBackendResponseStream( + QuicSimpleServerBackend::RequestHandler* quic_server_stream) override; + + private: + void AddResponseImpl(QuicStringPiece host, + QuicStringPiece path, + QuicBackendResponse::SpecialResponseType response_type, + spdy::SpdyHeaderBlock response_headers, + QuicStringPiece response_body, + spdy::SpdyHeaderBlock response_trailers, + uint16_t stop_sending_code); + + std::string GetKey(QuicStringPiece host, QuicStringPiece path) const; + + // Add some server push urls with given responses for specified + // request if these push resources are not associated with this request yet. + void MaybeAddServerPushResources( + QuicStringPiece request_host, + QuicStringPiece request_path, + std::list<QuicBackendResponse::ServerPushInfo> push_resources); + + // Check if push resource(push_host/push_path) associated with given request + // url already exists in server push map. + bool PushResourceExistsInCache(std::string original_request_url, + QuicBackendResponse::ServerPushInfo resource); + + // Cached responses. + QuicUnorderedMap<std::string, std::unique_ptr<QuicBackendResponse>> responses_ + GUARDED_BY(response_mutex_); + + // The default response for cache misses, if set. + std::unique_ptr<QuicBackendResponse> default_response_ + GUARDED_BY(response_mutex_); + + // A map from request URL to associated server push responses (if any). + std::multimap<std::string, QuicBackendResponse::ServerPushInfo> + server_push_resources_ GUARDED_BY(response_mutex_); + + // Protects against concurrent access from test threads setting responses, and + // server threads accessing those responses. + mutable QuicMutex response_mutex_; + bool cache_initialized_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc new file mode 100644 index 00000000000..80b1b293206 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc @@ -0,0 +1,235 @@ +// 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/tools/quic_memory_cache_backend.h" + +#include "net/third_party/quiche/src/quic/platform/api/quic_map_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/platform/api/quic_text_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h" + +namespace quic { +namespace test { + +namespace { +typedef QuicBackendResponse Response; +typedef QuicBackendResponse::ServerPushInfo ServerPushInfo; +} // namespace + +class QuicMemoryCacheBackendTest : public QuicTest { + protected: + void CreateRequest(std::string host, + std::string path, + spdy::SpdyHeaderBlock* headers) { + (*headers)[":method"] = "GET"; + (*headers)[":path"] = path; + (*headers)[":authority"] = host; + (*headers)[":scheme"] = "https"; + } + + std::string CacheDirectory() { return QuicGetTestMemoryCachePath(); } + + QuicMemoryCacheBackend cache_; +}; + +TEST_F(QuicMemoryCacheBackendTest, GetResponseNoMatch) { + const Response* response = + cache_.GetResponse("mail.google.com", "/index.html"); + ASSERT_FALSE(response); +} + +TEST_F(QuicMemoryCacheBackendTest, AddSimpleResponseGetResponse) { + std::string response_body("hello response"); + cache_.AddSimpleResponse("www.google.com", "/", 200, response_body); + + spdy::SpdyHeaderBlock request_headers; + CreateRequest("www.google.com", "/", &request_headers); + const Response* response = cache_.GetResponse("www.google.com", "/"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("200", response->headers().find(":status")->second); + EXPECT_EQ(response_body.size(), response->body().length()); +} + +TEST_F(QuicMemoryCacheBackendTest, AddResponse) { + const std::string kRequestHost = "www.foo.com"; + const std::string kRequestPath = "/"; + const std::string kResponseBody("hello response"); + + spdy::SpdyHeaderBlock response_headers; + response_headers[":version"] = "HTTP/1.1"; + response_headers[":status"] = "200"; + response_headers["content-length"] = + QuicTextUtils::Uint64ToString(kResponseBody.size()); + + spdy::SpdyHeaderBlock response_trailers; + response_trailers["key-1"] = "value-1"; + response_trailers["key-2"] = "value-2"; + response_trailers["key-3"] = "value-3"; + + cache_.AddResponse(kRequestHost, "/", response_headers.Clone(), kResponseBody, + response_trailers.Clone()); + + const Response* response = cache_.GetResponse(kRequestHost, kRequestPath); + EXPECT_EQ(response->headers(), response_headers); + EXPECT_EQ(response->body(), kResponseBody); + EXPECT_EQ(response->trailers(), response_trailers); +} + +TEST_F(QuicMemoryCacheBackendTest, ReadsCacheDir) { + cache_.InitializeBackend(CacheDirectory()); + const Response* response = + cache_.GetResponse("test.example.com", "/index.html"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("200", response->headers().find(":status")->second); + // Connection headers are not valid in HTTP/2. + EXPECT_FALSE(QuicContainsKey(response->headers(), "connection")); + EXPECT_LT(0U, response->body().length()); +} + +TEST_F(QuicMemoryCacheBackendTest, ReadsCacheDirWithServerPushResource) { + cache_.InitializeBackend(CacheDirectory() + "_with_push"); + std::list<ServerPushInfo> resources = + cache_.GetServerPushResources("test.example.com/"); + ASSERT_EQ(1UL, resources.size()); +} + +TEST_F(QuicMemoryCacheBackendTest, ReadsCacheDirWithServerPushResources) { + cache_.InitializeBackend(CacheDirectory() + "_with_push"); + std::list<ServerPushInfo> resources = + cache_.GetServerPushResources("test.example.com/index2.html"); + ASSERT_EQ(2UL, resources.size()); +} + +TEST_F(QuicMemoryCacheBackendTest, UsesOriginalUrl) { + cache_.InitializeBackend(CacheDirectory()); + const Response* response = + cache_.GetResponse("test.example.com", "/site_map.html"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("200", response->headers().find(":status")->second); + // Connection headers are not valid in HTTP/2. + EXPECT_FALSE(QuicContainsKey(response->headers(), "connection")); + EXPECT_LT(0U, response->body().length()); +} + +TEST_F(QuicMemoryCacheBackendTest, DefaultResponse) { + // Verify GetResponse returns nullptr when no default is set. + const Response* response = cache_.GetResponse("www.google.com", "/"); + ASSERT_FALSE(response); + + // Add a default response. + spdy::SpdyHeaderBlock response_headers; + response_headers[":version"] = "HTTP/1.1"; + response_headers[":status"] = "200"; + response_headers["content-length"] = "0"; + Response* default_response = new Response; + default_response->set_headers(std::move(response_headers)); + cache_.AddDefaultResponse(default_response); + + // Now we should get the default response for the original request. + response = cache_.GetResponse("www.google.com", "/"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("200", response->headers().find(":status")->second); + + // Now add a set response for / and make sure it is returned + cache_.AddSimpleResponse("www.google.com", "/", 302, ""); + response = cache_.GetResponse("www.google.com", "/"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("302", response->headers().find(":status")->second); + + // We should get the default response for other requests. + response = cache_.GetResponse("www.google.com", "/asd"); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ("200", response->headers().find(":status")->second); +} + +TEST_F(QuicMemoryCacheBackendTest, AddSimpleResponseWithServerPushResources) { + std::string request_host = "www.foo.com"; + std::string response_body("hello response"); + const size_t kNumResources = 5; + int NumResources = 5; + std::list<ServerPushInfo> push_resources; + std::string scheme = "http"; + for (int i = 0; i < NumResources; ++i) { + std::string path = "/server_push_src" + QuicTextUtils::Uint64ToString(i); + std::string url = scheme + "://" + request_host + path; + QuicUrl resource_url(url); + std::string body = + QuicStrCat("This is server push response body for ", path); + spdy::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( + ServerPushInfo(resource_url, response_headers.Clone(), i, body)); + } + + cache_.AddSimpleResponseWithServerPushResources( + request_host, "/", 200, response_body, push_resources); + + std::string request_url = request_host + "/"; + std::list<ServerPushInfo> resources = + cache_.GetServerPushResources(request_url); + ASSERT_EQ(kNumResources, resources.size()); + for (const auto& push_resource : push_resources) { + ServerPushInfo resource = resources.front(); + EXPECT_EQ(resource.request_url.ToString(), + push_resource.request_url.ToString()); + EXPECT_EQ(resource.priority, push_resource.priority); + resources.pop_front(); + } +} + +TEST_F(QuicMemoryCacheBackendTest, GetServerPushResourcesAndPushResponses) { + std::string request_host = "www.foo.com"; + std::string response_body("hello response"); + const size_t kNumResources = 4; + int NumResources = 4; + std::string scheme = "http"; + std::string push_response_status[kNumResources] = {"200", "200", "301", + "404"}; + std::list<ServerPushInfo> push_resources; + for (int i = 0; i < NumResources; ++i) { + std::string path = "/server_push_src" + QuicTextUtils::Uint64ToString(i); + std::string url = scheme + "://" + request_host + path; + QuicUrl resource_url(url); + std::string body = "This is server push response body for " + path; + spdy::SpdyHeaderBlock response_headers; + response_headers[":version"] = "HTTP/1.1"; + response_headers[":status"] = push_response_status[i]; + response_headers["content-length"] = + QuicTextUtils::Uint64ToString(body.size()); + push_resources.push_back( + ServerPushInfo(resource_url, response_headers.Clone(), i, body)); + } + cache_.AddSimpleResponseWithServerPushResources( + request_host, "/", 200, response_body, push_resources); + std::string request_url = request_host + "/"; + std::list<ServerPushInfo> resources = + cache_.GetServerPushResources(request_url); + ASSERT_EQ(kNumResources, resources.size()); + int i = 0; + for (const auto& push_resource : push_resources) { + QuicUrl url = resources.front().request_url; + std::string host = url.host(); + std::string path = url.path(); + const Response* response = cache_.GetResponse(host, path); + ASSERT_TRUE(response); + ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + EXPECT_EQ(push_response_status[i++], + response->headers().find(":status")->second); + EXPECT_EQ(push_resource.body, response->body()); + resources.pop_front(); + } +} + +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc new file mode 100644 index 00000000000..c8cfc11ec8f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc @@ -0,0 +1,246 @@ +// 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. + +// clang-format off + +// Dumps out the decryptable contents of a QUIC packet in a human-readable way. +// If the packet is null encrypted, this will dump full packet contents. +// Otherwise it will dump the header, and fail with an error that the +// packet is undecryptable. +// +// Usage: quic_packet_printer server|client <hex dump of packet> +// +// Example input: +// quic_packet_printer server 0c6b810308320f24c004a939a38a2e3fd6ca589917f200400201b80b0100501c0700060003023d0000001c00556e656e637279707465642073747265616d2064617461207365656e +// +// Example output: +// OnPacket +// OnUnauthenticatedPublicHeader +// OnUnauthenticatedHeader: { connection_id: 13845207862000976235, connection_id_length:8, packet_number_length:1, multipath_flag: 0, reset_flag: 0, version_flag: 0, path_id: , packet_number: 4 } +// OnDecryptedPacket +// OnPacketHeader +// OnAckFrame: largest_observed: 1 ack_delay_time: 3000 missing_packets: [ ] is_truncated: 0 received_packets: [ 1 at 466016 ] +// OnStopWaitingFrame +// OnConnectionCloseFrame: error_code { 61 } error_details { Unencrypted stream data seen } + +// clang-format on + +#include <iostream> + +#include "net/third_party/quiche/src/quic/core/quic_framer.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_text_utils.h" + +DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, + quic_version, + "", + "If set, specify the QUIC version to use."); + +namespace quic { + +class QuicPacketPrinter : public QuicFramerVisitorInterface { + public: + explicit QuicPacketPrinter(QuicFramer* framer) : framer_(framer) {} + + void OnError(QuicFramer* framer) override { + std::cerr << "OnError: " << QuicErrorCodeToString(framer->error()) + << " detail: " << framer->detailed_error() << "\n"; + } + bool OnProtocolVersionMismatch(ParsedQuicVersion received_version, + PacketHeaderFormat form) override { + framer_->set_version(received_version); + std::cerr << "OnProtocolVersionMismatch: " + << ParsedQuicVersionToString(received_version) << "\n"; + return true; + } + void OnPacket() override { std::cerr << "OnPacket\n"; } + void OnPublicResetPacket(const QuicPublicResetPacket& packet) override { + std::cerr << "OnPublicResetPacket\n"; + } + void OnVersionNegotiationPacket( + const QuicVersionNegotiationPacket& packet) override { + std::cerr << "OnVersionNegotiationPacket\n"; + } + bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override { + std::cerr << "OnUnauthenticatedPublicHeader\n"; + return true; + } + bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override { + std::cerr << "OnUnauthenticatedHeader: " << header; + return true; + } + void OnDecryptedPacket(EncryptionLevel level) override { + // This only currently supports "decrypting" null encrypted packets. + DCHECK_EQ(ENCRYPTION_INITIAL, level); + std::cerr << "OnDecryptedPacket\n"; + } + bool OnPacketHeader(const QuicPacketHeader& header) override { + std::cerr << "OnPacketHeader\n"; + return true; + } + void OnCoalescedPacket(const QuicEncryptedPacket& packet) override { + std::cerr << "OnCoalescedPacket\n"; + } + bool OnStreamFrame(const QuicStreamFrame& frame) override { + std::cerr << "OnStreamFrame: " << frame; + std::cerr << " data: { " + << QuicTextUtils::HexEncode(frame.data_buffer, frame.data_length) + << " }\n"; + return true; + } + bool OnCryptoFrame(const QuicCryptoFrame& frame) override { + std::cerr << "OnCryptoFrame: " << frame; + std::cerr << " data: { " + << QuicTextUtils::HexEncode(frame.data_buffer, frame.data_length) + << " }\n"; + return true; + } + bool OnAckFrameStart(QuicPacketNumber largest_acked, + QuicTime::Delta /*ack_delay_time*/) override { + std::cerr << "OnAckFrameStart, largest_acked: " << largest_acked; + return true; + } + bool OnAckRange(QuicPacketNumber start, QuicPacketNumber end) override { + std::cerr << "OnAckRange: [" << start << ", " << end << ")"; + return true; + } + bool OnAckTimestamp(QuicPacketNumber packet_number, + QuicTime timestamp) override { + std::cerr << "OnAckTimestamp: [" << packet_number << ", " + << timestamp.ToDebuggingValue() << ")"; + return true; + } + bool OnAckFrameEnd(QuicPacketNumber start) override { + std::cerr << "OnAckFrameEnd, start: " << start; + return true; + } + bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) override { + std::cerr << "OnStopWaitingFrame: " << frame; + return true; + } + bool OnPaddingFrame(const QuicPaddingFrame& frame) override { + std::cerr << "OnPaddingFrame: " << frame; + return true; + } + bool OnPingFrame(const QuicPingFrame& frame) override { + std::cerr << "OnPingFrame\n"; + return true; + } + bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override { + std::cerr << "OnRstStreamFrame: " << frame; + return true; + } + bool OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override { + // The frame printout will indicate whether it's a Google QUIC + // CONNECTION_CLOSE, IETF QUIC CONNECTION_CLOSE/Transport, or IETF QUIC + // CONNECTION_CLOSE/Application frame. + std::cerr << "OnConnectionCloseFrame: " << frame; + return true; + } + bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override { + std::cerr << "OnNewConnectionIdFrame: " << frame; + return true; + } + bool OnRetireConnectionIdFrame( + const QuicRetireConnectionIdFrame& frame) override { + std::cerr << "OnRetireConnectionIdFrame: " << frame; + return true; + } + bool OnNewTokenFrame(const QuicNewTokenFrame& frame) override { + std::cerr << "OnNewTokenFrame: " << frame; + return true; + } + bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override { + std::cerr << "OnStopSendingFrame: " << frame; + return true; + } + bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override { + std::cerr << "OnPathChallengeFrame: " << frame; + return true; + } + bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override { + std::cerr << "OnPathResponseFrame: " << frame; + return true; + } + bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override { + std::cerr << "OnGoAwayFrame: " << frame; + return true; + } + bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override { + std::cerr << "OnMaxStreamIdFrame: " << frame; + return true; + } + bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override { + std::cerr << "OnStreamIdBlockedFrame: " << frame; + return true; + } + bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override { + std::cerr << "OnWindowUpdateFrame: " << frame; + return true; + } + bool OnBlockedFrame(const QuicBlockedFrame& frame) override { + std::cerr << "OnBlockedFrame: " << frame; + return true; + } + bool OnMessageFrame(const QuicMessageFrame& frame) override { + std::cerr << "OnMessageFrame: " << frame; + return true; + } + void OnPacketComplete() override { std::cerr << "OnPacketComplete\n"; } + bool IsValidStatelessResetToken(QuicUint128 token) const override { + std::cerr << "IsValidStatelessResetToken\n"; + return false; + } + void OnAuthenticatedIetfStatelessResetPacket( + const QuicIetfStatelessResetPacket& packet) override { + std::cerr << "OnAuthenticatedIetfStatelessResetPacket\n"; + } + + private: + QuicFramer* framer_; // Unowned. +}; + +} // namespace quic + +int main(int argc, char* argv[]) { + const char* usage = "Usage: quic_packet_printer client|server <hex>"; + std::vector<std::string> args = + quic::QuicParseCommandLineFlags(usage, argc, argv); + + if (args.size() < 2) { + quic::QuicPrintCommandLineFlagHelp(usage); + return 1; + } + + std::string perspective_string = args[0]; + quic::Perspective perspective; + if (perspective_string == "client") { + perspective = quic::Perspective::IS_CLIENT; + } else if (perspective_string == "server") { + perspective = quic::Perspective::IS_SERVER; + } else { + std::cerr << "Invalid perspective" << std::endl; + quic::QuicPrintCommandLineFlagHelp(usage); + return 1; + } + std::string hex = quic::QuicTextUtils::HexDecode(args[1]); + quic::ParsedQuicVersionVector versions = quic::AllSupportedVersions(); + // Fake a time since we're not actually generating acks. + quic::QuicTime start(quic::QuicTime::Zero()); + quic::QuicFramer framer(versions, start, perspective, + quic::kQuicDefaultConnectionIdLength); + if (!GetQuicFlag(FLAGS_quic_version).empty()) { + for (quic::ParsedQuicVersion version : versions) { + if (quic::QuicVersionToString(version.transport_version) == + GetQuicFlag(FLAGS_quic_version)) { + framer.set_version(version); + } + } + } + quic::QuicPacketPrinter visitor(&framer); + framer.set_visitor(&visitor); + quic::QuicEncryptedPacket encrypted(hex.c_str(), hex.length()); + return framer.ProcessPacket(encrypted); +} diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc new file mode 100644 index 00000000000..de66774bab3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc @@ -0,0 +1,43 @@ +// 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. + +// Decodes the packet HandshakeFailureReason from the chromium histogram +// Net.QuicClientHelloRejectReasons + +#include <iostream> + +#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h" +#include "net/third_party/quiche/src/quic/core/crypto/crypto_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" + +using quic::CryptoUtils; +using quic::HandshakeFailureReason; +using quic::MAX_FAILURE_REASON; + +int main(int argc, char* argv[]) { + const char* usage = "Usage: quic_reject_reason_decoder <packed_reason>"; + std::vector<std::string> args = + quic::QuicParseCommandLineFlags(usage, argc, argv); + + if (args.size() != 1) { + std::cerr << usage << std::endl; + return 1; + } + + uint32_t packed_error = 0; + if (!quic::QuicTextUtils::StringToUint32(args[0], &packed_error)) { + std::cerr << "Unable to parse: " << args[0] << "\n"; + return 2; + } + + for (int i = 1; i < MAX_FAILURE_REASON; ++i) { + if ((packed_error & (1 << (i - 1))) == 0) { + continue; + } + HandshakeFailureReason reason = static_cast<HandshakeFailureReason>(i); + std::cout << CryptoUtils::HandshakeFailureReasonToString(reason) << "\n"; + } + return 0; +} diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_server.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_server.cc new file mode 100644 index 00000000000..d67bc3078d0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_server.cc @@ -0,0 +1,212 @@ +// 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/tools/quic_server.h" + +#include <errno.h> +#include <features.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> + +#include <cstdint> +#include <memory> + +#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h" +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_data_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_default_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_dispatcher.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.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/quic/platform/impl/quic_epoll_clock.h" +#include "net/quic/platform/impl/quic_socket_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +namespace quic { + +namespace { + +const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET; +const char kSourceAddressTokenSecret[] = "secret"; + +} // namespace + +const size_t kNumSessionsToCreatePerSocketEvent = 16; + +QuicServer::QuicServer(std::unique_ptr<ProofSource> proof_source, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicServer(std::move(proof_source), + QuicConfig(), + QuicCryptoServerConfig::ConfigOptions(), + AllSupportedVersions(), + quic_simple_server_backend, + kQuicDefaultConnectionIdLength) {} + +QuicServer::QuicServer( + std::unique_ptr<ProofSource> proof_source, + const QuicConfig& config, + const QuicCryptoServerConfig::ConfigOptions& crypto_config_options, + const ParsedQuicVersionVector& supported_versions, + QuicSimpleServerBackend* quic_simple_server_backend, + uint8_t expected_connection_id_length) + : port_(0), + fd_(-1), + packets_dropped_(0), + overflow_supported_(false), + silent_close_(false), + config_(config), + crypto_config_(kSourceAddressTokenSecret, + QuicRandom::GetInstance(), + std::move(proof_source), + KeyExchangeSource::Default(), + TlsServerHandshaker::CreateSslCtx()), + crypto_config_options_(crypto_config_options), + version_manager_(supported_versions), + packet_reader_(new QuicPacketReader()), + quic_simple_server_backend_(quic_simple_server_backend), + expected_connection_id_length_(expected_connection_id_length) { + Initialize(); +} + +void QuicServer::Initialize() { + // If an initial flow control window has not explicitly been set, then use a + // sensible value for a server: 1 MB for session, 64 KB for each stream. + const uint32_t kInitialSessionFlowControlWindow = 1 * 1024 * 1024; // 1 MB + const uint32_t kInitialStreamFlowControlWindow = 64 * 1024; // 64 KB + if (config_.GetInitialStreamFlowControlWindowToSend() == + kMinimumFlowControlSendWindow) { + config_.SetInitialStreamFlowControlWindowToSend( + kInitialStreamFlowControlWindow); + } + if (config_.GetInitialSessionFlowControlWindowToSend() == + kMinimumFlowControlSendWindow) { + config_.SetInitialSessionFlowControlWindowToSend( + kInitialSessionFlowControlWindow); + } + + epoll_server_.set_timeout_in_us(50 * 1000); + + QuicEpollClock clock(&epoll_server_); + + std::unique_ptr<CryptoHandshakeMessage> scfg(crypto_config_.AddDefaultConfig( + QuicRandom::GetInstance(), &clock, crypto_config_options_)); +} + +QuicServer::~QuicServer() = default; + +bool QuicServer::CreateUDPSocketAndListen(const QuicSocketAddress& address) { + fd_ = QuicSocketUtils::CreateUDPSocket( + address, + /*receive_buffer_size =*/kDefaultSocketReceiveBuffer, + /*send_buffer_size =*/kDefaultSocketReceiveBuffer, &overflow_supported_); + if (fd_ < 0) { + QUIC_LOG(ERROR) << "CreateSocket() failed: " << strerror(errno); + return false; + } + + sockaddr_storage addr = address.generic_address(); + int rc = bind(fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); + if (rc < 0) { + QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno); + return false; + } + QUIC_LOG(INFO) << "Listening on " << address.ToString(); + port_ = address.port(); + if (port_ == 0) { + QuicSocketAddress address; + if (address.FromSocket(fd_) != 0) { + QUIC_LOG(ERROR) << "Unable to get self address. Error: " + << strerror(errno); + } + port_ = address.port(); + } + + epoll_server_.RegisterFD(fd_, this, kEpollFlags); + dispatcher_.reset(CreateQuicDispatcher()); + dispatcher_->InitializeWithWriter(CreateWriter(fd_)); + + return true; +} + +QuicPacketWriter* QuicServer::CreateWriter(int fd) { + return new QuicDefaultPacketWriter(fd); +} + +QuicDispatcher* QuicServer::CreateQuicDispatcher() { + QuicEpollAlarmFactory alarm_factory(&epoll_server_); + return new QuicSimpleDispatcher( + &config_, &crypto_config_, &version_manager_, + std::unique_ptr<QuicEpollConnectionHelper>(new QuicEpollConnectionHelper( + &epoll_server_, QuicAllocator::BUFFER_POOL)), + std::unique_ptr<QuicCryptoServerStream::Helper>( + new QuicSimpleCryptoServerStreamHelper(QuicRandom::GetInstance())), + std::unique_ptr<QuicEpollAlarmFactory>( + new QuicEpollAlarmFactory(&epoll_server_)), + quic_simple_server_backend_, expected_connection_id_length_); +} + +void QuicServer::WaitForEvents() { + epoll_server_.WaitForEventsAndExecuteCallbacks(); +} + +void QuicServer::Shutdown() { + if (!silent_close_) { + // Before we shut down the epoll server, give all active sessions a chance + // to notify clients that they're closing. + dispatcher_->Shutdown(); + } + + epoll_server_.Shutdown(); + + close(fd_); + fd_ = -1; +} + +void QuicServer::OnEvent(int fd, QuicEpollEvent* event) { + DCHECK_EQ(fd, fd_); + event->out_ready_mask = 0; + + if (event->in_events & EPOLLIN) { + QUIC_DVLOG(1) << "EPOLLIN"; + + dispatcher_->ProcessBufferedChlos(kNumSessionsToCreatePerSocketEvent); + + bool more_to_read = true; + while (more_to_read) { + more_to_read = packet_reader_->ReadAndDispatchPackets( + fd_, port_, QuicEpollClock(&epoll_server_), dispatcher_.get(), + overflow_supported_ ? &packets_dropped_ : nullptr); + } + + if (dispatcher_->HasChlosBuffered()) { + // Register EPOLLIN event to consume buffered CHLO(s). + event->out_ready_mask |= EPOLLIN; + } + } + if (event->in_events & EPOLLOUT) { + dispatcher_->OnCanWrite(); + if (dispatcher_->HasPendingWrites()) { + event->out_ready_mask |= EPOLLOUT; + } + } + if (event->in_events & EPOLLERR) { + } +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_server.h b/chromium/net/third_party/quiche/src/quic/tools/quic_server.h new file mode 100644 index 00000000000..4d7e08199a9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_server.h @@ -0,0 +1,159 @@ +// 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 toy server, which listens on a specified address for QUIC traffic and +// handles incoming responses. +// +// Note that this server is intended to verify correctness of the client and is +// in no way expected to be performant. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_SERVER_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SERVER_H_ + +#include <memory> + +#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h" +#include "net/third_party/quiche/src/quic/core/quic_framer.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_version_manager.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" + +namespace quic { + +namespace test { +class QuicServerPeer; +} // namespace test + +class QuicDispatcher; +class QuicPacketReader; + +class QuicServer : public QuicEpollCallbackInterface { + public: + QuicServer(std::unique_ptr<ProofSource> proof_source, + QuicSimpleServerBackend* quic_simple_server_backend); + QuicServer(std::unique_ptr<ProofSource> proof_source, + const QuicConfig& config, + const QuicCryptoServerConfig::ConfigOptions& server_config_options, + const ParsedQuicVersionVector& supported_versions, + QuicSimpleServerBackend* quic_simple_server_backend, + uint8_t expected_connection_id_length); + QuicServer(const QuicServer&) = delete; + QuicServer& operator=(const QuicServer&) = delete; + + ~QuicServer() override; + + std::string Name() const override { return "QuicServer"; } + + // Start listening on the specified address. + bool CreateUDPSocketAndListen(const QuicSocketAddress& address); + + // Wait up to 50ms, and handle any events which occur. + void WaitForEvents(); + + // Server deletion is imminent. Start cleaning up the epoll server. + virtual void Shutdown(); + + // From EpollCallbackInterface + void OnRegistration(QuicEpollServer* eps, int fd, int event_mask) override {} + void OnModification(int fd, int event_mask) override {} + void OnEvent(int fd, QuicEpollEvent* event) override; + void OnUnregistration(int fd, bool replaced) override {} + + void OnShutdown(QuicEpollServer* eps, int fd) override {} + + void SetChloMultiplier(size_t multiplier) { + crypto_config_.set_chlo_multiplier(multiplier); + } + + void SetPreSharedKey(QuicStringPiece key) { + crypto_config_.set_pre_shared_key(key); + } + + bool overflow_supported() { return overflow_supported_; } + + QuicPacketCount packets_dropped() { return packets_dropped_; } + + int port() { return port_; } + + protected: + virtual QuicPacketWriter* CreateWriter(int fd); + + virtual QuicDispatcher* CreateQuicDispatcher(); + + const QuicConfig& config() const { return config_; } + const QuicCryptoServerConfig& crypto_config() const { return crypto_config_; } + QuicEpollServer* epoll_server() { return &epoll_server_; } + + QuicDispatcher* dispatcher() { return dispatcher_.get(); } + + QuicVersionManager* version_manager() { return &version_manager_; } + + QuicSimpleServerBackend* server_backend() { + return quic_simple_server_backend_; + } + + void set_silent_close(bool value) { silent_close_ = value; } + + uint8_t expected_connection_id_length() { + return expected_connection_id_length_; + } + + private: + friend class quic::test::QuicServerPeer; + + // Initialize the internal state of the server. + void Initialize(); + + // Accepts data from the framer and demuxes clients to sessions. + std::unique_ptr<QuicDispatcher> dispatcher_; + // Frames incoming packets and hands them to the dispatcher. + QuicEpollServer epoll_server_; + + // The port the server is listening on. + int port_; + + // Listening connection. Also used for outbound client communication. + int fd_; + + // If overflow_supported_ is true this will be the number of packets dropped + // during the lifetime of the server. This may overflow if enough packets + // are dropped. + QuicPacketCount packets_dropped_; + + // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped + // because the socket would otherwise overflow. + bool overflow_supported_; + + // If true, do not call Shutdown on the dispatcher. Connections will close + // without sending a final connection close. + bool silent_close_; + + // config_ contains non-crypto parameters that are negotiated in the crypto + // handshake. + QuicConfig config_; + // crypto_config_ contains crypto parameters for the handshake. + QuicCryptoServerConfig crypto_config_; + // crypto_config_options_ contains crypto parameters for the handshake. + QuicCryptoServerConfig::ConfigOptions crypto_config_options_; + + // Used to generate current supported versions. + QuicVersionManager version_manager_; + + // Point to a QuicPacketReader object on the heap. The reader allocates more + // space than allowed on the stack. + std::unique_ptr<QuicPacketReader> packet_reader_; + + QuicSimpleServerBackend* quic_simple_server_backend_; // unowned. + + // Connection ID length expected to be read on incoming IETF short headers. + uint8_t expected_connection_id_length_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SERVER_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc new file mode 100644 index 00000000000..128c873dfda --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc @@ -0,0 +1,70 @@ +// 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. + +// A binary wrapper for QuicServer. It listens forever on --port +// (default 6121) until it's killed or ctrl-cd to death. + +#include <vector> + +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_default_proof_providers.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/quic/tools/quic_memory_cache_backend.h" +#include "net/third_party/quiche/src/quic/tools/quic_server.h" + +DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, + port, + 6121, + "The port the quic server will listen on."); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + std::string, + quic_response_cache_dir, + "", + "Specifies the directory used during QuicHttpResponseCache " + "construction to seed the cache. Cache directory can be " + "generated using `wget -p --save-headers <url>`"); + +DEFINE_QUIC_COMMAND_LINE_FLAG( + int32_t, + quic_ietf_draft, + 0, + "QUIC IETF draft number to use over the wire, e.g. 18. " + "This also enables required internal QUIC flags."); + +int main(int argc, char* argv[]) { + const char* usage = "Usage: quic_server [options]"; + std::vector<std::string> non_option_args = + quic::QuicParseCommandLineFlags(usage, argc, argv); + if (!non_option_args.empty()) { + quic::QuicPrintCommandLineFlagHelp(usage); + exit(0); + } + + const int32_t quic_ietf_draft = GetQuicFlag(FLAGS_quic_ietf_draft); + if (quic_ietf_draft > 0) { + quic::QuicVersionInitializeSupportForIetfDraft(quic_ietf_draft); + quic::QuicEnableVersion( + quic::ParsedQuicVersion(quic::PROTOCOL_TLS1_3, quic::QUIC_VERSION_99)); + } + + quic::QuicMemoryCacheBackend memory_cache_backend; + if (!GetQuicFlag(FLAGS_quic_response_cache_dir).empty()) { + memory_cache_backend.InitializeBackend( + GetQuicFlag(FLAGS_quic_response_cache_dir)); + } + + quic::QuicServer server(quic::CreateDefaultProofSource(), + &memory_cache_backend); + + if (!server.CreateUDPSocketAndListen(quic::QuicSocketAddress( + quic::QuicIpAddress::Any6(), GetQuicFlag(FLAGS_port)))) { + return 1; + } + + while (true) { + server.WaitForEvents(); + } +} diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_server_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_server_test.cc new file mode 100644 index 00000000000..bb007cbb49a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_server_test.cc @@ -0,0 +1,215 @@ +// 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/tools/quic_server.h" + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.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_arraysize.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_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_test_loopback.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_dispatcher.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_server_peer.h" +#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h" + +namespace quic { +namespace test { + +using ::testing::_; + +namespace { + +class MockQuicSimpleDispatcher : public QuicSimpleDispatcher { + public: + MockQuicSimpleDispatcher( + const QuicConfig* config, + const QuicCryptoServerConfig* crypto_config, + QuicVersionManager* version_manager, + std::unique_ptr<QuicConnectionHelperInterface> helper, + std::unique_ptr<QuicCryptoServerStream::Helper> session_helper, + std::unique_ptr<QuicAlarmFactory> alarm_factory, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSimpleDispatcher(config, + crypto_config, + version_manager, + std::move(helper), + std::move(session_helper), + std::move(alarm_factory), + quic_simple_server_backend, + kQuicDefaultConnectionIdLength) {} + ~MockQuicSimpleDispatcher() override = default; + + MOCK_METHOD0(OnCanWrite, void()); + MOCK_CONST_METHOD0(HasPendingWrites, bool()); + MOCK_CONST_METHOD0(HasChlosBuffered, bool()); + MOCK_METHOD1(ProcessBufferedChlos, void(size_t)); +}; + +class TestQuicServer : public QuicServer { + public: + TestQuicServer() + : QuicServer(crypto_test_utils::ProofSourceForTesting(), + &quic_simple_server_backend_) {} + + ~TestQuicServer() override = default; + + MockQuicSimpleDispatcher* mock_dispatcher() { return mock_dispatcher_; } + + protected: + QuicDispatcher* CreateQuicDispatcher() override { + mock_dispatcher_ = new MockQuicSimpleDispatcher( + &config(), &crypto_config(), version_manager(), + std::unique_ptr<QuicEpollConnectionHelper>( + new QuicEpollConnectionHelper(epoll_server(), + QuicAllocator::BUFFER_POOL)), + std::unique_ptr<QuicCryptoServerStream::Helper>( + new QuicSimpleCryptoServerStreamHelper(QuicRandom::GetInstance())), + std::unique_ptr<QuicEpollAlarmFactory>( + new QuicEpollAlarmFactory(epoll_server())), + &quic_simple_server_backend_); + return mock_dispatcher_; + } + + MockQuicSimpleDispatcher* mock_dispatcher_ = nullptr; + QuicMemoryCacheBackend quic_simple_server_backend_; +}; + +class QuicServerEpollInTest : public QuicTest { + public: + QuicServerEpollInTest() + : port_(QuicPickUnusedPortOrDie()), + server_address_(TestLoopback(), port_) {} + + void StartListening() { + server_.CreateUDPSocketAndListen(server_address_); + ASSERT_TRUE(QuicServerPeer::SetSmallSocket(&server_)); + + if (!server_.overflow_supported()) { + QUIC_LOG(WARNING) << "Overflow not supported. Not testing."; + return; + } + } + + protected: + int port_; + QuicSocketAddress server_address_; + TestQuicServer server_; +}; + +// Tests that if dispatcher has CHLOs waiting for connection creation, EPOLLIN +// event should try to create connections for them. And set epoll mask with +// EPOLLIN if there are still CHLOs remaining at the end of epoll event. +TEST_F(QuicServerEpollInTest, ProcessBufferedCHLOsOnEpollin) { + // Given an EPOLLIN event, try to create session for buffered CHLOs. In first + // event, dispatcher can't create session for all of CHLOs. So listener should + // register another EPOLLIN event by itself. Even without new packet arrival, + // the rest CHLOs should be process in next epoll event. + StartListening(); + bool more_chlos = true; + MockQuicSimpleDispatcher* dispatcher_ = server_.mock_dispatcher(); + DCHECK(dispatcher_ != nullptr); + EXPECT_CALL(*dispatcher_, OnCanWrite()).Times(testing::AnyNumber()); + EXPECT_CALL(*dispatcher_, ProcessBufferedChlos(_)).Times(2); + EXPECT_CALL(*dispatcher_, HasPendingWrites()).Times(testing::AnyNumber()); + // Expect there are still CHLOs buffered after 1st event. But not any more + // after 2nd event. + EXPECT_CALL(*dispatcher_, HasChlosBuffered()) + .WillOnce(testing::Return(true)) + .WillOnce( + DoAll(testing::Assign(&more_chlos, false), testing::Return(false))); + + // Send a packet to trigger epoll event. + int fd = socket( + AddressFamilyUnderTest() == IpAddressFamily::IP_V4 ? AF_INET : AF_INET6, + SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); + ASSERT_LT(0, fd); + + char buf[1024]; + memset(buf, 0, QUIC_ARRAYSIZE(buf)); + sockaddr_storage storage = server_address_.generic_address(); + int rc = sendto(fd, buf, QUIC_ARRAYSIZE(buf), 0, + reinterpret_cast<sockaddr*>(&storage), sizeof(storage)); + if (rc < 0) { + QUIC_DLOG(INFO) << errno << " " << strerror(errno); + } + + while (more_chlos) { + server_.WaitForEvents(); + } +} + +class QuicServerDispatchPacketTest : public QuicTest { + public: + QuicServerDispatchPacketTest() + : crypto_config_("blah", + QuicRandom::GetInstance(), + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default(), + TlsServerHandshaker::CreateSslCtx()), + version_manager_(AllSupportedVersions()), + dispatcher_( + &config_, + &crypto_config_, + &version_manager_, + std::unique_ptr<QuicEpollConnectionHelper>( + new QuicEpollConnectionHelper(&eps_, + QuicAllocator::BUFFER_POOL)), + std::unique_ptr<QuicCryptoServerStream::Helper>( + new QuicSimpleCryptoServerStreamHelper( + QuicRandom::GetInstance())), + std::unique_ptr<QuicEpollAlarmFactory>( + new QuicEpollAlarmFactory(&eps_)), + &quic_simple_server_backend_) { + dispatcher_.InitializeWithWriter(new QuicDefaultPacketWriter(1234)); + } + + void DispatchPacket(const QuicReceivedPacket& packet) { + QuicSocketAddress client_addr, server_addr; + dispatcher_.ProcessPacket(server_addr, client_addr, packet); + } + + protected: + QuicConfig config_; + QuicCryptoServerConfig crypto_config_; + QuicVersionManager version_manager_; + QuicEpollServer eps_; + QuicMemoryCacheBackend quic_simple_server_backend_; + MockQuicDispatcher dispatcher_; +}; + +TEST_F(QuicServerDispatchPacketTest, DispatchPacket) { + // clang-format off + unsigned char valid_packet[] = { + // public flags (8 byte connection_id) + 0x3C, + // connection_id + 0x10, 0x32, 0x54, 0x76, + 0x98, 0xBA, 0xDC, 0xFE, + // packet number + 0xBC, 0x9A, 0x78, 0x56, + 0x34, 0x12, + // private flags + 0x00 + }; + // clang-format on + QuicReceivedPacket encrypted_valid_packet( + reinterpret_cast<char*>(valid_packet), QUIC_ARRAYSIZE(valid_packet), + QuicTime::Zero(), false); + + EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _)).Times(1); + DispatchPacket(encrypted_valid_packet); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc new file mode 100644 index 00000000000..7cc875b5a63 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc @@ -0,0 +1,18 @@ +// 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/tools/quic_simple_client_session.h" + +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" + +namespace quic { + +std::unique_ptr<QuicSpdyClientStream> +QuicSimpleClientSession::CreateClientStream() { + return QuicMakeUnique<QuicSimpleClientStream>( + GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL, + drop_response_body_); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h new file mode 100644 index 00000000000..1a17f3d74f3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h @@ -0,0 +1,38 @@ +// 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_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h" + +namespace quic { + +class QuicSimpleClientSession : public QuicSpdyClientSession { + public: + QuicSimpleClientSession(const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + QuicClientPushPromiseIndex* push_promise_index, + bool drop_response_body) + : QuicSpdyClientSession(config, + supported_versions, + connection, + server_id, + crypto_config, + push_promise_index), + drop_response_body_(drop_response_body) {} + + std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override; + + private: + const bool drop_response_body_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.cc new file mode 100644 index 00000000000..a627007972c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.cc @@ -0,0 +1,33 @@ +// 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/tools/quic_simple_client_stream.h" + +namespace quic { + +void QuicSimpleClientStream::OnBodyAvailable() { + if (!drop_response_body_) { + QuicSpdyClientStream::OnBodyAvailable(); + return; + } + + while (HasBytesToRead()) { + struct iovec iov; + if (GetReadableRegions(&iov, 1) == 0) { + break; + } + MarkConsumed(iov.iov_len); + } + if (sequencer()->IsClosed()) { + OnFinRead(); + } else { + sequencer()->SetUnblocked(); + } +} + +void QuicSimpleClientStream::OnStopSending(uint16_t code) { + last_stop_sending_code_ = code; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h new file mode 100644 index 00000000000..f1eb653bea4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h @@ -0,0 +1,34 @@ +// 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_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h" + +namespace quic { + +class QuicSimpleClientStream : public QuicSpdyClientStream { + public: + QuicSimpleClientStream(QuicStreamId id, + QuicSpdyClientSession* session, + StreamType type, + bool drop_response_body) + : QuicSpdyClientStream(id, session, type), + drop_response_body_(drop_response_body), + last_stop_sending_code_(0) {} + void OnBodyAvailable() override; + void OnStopSending(uint16_t code) override; + + uint16_t last_stop_sending_code() { return last_stop_sending_code_; } + private: + const bool drop_response_body_; + // Application code value that was in the most recently received + // STOP_SENDING frame for this stream. + uint16_t last_stop_sending_code_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.cc new file mode 100644 index 00000000000..2bb9656cb2f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.cc @@ -0,0 +1,34 @@ +// 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/tools/quic_simple_crypto_server_stream_helper.h" + +#include "net/third_party/quiche/src/quic/core/quic_utils.h" + +namespace quic { + +QuicSimpleCryptoServerStreamHelper::QuicSimpleCryptoServerStreamHelper( + QuicRandom* random) + : random_(random) {} + +QuicSimpleCryptoServerStreamHelper::~QuicSimpleCryptoServerStreamHelper() = + default; + +QuicConnectionId +QuicSimpleCryptoServerStreamHelper::GenerateConnectionIdForReject( + QuicTransportVersion /*version*/, + QuicConnectionId /*connection_id*/) const { + return QuicUtils::CreateRandomConnectionId(random_); +} + +bool QuicSimpleCryptoServerStreamHelper::CanAcceptClientHello( + const CryptoHandshakeMessage& message, + const QuicSocketAddress& client_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& self_address, + std::string* error_details) const { + return true; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h new file mode 100644 index 00000000000..f8a58f6f4d0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper.h @@ -0,0 +1,38 @@ +// 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_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_ + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h" + +namespace quic { + +// Simple helper for server crypto streams which generates a new random +// connection ID for stateless rejects. +class QuicSimpleCryptoServerStreamHelper + : public QuicCryptoServerStream::Helper { + public: + explicit QuicSimpleCryptoServerStreamHelper(QuicRandom* random); + + ~QuicSimpleCryptoServerStreamHelper() override; + + QuicConnectionId GenerateConnectionIdForReject( + QuicTransportVersion /*version*/, + QuicConnectionId /*connection_id*/) const override; + + bool CanAcceptClientHello(const CryptoHandshakeMessage& message, + const QuicSocketAddress& client_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& self_address, + std::string* error_details) const override; + + private: + QuicRandom* random_; // Unowned. +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper_test.cc new file mode 100644 index 00000000000..4ede5889a9f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_crypto_server_stream_helper_test.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2019 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/tools/quic_simple_crypto_server_stream_helper.h" + +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/test_tools/mock_random.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" + +namespace quic { + +class QuicSimpleCryptoServerStreamHelperTest : public QuicTest {}; + +TEST_F(QuicSimpleCryptoServerStreamHelperTest, GenerateConnectionIdForReject) { + test::MockRandom random; + QuicSimpleCryptoServerStreamHelper helper(&random); + + EXPECT_EQ(QuicUtils::CreateRandomConnectionId(&random), + helper.GenerateConnectionIdForReject(QUIC_VERSION_99, + test::TestConnectionId())); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.cc new file mode 100644 index 00000000000..4706b606843 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.cc @@ -0,0 +1,68 @@ +// 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/tools/quic_simple_dispatcher.h" + +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_session.h" + +namespace quic { + +QuicSimpleDispatcher::QuicSimpleDispatcher( + const QuicConfig* config, + const QuicCryptoServerConfig* crypto_config, + QuicVersionManager* version_manager, + std::unique_ptr<QuicConnectionHelperInterface> helper, + std::unique_ptr<QuicCryptoServerStream::Helper> session_helper, + std::unique_ptr<QuicAlarmFactory> alarm_factory, + QuicSimpleServerBackend* quic_simple_server_backend, + uint8_t expected_connection_id_length) + : QuicDispatcher(config, + crypto_config, + version_manager, + std::move(helper), + std::move(session_helper), + std::move(alarm_factory), + expected_connection_id_length), + quic_simple_server_backend_(quic_simple_server_backend) {} + +QuicSimpleDispatcher::~QuicSimpleDispatcher() = default; + +int QuicSimpleDispatcher::GetRstErrorCount( + QuicRstStreamErrorCode error_code) const { + auto it = rst_error_map_.find(error_code); + if (it == rst_error_map_.end()) { + return 0; + } + return it->second; +} + +void QuicSimpleDispatcher::OnRstStreamReceived( + const QuicRstStreamFrame& frame) { + auto it = rst_error_map_.find(frame.error_code); + if (it == rst_error_map_.end()) { + rst_error_map_.insert(std::make_pair(frame.error_code, 1)); + } else { + it->second++; + } +} + +QuicServerSessionBase* QuicSimpleDispatcher::CreateQuicSession( + QuicConnectionId connection_id, + const QuicSocketAddress& client_address, + QuicStringPiece /*alpn*/, + const ParsedQuicVersion& version) { + // The QuicServerSessionBase takes ownership of |connection| below. + QuicConnection* connection = new QuicConnection( + connection_id, client_address, helper(), alarm_factory(), writer(), + /* owns_writer= */ false, Perspective::IS_SERVER, + ParsedQuicVersionVector{version}); + + QuicServerSessionBase* session = new QuicSimpleServerSession( + config(), GetSupportedVersions(), connection, this, session_helper(), + crypto_config(), compressed_certs_cache(), quic_simple_server_backend_); + session->Initialize(); + return session; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h new file mode 100644 index 00000000000..46d976c7b5b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h @@ -0,0 +1,52 @@ +// 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_TOOLS_QUIC_SIMPLE_DISPATCHER_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_DISPATCHER_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_server_session_base.h" +#include "net/third_party/quiche/src/quic/core/quic_dispatcher.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" + +namespace quic { + +class QuicSimpleDispatcher : public QuicDispatcher { + public: + QuicSimpleDispatcher( + const QuicConfig* config, + const QuicCryptoServerConfig* crypto_config, + QuicVersionManager* version_manager, + std::unique_ptr<QuicConnectionHelperInterface> helper, + std::unique_ptr<QuicCryptoServerStream::Helper> session_helper, + std::unique_ptr<QuicAlarmFactory> alarm_factory, + QuicSimpleServerBackend* quic_simple_server_backend, + uint8_t expected_connection_id_length); + + ~QuicSimpleDispatcher() override; + + int GetRstErrorCount(QuicRstStreamErrorCode rst_error_code) const; + + void OnRstStreamReceived(const QuicRstStreamFrame& frame) override; + + protected: + QuicServerSessionBase* CreateQuicSession( + QuicConnectionId connection_id, + const QuicSocketAddress& client_address, + QuicStringPiece alpn, + const ParsedQuicVersion& version) override; + + QuicSimpleServerBackend* server_backend() { + return quic_simple_server_backend_; + } + + private: + QuicSimpleServerBackend* quic_simple_server_backend_; // Unowned. + + // The map of the reset error code with its counter. + std::map<QuicRstStreamErrorCode, int> rst_error_map_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_DISPATCHER_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h new file mode 100644 index 00000000000..74afb60c2d2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h @@ -0,0 +1,60 @@ +// 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_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_ + +#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h" + +namespace spdy { +class SpdyHeaderBlock; +} // namespace spdy + +namespace quic { + +// This interface implements the functionality to fetch a response +// from the backend (such as cache, http-proxy etc) to serve +// requests received by a Quic Server +class QuicSimpleServerBackend { + public: + // This interface implements the methods + // called by the QuicSimpleServerBackend implementation + // to process the request in the backend + class RequestHandler { + public: + virtual ~RequestHandler() {} + + virtual QuicConnectionId connection_id() const = 0; + virtual QuicStreamId stream_id() const = 0; + virtual std::string peer_host() const = 0; + // Called when the response is ready at the backend and can be send back to + // the QUIC client. + virtual void OnResponseBackendComplete( + const QuicBackendResponse* response, + std::list<QuicBackendResponse::ServerPushInfo> resources) = 0; + }; + + virtual ~QuicSimpleServerBackend() = default; + // This method initializes the backend instance to fetch responses + // from a backend server, in-memory cache etc. + virtual bool InitializeBackend(const std::string& backend_url) = 0; + // Returns true if the backend has been successfully initialized + // and could be used to fetch HTTP requests + virtual bool IsBackendInitialized() const = 0; + // Triggers a HTTP request to be sent to the backend server or cache + // If response is immediately available, the function synchronously calls + // the |request_handler| with the HTTP response. + // If the response has to be fetched over the network, the function + // asynchronously calls |request_handler| with the HTTP response. + virtual void FetchResponseFromBackend( + const spdy::SpdyHeaderBlock& request_headers, + const std::string& request_body, + RequestHandler* request_handler) = 0; + // Clears the state of the backend instance + virtual void CloseBackendResponseStream(RequestHandler* request_handler) = 0; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.cc new file mode 100644 index 00000000000..a722d373dd7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.cc @@ -0,0 +1,228 @@ +// 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/tools/quic_simple_server_session.h" + +#include <utility> + +#include "net/third_party/quiche/src/quic/core/quic_connection.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" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h" + +namespace quic { + +QuicSimpleServerSession::QuicSimpleServerSession( + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + QuicSession::Visitor* visitor, + QuicCryptoServerStream::Helper* helper, + const QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicServerSessionBase(config, + supported_versions, + connection, + visitor, + helper, + crypto_config, + compressed_certs_cache), + highest_promised_stream_id_( + QuicUtils::GetInvalidStreamId(connection->transport_version())), + quic_simple_server_backend_(quic_simple_server_backend) {} + +QuicSimpleServerSession::~QuicSimpleServerSession() { + delete connection(); +} + +QuicCryptoServerStreamBase* +QuicSimpleServerSession::CreateQuicCryptoServerStream( + const QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache) { + return new QuicCryptoServerStream( + crypto_config, compressed_certs_cache, + GetQuicReloadableFlag(enable_quic_stateless_reject_support), this, + stream_helper()); +} + +void QuicSimpleServerSession::OnStreamFrame(const QuicStreamFrame& frame) { + if (!IsIncomingStream(frame.stream_id)) { + QUIC_LOG(WARNING) << "Client shouldn't send data on server push stream"; + connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, "Client sent data on server push stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + QuicSpdySession::OnStreamFrame(frame); +} + +void QuicSimpleServerSession::PromisePushResources( + const std::string& request_url, + const std::list<QuicBackendResponse::ServerPushInfo>& resources, + QuicStreamId original_stream_id, + const spdy::SpdyHeaderBlock& original_request_headers) { + if (!server_push_enabled()) { + return; + } + + for (QuicBackendResponse::ServerPushInfo resource : resources) { + spdy::SpdyHeaderBlock headers = SynthesizePushRequestHeaders( + request_url, resource, original_request_headers); + highest_promised_stream_id_ += + QuicUtils::StreamIdDelta(connection()->transport_version()); + SendPushPromise(original_stream_id, highest_promised_stream_id_, + headers.Clone()); + promised_streams_.push_back(PromisedStreamInfo( + std::move(headers), highest_promised_stream_id_, resource.priority)); + } + + // Procese promised push request as many as possible. + HandlePromisedPushRequests(); +} + +QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream(QuicStreamId id) { + if (!ShouldCreateIncomingStream(id)) { + return nullptr; + } + + QuicSpdyStream* stream = new QuicSimpleServerStream( + id, this, BIDIRECTIONAL, quic_simple_server_backend_); + ActivateStream(QuicWrapUnique(stream)); + return stream; +} + +QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream( + PendingStream pending) { + QuicSpdyStream* stream = new QuicSimpleServerStream( + std::move(pending), this, BIDIRECTIONAL, quic_simple_server_backend_); + ActivateStream(QuicWrapUnique(stream)); + return stream; +} + +QuicSimpleServerStream* +QuicSimpleServerSession::CreateOutgoingBidirectionalStream() { + DCHECK(false); + return nullptr; +} + +QuicSimpleServerStream* +QuicSimpleServerSession::CreateOutgoingUnidirectionalStream() { + if (!ShouldCreateOutgoingUnidirectionalStream()) { + return nullptr; + } + + QuicSimpleServerStream* stream = new QuicSimpleServerStream( + GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL, + quic_simple_server_backend_); + ActivateStream(QuicWrapUnique(stream)); + return stream; +} + +void QuicSimpleServerSession::HandleFrameOnNonexistentOutgoingStream( + QuicStreamId stream_id) { + // If this stream is a promised but not created stream (stream_id within the + // range of next_outgoing_stream_id_ and highes_promised_stream_id_), + // connection shouldn't be closed. + // Otherwise behave in the same way as base class. + if (highest_promised_stream_id_ == + QuicUtils::GetInvalidStreamId(connection()->transport_version()) || + stream_id > highest_promised_stream_id_) { + QuicSession::HandleFrameOnNonexistentOutgoingStream(stream_id); + } +} + +void QuicSimpleServerSession::HandleRstOnValidNonexistentStream( + const QuicRstStreamFrame& frame) { + QuicSession::HandleRstOnValidNonexistentStream(frame); + if (!IsClosedStream(frame.stream_id)) { + // If a nonexistent stream is not a closed stream and still valid, it must + // be a locally preserved stream. Resetting this kind of stream means + // cancelling the promised server push. + // Since PromisedStreamInfo are queued in sequence, the corresponding + // index for it in promised_streams_ can be calculated. + QuicStreamId next_stream_id = next_outgoing_unidirectional_stream_id(); + if (connection()->transport_version() == QUIC_VERSION_99) { + DCHECK(!QuicUtils::IsBidirectionalStreamId(frame.stream_id)); + } + DCHECK_GE(frame.stream_id, next_stream_id); + size_t index = (frame.stream_id - next_stream_id) / + QuicUtils::StreamIdDelta(connection()->transport_version()); + DCHECK_LE(index, promised_streams_.size()); + promised_streams_[index].is_cancelled = true; + control_frame_manager().WriteOrBufferRstStream(frame.stream_id, + QUIC_RST_ACKNOWLEDGEMENT, 0); + connection()->OnStreamReset(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT); + } +} + +spdy::SpdyHeaderBlock QuicSimpleServerSession::SynthesizePushRequestHeaders( + std::string request_url, + QuicBackendResponse::ServerPushInfo resource, + const spdy::SpdyHeaderBlock& original_request_headers) { + QuicUrl push_request_url = resource.request_url; + + spdy::SpdyHeaderBlock spdy_headers = original_request_headers.Clone(); + // :authority could be different from original request. + spdy_headers[":authority"] = push_request_url.host(); + spdy_headers[":path"] = push_request_url.path(); + // Push request always use GET. + spdy_headers[":method"] = "GET"; + spdy_headers["referer"] = request_url; + spdy_headers[":scheme"] = push_request_url.scheme(); + // It is not possible to push a response to a request that includes a request + // body. + spdy_headers["content-length"] = "0"; + // Remove "host" field as push request is a directly generated HTTP2 request + // which should use ":authority" instead of "host". + spdy_headers.erase("host"); + return spdy_headers; +} + +void QuicSimpleServerSession::SendPushPromise(QuicStreamId original_stream_id, + QuicStreamId promised_stream_id, + spdy::SpdyHeaderBlock headers) { + QUIC_DLOG(INFO) << "stream " << original_stream_id + << " send PUSH_PROMISE for promised stream " + << promised_stream_id; + WritePushPromise(original_stream_id, promised_stream_id, std::move(headers)); +} + +void QuicSimpleServerSession::HandlePromisedPushRequests() { + while (!promised_streams_.empty() && + ShouldCreateOutgoingUnidirectionalStream()) { + PromisedStreamInfo& promised_info = promised_streams_.front(); + DCHECK_EQ(next_outgoing_unidirectional_stream_id(), + promised_info.stream_id); + + if (promised_info.is_cancelled) { + // This stream has been reset by client. Skip this stream id. + promised_streams_.pop_front(); + GetNextOutgoingUnidirectionalStreamId(); + return; + } + + QuicSimpleServerStream* promised_stream = + static_cast<QuicSimpleServerStream*>( + CreateOutgoingUnidirectionalStream()); + DCHECK_NE(promised_stream, nullptr); + DCHECK_EQ(promised_info.stream_id, promised_stream->id()); + QUIC_DLOG(INFO) << "created server push stream " << promised_stream->id(); + + promised_stream->SetPriority(promised_info.priority); + + spdy::SpdyHeaderBlock request_headers( + std::move(promised_info.request_headers)); + + promised_streams_.pop_front(); + promised_stream->PushResponse(std::move(request_headers)); + } +} + +void QuicSimpleServerSession::OnCanCreateNewOutgoingStream() { + HandlePromisedPushRequests(); +} +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.h new file mode 100644 index 00000000000..c3705342359 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session.h @@ -0,0 +1,154 @@ +// 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 toy server specific QuicSession subclass. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_ + +#include <stdint.h> + +#include <list> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "net/third_party/quiche/src/quic/core/http/quic_server_session_base.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_containers.h" +#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h" + +namespace quic { + +namespace test { +class QuicSimpleServerSessionPeer; +} // namespace test + +class QuicSimpleServerSession : public QuicServerSessionBase { + public: + // A PromisedStreamInfo is an element of the queue to store promised + // stream which hasn't been created yet. It keeps a mapping between promised + // stream id with its priority and the headers sent out in PUSH_PROMISE. + struct PromisedStreamInfo { + public: + PromisedStreamInfo(spdy::SpdyHeaderBlock request_headers, + QuicStreamId stream_id, + spdy::SpdyPriority priority) + : request_headers(std::move(request_headers)), + stream_id(stream_id), + priority(priority), + is_cancelled(false) {} + spdy::SpdyHeaderBlock request_headers; + QuicStreamId stream_id; + spdy::SpdyPriority priority; + bool is_cancelled; + }; + + // Takes ownership of |connection|. + QuicSimpleServerSession(const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + QuicSession::Visitor* visitor, + QuicCryptoServerStream::Helper* helper, + const QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache, + QuicSimpleServerBackend* quic_simple_server_backend); + QuicSimpleServerSession(const QuicSimpleServerSession&) = delete; + QuicSimpleServerSession& operator=(const QuicSimpleServerSession&) = delete; + + ~QuicSimpleServerSession() override; + + // Override base class to detact client sending data on server push stream. + void OnStreamFrame(const QuicStreamFrame& frame) override; + + // Send out PUSH_PROMISE for all |resources| promised stream id in each frame + // will increase by 2 for each item in |resources|. + // And enqueue HEADERS block in those PUSH_PROMISED for sending push response + // later. + virtual void PromisePushResources( + const std::string& request_url, + const std::list<QuicBackendResponse::ServerPushInfo>& resources, + QuicStreamId original_stream_id, + const spdy::SpdyHeaderBlock& original_request_headers); + + void OnCanCreateNewOutgoingStream() override; + + protected: + // QuicSession methods: + QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override; + QuicSpdyStream* CreateIncomingStream(PendingStream pending) override; + QuicSimpleServerStream* CreateOutgoingBidirectionalStream() override; + QuicSimpleServerStream* CreateOutgoingUnidirectionalStream() override; + // Override to return true for locally preserved server push stream. + void HandleFrameOnNonexistentOutgoingStream(QuicStreamId stream_id) override; + // Override to handle reseting locally preserved streams. + void HandleRstOnValidNonexistentStream( + const QuicRstStreamFrame& frame) override; + + // QuicServerSessionBaseMethod: + QuicCryptoServerStreamBase* CreateQuicCryptoServerStream( + const QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache) override; + + QuicSimpleServerBackend* server_backend() { + return quic_simple_server_backend_; + } + + private: + friend class test::QuicSimpleServerSessionPeer; + + // Create a server push headers block by copying request's headers block. + // But replace or add these pseudo-headers as they are specific to each + // request: + // :authority, :path, :method, :scheme, referer. + // Copying the rest headers ensures they are the same as the original + // request, especially cookies. + spdy::SpdyHeaderBlock SynthesizePushRequestHeaders( + std::string request_url, + QuicBackendResponse::ServerPushInfo resource, + const spdy::SpdyHeaderBlock& original_request_headers); + + // Send PUSH_PROMISE frame on headers stream. + void SendPushPromise(QuicStreamId original_stream_id, + QuicStreamId promised_stream_id, + spdy::SpdyHeaderBlock headers); + + // Fetch response from cache for request headers enqueued into + // promised_headers_and_streams_ and send them on dedicated stream until + // reaches max_open_stream_ limit. + // Called when return value of GetNumOpenOutgoingStreams() changes: + // CloseStreamInner(); + // StreamDraining(); + // Note that updateFlowControlOnFinalReceivedByteOffset() won't change the + // return value becasue all push streams are impossible to become locally + // closed. Since a locally preserved stream becomes remotely closed after + // HandlePromisedPushRequests() starts to process it, and if it is reset + // locally afterwards, it will be immediately become closed and never get into + // locally_closed_stream_highest_offset_. So all the streams in this map + // are not outgoing streams. + void HandlePromisedPushRequests(); + + // Keep track of the highest stream id which has been sent in PUSH_PROMISE. + QuicStreamId highest_promised_stream_id_; + + // Promised streams which hasn't been created yet because of max_open_stream_ + // limit. New element is added to the end of the queue. + // Since outgoing stream is created in sequence, stream_id of each element in + // the queue also increases by 2 from previous one's. The front element's + // stream_id is always next_outgoing_stream_id_, and the last one is always + // highest_promised_stream_id_. + QuicDeque<PromisedStreamInfo> promised_streams_; + + QuicSimpleServerBackend* quic_simple_server_backend_; // Not owned. +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc new file mode 100644 index 00000000000..7203f128caa --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc @@ -0,0 +1,846 @@ +// 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/tools/quic_simple_server_session.h" + +#include <algorithm> +#include <memory> + +#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_containers.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_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/crypto_test_utils.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_sent_packet_manager_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_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_backend_response.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::AtLeast; +using testing::InSequence; +using testing::Invoke; +using testing::Return; +using testing::StrictMock; + +namespace quic { +namespace test { +namespace { +typedef QuicSimpleServerSession::PromisedStreamInfo PromisedStreamInfo; +} // namespace + +class QuicSimpleServerSessionPeer { + public: + static void SetCryptoStream(QuicSimpleServerSession* s, + QuicCryptoServerStream* crypto_stream) { + s->crypto_stream_.reset(crypto_stream); + s->RegisterStaticStream( + QuicUtils::GetCryptoStreamId(s->connection()->transport_version()), + crypto_stream); + } + + static QuicSpdyStream* CreateIncomingStream(QuicSimpleServerSession* s, + QuicStreamId id) { + return s->CreateIncomingStream(id); + } + + static QuicSimpleServerStream* CreateOutgoingUnidirectionalStream( + QuicSimpleServerSession* s) { + return s->CreateOutgoingUnidirectionalStream(); + } +}; + +namespace { + +const size_t kMaxStreamsForTest = 10; + +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), // NOLINT + session, + helper) {} + MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete; + MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) = + delete; + ~MockQuicCryptoServerStream() override {} + + MOCK_METHOD1(SendServerConfigUpdate, + void(const CachedNetworkParameters* cached_network_parameters)); + + void set_encryption_established(bool has_established) { + encryption_established_override_ = has_established; + } + + bool encryption_established() const override { + return QuicCryptoServerStream::encryption_established() || + encryption_established_override_; + } + + private: + bool encryption_established_override_ = false; +}; + +class MockQuicConnectionWithSendStreamData : public MockQuicConnection { + public: + MockQuicConnectionWithSendStreamData( + MockQuicConnectionHelper* helper, + MockAlarmFactory* alarm_factory, + Perspective perspective, + const ParsedQuicVersionVector& supported_versions) + : MockQuicConnection(helper, + alarm_factory, + perspective, + supported_versions) { + auto consume_all_data = [](QuicStreamId id, size_t write_length, + QuicStreamOffset offset, + StreamSendingState state) { + return QuicConsumedData(write_length, state != NO_FIN); + }; + ON_CALL(*this, SendStreamData(_, _, _, _)) + .WillByDefault(Invoke(consume_all_data)); + } + + MOCK_METHOD4(SendStreamData, + QuicConsumedData(QuicStreamId id, + size_t write_length, + QuicStreamOffset offset, + StreamSendingState state)); +}; + +class MockQuicSimpleServerSession : public QuicSimpleServerSession { + public: + MockQuicSimpleServerSession( + const QuicConfig& config, + QuicConnection* connection, + QuicSession::Visitor* visitor, + QuicCryptoServerStream::Helper* helper, + const QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSimpleServerSession(config, + CurrentSupportedVersions(), + connection, + visitor, + helper, + crypto_config, + compressed_certs_cache, + quic_simple_server_backend) {} + // Methods taking non-copyable types like SpdyHeaderBlock by value cannot be + // mocked directly. + size_t WritePushPromise(QuicStreamId original_stream_id, + QuicStreamId promised_stream_id, + spdy::SpdyHeaderBlock headers) override { + return WritePushPromiseMock(original_stream_id, promised_stream_id, + headers); + } + MOCK_METHOD3(WritePushPromiseMock, + size_t(QuicStreamId original_stream_id, + QuicStreamId promised_stream_id, + const spdy::SpdyHeaderBlock& headers)); + + MOCK_METHOD1(SendBlocked, void(QuicStreamId)); +}; + +class QuicSimpleServerSessionTest + : public QuicTestWithParam<ParsedQuicVersion> { + public: + bool ClearControlFrame(const QuicFrame& frame) { + DeleteFrame(&const_cast<QuicFrame&>(frame)); + return true; + } + + // 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; + } + + protected: + QuicSimpleServerSessionTest() + : crypto_config_(QuicCryptoServerConfig::TESTING, + QuicRandom::GetInstance(), + crypto_test_utils::ProofSourceForTesting(), + 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<MockQuicConnectionWithSendStreamData>( + &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions); + connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1)); + session_ = QuicMakeUnique<MockQuicSimpleServerSession>( + 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_); + + if (IsVersion99()) { + EXPECT_CALL(*connection_, SendControlFrame(_)) + .WillRepeatedly(Invoke( + this, + &QuicSimpleServerSessionTest::ClearMaxStreamIdControlFrame)); + } + session_->OnConfigNegotiated(); + } + + QuicStreamId GetNthClientInitiatedBidirectionalId(int n) { + return GetNthClientInitiatedBidirectionalStreamId( + connection_->transport_version(), n); + } + + QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) { + return quic::test::GetNthServerInitiatedUnidirectionalStreamId( + connection_->transport_version(), n); + } + + bool IsVersion99() const { + return connection_->transport_version() == QUIC_VERSION_99; + } + + void InjectStopSending(QuicStreamId stream_id, + QuicRstStreamErrorCode rst_stream_code) { + // 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. + return; + } + EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1); + QuicStopSendingFrame stop_sending( + kInvalidControlFrameId, stream_id, + static_cast<QuicApplicationErrorCode>(rst_stream_code)); + // Expect the RESET_STREAM that is generated in response to receiving a + // STOP_SENDING. + 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<MockQuicConnectionWithSendStreamData>* connection_; + QuicConfig config_; + QuicCryptoServerConfig crypto_config_; + QuicCompressedCertsCache compressed_certs_cache_; + QuicMemoryCacheBackend memory_cache_backend_; + std::unique_ptr<MockQuicSimpleServerSession> session_; + std::unique_ptr<CryptoHandshakeMessage> handshake_message_; + QuicConnectionVisitorInterface* visitor_; +}; + +INSTANTIATE_TEST_SUITE_P(Tests, + QuicSimpleServerSessionTest, + ::testing::ValuesIn(AllSupportedVersions())); + +TEST_P(QuicSimpleServerSessionTest, 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()); + + // Receive a reset (and send a RST in response). + QuicRstStreamFrame rst1(kInvalidControlFrameId, + GetNthClientInitiatedBidirectionalId(0), + QUIC_ERROR_PROCESSING_STREAM, 0); + EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1); + EXPECT_CALL(*connection_, SendControlFrame(_)); + if (!IsVersion99()) { + // For version 99, this is covered in InjectStopSending() + EXPECT_CALL(*connection_, + OnStreamReset(GetNthClientInitiatedBidirectionalId(0), + QUIC_RST_ACKNOWLEDGEMENT)); + } + visitor_->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. + InjectStopSending(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(QuicSimpleServerSessionTest, 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 (!IsVersion99()) { + EXPECT_CALL(*connection_, SendControlFrame(_)); + // For version 99, this is covered in InjectStopSending() + EXPECT_CALL(*connection_, + OnStreamReset(GetNthClientInitiatedBidirectionalId(0), + QUIC_RST_ACKNOWLEDGEMENT)); + } + visitor_->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. + InjectStopSending(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(QuicSimpleServerSessionTest, 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 (!IsVersion99()) { + EXPECT_CALL(*connection_, SendControlFrame(_)); + // For version 99, this is covered in InjectStopSending() + EXPECT_CALL(*connection_, + OnStreamReset(GetNthClientInitiatedBidirectionalId(0), + QUIC_RST_ACKNOWLEDGEMENT)); + } + visitor_->OnRstStream(rst); + // 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. + InjectStopSending(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(QuicSimpleServerSessionTest, CreateIncomingStreamDisconnected) { + // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. + if (GetParam() != AllSupportedVersions()[0]) { + return; + } + + // Tests that incoming stream creation fails when connection is not connected. + size_t initial_num_open_stream = session_->GetNumOpenIncomingStreams(); + QuicConnectionPeer::TearDownLocalConnectionState(connection_); + EXPECT_QUIC_BUG(QuicSimpleServerSessionPeer::CreateIncomingStream( + session_.get(), GetNthClientInitiatedBidirectionalId(0)), + "ShouldCreateIncomingStream called when disconnected"); + EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenIncomingStreams()); +} + +TEST_P(QuicSimpleServerSessionTest, CreateEvenIncomingDynamicStream) { + // Tests that incoming stream creation fails when given stream id is even. + size_t initial_num_open_stream = session_->GetNumOpenIncomingStreams(); + EXPECT_CALL(*connection_, + CloseConnection(QUIC_INVALID_STREAM_ID, + "Client created even numbered stream", _)); + QuicSimpleServerSessionPeer::CreateIncomingStream( + session_.get(), GetNthServerInitiatedUnidirectionalId(0)); + EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenIncomingStreams()); +} + +TEST_P(QuicSimpleServerSessionTest, CreateIncomingStream) { + QuicSpdyStream* stream = QuicSimpleServerSessionPeer::CreateIncomingStream( + session_.get(), GetNthClientInitiatedBidirectionalId(0)); + EXPECT_NE(nullptr, stream); + EXPECT_EQ(GetNthClientInitiatedBidirectionalId(0), stream->id()); +} + +TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamDisconnected) { + // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. + if (GetParam() != AllSupportedVersions()[0]) { + return; + } + + // Tests that outgoing stream creation fails when connection is not connected. + size_t initial_num_open_stream = session_->GetNumOpenOutgoingStreams(); + QuicConnectionPeer::TearDownLocalConnectionState(connection_); + EXPECT_QUIC_BUG( + QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( + session_.get()), + "ShouldCreateOutgoingUnidirectionalStream called when disconnected"); + + EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenOutgoingStreams()); +} + +TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUnencrypted) { + // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. + if (GetParam() != AllSupportedVersions()[0]) { + return; + } + + // Tests that outgoing stream creation fails when encryption has not yet been + // established. + size_t initial_num_open_stream = session_->GetNumOpenOutgoingStreams(); + EXPECT_QUIC_BUG( + QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( + session_.get()), + "Encryption not established so no outgoing stream created."); + EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenOutgoingStreams()); +} + +TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUptoLimit) { + // Tests that outgoing stream creation should not be affected by existing + // incoming stream and vice-versa. But when reaching the limit of max outgoing + // stream allowed, creation should fail. + + // Receive some data to initiate a incoming stream which should not effect + // creating outgoing streams. + QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0, + QuicStringPiece("HT")); + session_->OnStreamFrame(data1); + EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams()); + EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams()); + + session_->UnregisterStreamPriority( + QuicUtils::GetHeadersStreamId(connection_->transport_version()), + /*is_static=*/true); + // Assume encryption already established. + QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr); + MockQuicCryptoServerStream* crypto_stream = + new MockQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_, + session_.get(), &stream_helper_); + crypto_stream->set_encryption_established(true); + QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream); + session_->RegisterStreamPriority( + QuicUtils::GetHeadersStreamId(connection_->transport_version()), + /*is_static=*/true, QuicStream::kDefaultPriority); + + // Create push streams till reaching the upper limit of allowed open streams. + for (size_t i = 0; i < kMaxStreamsForTest; ++i) { + QuicSpdyStream* created_stream = + QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( + session_.get()); + EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i), created_stream->id()); + EXPECT_EQ(i + 1, session_->GetNumOpenOutgoingStreams()); + } + + // Continuing creating push stream would fail. + EXPECT_EQ(nullptr, + QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( + session_.get())); + EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams()); + + // Create peer initiated stream should have no problem. + QuicStreamFrame data2(GetNthClientInitiatedBidirectionalId(1), false, 0, + QuicStringPiece("HT")); + session_->OnStreamFrame(data2); + EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams()); +} + +TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) { + QuicStreamFrame frame(GetNthServerInitiatedUnidirectionalId(0), false, 0, + QuicStringPiece()); + EXPECT_CALL(*connection_, + CloseConnection(QUIC_INVALID_STREAM_ID, + "Client sent data on server push stream", _)); + session_->OnStreamFrame(frame); +} + +TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) { + // Tests that calling GetOrCreateDynamicStream() on an outgoing stream not + // promised yet should result close connection. + EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, + "Data for nonexistent stream", _)); + EXPECT_EQ(nullptr, + QuicSessionPeer::GetOrCreateDynamicStream( + session_.get(), GetNthServerInitiatedUnidirectionalId(1))); +} + +// In order to test the case where server push stream creation goes beyond +// limit, server push streams need to be hanging there instead of +// immediately closing after sending back response. +// To achieve this goal, this class resets flow control windows so that large +// responses will not be sent fully in order to prevent push streams from being +// closed immediately. +// Also adjust connection-level flow control window to ensure a large response +// can cause stream-level flow control blocked but not connection-level. +class QuicSimpleServerSessionServerPushTest + : public QuicSimpleServerSessionTest { + protected: + const size_t kStreamFlowControlWindowSize = 32 * 1024; // 32KB. + + QuicSimpleServerSessionServerPushTest() { + // Reset stream level flow control window to be 32KB. + QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow( + &config_, kStreamFlowControlWindowSize); + // Reset connection level flow control window to be 1.5 MB which is large + // enough that it won't block any stream to write before stream level flow + // control blocks it. + QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow( + &config_, kInitialSessionFlowControlWindowForTest); + + ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam()); + connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>( + &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions); + session_ = QuicMakeUnique<MockQuicSimpleServerSession>( + config_, connection_, &owner_, &stream_helper_, &crypto_config_, + &compressed_certs_cache_, &memory_cache_backend_); + session_->Initialize(); + QuicSessionPeer::GetMutableCryptoStream(session_.get()) + ->OnSuccessfulVersionNegotiation(supported_versions.front()); + // Needed to make new session flow control window and server push work. + + if (IsVersion99()) { + EXPECT_CALL(*connection_, SendControlFrame(_)) + .WillRepeatedly(Invoke(this, &QuicSimpleServerSessionServerPushTest:: + ClearMaxStreamIdControlFrame)); + } + session_->OnConfigNegotiated(); + + visitor_ = QuicConnectionPeer::GetVisitor(connection_); + + session_->UnregisterStreamPriority( + QuicUtils::GetHeadersStreamId(connection_->transport_version()), + /*is_static=*/true); + QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr); + // Assume encryption already established. + MockQuicCryptoServerStream* crypto_stream = new MockQuicCryptoServerStream( + &crypto_config_, &compressed_certs_cache_, session_.get(), + &stream_helper_); + + crypto_stream->set_encryption_established(true); + QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream); + session_->RegisterStreamPriority( + QuicUtils::GetHeadersStreamId(connection_->transport_version()), + /*is_static=*/true, QuicStream::kDefaultPriority); + } + + // Given |num_resources|, create this number of fake push resources and push + // them by sending PUSH_PROMISE for all and sending push responses for as much + // as possible(limited by kMaxStreamsForTest). + // If |num_resources| > kMaxStreamsForTest, the left over will be queued. + // Returns the length of the DATA frame header, or 0 if the version does not + // use DATA frames. + QuicByteCount PromisePushResources(size_t num_resources) { + // testing::InSequence seq; + // To prevent push streams from being closed the response need to be larger + // than stream flow control window so stream won't send the full body. + size_t body_size = 2 * kStreamFlowControlWindowSize; // 64KB. + + std::string request_url = "mail.google.com/"; + spdy::SpdyHeaderBlock request_headers; + std::string resource_host = "www.google.com"; + std::string partial_push_resource_path = "/server_push_src"; + std::list<QuicBackendResponse::ServerPushInfo> push_resources; + std::string scheme = "http"; + QuicByteCount data_frame_header_length = 0; + for (unsigned int i = 1; i <= num_resources; ++i) { + QuicStreamId stream_id = GetNthServerInitiatedUnidirectionalId(i - 1); + std::string path = + partial_push_resource_path + QuicTextUtils::Uint64ToString(i); + std::string url = scheme + "://" + resource_host + path; + QuicUrl resource_url = QuicUrl(url); + std::string body(body_size, 'a'); + std::string data; + data_frame_header_length = 0; + if (VersionHasDataFrameHeader(connection_->transport_version())) { + HttpEncoder encoder; + std::unique_ptr<char[]> buffer; + data_frame_header_length = + encoder.SerializeDataFrameHeader(body.length(), &buffer); + std::string header(buffer.get(), data_frame_header_length); + data = header + body; + } else { + data = body; + } + + memory_cache_backend_.AddSimpleResponse(resource_host, path, 200, data); + push_resources.push_back(QuicBackendResponse::ServerPushInfo( + resource_url, spdy::SpdyHeaderBlock(), QuicStream::kDefaultPriority, + body)); + // PUSH_PROMISED are sent for all the resources. + EXPECT_CALL(*session_, + WritePushPromiseMock(GetNthClientInitiatedBidirectionalId(0), + stream_id, _)); + if (i <= kMaxStreamsForTest) { + // |kMaxStreamsForTest| promised responses should be sent. + // Since flow control window is smaller than response body, not the + // whole body will be sent. + QuicStreamOffset offset = 0; + if (VersionHasDataFrameHeader(connection_->transport_version())) { + EXPECT_CALL(*connection_, + SendStreamData(stream_id, data_frame_header_length, + offset, NO_FIN)); + offset += data_frame_header_length; + } + EXPECT_CALL(*connection_, SendStreamData(stream_id, _, offset, NO_FIN)) + .WillOnce(Return(QuicConsumedData( + kStreamFlowControlWindowSize - offset, false))); + EXPECT_CALL(*session_, SendBlocked(stream_id)); + } + } + session_->PromisePushResources(request_url, push_resources, + GetNthClientInitiatedBidirectionalId(0), + request_headers); + return data_frame_header_length; + } + + void ConsumeHeadersStreamData() { + QuicStreamId headers_stream_id = + QuicUtils::GetHeadersStreamId(connection_->transport_version()); + EXPECT_CALL(*connection_, SendStreamData(headers_stream_id, _, _, _)) + .Times(AtLeast(1)); + } +}; + +INSTANTIATE_TEST_SUITE_P(Tests, + QuicSimpleServerSessionServerPushTest, + ::testing::ValuesIn(AllSupportedVersions())); + +// Tests that given more than kMaxStreamsForTest resources, all their +// PUSH_PROMISE's will be sent out and only kMaxStreamsForTest streams will be +// opened and send push response. +TEST_P(QuicSimpleServerSessionServerPushTest, TestPromisePushResources) { + ConsumeHeadersStreamData(); + size_t num_resources = kMaxStreamsForTest + 5; + PromisePushResources(num_resources); + EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams()); +} + +// Tests that after promised stream queued up, when an opened stream is marked +// draining, a queued promised stream will become open and send push response. +TEST_P(QuicSimpleServerSessionServerPushTest, + HandlePromisedPushRequestsAfterStreamDraining) { + ConsumeHeadersStreamData(); + size_t num_resources = kMaxStreamsForTest + 1; + QuicByteCount data_frame_header_length = PromisePushResources(num_resources); + QuicStreamId next_out_going_stream_id = + GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest); + + // After an open stream is marked draining, a new stream is expected to be + // created and a response sent on the stream. + QuicStreamOffset offset = 0; + if (VersionHasDataFrameHeader(connection_->transport_version())) { + EXPECT_CALL(*connection_, + SendStreamData(next_out_going_stream_id, + data_frame_header_length, offset, NO_FIN)); + offset += data_frame_header_length; + } + EXPECT_CALL(*connection_, + SendStreamData(next_out_going_stream_id, _, offset, NO_FIN)) + .WillOnce(Return( + QuicConsumedData(kStreamFlowControlWindowSize - offset, false))); + EXPECT_CALL(*session_, SendBlocked(next_out_going_stream_id)); + + if (IsVersion99()) { + // The PromisePushedResources call, above, will have used all available + // stream ids. For version 99, stream ids are not made available until + // a MAX_STREAM_ID frame is received. This emulates the reception of one. + // For pre-v-99, the node monitors its own stream usage and makes streams + // available as it closes/etc them. + session_->OnMaxStreamIdFrame( + QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10))); + } + session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0)); + // Number of open outgoing streams should still be the same, because a new + // stream is opened. And the queue should be empty. + EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams()); +} + +// Tests that after all resources are promised, a RST frame from client can +// prevent a promised resource to be send out. +TEST_P(QuicSimpleServerSessionServerPushTest, + ResetPromisedStreamToCancelServerPush) { + ConsumeHeadersStreamData(); + + // Having two extra resources to be send later. One of them will be reset, so + // when opened stream become close, only one will become open. + size_t num_resources = kMaxStreamsForTest + 2; + if (IsVersion99()) { + // V99 will send out a stream-id-blocked frame when the we desired to exceed + // the limit. This will clear the frames so that they do not block the later + // rst-stream frame. + EXPECT_CALL(*connection_, SendControlFrame(_)) + .WillOnce(Invoke( + this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame)); + } + QuicByteCount data_frame_header_length = PromisePushResources(num_resources); + + // Reset the last stream in the queue. It should be marked cancelled. + QuicStreamId stream_got_reset = + GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest + 1); + QuicRstStreamFrame rst(kInvalidControlFrameId, stream_got_reset, + QUIC_STREAM_CANCELLED, 0); + EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1); + EXPECT_CALL(*connection_, SendControlFrame(_)) + .WillOnce(Invoke( + this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame)); + EXPECT_CALL(*connection_, + OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT)); + visitor_->OnRstStream(rst); + + // When the first 2 streams becomes draining, the two queued up stream could + // be created. But since one of them was marked cancelled due to RST frame, + // only one queued resource will be sent out. + QuicStreamId stream_not_reset = + GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest); + InSequence s; + QuicStreamOffset offset = 0; + if (VersionHasDataFrameHeader(connection_->transport_version())) { + EXPECT_CALL(*connection_, + SendStreamData(stream_not_reset, data_frame_header_length, + offset, NO_FIN)); + offset += data_frame_header_length; + } + EXPECT_CALL(*connection_, SendStreamData(stream_not_reset, _, offset, NO_FIN)) + .WillOnce(Return( + QuicConsumedData(kStreamFlowControlWindowSize - offset, false))); + EXPECT_CALL(*session_, SendBlocked(stream_not_reset)); + + if (IsVersion99()) { + // The PromisePushedResources call, above, will have used all available + // stream ids. For version 99, stream ids are not made available until + // a MAX_STREAM_ID frame is received. This emulates the reception of one. + // For pre-v-99, the node monitors its own stream usage and makes streams + // available as it closes/etc them. + session_->OnMaxStreamIdFrame( + QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(11))); + } + session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0)); + session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(1)); +} + +// Tests that closing a open outgoing stream can trigger a promised resource in +// the queue to be send out. +TEST_P(QuicSimpleServerSessionServerPushTest, + CloseStreamToHandleMorePromisedStream) { + ConsumeHeadersStreamData(); + size_t num_resources = kMaxStreamsForTest + 1; + if (IsVersion99()) { + // V99 will send out a stream-id-blocked frame when the we desired to exceed + // the limit. This will clear the frames so that they do not block the later + // rst-stream frame. + EXPECT_CALL(*connection_, SendControlFrame(_)) + .WillOnce(Invoke( + this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame)); + } + QuicByteCount data_frame_header_length = PromisePushResources(num_resources); + QuicStreamId stream_to_open = + GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest); + + // Resetting 1st open stream will close the stream and give space for extra + // stream to be opened. + QuicStreamId stream_got_reset = GetNthServerInitiatedUnidirectionalId(0); + EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1); + EXPECT_CALL(*connection_, SendControlFrame(_)); + if (!IsVersion99()) { + // For version 99, this is covered in InjectStopSending() + EXPECT_CALL(*connection_, + OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT)); + } + QuicStreamOffset offset = 0; + if (VersionHasDataFrameHeader(connection_->transport_version())) { + EXPECT_CALL(*connection_, + SendStreamData(stream_to_open, data_frame_header_length, offset, + NO_FIN)); + offset += data_frame_header_length; + } + EXPECT_CALL(*connection_, SendStreamData(stream_to_open, _, offset, NO_FIN)) + .WillOnce(Return( + QuicConsumedData(kStreamFlowControlWindowSize - offset, false))); + + EXPECT_CALL(*session_, SendBlocked(stream_to_open)); + QuicRstStreamFrame rst(kInvalidControlFrameId, stream_got_reset, + QUIC_STREAM_CANCELLED, 0); + if (IsVersion99()) { + // The PromisePushedResources call, above, will have used all available + // stream ids. For version 99, stream ids are not made available until + // a MAX_STREAM_ID frame is received. This emulates the reception of one. + // For pre-v-99, the node monitors its own stream usage and makes streams + // available as it closes/etc them. + session_->OnMaxStreamIdFrame( + QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10))); + } + visitor_->OnRstStream(rst); + // 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. + InjectStopSending(stream_got_reset, QUIC_STREAM_CANCELLED); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc new file mode 100644 index 00000000000..4e25552d03c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc @@ -0,0 +1,341 @@ +// 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/tools/quic_simple_server_stream.h" + +#include <list> +#include <utility> + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_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/platform/api/quic_bug_tracker.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_text_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_session.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" + +using spdy::SpdyHeaderBlock; + +namespace quic { + +QuicSimpleServerStream::QuicSimpleServerStream( + QuicStreamId id, + QuicSpdySession* session, + StreamType type, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSpdyServerStreamBase(id, session, type), + content_length_(-1), + quic_simple_server_backend_(quic_simple_server_backend) {} + +QuicSimpleServerStream::QuicSimpleServerStream( + PendingStream pending, + QuicSpdySession* session, + StreamType type, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSpdyServerStreamBase(std::move(pending), session, type), + content_length_(-1), + quic_simple_server_backend_(quic_simple_server_backend) {} + +QuicSimpleServerStream::~QuicSimpleServerStream() { + quic_simple_server_backend_->CloseBackendResponseStream(this); +} + +void QuicSimpleServerStream::OnInitialHeadersComplete( + bool fin, + size_t frame_len, + const QuicHeaderList& header_list) { + QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_, + &request_headers_)) { + QUIC_DVLOG(1) << "Invalid headers"; + SendErrorResponse(); + } + ConsumeHeaderList(); +} + +void QuicSimpleServerStream::OnTrailingHeadersComplete( + bool fin, + size_t frame_len, + const QuicHeaderList& header_list) { + QUIC_BUG << "Server does not support receiving Trailers."; + SendErrorResponse(); +} + +void QuicSimpleServerStream::OnBodyAvailable() { + while (HasBytesToRead()) { + struct iovec iov; + if (GetReadableRegions(&iov, 1) == 0) { + // No more data to read. + break; + } + QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len + << " bytes."; + body_.append(static_cast<char*>(iov.iov_base), iov.iov_len); + + if (content_length_ >= 0 && + body_.size() > static_cast<uint64_t>(content_length_)) { + QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length (" + << content_length_ << ")."; + SendErrorResponse(); + return; + } + 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(); +} + +void QuicSimpleServerStream::PushResponse( + SpdyHeaderBlock push_request_headers) { + if (QuicUtils::IsClientInitiatedStreamId( + session()->connection()->transport_version(), id())) { + QUIC_BUG << "Client initiated stream shouldn't be used as promised stream."; + return; + } + // Change the stream state to emulate a client request. + request_headers_ = std::move(push_request_headers); + content_length_ = 0; + QUIC_DVLOG(1) << "Stream " << id() + << " ready to receive server push response."; + DCHECK(reading_stopped()); + + // Directly send response based on the emulated request_headers_. + SendResponse(); +} + +void QuicSimpleServerStream::SendResponse() { + if (request_headers_.empty()) { + QUIC_DVLOG(1) << "Request headers empty."; + SendErrorResponse(); + return; + } + + if (content_length_ > 0 && + static_cast<uint64_t>(content_length_) != body_.size()) { + QUIC_DVLOG(1) << "Content length (" << content_length_ << ") != body size (" + << body_.size() << ")."; + SendErrorResponse(); + return; + } + + if (!QuicContainsKey(request_headers_, ":authority") || + !QuicContainsKey(request_headers_, ":path")) { + QUIC_DVLOG(1) << "Request headers do not contain :authority or :path."; + SendErrorResponse(); + return; + } + + // Fetch the response from the backend interface and wait for callback once + // response is ready + quic_simple_server_backend_->FetchResponseFromBackend(request_headers_, body_, + this); +} + +QuicConnectionId QuicSimpleServerStream::connection_id() const { + return spdy_session()->connection_id(); +} + +QuicStreamId QuicSimpleServerStream::stream_id() const { + return id(); +} + +std::string QuicSimpleServerStream::peer_host() const { + return spdy_session()->peer_address().host().ToString(); +} + +void QuicSimpleServerStream::OnResponseBackendComplete( + const QuicBackendResponse* response, + std::list<QuicBackendResponse::ServerPushInfo> resources) { + if (response == nullptr) { + QUIC_DVLOG(1) << "Response not found in cache."; + SendNotFoundResponse(); + return; + } + + if (response->response_type() == QuicBackendResponse::CLOSE_CONNECTION) { + QUIC_DVLOG(1) << "Special response: closing connection."; + CloseConnectionWithDetails(QUIC_NO_ERROR, "Toy server forcing close"); + return; + } + + if (response->response_type() == QuicBackendResponse::IGNORE_REQUEST) { + QUIC_DVLOG(1) << "Special response: ignoring request."; + return; + } + + if (response->response_type() == QuicBackendResponse::BACKEND_ERR_RESPONSE) { + QUIC_DVLOG(1) << "Quic Proxy: Backend connection error."; + /*502 Bad Gateway + The server was acting as a gateway or proxy and received an + invalid response from the upstream server.*/ + SendErrorResponse(502); + return; + } + + // Examing response status, if it was not pure integer as typical h2 + // response status, send error response. Notice that + // QuicHttpResponseCache push urls are strictly authority + path only, + // scheme is not included (see |QuicHttpResponseCache::GetKey()|). + std::string request_url = request_headers_[":authority"].as_string() + + request_headers_[":path"].as_string(); + int response_code; + const SpdyHeaderBlock& response_headers = response->headers(); + if (!ParseHeaderStatusCode(response_headers, &response_code)) { + auto status = response_headers.find(":status"); + if (status == response_headers.end()) { + QUIC_LOG(WARNING) + << ":status not present in response from cache for request " + << request_url; + } else { + QUIC_LOG(WARNING) << "Illegal (non-integer) response :status from cache: " + << status->second << " for request " << request_url; + } + SendErrorResponse(); + return; + } + + if (QuicUtils::IsServerInitiatedStreamId( + session()->connection()->transport_version(), id())) { + // A server initiated stream is only used for a server push response, + // and only 200 and 30X response codes are supported for server push. + // This behavior mirrors the HTTP/2 implementation. + bool is_redirection = response_code / 100 == 3; + if (response_code != 200 && !is_redirection) { + QUIC_LOG(WARNING) << "Response to server push request " << request_url + << " result in response code " << response_code; + Reset(QUIC_STREAM_CANCELLED); + return; + } + } + + if (!resources.empty()) { + QUIC_DVLOG(1) << "Stream " << id() << " found " << resources.size() + << " push resources."; + QuicSimpleServerSession* session = + static_cast<QuicSimpleServerSession*>(spdy_session()); + session->PromisePushResources(request_url, resources, id(), + request_headers_); + } + + if (response->response_type() == QuicBackendResponse::INCOMPLETE_RESPONSE) { + QUIC_DVLOG(1) + << "Stream " << id() + << " sending an incomplete response, i.e. no trailer, no fin."; + SendIncompleteResponse(response->headers().Clone(), response->body()); + return; + } + + if (response->response_type() == QuicBackendResponse::STOP_SENDING) { + QUIC_DVLOG(1) + << "Stream " << id() + << " sending an incomplete response, i.e. no trailer, no fin."; + SendIncompleteResponse(response->headers().Clone(), response->body()); + SendStopSending(response->stop_sending_code()); + return; + } + + QUIC_DVLOG(1) << "Stream " << id() << " sending response."; + SendHeadersAndBodyAndTrailers(response->headers().Clone(), response->body(), + response->trailers().Clone()); +} + +void QuicSimpleServerStream::SendNotFoundResponse() { + QUIC_DVLOG(1) << "Stream " << id() << " sending not found response."; + SpdyHeaderBlock headers; + headers[":status"] = "404"; + headers["content-length"] = + QuicTextUtils::Uint64ToString(strlen(kNotFoundResponseBody)); + SendHeadersAndBody(std::move(headers), kNotFoundResponseBody); +} + +void QuicSimpleServerStream::SendErrorResponse() { + SendErrorResponse(0); +} + +void QuicSimpleServerStream::SendErrorResponse(int resp_code) { + QUIC_DVLOG(1) << "Stream " << id() << " sending error response."; + SpdyHeaderBlock headers; + if (resp_code <= 0) { + headers[":status"] = "500"; + } else { + headers[":status"] = QuicTextUtils::Uint64ToString(resp_code); + } + headers["content-length"] = + QuicTextUtils::Uint64ToString(strlen(kErrorResponseBody)); + SendHeadersAndBody(std::move(headers), kErrorResponseBody); +} + +void QuicSimpleServerStream::SendIncompleteResponse( + SpdyHeaderBlock response_headers, + QuicStringPiece body) { + QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = false) : " + << response_headers.DebugString(); + WriteHeaders(std::move(response_headers), /*fin=*/false, nullptr); + + QUIC_DLOG(INFO) << "Stream " << id() + << " writing body (fin = false) with size: " << body.size(); + if (!body.empty()) { + WriteOrBufferBody(body, /*fin=*/false); + } +} + +void QuicSimpleServerStream::SendHeadersAndBody( + SpdyHeaderBlock response_headers, + QuicStringPiece body) { + SendHeadersAndBodyAndTrailers(std::move(response_headers), body, + SpdyHeaderBlock()); +} + +void QuicSimpleServerStream::SendHeadersAndBodyAndTrailers( + SpdyHeaderBlock response_headers, + QuicStringPiece body, + SpdyHeaderBlock response_trailers) { + // Send the headers, with a FIN if there's nothing else to send. + bool send_fin = (body.empty() && response_trailers.empty()); + QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = " << send_fin + << ") : " << response_headers.DebugString(); + WriteHeaders(std::move(response_headers), send_fin, nullptr); + if (send_fin) { + // Nothing else to send. + return; + } + + // Send the body, with a FIN if there's no trailers to send. + send_fin = response_trailers.empty(); + QUIC_DLOG(INFO) << "Stream " << id() << " writing body (fin = " << send_fin + << ") with size: " << body.size(); + if (!body.empty() || send_fin) { + WriteOrBufferBody(body, send_fin); + } + if (send_fin) { + // Nothing else to send. + return; + } + + // Send the trailers. A FIN is always sent with trailers. + QUIC_DLOG(INFO) << "Stream " << id() << " writing trailers (fin = true): " + << response_trailers.DebugString(); + WriteTrailers(std::move(response_trailers), nullptr); +} + +const char* const QuicSimpleServerStream::kErrorResponseBody = "bad"; +const char* const QuicSimpleServerStream::kNotFoundResponseBody = + "file not found"; + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h new file mode 100644 index 00000000000..e3eaa12c967 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h @@ -0,0 +1,102 @@ +// 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_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.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/quic/tools/quic_backend_response.h" +#include "net/third_party/quiche/src/quic/tools/quic_simple_server_backend.h" +#include "net/third_party/quiche/src/spdy/core/spdy_framer.h" + +namespace quic { + +// All this does right now is aggregate data, and on fin, send an HTTP +// response. +class QuicSimpleServerStream : public QuicSpdyServerStreamBase, + public QuicSimpleServerBackend::RequestHandler { + public: + QuicSimpleServerStream(QuicStreamId id, + QuicSpdySession* session, + StreamType type, + QuicSimpleServerBackend* quic_simple_server_backend); + QuicSimpleServerStream(PendingStream pending, + QuicSpdySession* session, + StreamType type, + QuicSimpleServerBackend* quic_simple_server_backend); + QuicSimpleServerStream(const QuicSimpleServerStream&) = delete; + QuicSimpleServerStream& operator=(const QuicSimpleServerStream&) = delete; + ~QuicSimpleServerStream() override; + + // QuicSpdyStream + void OnInitialHeadersComplete(bool fin, + size_t frame_len, + const QuicHeaderList& header_list) override; + void OnTrailingHeadersComplete(bool fin, + size_t frame_len, + const QuicHeaderList& header_list) override; + + // QuicStream implementation called by the sequencer when there is + // data (or a FIN) to be read. + void OnBodyAvailable() override; + + // Make this stream start from as if it just finished parsing an incoming + // request whose headers are equivalent to |push_request_headers|. + // Doing so will trigger this toy stream to fetch response and send it back. + virtual void PushResponse(spdy::SpdyHeaderBlock push_request_headers); + + // The response body of error responses. + static const char* const kErrorResponseBody; + static const char* const kNotFoundResponseBody; + + // Implements QuicSimpleServerBackend::RequestHandler callbacks + QuicConnectionId connection_id() const override; + QuicStreamId stream_id() const override; + std::string peer_host() const override; + void OnResponseBackendComplete( + const QuicBackendResponse* response, + std::list<QuicBackendResponse::ServerPushInfo> resources) override; + + protected: + // Sends a basic 200 response using SendHeaders for the headers and WriteData + // for the body. + virtual void SendResponse(); + + // Sends a basic 500 response using SendHeaders for the headers and WriteData + // for the body. + virtual void SendErrorResponse(); + void SendErrorResponse(int resp_code); + + // Sends a basic 404 response using SendHeaders for the headers and WriteData + // for the body. + void SendNotFoundResponse(); + + // Sends the response header and body, but not the fin. + void SendIncompleteResponse(spdy::SpdyHeaderBlock response_headers, + QuicStringPiece body); + + void SendHeadersAndBody(spdy::SpdyHeaderBlock response_headers, + QuicStringPiece body); + void SendHeadersAndBodyAndTrailers(spdy::SpdyHeaderBlock response_headers, + QuicStringPiece body, + spdy::SpdyHeaderBlock response_trailers); + + spdy::SpdyHeaderBlock* request_headers() { return &request_headers_; } + + const std::string& body() { return body_; } + + // The parsed headers received from the client. + spdy::SpdyHeaderBlock request_headers_; + int64_t content_length_; + std::string body_; + + private: + QuicSimpleServerBackend* quic_simple_server_backend_; // Not owned. +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc new file mode 100644 index 00000000000..7572a75da84 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc @@ -0,0 +1,679 @@ +// 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/tools/quic_simple_server_stream.h" + +#include <list> +#include <memory> +#include <utility> + +#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_server_handshaker.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_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_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_test_utils.h" +#include "net/third_party/quiche/src/quic/tools/quic_backend_response.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_session.h" + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; +using testing::Invoke; +using testing::Return; +using testing::StrictMock; +using testing::ValuesIn; + +namespace quic { +namespace test { + +const size_t kFakeFrameLen = 60; +const size_t kErrorLength = strlen(QuicSimpleServerStream::kErrorResponseBody); +const size_t kDataFrameHeaderLength = 2; + +class TestStream : public QuicSimpleServerStream { + public: + TestStream(QuicStreamId stream_id, + QuicSpdySession* session, + StreamType type, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSimpleServerStream(stream_id, + session, + type, + quic_simple_server_backend) {} + + ~TestStream() override = default; + + MOCK_METHOD1(WriteHeadersMock, void(bool fin)); + + size_t WriteHeaders(spdy::SpdyHeaderBlock header_block, + bool fin, + QuicReferenceCountedPointer<QuicAckListenerInterface> + ack_listener) override { + WriteHeadersMock(fin); + return 0; + } + + // Expose protected QuicSimpleServerStream methods. + void DoSendResponse() { SendResponse(); } + void DoSendErrorResponse() { SendErrorResponse(); } + + spdy::SpdyHeaderBlock* mutable_headers() { return &request_headers_; } + void set_body(std::string body) { body_ = std::move(body); } + const std::string& body() const { return body_; } + int content_length() const { return content_length_; } + + QuicStringPiece GetHeader(QuicStringPiece key) const { + auto it = request_headers_.find(key); + DCHECK(it != request_headers_.end()); + return it->second; + } +}; + +namespace { + +class MockQuicSimpleServerSession : public QuicSimpleServerSession { + public: + const size_t kMaxStreamsForTest = 100; + + MockQuicSimpleServerSession( + QuicConnection* connection, + MockQuicSessionVisitor* owner, + MockQuicCryptoServerStreamHelper* helper, + QuicCryptoServerConfig* crypto_config, + QuicCompressedCertsCache* compressed_certs_cache, + QuicSimpleServerBackend* quic_simple_server_backend) + : QuicSimpleServerSession(DefaultQuicConfig(), + CurrentSupportedVersions(), + connection, + owner, + helper, + crypto_config, + compressed_certs_cache, + quic_simple_server_backend) { + QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest); + QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest); + ON_CALL(*this, WritevData(_, _, _, _, _)) + .WillByDefault(Invoke(MockQuicSession::ConsumeData)); + } + + MockQuicSimpleServerSession(const MockQuicSimpleServerSession&) = delete; + MockQuicSimpleServerSession& operator=(const MockQuicSimpleServerSession&) = + delete; + ~MockQuicSimpleServerSession() override = default; + + MOCK_METHOD3(OnConnectionClosed, + void(QuicErrorCode error, + const std::string& error_details, + ConnectionCloseSource source)); + MOCK_METHOD1(CreateIncomingStream, QuicSpdyStream*(QuicStreamId id)); + MOCK_METHOD5(WritevData, + QuicConsumedData(QuicStream* stream, + QuicStreamId id, + size_t write_length, + QuicStreamOffset offset, + StreamSendingState state)); + MOCK_METHOD4(OnStreamHeaderList, + void(QuicStreamId stream_id, + bool fin, + size_t frame_len, + const QuicHeaderList& header_list)); + MOCK_METHOD2(OnStreamHeadersPriority, + void(QuicStreamId stream_id, spdy::SpdyPriority priority)); + MOCK_METHOD3(SendRstStream, + void(QuicStreamId stream_id, + QuicRstStreamErrorCode error, + QuicStreamOffset bytes_written)); + MOCK_METHOD1(OnHeadersHeadOfLineBlocking, void(QuicTime::Delta delta)); + // Matchers cannot be used on non-copyable types like SpdyHeaderBlock. + void PromisePushResources( + const std::string& request_url, + const std::list<QuicBackendResponse::ServerPushInfo>& resources, + QuicStreamId original_stream_id, + const spdy::SpdyHeaderBlock& original_request_headers) override { + original_request_headers_ = original_request_headers.Clone(); + PromisePushResourcesMock(request_url, resources, original_stream_id, + original_request_headers); + } + MOCK_METHOD4(PromisePushResourcesMock, + void(const std::string&, + const std::list<QuicBackendResponse::ServerPushInfo>&, + QuicStreamId, + const spdy::SpdyHeaderBlock&)); + + using QuicSession::ActivateStream; + + MOCK_METHOD1(OnStopSendingReceived, void(const QuicStopSendingFrame& frame)); + + spdy::SpdyHeaderBlock original_request_headers_; +}; + +class QuicSimpleServerStreamTest : public QuicTestWithParam<ParsedQuicVersion> { + public: + QuicSimpleServerStreamTest() + : connection_( + new StrictMock<MockQuicConnection>(&helper_, + &alarm_factory_, + Perspective::IS_SERVER, + SupportedVersions(GetParam()))), + crypto_config_(new QuicCryptoServerConfig( + QuicCryptoServerConfig::TESTING, + QuicRandom::GetInstance(), + crypto_test_utils::ProofSourceForTesting(), + KeyExchangeSource::Default(), + TlsServerHandshaker::CreateSslCtx())), + compressed_certs_cache_( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), + session_(connection_, + &session_owner_, + &session_helper_, + crypto_config_.get(), + &compressed_certs_cache_, + &memory_cache_backend_), + quic_response_(new QuicBackendResponse), + body_("hello world") { + connection_->set_visitor(&session_); + header_list_.OnHeaderBlockStart(); + header_list_.OnHeader(":authority", "www.google.com"); + header_list_.OnHeader(":path", "/"); + header_list_.OnHeader(":method", "POST"); + header_list_.OnHeader(":version", "HTTP/1.1"); + header_list_.OnHeader("content-length", "11"); + + header_list_.OnHeaderBlockEnd(128, 128); + + // New streams rely on having the peer's flow control receive window + // negotiated in the config. + session_.config()->SetInitialStreamFlowControlWindowToSend( + kInitialStreamFlowControlWindowForTest); + session_.config()->SetInitialSessionFlowControlWindowToSend( + kInitialSessionFlowControlWindowForTest); + stream_ = new StrictMock<TestStream>( + GetNthClientInitiatedBidirectionalStreamId( + connection_->transport_version(), 0), + &session_, BIDIRECTIONAL, &memory_cache_backend_); + // Register stream_ in dynamic_stream_map_ and pass ownership to session_. + session_.ActivateStream(QuicWrapUnique(stream_)); + connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1)); + } + + const std::string& StreamBody() { return stream_->body(); } + + std::string StreamHeadersValue(const std::string& key) { + return (*stream_->mutable_headers())[key].as_string(); + } + + bool IsVersion99() const { + return connection_->transport_version() == QUIC_VERSION_99; + } + + bool HasFrameHeader() const { + return VersionHasDataFrameHeader(connection_->transport_version()); + } + + spdy::SpdyHeaderBlock response_headers_; + MockQuicConnectionHelper helper_; + MockAlarmFactory alarm_factory_; + StrictMock<MockQuicConnection>* connection_; + StrictMock<MockQuicSessionVisitor> session_owner_; + StrictMock<MockQuicCryptoServerStreamHelper> session_helper_; + std::unique_ptr<QuicCryptoServerConfig> crypto_config_; + QuicCompressedCertsCache compressed_certs_cache_; + QuicMemoryCacheBackend memory_cache_backend_; + StrictMock<MockQuicSimpleServerSession> session_; + StrictMock<TestStream>* stream_; // Owned by session_. + std::unique_ptr<QuicBackendResponse> quic_response_; + std::string body_; + QuicHeaderList header_list_; + HttpEncoder encoder_; +}; + +INSTANTIATE_TEST_SUITE_P(Tests, + QuicSimpleServerStreamTest, + ValuesIn(AllSupportedVersions())); + +TEST_P(QuicSimpleServerStreamTest, TestFraming) { + EXPECT_CALL(session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); + stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); + 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_; + stream_->OnStreamFrame( + QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); + EXPECT_EQ("11", StreamHeadersValue("content-length")); + EXPECT_EQ("/", StreamHeadersValue(":path")); + EXPECT_EQ("POST", StreamHeadersValue(":method")); + EXPECT_EQ(body_, StreamBody()); +} + +TEST_P(QuicSimpleServerStreamTest, TestFramingOnePacket) { + EXPECT_CALL(session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); + + stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); + 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_; + stream_->OnStreamFrame( + QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); + EXPECT_EQ("11", StreamHeadersValue("content-length")); + EXPECT_EQ("/", StreamHeadersValue(":path")); + EXPECT_EQ("POST", StreamHeadersValue(":method")); + EXPECT_EQ(body_, StreamBody()); +} + +TEST_P(QuicSimpleServerStreamTest, SendQuicRstStreamNoErrorInStopReading) { + EXPECT_CALL(session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); + + EXPECT_FALSE(stream_->fin_received()); + EXPECT_FALSE(stream_->rst_received()); + + stream_->set_fin_sent(true); + stream_->CloseWriteSide(); + + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(1); + stream_->StopReading(); +} + +TEST_P(QuicSimpleServerStreamTest, TestFramingExtraData) { + InSequence seq; + std::string large_body = "hello world!!!!!!"; + + // We'll automatically write out an error (headers + body) + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, kDataFrameHeaderLength, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); + + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); + + stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); + 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_; + + stream_->OnStreamFrame( + QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); + // Content length is still 11. This will register as an error and we won't + // accept the bytes. + header_length = + encoder_.SerializeDataFrameHeader(large_body.length(), &buffer); + header = std::string(buffer.get(), header_length); + std::string data2 = HasFrameHeader() ? header + large_body : large_body; + stream_->OnStreamFrame( + QuicStreamFrame(stream_->id(), /*fin=*/true, data.size(), data2)); + EXPECT_EQ("11", StreamHeadersValue("content-length")); + EXPECT_EQ("/", StreamHeadersValue(":path")); + EXPECT_EQ("POST", StreamHeadersValue(":method")); +} + +TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus) { + // Send an illegal response with response status not supported by HTTP/2. + spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); + (*request_headers)[":path"] = "/bar"; + (*request_headers)[":authority"] = "www.google.com"; + (*request_headers)[":version"] = "HTTP/1.1"; + (*request_headers)[":method"] = "GET"; + + response_headers_[":version"] = "HTTP/1.1"; + // HTTP/2 only supports integer responsecode, so "200 OK" is illegal. + response_headers_[":status"] = "200 OK"; + response_headers_["content-length"] = "5"; + std::string body = "Yummm"; + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = + encoder_.SerializeDataFrameHeader(body.length(), &buffer); + + memory_cache_backend_.AddResponse("www.google.com", "/bar", + std::move(response_headers_), body); + + stream_->set_fin_received(true); + + InSequence s; + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); + + stream_->DoSendResponse(); + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus2) { + // Send an illegal response with response status not supported by HTTP/2. + spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); + (*request_headers)[":path"] = "/bar"; + (*request_headers)[":authority"] = "www.google.com"; + (*request_headers)[":version"] = "HTTP/1.1"; + (*request_headers)[":method"] = "GET"; + + response_headers_[":version"] = "HTTP/1.1"; + // HTTP/2 only supports 3-digit-integer, so "+200" is illegal. + response_headers_[":status"] = "+200"; + response_headers_["content-length"] = "5"; + std::string body = "Yummm"; + + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = + encoder_.SerializeDataFrameHeader(body.length(), &buffer); + + memory_cache_backend_.AddResponse("www.google.com", "/bar", + std::move(response_headers_), body); + + stream_->set_fin_received(true); + + InSequence s; + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); + + stream_->DoSendResponse(); + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, SendPushResponseWith404Response) { + // Create a new promised stream with even id(). + auto promised_stream = new StrictMock<TestStream>( + GetNthServerInitiatedUnidirectionalStreamId( + connection_->transport_version(), 0), + &session_, WRITE_UNIDIRECTIONAL, &memory_cache_backend_); + session_.ActivateStream(QuicWrapUnique(promised_stream)); + + // Send a push response with response status 404, which will be regarded as + // invalid server push response. + spdy::SpdyHeaderBlock* request_headers = promised_stream->mutable_headers(); + (*request_headers)[":path"] = "/bar"; + (*request_headers)[":authority"] = "www.google.com"; + (*request_headers)[":version"] = "HTTP/1.1"; + (*request_headers)[":method"] = "GET"; + + response_headers_[":version"] = "HTTP/1.1"; + response_headers_[":status"] = "404"; + response_headers_["content-length"] = "8"; + std::string body = "NotFound"; + + memory_cache_backend_.AddResponse("www.google.com", "/bar", + std::move(response_headers_), body); + + InSequence s; + EXPECT_CALL(session_, + SendRstStream(promised_stream->id(), QUIC_STREAM_CANCELLED, 0)); + + promised_stream->DoSendResponse(); +} + +TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) { + // Add a request and response with valid headers. + spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); + (*request_headers)[":path"] = "/bar"; + (*request_headers)[":authority"] = "www.google.com"; + (*request_headers)[":version"] = "HTTP/1.1"; + (*request_headers)[":method"] = "GET"; + + response_headers_[":version"] = "HTTP/1.1"; + response_headers_[":status"] = "200"; + response_headers_["content-length"] = "5"; + std::string body = "Yummm"; + + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = + encoder_.SerializeDataFrameHeader(body.length(), &buffer); + + memory_cache_backend_.AddResponse("www.google.com", "/bar", + std::move(response_headers_), body); + stream_->set_fin_received(true); + + InSequence s; + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, body.length(), _, FIN)); + + stream_->DoSendResponse(); + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, SendResponseWithPushResources) { + // Tests that if a response has push resources to be send, SendResponse() will + // call PromisePushResources() to handle these resources. + + // Add a request and response with valid headers into cache. + std::string host = "www.google.com"; + std::string request_path = "/foo"; + std::string body = "Yummm"; + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = + encoder_.SerializeDataFrameHeader(body.length(), &buffer); + QuicBackendResponse::ServerPushInfo push_info( + QuicUrl(host, "/bar"), spdy::SpdyHeaderBlock(), + QuicStream::kDefaultPriority, "Push body"); + std::list<QuicBackendResponse::ServerPushInfo> push_resources; + push_resources.push_back(push_info); + memory_cache_backend_.AddSimpleResponseWithServerPushResources( + host, request_path, 200, body, push_resources); + + spdy::SpdyHeaderBlock* request_headers = stream_->mutable_headers(); + (*request_headers)[":path"] = request_path; + (*request_headers)[":authority"] = host; + (*request_headers)[":version"] = "HTTP/1.1"; + (*request_headers)[":method"] = "GET"; + + stream_->set_fin_received(true); + InSequence s; + EXPECT_CALL(session_, PromisePushResourcesMock( + host + request_path, _, + GetNthClientInitiatedBidirectionalStreamId( + connection_->transport_version(), 0), + _)); + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, header_length, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, body.length(), _, FIN)); + stream_->DoSendResponse(); + EXPECT_EQ(*request_headers, session_.original_request_headers_); +} + +TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) { + // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. + if (GetParam() != AllSupportedVersions()[0]) { + return; + } + + // Calling PushResponse() on a client initialted stream is never supposed to + // happen. + EXPECT_QUIC_BUG(stream_->PushResponse(spdy::SpdyHeaderBlock()), + "Client initiated stream" + " shouldn't be used as promised stream."); +} + +TEST_P(QuicSimpleServerStreamTest, PushResponseOnServerInitiatedStream) { + // Tests that PushResponse() should take the given headers as request headers + // and fetch response from cache, and send it out. + + // Create a stream with even stream id and test against this stream. + const QuicStreamId kServerInitiatedStreamId = + GetNthServerInitiatedUnidirectionalStreamId( + connection_->transport_version(), 0); + // Create a server initiated stream and pass it to session_. + auto server_initiated_stream = + new StrictMock<TestStream>(kServerInitiatedStreamId, &session_, + WRITE_UNIDIRECTIONAL, &memory_cache_backend_); + session_.ActivateStream(QuicWrapUnique(server_initiated_stream)); + + const std::string kHost = "www.foo.com"; + const std::string kPath = "/bar"; + spdy::SpdyHeaderBlock headers; + headers[":path"] = kPath; + headers[":authority"] = kHost; + headers[":version"] = "HTTP/1.1"; + headers[":method"] = "GET"; + + response_headers_[":version"] = "HTTP/1.1"; + response_headers_[":status"] = "200"; + response_headers_["content-length"] = "5"; + const std::string kBody = "Hello"; + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = + encoder_.SerializeDataFrameHeader(kBody.length(), &buffer); + memory_cache_backend_.AddResponse(kHost, kPath, std::move(response_headers_), + kBody); + + // Call PushResponse() should trigger stream to fetch response from cache + // and send it back. + InSequence s; + EXPECT_CALL(*server_initiated_stream, WriteHeadersMock(false)); + + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, kServerInitiatedStreamId, header_length, + _, NO_FIN)); + } + EXPECT_CALL(session_, + WritevData(_, kServerInitiatedStreamId, kBody.size(), _, FIN)); + server_initiated_stream->PushResponse(std::move(headers)); + EXPECT_EQ(kPath, server_initiated_stream->GetHeader(":path")); + EXPECT_EQ("GET", server_initiated_stream->GetHeader(":method")); +} + +TEST_P(QuicSimpleServerStreamTest, TestSendErrorResponse) { + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); + + stream_->set_fin_received(true); + + InSequence s; + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + if (HasFrameHeader()) { + EXPECT_CALL(session_, WritevData(_, _, kDataFrameHeaderLength, _, NO_FIN)); + } + EXPECT_CALL(session_, WritevData(_, _, kErrorLength, _, FIN)); + + stream_->DoSendErrorResponse(); + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, InvalidMultipleContentLength) { + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); + + spdy::SpdyHeaderBlock request_headers; + // \000 is a way to write the null byte when followed by a literal digit. + header_list_.OnHeader("content-length", QuicStringPiece("11\00012", 5)); + + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + EXPECT_CALL(session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); + stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_); + + EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->reading_stopped()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, InvalidLeadingNullContentLength) { + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); + + spdy::SpdyHeaderBlock request_headers; + // \000 is a way to write the null byte when followed by a literal digit. + header_list_.OnHeader("content-length", QuicStringPiece("\00012", 3)); + + EXPECT_CALL(*stream_, WriteHeadersMock(false)); + EXPECT_CALL(session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); + stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_); + + EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_TRUE(stream_->reading_stopped()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, ValidMultipleContentLength) { + spdy::SpdyHeaderBlock request_headers; + // \000 is a way to write the null byte when followed by a literal digit. + header_list_.OnHeader("content-length", QuicStringPiece("11\00011", 5)); + + stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); + + EXPECT_EQ(11, stream_->content_length()); + EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_)); + EXPECT_FALSE(stream_->reading_stopped()); + EXPECT_FALSE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, + DoNotSendQuicRstStreamNoErrorWithRstReceived) { + InSequence s; + EXPECT_FALSE(stream_->reading_stopped()); + + EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0); + EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _)).Times(1); + QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(), + QUIC_STREAM_CANCELLED, 1234); + stream_->OnStreamReset(rst_frame); + if (IsVersion99()) { + // For V99 receiving a RST_STREAM causes a 1-way close; the test requires + // a full close. A CloseWriteSide closes the other half of the stream. + // Everything should then work properly. + stream_->CloseWriteSide(); + } + EXPECT_TRUE(stream_->reading_stopped()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_P(QuicSimpleServerStreamTest, InvalidHeadersWithFin) { + char arr[] = { + 0x3a, 0x68, 0x6f, 0x73, // :hos + 0x74, 0x00, 0x00, 0x00, // t... + 0x00, 0x00, 0x00, 0x00, // .... + 0x07, 0x3a, 0x6d, 0x65, // .:me + 0x74, 0x68, 0x6f, 0x64, // thod + 0x00, 0x00, 0x00, 0x03, // .... + 0x47, 0x45, 0x54, 0x00, // GET. + 0x00, 0x00, 0x05, 0x3a, // ...: + 0x70, 0x61, 0x74, 0x68, // path + 0x00, 0x00, 0x00, 0x04, // .... + 0x2f, 0x66, 0x6f, 0x6f, // /foo + 0x00, 0x00, 0x00, 0x07, // .... + 0x3a, 0x73, 0x63, 0x68, // :sch + 0x65, 0x6d, 0x65, 0x00, // eme. + 0x00, 0x00, 0x00, 0x00, // .... + 0x00, 0x00, 0x08, 0x3a, // ...: + 0x76, 0x65, 0x72, 0x73, // vers + 0x96, 0x6f, 0x6e, 0x00, // <i(69)>on. + 0x00, 0x00, 0x08, 0x48, // ...H + 0x54, 0x54, 0x50, 0x2f, // TTP/ + 0x31, 0x2e, 0x31, // 1.1 + }; + QuicStringPiece data(arr, QUIC_ARRAYSIZE(arr)); + QuicStreamFrame frame(stream_->id(), true, 0, data); + // Verify that we don't crash when we get a invalid headers in stream frame. + stream_->OnStreamFrame(frame); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc new file mode 100644 index 00000000000..3d7ef028e8a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc @@ -0,0 +1,273 @@ +// 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/tools/quic_spdy_client_base.h" + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.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/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_text_utils.h" + +using spdy::SpdyHeaderBlock; + +namespace quic { + +void QuicSpdyClientBase::ClientQuicDataToResend::Resend() { + client_->SendRequest(*headers_, body_, fin_); + headers_ = nullptr; +} + +QuicSpdyClientBase::QuicDataToResend::QuicDataToResend( + std::unique_ptr<SpdyHeaderBlock> headers, + QuicStringPiece body, + bool fin) + : headers_(std::move(headers)), body_(body), fin_(fin) {} + +QuicSpdyClientBase::QuicDataToResend::~QuicDataToResend() = default; + +QuicSpdyClientBase::QuicSpdyClientBase( + const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, + std::unique_ptr<NetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier) + : QuicClientBase(server_id, + supported_versions, + config, + helper, + alarm_factory, + std::move(network_helper), + std::move(proof_verifier)), + store_response_(false), + latest_response_code_(-1) {} + +QuicSpdyClientBase::~QuicSpdyClientBase() { + // We own the push promise index. We need to explicitly kill + // the session before the push promise index goes out of scope. + ResetSession(); +} + +QuicSpdyClientSession* QuicSpdyClientBase::client_session() { + return static_cast<QuicSpdyClientSession*>(QuicClientBase::session()); +} + +void QuicSpdyClientBase::InitializeSession() { + client_session()->Initialize(); + client_session()->CryptoConnect(); +} + +void QuicSpdyClientBase::OnClose(QuicSpdyStream* stream) { + DCHECK(stream != nullptr); + QuicSpdyClientStream* client_stream = + static_cast<QuicSpdyClientStream*>(stream); + + const SpdyHeaderBlock& response_headers = client_stream->response_headers(); + if (response_listener_ != nullptr) { + response_listener_->OnCompleteResponse(stream->id(), response_headers, + client_stream->data()); + } + + // Store response headers and body. + if (store_response_) { + auto status = response_headers.find(":status"); + if (status == response_headers.end() || + !QuicTextUtils::StringToInt(status->second, &latest_response_code_)) { + QUIC_LOG(ERROR) << "Invalid response headers"; + } + latest_response_headers_ = response_headers.DebugString(); + preliminary_response_headers_ = + client_stream->preliminary_headers().DebugString(); + latest_response_header_block_ = response_headers.Clone(); + latest_response_body_ = client_stream->data(); + latest_response_trailers_ = + client_stream->received_trailers().DebugString(); + } +} + +std::unique_ptr<QuicSession> QuicSpdyClientBase::CreateQuicClientSession( + const quic::ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) { + return QuicMakeUnique<QuicSpdyClientSession>( + *config(), supported_versions, connection, server_id(), crypto_config(), + &push_promise_index_); +} + +void QuicSpdyClientBase::SendRequest(const SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin) { + QuicClientPushPromiseIndex::TryHandle* handle; + QuicAsyncStatus rv = push_promise_index()->Try(headers, this, &handle); + if (rv == QUIC_SUCCESS) + return; + + if (rv == QUIC_PENDING) { + // May need to retry request if asynchronous rendezvous fails. + AddPromiseDataToResend(headers, body, fin); + return; + } + + QuicSpdyClientStream* stream = CreateClientStream(); + if (stream == nullptr) { + QUIC_BUG << "stream creation failed!"; + return; + } + stream->SendRequest(headers.Clone(), body, fin); + // Record this in case we need to resend. + MaybeAddDataToResend(headers, body, fin); +} + +void QuicSpdyClientBase::SendRequestAndWaitForResponse( + const SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin) { + SendRequest(headers, body, fin); + while (WaitForEvents()) { + } +} + +void QuicSpdyClientBase::SendRequestsAndWaitForResponse( + const std::vector<std::string>& url_list) { + for (size_t i = 0; i < url_list.size(); ++i) { + SpdyHeaderBlock headers; + if (!SpdyUtils::PopulateHeaderBlockFromUrl(url_list[i], &headers)) { + QUIC_BUG << "Unable to create request"; + continue; + } + SendRequest(headers, "", true); + } + while (WaitForEvents()) { + } +} + +QuicSpdyClientStream* QuicSpdyClientBase::CreateClientStream() { + if (!connected()) { + return nullptr; + } + + auto* stream = static_cast<QuicSpdyClientStream*>( + client_session()->CreateOutgoingBidirectionalStream()); + if (stream) { + stream->SetPriority(QuicStream::kDefaultPriority); + stream->set_visitor(this); + } + return stream; +} + +int QuicSpdyClientBase::GetNumSentClientHellosFromSession() { + return client_session()->GetNumSentClientHellos(); +} + +int QuicSpdyClientBase::GetNumReceivedServerConfigUpdatesFromSession() { + return client_session()->GetNumReceivedServerConfigUpdates(); +} + +void QuicSpdyClientBase::MaybeAddDataToResend(const SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin) { + if (!GetQuicReloadableFlag(enable_quic_stateless_reject_support)) { + return; + } + + if (client_session()->IsCryptoHandshakeConfirmed()) { + // The handshake is confirmed. No need to continue saving requests to + // resend. + data_to_resend_on_connect_.clear(); + return; + } + + // The handshake is not confirmed. Push the data onto the queue of data to + // resend if statelessly rejected. + std::unique_ptr<SpdyHeaderBlock> new_headers( + new SpdyHeaderBlock(headers.Clone())); + std::unique_ptr<QuicDataToResend> data_to_resend( + new ClientQuicDataToResend(std::move(new_headers), body, fin, this)); + MaybeAddQuicDataToResend(std::move(data_to_resend)); +} + +void QuicSpdyClientBase::MaybeAddQuicDataToResend( + std::unique_ptr<QuicDataToResend> data_to_resend) { + data_to_resend_on_connect_.push_back(std::move(data_to_resend)); +} + +void QuicSpdyClientBase::ClearDataToResend() { + data_to_resend_on_connect_.clear(); +} + +void QuicSpdyClientBase::ResendSavedData() { + // Calling Resend will re-enqueue the data, so swap out + // data_to_resend_on_connect_ before iterating. + std::vector<std::unique_ptr<QuicDataToResend>> old_data; + old_data.swap(data_to_resend_on_connect_); + for (const auto& data : old_data) { + data->Resend(); + } +} + +void QuicSpdyClientBase::AddPromiseDataToResend(const SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin) { + std::unique_ptr<SpdyHeaderBlock> new_headers( + new SpdyHeaderBlock(headers.Clone())); + push_promise_data_to_resend_.reset( + new ClientQuicDataToResend(std::move(new_headers), body, fin, this)); +} + +bool QuicSpdyClientBase::CheckVary(const SpdyHeaderBlock& client_request, + const SpdyHeaderBlock& promise_request, + const SpdyHeaderBlock& promise_response) { + return true; +} + +void QuicSpdyClientBase::OnRendezvousResult(QuicSpdyStream* stream) { + std::unique_ptr<ClientQuicDataToResend> data_to_resend = + std::move(push_promise_data_to_resend_); + if (stream) { + stream->set_visitor(this); + stream->OnBodyAvailable(); + } else if (data_to_resend) { + data_to_resend->Resend(); + } +} + +size_t QuicSpdyClientBase::latest_response_code() const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return latest_response_code_; +} + +const std::string& QuicSpdyClientBase::latest_response_headers() const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return latest_response_headers_; +} + +const std::string& QuicSpdyClientBase::preliminary_response_headers() const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return preliminary_response_headers_; +} + +const SpdyHeaderBlock& QuicSpdyClientBase::latest_response_header_block() + const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return latest_response_header_block_; +} + +const std::string& QuicSpdyClientBase::latest_response_body() const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return latest_response_body_; +} + +const std::string& QuicSpdyClientBase::latest_response_trailers() const { + QUIC_BUG_IF(!store_response_) << "Response not stored!"; + return latest_response_trailers_; +} + +bool QuicSpdyClientBase::HasActiveRequests() { + return client_session()->HasActiveRequestStreams(); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h new file mode 100644 index 00000000000..03a981e1b1c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h @@ -0,0 +1,216 @@ +// 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. + +// A base class for the toy client, which connects to a specified port and sends +// QUIC request to that endpoint. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_ +#define QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_ + +#include <string> + +#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h" +#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.h" +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" +#include "net/third_party/quiche/src/quic/tools/quic_client_base.h" + +namespace quic { + +class ProofVerifier; +class QuicServerId; + +class QuicSpdyClientBase : public QuicClientBase, + public QuicClientPushPromiseIndex::Delegate, + public QuicSpdyStream::Visitor { + public: + // A ResponseListener is notified when a complete response is received. + class ResponseListener { + public: + ResponseListener() {} + virtual ~ResponseListener() {} + virtual void OnCompleteResponse( + QuicStreamId id, + const spdy::SpdyHeaderBlock& response_headers, + const std::string& response_body) = 0; + }; + + // The client uses these objects to keep track of any data to resend upon + // receipt of a stateless reject. Recall that the client API allows callers + // to optimistically send data to the server prior to handshake-confirmation. + // If the client subsequently receives a stateless reject, it must tear down + // its existing session, create a new session, and resend all previously sent + // data. It uses these objects to keep track of all the sent data, and to + // resend the data upon a subsequent connection. + class QuicDataToResend { + public: + // |headers| may be null, since it's possible to send data without headers. + QuicDataToResend(std::unique_ptr<spdy::SpdyHeaderBlock> headers, + QuicStringPiece body, + bool fin); + QuicDataToResend(const QuicDataToResend&) = delete; + QuicDataToResend& operator=(const QuicDataToResend&) = delete; + + virtual ~QuicDataToResend(); + + // Must be overridden by specific classes with the actual method for + // re-sending data. + virtual void Resend() = 0; + + protected: + std::unique_ptr<spdy::SpdyHeaderBlock> headers_; + QuicStringPiece body_; + bool fin_; + }; + + QuicSpdyClientBase(const QuicServerId& server_id, + const ParsedQuicVersionVector& supported_versions, + const QuicConfig& config, + QuicConnectionHelperInterface* helper, + QuicAlarmFactory* alarm_factory, + std::unique_ptr<NetworkHelper> network_helper, + std::unique_ptr<ProofVerifier> proof_verifier); + QuicSpdyClientBase(const QuicSpdyClientBase&) = delete; + QuicSpdyClientBase& operator=(const QuicSpdyClientBase&) = delete; + + ~QuicSpdyClientBase() override; + + // QuicSpdyStream::Visitor + void OnClose(QuicSpdyStream* stream) override; + + // A spdy session has to call CryptoConnect on top of the regular + // initialization. + void InitializeSession() override; + + // Sends an HTTP request and does not wait for response before returning. + void SendRequest(const spdy::SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin); + + // Sends an HTTP request and waits for response before returning. + void SendRequestAndWaitForResponse(const spdy::SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin); + + // Sends a request simple GET for each URL in |url_list|, and then waits for + // each to complete. + void SendRequestsAndWaitForResponse(const std::vector<std::string>& url_list); + + // Returns a newly created QuicSpdyClientStream. + QuicSpdyClientStream* CreateClientStream(); + + // Returns a the session used for this client downcasted to a + // QuicSpdyClientSession. + QuicSpdyClientSession* client_session(); + + QuicClientPushPromiseIndex* push_promise_index() { + return &push_promise_index_; + } + + bool CheckVary(const spdy::SpdyHeaderBlock& client_request, + const spdy::SpdyHeaderBlock& promise_request, + const spdy::SpdyHeaderBlock& promise_response) override; + void OnRendezvousResult(QuicSpdyStream*) override; + + // If the crypto handshake has not yet been confirmed, adds the data to the + // queue of data to resend if the client receives a stateless reject. + // Otherwise, deletes the data. + void MaybeAddQuicDataToResend( + std::unique_ptr<QuicDataToResend> data_to_resend); + + void set_store_response(bool val) { store_response_ = val; } + + size_t latest_response_code() const; + const std::string& latest_response_headers() const; + const std::string& preliminary_response_headers() const; + const spdy::SpdyHeaderBlock& latest_response_header_block() const; + const std::string& latest_response_body() const; + const std::string& latest_response_trailers() const; + + void set_response_listener(std::unique_ptr<ResponseListener> listener) { + response_listener_ = std::move(listener); + } + + protected: + int GetNumSentClientHellosFromSession() override; + int GetNumReceivedServerConfigUpdatesFromSession() override; + + // Takes ownership of |connection|. + std::unique_ptr<QuicSession> CreateQuicClientSession( + const quic::ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) override; + + // If the crypto handshake has not yet been confirmed, adds the data to the + // queue of data to resend if the client receives a stateless reject. + // Otherwise, deletes the data. + void MaybeAddDataToResend(const spdy::SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin); + + void ClearDataToResend() override; + + void ResendSavedData() override; + + void AddPromiseDataToResend(const spdy::SpdyHeaderBlock& headers, + QuicStringPiece body, + bool fin); + bool HasActiveRequests() override; + + private: + // Specific QuicClient class for storing data to resend. + class ClientQuicDataToResend : public QuicDataToResend { + public: + ClientQuicDataToResend(std::unique_ptr<spdy::SpdyHeaderBlock> headers, + QuicStringPiece body, + bool fin, + QuicSpdyClientBase* client) + : QuicDataToResend(std::move(headers), body, fin), client_(client) { + DCHECK(headers_); + DCHECK(client); + } + + ClientQuicDataToResend(const ClientQuicDataToResend&) = delete; + ClientQuicDataToResend& operator=(const ClientQuicDataToResend&) = delete; + ~ClientQuicDataToResend() override {} + + void Resend() override; + + private: + QuicSpdyClientBase* client_; + }; + + // Index of pending promised streams. Must outlive |session_|. + QuicClientPushPromiseIndex push_promise_index_; + + // If true, store the latest response code, headers, and body. + bool store_response_; + // HTTP response code from most recent response. + int latest_response_code_; + // HTTP/2 headers from most recent response. + std::string latest_response_headers_; + // preliminary 100 Continue HTTP/2 headers from most recent response, if any. + std::string preliminary_response_headers_; + // HTTP/2 headers from most recent response. + spdy::SpdyHeaderBlock latest_response_header_block_; + // Body of most recent response. + std::string latest_response_body_; + // HTTP/2 trailers from most recent response. + std::string latest_response_trailers_; + + // Listens for full responses. + std::unique_ptr<ResponseListener> response_listener_; + + // Keeps track of any data that must be resent upon a subsequent successful + // connection, in case the client receives a stateless reject. + std::vector<std::unique_ptr<QuicDataToResend>> data_to_resend_on_connect_; + + std::unique_ptr<ClientQuicDataToResend> push_promise_data_to_resend_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.cc new file mode 100644 index 00000000000..63bbc5d549c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.cc @@ -0,0 +1,111 @@ +// 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/tools/quic_tcp_like_trace_converter.h" + +#include "net/third_party/quiche/src/quic/core/quic_constants.h" + +namespace quic { + +QuicTcpLikeTraceConverter::QuicTcpLikeTraceConverter() + : largest_observed_control_frame_id_(kInvalidControlFrameId), + connection_offset_(0) {} + +QuicTcpLikeTraceConverter::StreamOffsetSegment::StreamOffsetSegment() + : connection_offset(0) {} + +QuicTcpLikeTraceConverter::StreamOffsetSegment::StreamOffsetSegment( + QuicStreamOffset stream_offset, + uint64_t connection_offset, + QuicByteCount data_length) + : stream_data(stream_offset, stream_offset + data_length), + connection_offset(connection_offset) {} + +QuicTcpLikeTraceConverter::StreamInfo::StreamInfo() : fin(false) {} + +QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnStreamFrameSent( + QuicStreamId stream_id, + QuicStreamOffset offset, + QuicByteCount data_length, + bool fin) { + QuicIntervalSet<uint64_t> connection_offsets; + if (fin) { + // Stream fin consumes a connection offset. + ++data_length; + } + StreamInfo* stream_info = + &streams_info_.emplace(stream_id, StreamInfo()).first->second; + // Get connection offsets of retransmission data in this frame. + for (const auto& segment : stream_info->segments) { + QuicInterval<QuicStreamOffset> retransmission(offset, offset + data_length); + retransmission.IntersectWith(segment.stream_data); + if (retransmission.Empty()) { + continue; + } + const uint64_t connection_offset = segment.connection_offset + + retransmission.min() - + segment.stream_data.min(); + connection_offsets.Add(connection_offset, + connection_offset + retransmission.Length()); + } + + if (stream_info->fin) { + return connection_offsets; + } + + // Get connection offsets of new data in this frame. + QuicStreamOffset least_unsent_offset = + stream_info->segments.empty() + ? 0 + : stream_info->segments.back().stream_data.max(); + if (least_unsent_offset >= offset + data_length) { + return connection_offsets; + } + // Ignore out-of-order stream data so that as connection offset increases, + // stream offset increases. + QuicStreamOffset new_data_offset = std::max(least_unsent_offset, offset); + QuicByteCount new_data_length = offset + data_length - new_data_offset; + connection_offsets.Add(connection_offset_, + connection_offset_ + new_data_length); + if (!stream_info->segments.empty() && + new_data_offset == least_unsent_offset && + connection_offset_ == + stream_info->segments.back().connection_offset + + stream_info->segments.back().stream_data.Length()) { + // Extend the last segment if both stream and connection offsets are + // contiguous. + stream_info->segments.back().stream_data.SetMax(new_data_offset + + new_data_length); + } else { + stream_info->segments.emplace_back(new_data_offset, connection_offset_, + new_data_length); + } + stream_info->fin = fin; + connection_offset_ += new_data_length; + + return connection_offsets; +} + +QuicInterval<uint64_t> QuicTcpLikeTraceConverter::OnControlFrameSent( + QuicControlFrameId control_frame_id, + QuicByteCount control_frame_length) { + if (control_frame_id > largest_observed_control_frame_id_) { + // New control frame. + QuicInterval<uint64_t> connection_offset = QuicInterval<uint64_t>( + connection_offset_, connection_offset_ + control_frame_length); + connection_offset_ += control_frame_length; + control_frames_info_[control_frame_id] = QuicInterval<uint64_t>( + connection_offset_, connection_offset_ + control_frame_length); + largest_observed_control_frame_id_ = control_frame_id; + return connection_offset; + } + const auto iter = control_frames_info_.find(control_frame_id); + if (iter == control_frames_info_.end()) { + // Ignore out of order control frames. + return {}; + } + return iter->second; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.h b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.h new file mode 100644 index 00000000000..432980531be --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter.h @@ -0,0 +1,73 @@ +// 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_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_ +#define QUICHE_QUIC_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_ + +#include <vector> + +#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h" +#include "net/third_party/quiche/src/quic/core/quic_interval.h" +#include "net/third_party/quiche/src/quic/core/quic_interval_set.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" + +namespace quic { + +// This converter converts sent QUIC frames to connection byte offset (just like +// TCP byte sequence number). +class QuicTcpLikeTraceConverter { + public: + // StreamOffsetSegment stores a stream offset range which has contiguous + // connection offset. + struct StreamOffsetSegment { + StreamOffsetSegment(); + StreamOffsetSegment(QuicStreamOffset stream_offset, + uint64_t connection_offset, + QuicByteCount data_length); + + QuicInterval<QuicStreamOffset> stream_data; + uint64_t connection_offset; + }; + + QuicTcpLikeTraceConverter(); + QuicTcpLikeTraceConverter(const QuicTcpLikeTraceConverter& other) = delete; + QuicTcpLikeTraceConverter(QuicTcpLikeTraceConverter&& other) = delete; + + ~QuicTcpLikeTraceConverter() {} + + // Called when a stream frame is sent. Returns the corresponding connection + // offsets. + QuicIntervalSet<uint64_t> OnStreamFrameSent(QuicStreamId stream_id, + QuicStreamOffset offset, + QuicByteCount data_length, + bool fin); + + // Called when a control frame is sent. Returns the corresponding connection + // offsets. + QuicInterval<uint64_t> OnControlFrameSent(QuicControlFrameId control_frame_id, + QuicByteCount control_frame_length); + + private: + struct StreamInfo { + StreamInfo(); + + // Stores contiguous connection offset pieces. + std::vector<StreamOffsetSegment> segments; + // Indicates whether fin has been sent. + bool fin; + }; + + QuicUnorderedMap<QuicStreamId, StreamInfo> streams_info_; + QuicUnorderedMap<QuicControlFrameId, QuicInterval<uint64_t>> + control_frames_info_; + + QuicControlFrameId largest_observed_control_frame_id_; + + uint64_t connection_offset_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter_test.cc new file mode 100644 index 00000000000..44f7351036b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_tcp_like_trace_converter_test.cc @@ -0,0 +1,103 @@ +// 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/tools/quic_tcp_like_trace_converter.h" + +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { +namespace { + +TEST(QuicTcpLikeTraceConverterTest, BasicTest) { + QuicTcpLikeTraceConverter converter; + + EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100), + converter.OnStreamFrameSent(1, 0, 100, false)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 200), + converter.OnStreamFrameSent(3, 0, 100, false)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(200, 300), + converter.OnStreamFrameSent(3, 100, 100, false)); + EXPECT_EQ(QuicInterval<uint64_t>(300, 450), + converter.OnControlFrameSent(2, 150)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(450, 550), + converter.OnStreamFrameSent(1, 100, 100, false)); + EXPECT_EQ(QuicInterval<uint64_t>(550, 650), + converter.OnControlFrameSent(3, 100)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(650, 850), + converter.OnStreamFrameSent(3, 200, 200, false)); + EXPECT_EQ(QuicInterval<uint64_t>(850, 1050), + converter.OnControlFrameSent(4, 200)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(1050, 1100), + converter.OnStreamFrameSent(1, 200, 50, false)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(1100, 1150), + converter.OnStreamFrameSent(1, 250, 50, false)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(1150, 1350), + converter.OnStreamFrameSent(3, 400, 200, false)); + + // Stream 1 retransmits [50, 300) and sends new data [300, 350) in the same + // frame. + QuicIntervalSet<uint64_t> expected; + expected.Add(50, 100); + expected.Add(450, 550); + expected.Add(1050, 1150); + expected.Add(1350, 1401); + EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 50, 300, true)); + + expected.Clear(); + // Stream 3 retransmits [150, 500). + expected.Add(250, 300); + expected.Add(650, 850); + expected.Add(1150, 1250); + EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 150, 350, false)); + + // Stream 3 retransmits [300, 600) and sends new data [600, 800) in the same + // frame. + expected.Clear(); + expected.Add(750, 850); + expected.Add(1150, 1350); + expected.Add(1401, 1602); + EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 300, 500, true)); + + // Stream 3 retransmits fin only frame. + expected.Clear(); + expected.Add(1601, 1602); + EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 800, 0, true)); + + QuicInterval<uint64_t> expected2; + // Ignore out of order control frames. + EXPECT_EQ(expected2, converter.OnControlFrameSent(1, 100)); + + // Ignore passed in length for retransmitted frame. + expected2 = {450, 600}; + EXPECT_EQ(expected2, converter.OnControlFrameSent(2, 200)); + + expected2 = {1602, 1702}; + EXPECT_EQ(expected2, converter.OnControlFrameSent(10, 100)); +} + +TEST(QuicTcpLikeTraceConverterTest, FuzzerTest) { + QuicTcpLikeTraceConverter converter; + // Stream does not start from offset 0. + EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100), + converter.OnStreamFrameSent(1, 100, 100, false)); + EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 300), + converter.OnStreamFrameSent(3, 200, 200, false)); + // Stream does not send data contiguously. + EXPECT_EQ(QuicIntervalSet<uint64_t>(300, 400), + converter.OnStreamFrameSent(1, 300, 100, false)); + + // Stream fills existing holes. + QuicIntervalSet<uint64_t> expected; + expected.Add(0, 100); + expected.Add(300, 501); + EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 0, 500, true)); + + // Stream sends frame after fin. + EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 50, 600, false)); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_url.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_url.cc new file mode 100644 index 00000000000..4094ffebb7e --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_url.cc @@ -0,0 +1,101 @@ +// 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/tools/quic_url.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" + +namespace quic { + +static constexpr size_t kMaxHostNameLength = 256; + +QuicUrl::QuicUrl(QuicStringPiece url) : url_(static_cast<std::string>(url)) {} + +QuicUrl::QuicUrl(QuicStringPiece url, QuicStringPiece default_scheme) + : QuicUrl(url) { + if (url_.has_scheme()) { + return; + } + + url_ = GURL(QuicStrCat(default_scheme, "://", url)); +} + +std::string QuicUrl::ToString() const { + if (IsValid()) { + return url_.spec(); + } + return ""; +} + +bool QuicUrl::IsValid() const { + if (!url_.is_valid() || !url_.has_scheme()) { + return false; + } + + if (url_.has_host() && url_.host().length() > kMaxHostNameLength) { + return false; + } + + return true; +} + +std::string QuicUrl::HostPort() const { + if (!IsValid() || !url_.has_host()) { + return ""; + } + + std::string host = url_.host(); + int port = url_.IntPort(); + if (port == url::PORT_UNSPECIFIED) { + return host; + } + return QuicStrCat(host, ":", port); +} + +std::string QuicUrl::PathParamsQuery() const { + if (!IsValid() || !url_.has_path()) { + return "/"; + } + + return url_.PathForRequest(); +} + +std::string QuicUrl::scheme() const { + if (!IsValid()) { + return ""; + } + + return url_.scheme(); +} + +std::string QuicUrl::host() const { + if (!IsValid()) { + return ""; + } + + return url_.HostNoBrackets(); +} + +std::string QuicUrl::path() const { + if (!IsValid()) { + return ""; + } + + return url_.path(); +} + +uint16_t QuicUrl::port() const { + if (!IsValid()) { + return 0; + } + + int port = url_.EffectiveIntPort(); + if (port == url::PORT_UNSPECIFIED) { + return 0; + } + return port; +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_url.h b/chromium/net/third_party/quiche/src/quic/tools/quic_url.h new file mode 100644 index 00000000000..da1b69db14d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_url.h @@ -0,0 +1,61 @@ +// 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_TOOLS_QUIC_URL_H_ +#define QUICHE_QUIC_TOOLS_QUIC_URL_H_ + +#include <string> + +#include "url/gurl.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 { + +// A utility class that wraps GURL. +class QuicUrl { + public: + // Constructs an empty QuicUrl. + QuicUrl() = default; + + // Constructs a QuicUrl from the url string |url|. + // + // NOTE: If |url| doesn't have a scheme, it will have an empty scheme + // field. If that's not what you want, use the QuicUrlImpl(url, + // default_scheme) form below. + explicit QuicUrl(QuicStringPiece url); + + // Constructs a QuicUrlImpl from |url|, assuming that the scheme for the URL + // is |default_scheme| if there is no scheme specified in |url|. + QuicUrl(QuicStringPiece url, QuicStringPiece default_scheme); + + // Returns false if the URL is not valid. + bool IsValid() const; + + // Returns full text of the QuicUrl if it is valid. Return empty string + // otherwise. + std::string ToString() const; + + // Returns host:port. + // If the host is empty, it will return an empty string. + // If the host is an IPv6 address, it will be bracketed. + // If port is not present or is equal to default_port of scheme (e.g., port + // 80 for HTTP), it won't be returned. + std::string HostPort() const; + + // Returns a string assembles path, parameters and query. + std::string PathParamsQuery() const; + + std::string scheme() const; + std::string host() const; + std::string path() const; + uint16_t port() const; + + private: + GURL url_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_URL_H_ diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_url_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_url_test.cc new file mode 100644 index 00000000000..608fdb107fc --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_url_test.cc @@ -0,0 +1,157 @@ +// 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/tools/quic_url.h" + +#include <string> + +#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" + +namespace quic { +namespace test { +namespace { + +class QuicUrlTest : public QuicTest {}; + +TEST_F(QuicUrlTest, Basic) { + // No scheme specified. + std::string url_str = "www.example.com"; + QuicUrl url(url_str); + EXPECT_FALSE(url.IsValid()); + + // scheme is HTTP. + url_str = "http://www.example.com"; + url = QuicUrl(url_str); + EXPECT_TRUE(url.IsValid()); + EXPECT_EQ("http://www.example.com/", url.ToString()); + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("www.example.com", url.HostPort()); + EXPECT_EQ("/", url.PathParamsQuery()); + EXPECT_EQ(80u, url.port()); + + // scheme is HTTPS. + url_str = "https://www.example.com:12345/path/to/resource?a=1&campaign=2"; + url = QuicUrl(url_str); + EXPECT_TRUE(url.IsValid()); + EXPECT_EQ("https://www.example.com:12345/path/to/resource?a=1&campaign=2", + url.ToString()); + EXPECT_EQ("https", url.scheme()); + EXPECT_EQ("www.example.com:12345", url.HostPort()); + EXPECT_EQ("/path/to/resource?a=1&campaign=2", url.PathParamsQuery()); + EXPECT_EQ(12345u, url.port()); + + // scheme is FTP. + url_str = "ftp://www.example.com"; + url = QuicUrl(url_str); + EXPECT_TRUE(url.IsValid()); + EXPECT_EQ("ftp://www.example.com/", url.ToString()); + EXPECT_EQ("ftp", url.scheme()); + EXPECT_EQ("www.example.com", url.HostPort()); + EXPECT_EQ("/", url.PathParamsQuery()); + EXPECT_EQ(21u, url.port()); +} + +TEST_F(QuicUrlTest, DefaultScheme) { + // Default scheme to HTTP. + std::string url_str = "www.example.com"; + QuicUrl url(url_str, "http"); + EXPECT_EQ("http://www.example.com/", url.ToString()); + EXPECT_EQ("http", url.scheme()); + + // URL already has a scheme specified. + url_str = "http://www.example.com"; + url = QuicUrl(url_str, "https"); + EXPECT_EQ("http://www.example.com/", url.ToString()); + EXPECT_EQ("http", url.scheme()); + + // Default scheme to FTP. + url_str = "www.example.com"; + url = QuicUrl(url_str, "ftp"); + EXPECT_EQ("ftp://www.example.com/", url.ToString()); + EXPECT_EQ("ftp", url.scheme()); +} + +TEST_F(QuicUrlTest, IsValid) { + std::string url_str = + "ftp://www.example.com:12345/path/to/resource?a=1&campaign=2"; + EXPECT_TRUE(QuicUrl(url_str).IsValid()); + + // Invalid characters in host name. + url_str = "https://www%.example.com:12345/path/to/resource?a=1&campaign=2"; + EXPECT_FALSE(QuicUrl(url_str).IsValid()); + + // Invalid characters in scheme. + url_str = "%http://www.example.com:12345/path/to/resource?a=1&campaign=2"; + EXPECT_FALSE(QuicUrl(url_str).IsValid()); + + // Host name too long. + std::string host(1024, 'a'); + url_str = "https://" + host; + EXPECT_FALSE(QuicUrl(url_str).IsValid()); + + // Invalid port number. + url_str = "https://www..example.com:123456/path/to/resource?a=1&campaign=2"; + EXPECT_FALSE(QuicUrl(url_str).IsValid()); +} + +TEST_F(QuicUrlTest, HostPort) { + std::string url_str = "http://www.example.com/"; + QuicUrl url(url_str); + EXPECT_EQ("www.example.com", url.HostPort()); + EXPECT_EQ("www.example.com", url.host()); + EXPECT_EQ(80u, url.port()); + + url_str = "http://www.example.com:80/"; + url = QuicUrl(url_str); + EXPECT_EQ("www.example.com", url.HostPort()); + EXPECT_EQ("www.example.com", url.host()); + EXPECT_EQ(80u, url.port()); + + url_str = "http://www.example.com:81/"; + url = QuicUrl(url_str); + EXPECT_EQ("www.example.com:81", url.HostPort()); + EXPECT_EQ("www.example.com", url.host()); + EXPECT_EQ(81u, url.port()); + + url_str = "https://192.168.1.1:443/"; + url = QuicUrl(url_str); + EXPECT_EQ("192.168.1.1", url.HostPort()); + EXPECT_EQ("192.168.1.1", url.host()); + EXPECT_EQ(443u, url.port()); + + url_str = "http://[2001::1]:80/"; + url = QuicUrl(url_str); + EXPECT_EQ("[2001::1]", url.HostPort()); + EXPECT_EQ("2001::1", url.host()); + EXPECT_EQ(80u, url.port()); + + url_str = "http://[2001::1]:81/"; + url = QuicUrl(url_str); + EXPECT_EQ("[2001::1]:81", url.HostPort()); + EXPECT_EQ("2001::1", url.host()); + EXPECT_EQ(81u, url.port()); +} + +TEST_F(QuicUrlTest, PathParamsQuery) { + std::string url_str = + "https://www.example.com:12345/path/to/resource?a=1&campaign=2"; + QuicUrl url(url_str); + EXPECT_EQ("/path/to/resource?a=1&campaign=2", url.PathParamsQuery()); + EXPECT_EQ("/path/to/resource", url.path()); + + url_str = "https://www.example.com/?"; + url = QuicUrl(url_str); + EXPECT_EQ("/?", url.PathParamsQuery()); + EXPECT_EQ("/", url.path()); + + url_str = "https://www.example.com/"; + url = QuicUrl(url_str); + EXPECT_EQ("/", url.PathParamsQuery()); + EXPECT_EQ("/", url.path()); +} + +} // namespace +} // namespace test +} // namespace quic |