Relocate QUICHE files into quiche/ directory within the quiche repo, and change the relative include paths accordingly.
PiperOrigin-RevId: 440164720
Change-Id: I64d8a975d08888a3a86f6c51908e63d5cd45fa35
diff --git a/quiche/quic/tools/fake_proof_verifier.h b/quiche/quic/tools/fake_proof_verifier.h
new file mode 100644
index 0000000..62ab87a
--- /dev/null
+++ b/quiche/quic/tools/fake_proof_verifier.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_FAKE_PROOF_VERIFIER_H_
+#define QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/proof_verifier.h"
+
+namespace quic {
+
+// ProofVerifier implementation which always returns success.
+class FakeProofVerifier : public ProofVerifier {
+ public:
+ ~FakeProofVerifier() override {}
+ QuicAsyncStatus VerifyProof(
+ const std::string& /*hostname*/,
+ const uint16_t /*port*/,
+ const std::string& /*server_config*/,
+ QuicTransportVersion /*quic_version*/,
+ absl::string_view /*chlo_hash*/,
+ const std::vector<std::string>& /*certs*/,
+ const std::string& /*cert_sct*/,
+ const std::string& /*signature*/,
+ const ProofVerifyContext* /*context*/,
+ std::string* /*error_details*/,
+ std::unique_ptr<ProofVerifyDetails>* /*details*/,
+ std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+ return QUIC_SUCCESS;
+ }
+ QuicAsyncStatus VerifyCertChain(
+ const std::string& /*hostname*/,
+ const uint16_t /*port*/,
+ const std::vector<std::string>& /*certs*/,
+ const std::string& /*ocsp_response*/,
+ const std::string& /*cert_sct*/,
+ const ProofVerifyContext* /*context*/,
+ std::string* /*error_details*/,
+ std::unique_ptr<ProofVerifyDetails>* /*details*/,
+ uint8_t* /*out_alert*/,
+ std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+ return QUIC_SUCCESS;
+ }
+ std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
+ return nullptr;
+ }
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
diff --git a/quiche/quic/tools/quic_backend_response.cc b/quiche/quic/tools/quic_backend_response.cc
new file mode 100644
index 0000000..d079d63
--- /dev/null
+++ b/quiche/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 "quiche/quic/tools/quic_backend_response.h"
+
+namespace quic {
+
+QuicBackendResponse::ServerPushInfo::ServerPushInfo(
+ QuicUrl request_url,
+ spdy::Http2HeaderBlock 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/quiche/quic/tools/quic_backend_response.h b/quiche/quic/tools/quic_backend_response.h
new file mode 100644
index 0000000..e67e186
--- /dev/null
+++ b/quiche/quic/tools/quic_backend_response.h
@@ -0,0 +1,92 @@
+// 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 "absl/strings/string_view.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/spdy/core/spdy_protocol.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.
+ // TODO(b/171463363): Remove.
+ struct ServerPushInfo {
+ ServerPushInfo(QuicUrl request_url,
+ spdy::Http2HeaderBlock headers,
+ spdy::SpdyPriority priority,
+ std::string body);
+ ServerPushInfo(const ServerPushInfo& other);
+
+ QuicUrl request_url;
+ spdy::Http2HeaderBlock 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.
+ GENERATE_BYTES // Sends a response with a length equal to the number
+ // of bytes in the URL path.
+ };
+ QuicBackendResponse();
+
+ QuicBackendResponse(const QuicBackendResponse& other) = delete;
+ QuicBackendResponse& operator=(const QuicBackendResponse& other) = delete;
+
+ ~QuicBackendResponse();
+
+ const std::vector<spdy::Http2HeaderBlock>& early_hints() const {
+ return early_hints_;
+ }
+ SpecialResponseType response_type() const { return response_type_; }
+ const spdy::Http2HeaderBlock& headers() const { return headers_; }
+ const spdy::Http2HeaderBlock& trailers() const { return trailers_; }
+ const absl::string_view body() const { return absl::string_view(body_); }
+
+ void AddEarlyHints(const spdy::Http2HeaderBlock& headers) {
+ spdy::Http2HeaderBlock hints = headers.Clone();
+ hints[":status"] = "103";
+ early_hints_.push_back(std::move(hints));
+ }
+
+ void set_response_type(SpecialResponseType response_type) {
+ response_type_ = response_type;
+ }
+
+ void set_headers(spdy::Http2HeaderBlock headers) {
+ headers_ = std::move(headers);
+ }
+ void set_trailers(spdy::Http2HeaderBlock trailers) {
+ trailers_ = std::move(trailers);
+ }
+ void set_body(absl::string_view body) {
+ body_.assign(body.data(), body.size());
+ }
+
+ private:
+ std::vector<spdy::Http2HeaderBlock> early_hints_;
+ SpecialResponseType response_type_;
+ spdy::Http2HeaderBlock headers_;
+ spdy::Http2HeaderBlock trailers_;
+ std::string body_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_
diff --git a/quiche/quic/tools/quic_client.cc b/quiche/quic/tools/quic_client.cc
new file mode 100644
index 0000000..a493e9c
--- /dev/null
+++ b/quiche/quic/tools/quic_client.cc
@@ -0,0 +1,179 @@
+// 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 "quiche/quic/tools/quic_client.h"
+
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_simple_client_session.h"
+
+namespace quic {
+
+namespace tools {
+
+QuicSocketAddress LookupAddress(int address_family_for_lookup,
+ std::string host,
+ std::string port) {
+ addrinfo hint;
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_family = address_family_for_lookup;
+ 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();
+ }
+
+ QUICHE_CHECK(info_list != nullptr);
+ std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list,
+ freeaddrinfo);
+ return QuicSocketAddress(info_list->ai_addr, info_list->ai_addrlen);
+}
+
+} // namespace tools
+
+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,
+ std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+ std::move(proof_verifier),
+ nullptr) {}
+
+QuicClient::QuicClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ std::unique_ptr<SessionCache> session_cache)
+ : QuicClient(
+ server_address,
+ server_id,
+ supported_versions,
+ QuicConfig(),
+ epoll_server,
+ std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+ std::move(proof_verifier),
+ std::move(session_cache)) {}
+
+QuicClient::QuicClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicConfig& config,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ std::unique_ptr<SessionCache> session_cache)
+ : QuicClient(
+ server_address,
+ server_id,
+ supported_versions,
+ config,
+ epoll_server,
+ std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+ std::move(proof_verifier),
+ std::move(session_cache)) {}
+
+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),
+ nullptr) {}
+
+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)
+ : QuicClient(server_address,
+ server_id,
+ supported_versions,
+ config,
+ epoll_server,
+ std::move(network_helper),
+ std::move(proof_verifier),
+ nullptr) {}
+
+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,
+ std::unique_ptr<SessionCache> session_cache)
+ : QuicSpdyClientBase(
+ server_id,
+ supported_versions,
+ config,
+ new QuicEpollConnectionHelper(epoll_server, QuicAllocator::SIMPLE),
+ new QuicEpollAlarmFactory(epoll_server),
+ std::move(network_helper),
+ std::move(proof_verifier),
+ std::move(session_cache)) {
+ set_server_address(server_address);
+}
+
+QuicClient::~QuicClient() = default;
+
+std::unique_ptr<QuicSession> QuicClient::CreateQuicClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection) {
+ return std::make_unique<QuicSimpleClientSession>(
+ *config(), supported_versions, connection, server_id(), crypto_config(),
+ push_promise_index(), drop_response_body(), enable_web_transport(),
+ use_datagram_contexts());
+}
+
+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/quiche/quic/tools/quic_client.h b/quiche/quic/tools/quic_client.h
new file mode 100644
index 0000000..446e9c6
--- /dev/null
+++ b/quiche/quic/tools/quic_client.h
@@ -0,0 +1,108 @@
+// 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 "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_client_epoll_network_helper.h"
+#include "quiche/quic/tools/quic_spdy_client_base.h"
+
+namespace quic {
+
+class QuicServerId;
+
+namespace test {
+class QuicClientPeer;
+} // namespace test
+
+namespace tools {
+
+QuicSocketAddress LookupAddress(int address_family_for_lookup,
+ std::string host,
+ std::string port);
+
+inline QuicSocketAddress LookupAddress(std::string host, std::string port) {
+ return LookupAddress(0, host, port);
+}
+
+} // namespace tools
+
+class QuicClient : public QuicSpdyClientBase {
+ public:
+ // These will create their own QuicClientEpollNetworkHelper.
+ QuicClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier);
+ QuicClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ std::unique_ptr<SessionCache> session_cache);
+ QuicClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicConfig& config,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ std::unique_ptr<SessionCache> session_cache);
+ // 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(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,
+ std::unique_ptr<SessionCache> session_cache);
+ 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;
+
+ private:
+ friend class test::QuicClientPeer;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_
diff --git a/quiche/quic/tools/quic_client_base.cc b/quiche/quic/tools/quic_client_base.cc
new file mode 100644
index 0000000..8cabb60
--- /dev/null
+++ b/quiche/quic/tools/quic_client_base.cc
@@ -0,0 +1,514 @@
+// 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 "quiche/quic/tools/quic_client_base.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_packet_writer.h"
+#include "quiche/quic/core/quic_path_validator.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+// A path context which owns the writer.
+class QUIC_EXPORT_PRIVATE PathMigrationContext
+ : public QuicPathValidationContext {
+ public:
+ PathMigrationContext(std::unique_ptr<QuicPacketWriter> writer,
+ const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address)
+ : QuicPathValidationContext(self_address, peer_address),
+ alternative_writer_(std::move(writer)) {}
+
+ QuicPacketWriter* WriterToUse() override { return alternative_writer_.get(); }
+
+ QuicPacketWriter* ReleaseWriter() { return alternative_writer_.release(); }
+
+ private:
+ std::unique_ptr<QuicPacketWriter> alternative_writer_;
+};
+
+// Implements the basic feature of a result delegate for path validation for
+// connection migration. If the validation succeeds, migrate to the alternative
+// path. Otherwise, stay on the current path.
+class QuicClientSocketMigrationValidationResultDelegate
+ : public QuicPathValidator::ResultDelegate {
+ public:
+ QuicClientSocketMigrationValidationResultDelegate(QuicClientBase* client)
+ : QuicPathValidator::ResultDelegate(), client_(client) {}
+
+ // QuicPathValidator::ResultDelegate
+ // Overridden to start migration and takes the ownership of the writer in the
+ // context.
+ void OnPathValidationSuccess(
+ std::unique_ptr<QuicPathValidationContext> context) override {
+ QUIC_DLOG(INFO) << "Successfully validated path from " << *context
+ << ". Migrate to it now.";
+ auto migration_context = std::unique_ptr<PathMigrationContext>(
+ static_cast<PathMigrationContext*>(context.release()));
+ client_->session()->MigratePath(
+ migration_context->self_address(), migration_context->peer_address(),
+ migration_context->WriterToUse(), /*owns_writer=*/false);
+ QUICHE_DCHECK(migration_context->WriterToUse() != nullptr);
+ // Hand the ownership of the alternative writer to the client.
+ client_->set_writer(migration_context->ReleaseWriter());
+ }
+
+ void OnPathValidationFailure(
+ std::unique_ptr<QuicPathValidationContext> context) override {
+ QUIC_LOG(WARNING) << "Fail to validate path " << *context
+ << ", stop migrating.";
+ client_->session()->connection()->OnPathValidationFailureAtClient();
+ }
+
+ private:
+ QuicClientBase* client_;
+};
+
+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,
+ std::unique_ptr<SessionCache> session_cache)
+ : server_id_(server_id),
+ initialized_(false),
+ local_port_(0),
+ config_(config),
+ crypto_config_(std::move(proof_verifier), std::move(session_cache)),
+ helper_(helper),
+ alarm_factory_(alarm_factory),
+ supported_versions_(supported_versions),
+ initial_max_packet_length_(0),
+ num_sent_client_hellos_(0),
+ connection_error_(QUIC_NO_ERROR),
+ connected_or_attempting_connect_(false),
+ network_helper_(std::move(network_helper)),
+ connection_debug_visitor_(nullptr),
+ server_connection_id_length_(kQuicDefaultConnectionIdLength),
+ client_connection_id_length_(0) {}
+
+QuicClientBase::~QuicClientBase() = default;
+
+bool QuicClientBase::Initialize() {
+ num_sent_client_hellos_ = 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() ==
+ kDefaultFlowControlSendWindow) {
+ config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
+ }
+ if (config()->GetInitialSessionFlowControlWindowToSend() ==
+ kDefaultFlowControlSendWindow) {
+ 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.
+ int num_attempts = 0;
+ while (!connected() &&
+ num_attempts <= QuicCryptoClientStream::kMaxClientHellos) {
+ StartConnect();
+ while (EncryptionBeingEstablished()) {
+ WaitForEvents();
+ }
+ ParsedQuicVersion version = UnsupportedQuicVersion();
+ if (session() != nullptr && !CanReconnectWithDifferentVersion(&version)) {
+ // We've successfully created a session but we're not connected, and we
+ // cannot reconnect with a different version. Give up trying.
+ break;
+ }
+ num_attempts++;
+ }
+ if (session() == nullptr) {
+ QUIC_BUG(quic_bug_10906_1) << "Missing session after Connect";
+ return false;
+ }
+ return session()->connection()->connected();
+}
+
+void QuicClientBase::StartConnect() {
+ QUICHE_DCHECK(initialized_);
+ QUICHE_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()) {
+ // Clear queued up data if client can not try to connect with a different
+ // version.
+ if (!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();
+ }
+
+ const quic::ParsedQuicVersionVector client_supported_versions =
+ can_reconnect_with_different_version
+ ? ParsedQuicVersionVector{mutual_version}
+ : supported_versions();
+
+ session_ = CreateQuicClientSession(
+ client_supported_versions,
+ new QuicConnection(GetNextConnectionId(), QuicSocketAddress(),
+ server_address(), helper(), alarm_factory(), writer,
+ /* owns_writer= */ false, Perspective::IS_CLIENT,
+ client_supported_versions));
+ if (can_reconnect_with_different_version) {
+ session()->set_client_original_supported_versions(supported_versions());
+ }
+ if (connection_debug_visitor_ != nullptr) {
+ session()->connection()->set_debug_visitor(connection_debug_visitor_);
+ }
+ session()->connection()->set_client_connection_id(GetClientConnectionId());
+ 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();
+ if (can_reconnect_with_different_version) {
+ // This is a reconnect using server supported |mutual_version|.
+ session()->connection()->SetVersionNegotiated();
+ }
+ set_connected_or_attempting_connect(true);
+}
+
+void QuicClientBase::InitializeSession() {
+ session()->Initialize();
+}
+
+void QuicClientBase::Disconnect() {
+ QUICHE_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() {
+ if (!connected()) {
+ QUIC_BUG(quic_bug_10906_2)
+ << "Cannot call WaitForEvents on non-connected client";
+ return false;
+ }
+
+ network_helper_->RunEventLoop();
+
+ QUICHE_DCHECK(session() != nullptr);
+ ParsedQuicVersion version = UnsupportedQuicVersion();
+ if (!connected() && CanReconnectWithDifferentVersion(&version)) {
+ 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()) {
+ QUICHE_DVLOG(1)
+ << "MigrateSocketWithSpecifiedPort failed as connection has closed";
+ return false;
+ }
+
+ network_helper_->CleanUpAllUDPSockets();
+ std::unique_ptr<QuicPacketWriter> writer =
+ CreateWriterForNewNetwork(new_host, port);
+ if (writer == nullptr) {
+ QUICHE_DVLOG(1)
+ << "MigrateSocketWithSpecifiedPort failed from writer creation";
+ return false;
+ }
+ if (!session()->MigratePath(network_helper_->GetLatestClientAddress(),
+ session()->connection()->peer_address(),
+ writer.get(), false)) {
+ QUICHE_DVLOG(1)
+ << "MigrateSocketWithSpecifiedPort failed from session()->MigratePath";
+ return false;
+ }
+ set_writer(writer.release());
+ return true;
+}
+
+bool QuicClientBase::ValidateAndMigrateSocket(const QuicIpAddress& new_host) {
+ QUICHE_DCHECK(VersionHasIetfQuicFrames(
+ session_->connection()->version().transport_version) &&
+ session_->connection()->use_path_validator());
+ if (!connected()) {
+ return false;
+ }
+
+ std::unique_ptr<QuicPacketWriter> writer =
+ CreateWriterForNewNetwork(new_host, local_port_);
+ if (writer == nullptr) {
+ return false;
+ }
+ // Asynchronously start migration.
+ session_->ValidatePath(
+ std::make_unique<PathMigrationContext>(
+ std::move(writer), network_helper_->GetLatestClientAddress(),
+ session_->peer_address()),
+ std::make_unique<QuicClientSocketMigrationValidationResultDelegate>(
+ this));
+ return true;
+}
+
+std::unique_ptr<QuicPacketWriter> QuicClientBase::CreateWriterForNewNetwork(
+ const QuicIpAddress& new_host,
+ int port) {
+ set_bind_to_address(new_host);
+ set_local_port(port);
+ if (!network_helper_->CreateUDPSocketAndBind(server_address_,
+ bind_to_address_, port)) {
+ return nullptr;
+ }
+
+ QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
+ QUIC_LOG_IF(WARNING, writer == writer_.get())
+ << "The new writer is wrapped in the same wrapper as the old one, thus "
+ "appearing to have the same address as the old one.";
+ return std::unique_ptr<QuicPacketWriter>(writer);
+}
+
+bool QuicClientBase::ChangeEphemeralPort() {
+ auto current_host = network_helper_->GetLatestClientAddress().host();
+ return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
+}
+
+QuicSession* QuicClientBase::session() {
+ return session_.get();
+}
+
+const QuicSession* QuicClientBase::session() const {
+ 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) {
+ if (!connected()) {
+ QUIC_BUG(quic_bug_10906_3)
+ << "Cannot WaitForStreamToClose on non-connected client";
+ return;
+ }
+
+ while (connected() && !session_->IsClosedStream(id)) {
+ WaitForEvents();
+ }
+}
+
+bool QuicClientBase::WaitForOneRttKeysAvailable() {
+ if (!connected()) {
+ QUIC_BUG(quic_bug_10906_4)
+ << "Cannot WaitForOneRttKeysAvailable on non-connected client";
+ return false;
+ }
+
+ while (connected() && !session_->OneRttKeysAvailable()) {
+ 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::WaitForHandshakeConfirmed() {
+ if (!session_->connection()->version().UsesTls()) {
+ return WaitForOneRttKeysAvailable();
+ }
+ // Otherwise, wait for receipt of HANDSHAKE_DONE frame.
+ while (connected() && session_->GetHandshakeState() < HANDSHAKE_CONFIRMED) {
+ 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_->transport_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();
+}
+
+int QuicClientBase::GetNumReceivedServerConfigUpdates() {
+ // If we are not actively attempting to connect, the session object
+ // corresponds to the previous connection and should not be used.
+ 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() {
+ return GenerateNewConnectionId();
+}
+
+QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
+ return QuicUtils::CreateRandomConnectionId(server_connection_id_length_);
+}
+
+QuicConnectionId QuicClientBase::GetClientConnectionId() {
+ return QuicUtils::CreateRandomConnectionId(client_connection_id_length_);
+}
+
+bool QuicClientBase::CanReconnectWithDifferentVersion(
+ ParsedQuicVersion* version) const {
+ if (session_ == nullptr || session_->connection() == nullptr ||
+ session_->error() != QUIC_INVALID_VERSION) {
+ return false;
+ }
+
+ const auto& server_supported_versions =
+ session_->connection()->server_supported_versions();
+ if (server_supported_versions.empty()) {
+ return false;
+ }
+
+ for (const auto& client_version : supported_versions_) {
+ if (std::find(server_supported_versions.begin(),
+ server_supported_versions.end(),
+ client_version) != server_supported_versions.end()) {
+ *version = client_version;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QuicClientBase::HasPendingPathValidation() {
+ return session()->HasPendingPathValidation();
+}
+
+class ValidationResultDelegate : public QuicPathValidator::ResultDelegate {
+ public:
+ ValidationResultDelegate(QuicClientBase* client)
+ : QuicPathValidator::ResultDelegate(), client_(client) {}
+
+ void OnPathValidationSuccess(
+ std::unique_ptr<QuicPathValidationContext> context) override {
+ QUIC_DLOG(INFO) << "Successfully validated path from " << *context;
+ client_->AddValidatedPath(std::move(context));
+ }
+ void OnPathValidationFailure(
+ std::unique_ptr<QuicPathValidationContext> context) override {
+ QUIC_LOG(WARNING) << "Fail to validate path " << *context
+ << ", stop migrating.";
+ client_->session()->connection()->OnPathValidationFailureAtClient();
+ }
+
+ private:
+ QuicClientBase* client_;
+};
+
+void QuicClientBase::ValidateNewNetwork(const QuicIpAddress& host) {
+ std::unique_ptr<QuicPacketWriter> writer =
+ CreateWriterForNewNetwork(host, local_port_);
+ auto result_delegate = std::make_unique<ValidationResultDelegate>(this);
+ if (writer == nullptr) {
+ result_delegate->OnPathValidationFailure(
+ std::make_unique<PathMigrationContext>(
+ nullptr, network_helper_->GetLatestClientAddress(),
+ session_->peer_address()));
+ return;
+ }
+ session()->ValidatePath(
+ std::make_unique<PathMigrationContext>(
+ std::move(writer), network_helper_->GetLatestClientAddress(),
+ session_->peer_address()),
+ std::move(result_delegate));
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_client_base.h b/quiche/quic/tools/quic_client_base.h
new file mode 100644
index 0000000..9b568eb
--- /dev/null
+++ b/quiche/quic/tools/quic_client_base.h
@@ -0,0 +1,408 @@
+// 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 <memory>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+
+namespace quic {
+
+class ProofVerifier;
+class QuicServerId;
+class SessionCache;
+
+// 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,
+ std::unique_ptr<SessionCache> session_cache);
+ 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 1-RTT keys become available.
+ // Returns true once 1-RTT keys are available, false otherwise.
+ ABSL_MUST_USE_RESULT bool WaitForOneRttKeysAvailable();
+
+ // Wait for handshake state proceeds to HANDSHAKE_CONFIRMED.
+ // In QUIC crypto, this does the same as WaitForOneRttKeysAvailable, while in
+ // TLS, this waits for HANDSHAKE_DONE frame is received.
+ ABSL_MUST_USE_RESULT bool WaitForHandshakeConfirmed();
+
+ // 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);
+
+ // Validate the new socket and migrate to it if the validation succeeds.
+ // Otherwise stay on the current socket. Return true if the validation has
+ // started.
+ bool ValidateAndMigrateSocket(const QuicIpAddress& new_host);
+
+ // Open a new socket to change to a new ephemeral port.
+ bool ChangeEphemeralPort();
+
+ QuicSession* session();
+ const QuicSession* session() const;
+
+ bool connected() const;
+ virtual 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);
+ }
+
+ void SetTlsSignatureAlgorithms(std::string signature_algorithms) {
+ crypto_config_.set_tls_signature_algorithms(
+ std::move(signature_algorithms));
+ }
+
+ 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;
+ }
+
+ // The number of client hellos sent.
+ int GetNumSentClientHellos();
+
+ // Returns true if early data (0-RTT data) was sent and the server accepted
+ // it.
+ virtual bool EarlyDataAccepted() = 0;
+
+ // Returns true if the handshake was delayed one round trip by the server
+ // because the server wanted proof the client controls its source address
+ // before progressing further. In Google QUIC, this would be due to an
+ // inchoate REJ in the QUIC Crypto handshake; in IETF QUIC this would be due
+ // to a Retry packet.
+ // TODO(nharper): Consider a better name for this method.
+ virtual bool ReceivedInchoateReject() = 0;
+
+ // Gather the stats for the last session and update the stats for the overall
+ // connection.
+ void UpdateStats();
+
+ // The number of server config updates received.
+ int GetNumReceivedServerConfigUpdates();
+
+ // Returns any errors that occurred at the connection-level.
+ 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(absl::string_view key) {
+ crypto_config_.set_pre_shared_key(key);
+ }
+
+ void set_connection_debug_visitor(
+ QuicConnectionDebugVisitor* connection_debug_visitor) {
+ connection_debug_visitor_ = connection_debug_visitor;
+ }
+
+ void set_server_connection_id_length(uint8_t server_connection_id_length) {
+ server_connection_id_length_ = server_connection_id_length;
+ }
+
+ void set_client_connection_id_length(uint8_t client_connection_id_length) {
+ client_connection_id_length_ = client_connection_id_length;
+ }
+
+ bool HasPendingPathValidation();
+
+ void ValidateNewNetwork(const QuicIpAddress& host);
+
+ void AddValidatedPath(std::unique_ptr<QuicPathValidationContext> context) {
+ validated_paths_.push_back(std::move(context));
+ }
+
+ const std::vector<std::unique_ptr<QuicPathValidationContext>>&
+ validated_paths() const {
+ return validated_paths_;
+ }
+
+ 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.
+ 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();
+
+ // Generates a new, random connection ID (as opposed to a server-designated
+ // connection ID).
+ virtual QuicConnectionId GenerateNewConnectionId();
+
+ // Returns the client connection ID to use.
+ virtual QuicConnectionId GetClientConnectionId();
+
+ 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;
+
+ std::unique_ptr<QuicPacketWriter> CreateWriterForNewNetwork(
+ const QuicIpAddress& new_host,
+ int port);
+
+ // |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 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. 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_;
+
+ // The debug visitor set on the connection right after it is constructed.
+ // Not owned, must be valid for the lifetime of the QuicClientBase instance.
+ QuicConnectionDebugVisitor* connection_debug_visitor_;
+
+ // GenerateNewConnectionId creates a random connection ID of this length.
+ // Defaults to 8.
+ uint8_t server_connection_id_length_;
+
+ // GetClientConnectionId creates a random connection ID of this length.
+ // Defaults to 0.
+ uint8_t client_connection_id_length_;
+
+ // Stores validated paths.
+ std::vector<std::unique_ptr<QuicPathValidationContext>> validated_paths_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_
diff --git a/quiche/quic/tools/quic_client_bin.cc b/quiche/quic/tools/quic_client_bin.cc
new file mode 100644
index 0000000..ad2acf9
--- /dev/null
+++ b/quiche/quic/tools/quic_client_bin.cc
@@ -0,0 +1,67 @@
+// 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
+//
+// This tool is available as a built binary at:
+// /google/data/ro/teams/quic/tools/quic_client
+// After submitting changes to this file, you will need to follow the
+// instructions at go/quic_client_binary_update
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "quiche/quic/tools/quic_epoll_client_factory.h"
+#include "quiche/quic/tools/quic_toy_client.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+int main(int argc, char* argv[]) {
+ quiche::QuicheSystemEventLoop 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 =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+ if (urls.size() != 1) {
+ quiche::QuichePrintCommandLineFlagHelp(usage);
+ exit(0);
+ }
+
+ quic::QuicEpollClientFactory factory;
+ quic::QuicToyClient client(&factory);
+ return client.SendRequestsAndPrintResponses(urls);
+}
diff --git a/quiche/quic/tools/quic_client_epoll_network_helper.cc b/quiche/quic/tools/quic_client_epoll_network_helper.cc
new file mode 100644
index 0000000..f42c4ed
--- /dev/null
+++ b/quiche/quic/tools/quic_client_epoll_network_helper.cc
@@ -0,0 +1,227 @@
+// 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 "quiche/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 "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_udp_socket.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+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);
+ }
+
+ // Some platforms expect that the addrlen given to bind() exactly matches the
+ // size of the associated protocol family's sockaddr struct.
+ // TODO(b/179430548): Revert this when affected platforms are updated to
+ // to support binding with an addrelen of sizeof(sockaddr_storage)
+ socklen_t addrlen;
+ switch (client_address.host().address_family()) {
+ case IpAddressFamily::IP_V4:
+ addrlen = sizeof(sockaddr_in);
+ break;
+ case IpAddressFamily::IP_V6:
+ addrlen = sizeof(sockaddr_in6);
+ break;
+ case IpAddressFamily::IP_UNSPEC:
+ addrlen = 0;
+ break;
+ }
+
+ sockaddr_storage addr = client_address.generic_address();
+ int rc = bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen);
+ if (rc < 0) {
+ QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno)
+ << " bind_to_address:" << bind_to_address
+ << ", bind_to_port:" << bind_to_port
+ << ", client_address:" << client_address;
+ 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);
+ QUICHE_DCHECK_EQ(0, rc);
+ }
+}
+
+void QuicClientEpollNetworkHelper::RunEventLoop() {
+ quiche::QuicheRunSystemEventLoopIteration();
+ 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) {
+ if (event->in_events & EPOLLIN) {
+ QUIC_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(
+ fd, 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) {
+ QuicUdpSocketApi api;
+ int fd = api.Create(server_address.host().AddressFamilyToInt(),
+ /*receive_buffer_size =*/kDefaultSocketReceiveBuffer,
+ /*send_buffer_size =*/kDefaultSocketReceiveBuffer);
+ if (fd < 0) {
+ return fd;
+ }
+
+ *overflow_supported = api.EnableDroppedPacketCount(fd);
+ api.EnableReceiveTimestamp(fd);
+ return fd;
+}
+} // namespace quic
diff --git a/quiche/quic/tools/quic_client_epoll_network_helper.h b/quiche/quic/tools/quic_client_epoll_network_helper.h
new file mode 100644
index 0000000..637af45
--- /dev/null
+++ b/quiche/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 "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_client_base.h"
+#include "quiche/common/quiche_linked_hash_map.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 quiche::QuicheLinkedHashMap<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.
+ quiche::QuicheLinkedHashMap<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/quiche/quic/tools/quic_client_interop_test_bin.cc b/quiche/quic/tools/quic_client_interop_test_bin.cc
new file mode 100644
index 0000000..ece457f
--- /dev/null
+++ b/quiche/quic/tools/quic_client_interop_test_bin.cc
@@ -0,0 +1,467 @@
+// 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 <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/crypto/quic_client_session_cache.h"
+#include "quiche/quic/core/quic_epoll_clock.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/tools/fake_proof_verifier.h"
+#include "quiche/quic/tools/quic_client.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, host, "",
+ "The IP or hostname to connect to.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, quic_version, "",
+ "The QUIC version to use. Defaults to most recent IETF QUIC version.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
+
+namespace quic {
+
+enum class Feature {
+ // First row of features ("table stakes")
+ // A version negotiation response is elicited and acted on.
+ kVersionNegotiation,
+ // The handshake completes successfully.
+ kHandshake,
+ // Stream data is being exchanged and ACK'ed.
+ kStreamData,
+ // The connection close procedcure completes with a zero error code.
+ kConnectionClose,
+ // The connection was established using TLS resumption.
+ kResumption,
+ // 0-RTT data is being sent and acted on.
+ kZeroRtt,
+ // A RETRY packet was successfully processed.
+ kRetry,
+ // A handshake using a ClientHello that spans multiple packets completed
+ // successfully.
+ kQuantum,
+
+ // Second row of features (anything else protocol-related)
+ // We switched to a different port and the server migrated to it.
+ kRebinding,
+ // One endpoint can update keys and its peer responds correctly.
+ kKeyUpdate,
+
+ // Third row of features (H3 tests)
+ // An H3 transaction succeeded.
+ kHttp3,
+ // One or both endpoints insert entries into dynamic table and subsequenly
+ // reference them from header blocks.
+ kDynamicEntryReferenced,
+};
+
+char MatrixLetter(Feature f) {
+ switch (f) {
+ case Feature::kVersionNegotiation:
+ return 'V';
+ case Feature::kHandshake:
+ return 'H';
+ case Feature::kStreamData:
+ return 'D';
+ case Feature::kConnectionClose:
+ return 'C';
+ case Feature::kResumption:
+ return 'R';
+ case Feature::kZeroRtt:
+ return 'Z';
+ case Feature::kRetry:
+ return 'S';
+ case Feature::kQuantum:
+ return 'Q';
+ case Feature::kRebinding:
+ return 'B';
+ case Feature::kKeyUpdate:
+ return 'U';
+ case Feature::kHttp3:
+ return '3';
+ case Feature::kDynamicEntryReferenced:
+ return 'd';
+ }
+}
+
+class QuicClientInteropRunner : QuicConnectionDebugVisitor {
+ public:
+ QuicClientInteropRunner() {}
+
+ void InsertFeature(Feature feature) { features_.insert(feature); }
+
+ std::set<Feature> features() const { return features_; }
+
+ // Attempts a resumption using |client| by disconnecting and reconnecting. If
+ // resumption is successful, |features_| is modified to add
+ // Feature::kResumption to it, otherwise it is left unmodified.
+ void AttemptResumption(QuicClient* client, const std::string& authority);
+
+ void AttemptRequest(QuicSocketAddress addr,
+ std::string authority,
+ QuicServerId server_id,
+ ParsedQuicVersion version,
+ bool test_version_negotiation,
+ bool attempt_rebind,
+ bool attempt_multi_packet_chlo,
+ bool attempt_key_update);
+
+ // Constructs a SpdyHeaderBlock containing the pseudo-headers needed to make a
+ // GET request to "/" on the hostname |authority|.
+ spdy::Http2HeaderBlock ConstructHeaderBlock(const std::string& authority);
+
+ // Sends an HTTP request represented by |header_block| using |client|.
+ void SendRequest(QuicClient* client,
+ const spdy::Http2HeaderBlock& header_block);
+
+ void OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
+ switch (frame.close_type) {
+ case GOOGLE_QUIC_CONNECTION_CLOSE:
+ QUIC_LOG(ERROR) << "Received unexpected GoogleQUIC connection close";
+ break;
+ case IETF_QUIC_TRANSPORT_CONNECTION_CLOSE:
+ if (frame.wire_error_code == NO_IETF_QUIC_ERROR) {
+ InsertFeature(Feature::kConnectionClose);
+ } else {
+ QUIC_LOG(ERROR) << "Received transport connection close "
+ << QuicIetfTransportErrorCodeString(
+ static_cast<QuicIetfTransportErrorCodes>(
+ frame.wire_error_code));
+ }
+ break;
+ case IETF_QUIC_APPLICATION_CONNECTION_CLOSE:
+ if (frame.wire_error_code == 0) {
+ InsertFeature(Feature::kConnectionClose);
+ } else {
+ QUIC_LOG(ERROR) << "Received application connection close "
+ << frame.wire_error_code;
+ }
+ break;
+ }
+ }
+
+ void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& /*packet*/) override {
+ InsertFeature(Feature::kVersionNegotiation);
+ }
+
+ private:
+ std::set<Feature> features_;
+};
+
+void QuicClientInteropRunner::AttemptResumption(QuicClient* client,
+ const std::string& authority) {
+ client->Disconnect();
+ if (!client->Initialize()) {
+ QUIC_LOG(ERROR) << "Failed to reinitialize client";
+ return;
+ }
+ if (!client->Connect()) {
+ return;
+ }
+
+ bool zero_rtt_attempt = !client->session()->OneRttKeysAvailable();
+
+ spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
+ SendRequest(client, header_block);
+
+ if (!client->session()->OneRttKeysAvailable()) {
+ return;
+ }
+
+ if (static_cast<QuicCryptoClientStream*>(
+ test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
+ ->IsResumption()) {
+ InsertFeature(Feature::kResumption);
+ }
+ if (static_cast<QuicCryptoClientStream*>(
+ test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
+ ->EarlyDataAccepted() &&
+ zero_rtt_attempt && client->latest_response_code() != -1) {
+ InsertFeature(Feature::kZeroRtt);
+ }
+}
+
+void QuicClientInteropRunner::AttemptRequest(QuicSocketAddress addr,
+ std::string authority,
+ QuicServerId server_id,
+ ParsedQuicVersion version,
+ bool test_version_negotiation,
+ bool attempt_rebind,
+ bool attempt_multi_packet_chlo,
+ bool attempt_key_update) {
+ ParsedQuicVersionVector versions = {version};
+ if (test_version_negotiation) {
+ versions.insert(versions.begin(), QuicVersionReservedForNegotiation());
+ }
+
+ auto proof_verifier = std::make_unique<FakeProofVerifier>();
+ auto session_cache = std::make_unique<QuicClientSessionCache>();
+ QuicEpollServer epoll_server;
+ QuicEpollClock epoll_clock(&epoll_server);
+ QuicConfig config;
+ QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(20);
+ config.SetIdleNetworkTimeout(timeout);
+ if (attempt_multi_packet_chlo) {
+ // Make the ClientHello span multiple packets by adding a custom transport
+ // parameter.
+ constexpr auto kCustomParameter =
+ static_cast<TransportParameters::TransportParameterId>(0x173E);
+ std::string custom_value(2000, '?');
+ config.custom_transport_parameters_to_send()[kCustomParameter] =
+ custom_value;
+ }
+ auto client = std::make_unique<QuicClient>(
+ addr, server_id, versions, config, &epoll_server,
+ std::move(proof_verifier), std::move(session_cache));
+ client->set_connection_debug_visitor(this);
+ if (!client->Initialize()) {
+ QUIC_LOG(ERROR) << "Failed to initialize client";
+ return;
+ }
+ const bool connect_result = client->Connect();
+ QuicConnection* connection = client->session()->connection();
+ if (connection == nullptr) {
+ QUIC_LOG(ERROR) << "No QuicConnection object";
+ return;
+ }
+ QuicConnectionStats client_stats = connection->GetStats();
+ if (client_stats.retry_packet_processed) {
+ InsertFeature(Feature::kRetry);
+ }
+ if (test_version_negotiation && connection->version() == version) {
+ InsertFeature(Feature::kVersionNegotiation);
+ }
+ if (test_version_negotiation && !connect_result) {
+ // Failed to negotiate version, retry without version negotiation.
+ AttemptRequest(addr, authority, server_id, version,
+ /*test_version_negotiation=*/false, attempt_rebind,
+ attempt_multi_packet_chlo, attempt_key_update);
+ return;
+ }
+ if (!client->session()->OneRttKeysAvailable()) {
+ if (attempt_multi_packet_chlo) {
+ // Failed to handshake with multi-packet client hello, retry without it.
+ AttemptRequest(addr, authority, server_id, version,
+ test_version_negotiation, attempt_rebind,
+ /*attempt_multi_packet_chlo=*/false, attempt_key_update);
+ return;
+ }
+ return;
+ }
+ InsertFeature(Feature::kHandshake);
+ if (attempt_multi_packet_chlo) {
+ InsertFeature(Feature::kQuantum);
+ }
+
+ spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
+ SendRequest(client.get(), header_block);
+
+ if (!client->connected()) {
+ return;
+ }
+
+ if (client->latest_response_code() != -1) {
+ InsertFeature(Feature::kHttp3);
+
+ if (client->client_session()->dynamic_table_entry_referenced()) {
+ InsertFeature(Feature::kDynamicEntryReferenced);
+ }
+
+ if (attempt_rebind) {
+ // Now make a second request after switching to a different client port.
+ if (client->ChangeEphemeralPort()) {
+ client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+ if (!client->connected()) {
+ // Rebinding does not work, retry without attempting it.
+ AttemptRequest(addr, authority, server_id, version,
+ test_version_negotiation, /*attempt_rebind=*/false,
+ attempt_multi_packet_chlo, attempt_key_update);
+ return;
+ }
+ InsertFeature(Feature::kRebinding);
+
+ if (client->client_session()->dynamic_table_entry_referenced()) {
+ InsertFeature(Feature::kDynamicEntryReferenced);
+ }
+ } else {
+ QUIC_LOG(ERROR) << "Failed to change ephemeral port";
+ }
+ }
+
+ if (attempt_key_update) {
+ if (connection->IsKeyUpdateAllowed()) {
+ if (connection->InitiateKeyUpdate(
+ KeyUpdateReason::kLocalForInteropRunner)) {
+ client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+ if (!client->connected()) {
+ // Key update does not work, retry without attempting it.
+ AttemptRequest(addr, authority, server_id, version,
+ test_version_negotiation, attempt_rebind,
+ attempt_multi_packet_chlo,
+ /*attempt_key_update=*/false);
+ return;
+ }
+ InsertFeature(Feature::kKeyUpdate);
+ } else {
+ QUIC_LOG(ERROR) << "Failed to initiate key update";
+ }
+ } else {
+ QUIC_LOG(ERROR) << "Key update not allowed";
+ }
+ }
+ }
+
+ if (connection->connected()) {
+ connection->CloseConnection(
+ QUIC_NO_ERROR, "Graceful close",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ InsertFeature(Feature::kConnectionClose);
+ }
+
+ AttemptResumption(client.get(), authority);
+}
+
+spdy::Http2HeaderBlock QuicClientInteropRunner::ConstructHeaderBlock(
+ const std::string& authority) {
+ // Construct and send a request.
+ spdy::Http2HeaderBlock header_block;
+ header_block[":method"] = "GET";
+ header_block[":scheme"] = "https";
+ header_block[":authority"] = authority;
+ header_block[":path"] = "/";
+ return header_block;
+}
+
+void QuicClientInteropRunner::SendRequest(
+ QuicClient* client,
+ const spdy::Http2HeaderBlock& header_block) {
+ client->set_store_response(true);
+ client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+
+ QuicConnection* connection = client->session()->connection();
+ if (connection == nullptr) {
+ QUIC_LOG(ERROR) << "No QuicConnection object";
+ return;
+ }
+ QuicConnectionStats client_stats = connection->GetStats();
+ QuicSentPacketManager* sent_packet_manager =
+ test::QuicConnectionPeer::GetSentPacketManager(connection);
+ const bool received_forward_secure_ack =
+ sent_packet_manager != nullptr &&
+ sent_packet_manager->GetLargestAckedPacket(ENCRYPTION_FORWARD_SECURE)
+ .IsInitialized();
+ if (client_stats.stream_bytes_received > 0 && received_forward_secure_ack) {
+ InsertFeature(Feature::kStreamData);
+ }
+}
+
+std::set<Feature> ServerSupport(std::string dns_host,
+ std::string url_host,
+ int port,
+ ParsedQuicVersion version) {
+ std::cout << "Attempting interop with version " << version << std::endl;
+
+ // Build the client, and try to connect.
+ QuicSocketAddress addr = tools::LookupAddress(dns_host, absl::StrCat(port));
+ if (!addr.IsInitialized()) {
+ QUIC_LOG(ERROR) << "Failed to resolve " << dns_host;
+ return std::set<Feature>();
+ }
+ QuicServerId server_id(url_host, port, false);
+ std::string authority = absl::StrCat(url_host, ":", port);
+
+ QuicClientInteropRunner runner;
+
+ runner.AttemptRequest(addr, authority, server_id, version,
+ /*test_version_negotiation=*/true,
+ /*attempt_rebind=*/true,
+ /*attempt_multi_packet_chlo=*/true,
+ /*attempt_key_update=*/true);
+
+ return runner.features();
+}
+
+} // namespace quic
+
+int main(int argc, char* argv[]) {
+ quiche::QuicheSystemEventLoop event_loop("quic_client");
+ const char* usage = "Usage: quic_client_interop_test [options] [url]";
+
+ std::vector<std::string> args =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+ if (args.size() > 1) {
+ quiche::QuichePrintCommandLineFlagHelp(usage);
+ exit(1);
+ }
+ std::string dns_host = GetQuicFlag(FLAGS_host);
+ std::string url_host = "";
+ int port = GetQuicFlag(FLAGS_port);
+
+ if (!args.empty()) {
+ quic::QuicUrl url(args[0], "https");
+ url_host = url.host();
+ if (dns_host.empty()) {
+ dns_host = url_host;
+ }
+ if (port == 0) {
+ port = url.port();
+ }
+ }
+ if (port == 0) {
+ port = 443;
+ }
+ if (dns_host.empty()) {
+ quiche::QuichePrintCommandLineFlagHelp(usage);
+ exit(1);
+ }
+ if (url_host.empty()) {
+ url_host = dns_host;
+ }
+
+ // Pick QUIC version to use.
+ quic::QuicVersionInitializeSupportForIetfDraft();
+ quic::ParsedQuicVersion version = quic::UnsupportedQuicVersion();
+ std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
+ if (!quic_version_string.empty()) {
+ version = quic::ParseQuicVersionString(quic_version_string);
+ } else {
+ for (const quic::ParsedQuicVersion& vers : quic::AllSupportedVersions()) {
+ // Use the most recent IETF QUIC version.
+ if (vers.HasIetfQuicFrames() && vers.UsesHttp3() && vers.UsesTls()) {
+ version = vers;
+ break;
+ }
+ }
+ }
+ QUICHE_CHECK(version.IsKnown());
+ QuicEnableVersion(version);
+
+ auto supported_features =
+ quic::ServerSupport(dns_host, url_host, port, version);
+ std::cout << "Results for " << url_host << ":" << port << std::endl;
+ int current_row = 1;
+ for (auto feature : supported_features) {
+ if (current_row < 2 && feature >= quic::Feature::kRebinding) {
+ std::cout << std::endl;
+ current_row = 2;
+ }
+ if (current_row < 3 && feature >= quic::Feature::kHttp3) {
+ std::cout << std::endl;
+ current_row = 3;
+ }
+ std::cout << MatrixLetter(feature);
+ }
+ std::cout << std::endl;
+}
diff --git a/quiche/quic/tools/quic_client_test.cc b/quiche/quic/tools/quic_client_test.cc
new file mode 100644
index 0000000..5c3aec6
--- /dev/null
+++ b/quiche/quic/tools/quic_client_test.cc
@@ -0,0 +1,134 @@
+// 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 "quiche/quic/tools/quic_client.h"
+
+#include <dirent.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/platform/api/quic_test_loopback.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/quic_client_peer.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+const char* kPathToFds = "/proc/self/fd";
+
+// Return the value of a symbolic link in |path|, if |path| is not found, return
+// an empty string.
+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());
+ if (result_size < 0 && errno == ENOENT) {
+ return "";
+ }
+ QUICHE_CHECK(result_size > 0 &&
+ static_cast<size_t>(result_size) < result.size())
+ << "result_size:" << result_size << ", errno:" << errno
+ << ", path:" << path;
+ 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) {
+ absl::string_view name(file->d_name);
+ if (name == "." || name == "..") {
+ continue;
+ }
+
+ std::string fd_path = ReadLink(absl::StrCat(kPathToFds, "/", name));
+ if (absl::StartsWith(fd_path, "socket:")) {
+ socket_count++;
+ }
+ }
+ return socket_count;
+}
+
+class QuicClientTest : public QuicTest {
+ public:
+ QuicClientTest() {
+ // Creates and destroys a single client first which may open persistent
+ // sockets when initializing platform dependencies like certificate
+ // verifier. Future creation of addtional clients will deterministically
+ // open one socket per client.
+ CreateAndInitializeQuicClient();
+ }
+
+ // Creates a new QuicClient and Initializes it on an unused port.
+ // Caller is responsible for deletion.
+ std::unique_ptr<QuicClient> CreateAndInitializeQuicClient() {
+ QuicSocketAddress server_address(QuicSocketAddress(TestLoopback(), 0));
+ QuicServerId server_id("hostname", server_address.port(), false);
+ ParsedQuicVersionVector versions = AllSupportedVersions();
+ auto client = std::make_unique<QuicClient>(
+ server_address, server_id, versions, &epoll_server_,
+ crypto_test_utils::ProofVerifierForTesting());
+ EXPECT_TRUE(client->Initialize());
+ return client;
+ }
+
+ private:
+ QuicEpollServer epoll_server_;
+};
+
+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 the initial number of FDs.
+ 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) {
+ EXPECT_EQ(number_of_open_fds, NumOpenSocketFDs());
+ std::unique_ptr<QuicClient> client(CreateAndInitializeQuicClient());
+ // Initializing the client will create a new FD.
+ EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs());
+ }
+
+ // The FDs created by the QuicClients should now be closed.
+ EXPECT_EQ(number_of_open_fds, NumOpenSocketFDs());
+}
+
+TEST_F(QuicClientTest, CreateAndCleanUpUDPSockets) {
+ size_t number_of_open_fds = NumOpenSocketFDs();
+
+ std::unique_ptr<QuicClient> client(CreateAndInitializeQuicClient());
+ // Creating and initializing a client will result in one socket being opened.
+ 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/quiche/quic/tools/quic_epoll_client_factory.cc b/quiche/quic/tools/quic_epoll_client_factory.cc
new file mode 100644
index 0000000..2719057
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_client_factory.cc
@@ -0,0 +1,41 @@
+// 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 "quiche/quic/tools/quic_epoll_client_factory.h"
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_client.h"
+
+namespace quic {
+
+std::unique_ptr<QuicSpdyClientBase> QuicEpollClientFactory::CreateClient(
+ std::string host_for_handshake,
+ std::string host_for_lookup,
+ int address_family_for_lookup,
+ uint16_t port,
+ ParsedQuicVersionVector versions,
+ const QuicConfig& config,
+ std::unique_ptr<ProofVerifier> verifier,
+ std::unique_ptr<SessionCache> session_cache) {
+ QuicSocketAddress addr = tools::LookupAddress(
+ address_family_for_lookup, host_for_lookup, absl::StrCat(port));
+ if (!addr.IsInitialized()) {
+ QUIC_LOG(ERROR) << "Unable to resolve address: " << host_for_lookup;
+ return nullptr;
+ }
+ QuicServerId server_id(host_for_handshake, port, false);
+ return std::make_unique<QuicClient>(addr, server_id, versions, config,
+ &epoll_server_, std::move(verifier),
+ std::move(session_cache));
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_epoll_client_factory.h b/quiche/quic/tools/quic_epoll_client_factory.h
new file mode 100644
index 0000000..1e5708f
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_client_factory.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
+#define QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
+
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_toy_client.h"
+
+namespace quic {
+
+// Factory creating QuicClient instances.
+class QuicEpollClientFactory : public QuicToyClient::ClientFactory {
+ public:
+ std::unique_ptr<QuicSpdyClientBase> CreateClient(
+ std::string host_for_handshake,
+ std::string host_for_lookup,
+ int address_family_for_lookup,
+ uint16_t port,
+ ParsedQuicVersionVector versions,
+ const QuicConfig& config,
+ std::unique_ptr<ProofVerifier> verifier,
+ std::unique_ptr<SessionCache> session_cache) override;
+
+ private:
+ QuicEpollServer epoll_server_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
diff --git a/quiche/quic/tools/quic_epoll_server_factory.cc b/quiche/quic/tools/quic_epoll_server_factory.cc
new file mode 100644
index 0000000..a81b973
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_server_factory.cc
@@ -0,0 +1,21 @@
+// 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 "quiche/quic/tools/quic_epoll_server_factory.h"
+
+#include <utility>
+
+#include "quiche/quic/tools/quic_server.h"
+
+namespace quic {
+
+std::unique_ptr<quic::QuicSpdyServerBase> QuicEpollServerFactory::CreateServer(
+ quic::QuicSimpleServerBackend* backend,
+ std::unique_ptr<quic::ProofSource> proof_source,
+ const quic::ParsedQuicVersionVector& supported_versions) {
+ return std::make_unique<quic::QuicServer>(std::move(proof_source), backend,
+ supported_versions);
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_epoll_server_factory.h b/quiche/quic/tools/quic_epoll_server_factory.h
new file mode 100644
index 0000000..6d6876a
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_server_factory.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
+#define QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
+
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_toy_server.h"
+
+namespace quic {
+
+// Factory creating QuicServer instances.
+class QuicEpollServerFactory : public QuicToyServer::ServerFactory {
+ public:
+ std::unique_ptr<QuicSpdyServerBase> CreateServer(
+ QuicSimpleServerBackend* backend,
+ std::unique_ptr<ProofSource> proof_source,
+ const quic::ParsedQuicVersionVector& supported_versions) override;
+
+ private:
+ QuicEpollServer epoll_server_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
diff --git a/quiche/quic/tools/quic_memory_cache_backend.cc b/quiche/quic/tools/quic_memory_cache_backend.cc
new file mode 100644
index 0000000..0e50139
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend.cc
@@ -0,0 +1,496 @@
+// 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 "quiche/quic/tools/quic_memory_cache_backend.h"
+
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/web_transport_test_visitors.h"
+#include "quiche/common/platform/api/quiche_file_utils.h"
+#include "quiche/common/quiche_text_utils.h"
+
+using spdy::Http2HeaderBlock;
+using spdy::kV3LowestPriority;
+
+namespace quic {
+
+QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& file_name)
+ : file_name_(file_name) {}
+
+QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default;
+
+void QuicMemoryCacheBackend::ResourceFile::Read() {
+ absl::optional<std::string> maybe_file_contents =
+ quiche::ReadFileContents(file_name_);
+ if (!maybe_file_contents) {
+ QUIC_LOG(DFATAL) << "Failed to read file for the memory cache backend: "
+ << file_name_;
+ return;
+ }
+ file_contents_ = *maybe_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;
+ }
+ absl::string_view 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(
+ quiche::QuicheTextUtils::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()) {
+ absl::string_view 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(absl::string_view(push_urls.data() + start,
+ push_urls.length() - start));
+ break;
+ }
+ push_urls_.push_back(absl::string_view(push_urls.data() + start, pos));
+ start += pos + 1;
+ }
+ }
+
+ body_ = absl::string_view(file_contents_.data() + start,
+ file_contents_.size() - start);
+}
+
+void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase(
+ absl::string_view base) {
+ QUICHE_DCHECK(base[0] != '/') << base;
+ size_t path_start = base.find_first_of('/');
+ if (path_start == absl::string_view::npos) {
+ host_ = std::string(base);
+ path_ = "";
+ return;
+ }
+
+ host_ = std::string(base.substr(0, path_start));
+ size_t query_start = base.find_first_of(',');
+ if (query_start > 0) {
+ path_ = std::string(base.substr(path_start, query_start - 1));
+ } else {
+ path_ = std::string(base.substr(path_start));
+ }
+}
+
+absl::string_view QuicMemoryCacheBackend::ResourceFile::RemoveScheme(
+ absl::string_view url) {
+ if (absl::StartsWith(url, "https://")) {
+ url.remove_prefix(8);
+ } else if (absl::StartsWith(url, "http://")) {
+ url.remove_prefix(7);
+ }
+ return url;
+}
+
+void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() {
+ absl::string_view url(x_original_url_);
+ SetHostPathFromBase(RemoveScheme(url));
+}
+
+const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
+ absl::string_view host, absl::string_view path) const {
+ QuicWriterMutexLock lock(&response_mutex_);
+
+ auto it = responses_.find(GetKey(host, path));
+ if (it == responses_.end()) {
+ uint64_t ignored = 0;
+ if (generate_bytes_response_) {
+ if (absl::SimpleAtoi(absl::string_view(path.data() + 1, path.size() - 1),
+ &ignored)) {
+ // The actual parsed length is ignored here and will be recomputed
+ // by the caller.
+ return generate_bytes_response_.get();
+ }
+ }
+ QUIC_DVLOG(1) << "Get response for resource failed: host " << host
+ << " path " << path;
+ if (default_response_) {
+ return default_response_.get();
+ }
+ return nullptr;
+ }
+ return it->second.get();
+}
+
+using ServerPushInfo = QuicBackendResponse::ServerPushInfo;
+using SpecialResponseType = QuicBackendResponse::SpecialResponseType;
+
+void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host,
+ absl::string_view path,
+ int response_code,
+ absl::string_view body) {
+ Http2HeaderBlock response_headers;
+ response_headers[":status"] = absl::StrCat(response_code);
+ response_headers["content-length"] = absl::StrCat(body.length());
+ AddResponse(host, path, std::move(response_headers), body);
+}
+
+void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources(
+ absl::string_view host, absl::string_view path, int response_code,
+ absl::string_view 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(absl::string_view host,
+ absl::string_view path,
+ Http2HeaderBlock response_headers,
+ absl::string_view response_body) {
+ AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+ std::move(response_headers), response_body,
+ Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
+ absl::string_view path,
+ Http2HeaderBlock response_headers,
+ absl::string_view response_body,
+ Http2HeaderBlock response_trailers) {
+ AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+ std::move(response_headers), response_body,
+ std::move(response_trailers),
+ std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
+ absl::string_view host, absl::string_view path,
+ spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
+ const std::vector<spdy::Http2HeaderBlock>& early_hints) {
+ AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+ std::move(response_headers), response_body,
+ Http2HeaderBlock(), early_hints);
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+ absl::string_view host, absl::string_view path,
+ SpecialResponseType response_type) {
+ AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
+ Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+ absl::string_view host, absl::string_view path,
+ spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
+ SpecialResponseType response_type) {
+ AddResponseImpl(host, path, response_type, std::move(response_headers),
+ response_body, Http2HeaderBlock(),
+ std::vector<spdy::Http2HeaderBlock>());
+}
+
+QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
+
+bool QuicMemoryCacheBackend::InitializeBackend(
+ const std::string& cache_directory) {
+ if (cache_directory.empty()) {
+ QUIC_BUG(quic_bug_10932_1) << "cache_directory must not be empty.";
+ return false;
+ }
+ QUIC_LOG(INFO)
+ << "Attempting to initialize QuicMemoryCacheBackend from directory: "
+ << cache_directory;
+ std::vector<std::string> files;
+ if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
+ QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
+ << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
+ return false;
+ }
+ 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.
+ std::string base(resource_file->file_name());
+ // Transform windows path separators to URL path separators.
+ for (size_t i = 0; i < base.length(); ++i) {
+ if (base[i] == '\\') {
+ base[i] = '/';
+ }
+ }
+ base.erase(0, cache_directory.length());
+ if (base[0] == '/') {
+ base.erase(0, 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(quic_bug_10932_2)
+ << "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;
+}
+
+void QuicMemoryCacheBackend::GenerateDynamicResponses() {
+ QuicWriterMutexLock lock(&response_mutex_);
+ // Add a generate bytes response.
+ spdy::Http2HeaderBlock response_headers;
+ response_headers[":status"] = "200";
+ generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
+ generate_bytes_response_->set_headers(std::move(response_headers));
+ generate_bytes_response_->set_response_type(
+ QuicBackendResponse::GENERATE_BYTES);
+}
+
+void QuicMemoryCacheBackend::EnableWebTransport() {
+ enable_webtransport_ = true;
+}
+
+bool QuicMemoryCacheBackend::IsBackendInitialized() const {
+ return cache_initialized_;
+}
+
+void QuicMemoryCacheBackend::FetchResponseFromBackend(
+ const Http2HeaderBlock& 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;
+ if (authority != request_headers.end()) {
+ request_url = std::string(authority->second);
+ }
+ if (path != request_headers.end()) {
+ request_url += std::string(path->second);
+ }
+ QUIC_DVLOG(1)
+ << "Fetching QUIC response from backend in-memory cache for url "
+ << request_url;
+ quic_stream->OnResponseBackendComplete(quic_response);
+}
+
+// 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::WebTransportResponse
+QuicMemoryCacheBackend::ProcessWebTransportRequest(
+ const spdy::Http2HeaderBlock& request_headers,
+ WebTransportSession* session) {
+ if (!SupportsWebTransport()) {
+ return QuicSimpleServerBackend::ProcessWebTransportRequest(request_headers,
+ session);
+ }
+
+ auto path_it = request_headers.find(":path");
+ if (path_it == request_headers.end()) {
+ WebTransportResponse response;
+ response.response_headers[":status"] = "400";
+ return response;
+ }
+ absl::string_view path = path_it->second;
+ if (path == "/echo") {
+ WebTransportResponse response;
+ response.response_headers[":status"] = "200";
+ response.visitor =
+ std::make_unique<EchoWebTransportSessionVisitor>(session);
+ return response;
+ }
+
+ WebTransportResponse response;
+ response.response_headers[":status"] = "404";
+ return response;
+}
+
+QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
+ {
+ QuicWriterMutexLock lock(&response_mutex_);
+ responses_.clear();
+ }
+}
+
+void QuicMemoryCacheBackend::AddResponseImpl(
+ absl::string_view host, absl::string_view path,
+ SpecialResponseType response_type, Http2HeaderBlock response_headers,
+ absl::string_view response_body, Http2HeaderBlock response_trailers,
+ const std::vector<spdy::Http2HeaderBlock>& early_hints) {
+ QuicWriterMutexLock lock(&response_mutex_);
+
+ QUICHE_DCHECK(!host.empty())
+ << "Host must be populated, e.g. \"www.google.com\"";
+ std::string key = GetKey(host, path);
+ if (responses_.contains(key)) {
+ QUIC_BUG(quic_bug_10932_3)
+ << "Response for '" << key << "' already exists!";
+ return;
+ }
+ auto new_response = std::make_unique<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));
+ for (auto& headers : early_hints) {
+ new_response->AddEarlyHints(headers);
+ }
+ QUIC_DVLOG(1) << "Add response with key " << key;
+ responses_[key] = std::move(new_response);
+}
+
+std::string QuicMemoryCacheBackend::GetKey(absl::string_view host,
+ absl::string_view 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(
+ absl::string_view request_host, absl::string_view 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 = responses_.contains(GetKey(host, path));
+ }
+ if (!found_existing_response) {
+ // Add a server push response to responses map, if it is not in the map.
+ absl::string_view 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/quiche/quic/tools/quic_memory_cache_backend.h b/quiche/quic/tools/quic_memory_cache_backend.h
new file mode 100644
index 0000000..986badb
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend.h
@@ -0,0 +1,217 @@
+// 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 "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_mutex.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/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(absl::string_view base);
+
+ const std::string& file_name() { return file_name_; }
+
+ absl::string_view host() { return host_; }
+
+ absl::string_view path() { return path_; }
+
+ const spdy::Http2HeaderBlock& spdy_headers() { return spdy_headers_; }
+
+ absl::string_view body() { return body_; }
+
+ const std::vector<absl::string_view>& push_urls() { return push_urls_; }
+
+ private:
+ void HandleXOriginalUrl();
+ absl::string_view RemoveScheme(absl::string_view url);
+
+ std::string file_name_;
+ std::string file_contents_;
+ absl::string_view body_;
+ spdy::Http2HeaderBlock spdy_headers_;
+ absl::string_view x_original_url_;
+ std::vector<absl::string_view> push_urls_;
+ std::string host_;
+ std::string 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(absl::string_view host,
+ absl::string_view 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(absl::string_view host,
+ absl::string_view path,
+ int response_code,
+ absl::string_view 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.
+ // TODO(b/171463363): Remove.
+ void AddSimpleResponseWithServerPushResources(
+ absl::string_view host,
+ absl::string_view path,
+ int response_code,
+ absl::string_view body,
+ std::list<QuicBackendResponse::ServerPushInfo> push_resources);
+
+ // Add a response to the cache.
+ void AddResponse(absl::string_view host,
+ absl::string_view path,
+ spdy::Http2HeaderBlock response_headers,
+ absl::string_view response_body);
+
+ // Add a response, with trailers, to the cache.
+ void AddResponse(absl::string_view host,
+ absl::string_view path,
+ spdy::Http2HeaderBlock response_headers,
+ absl::string_view response_body,
+ spdy::Http2HeaderBlock response_trailers);
+
+ // Add a response, with 103 Early Hints, to the cache.
+ void AddResponseWithEarlyHints(
+ absl::string_view host,
+ absl::string_view path,
+ spdy::Http2HeaderBlock response_headers,
+ absl::string_view response_body,
+ const std::vector<spdy::Http2HeaderBlock>& early_hints);
+
+ // Simulate a special behavior at a particular path.
+ void AddSpecialResponse(
+ absl::string_view host,
+ absl::string_view path,
+ QuicBackendResponse::SpecialResponseType response_type);
+
+ void AddSpecialResponse(
+ absl::string_view host,
+ absl::string_view path,
+ spdy::Http2HeaderBlock response_headers,
+ absl::string_view response_body,
+ QuicBackendResponse::SpecialResponseType response_type);
+
+ // Sets a default response in case of cache misses. Takes ownership of
+ // 'response'.
+ void AddDefaultResponse(QuicBackendResponse* response);
+
+ // Once called, URLs which have a numeric path will send a dynamically
+ // generated response of that many bytes.
+ void GenerateDynamicResponses();
+
+ void EnableWebTransport();
+
+ // Find all the server push resources associated with |request_url|.
+ // TODO(b/171463363): Remove.
+ 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::Http2HeaderBlock& request_headers,
+ const std::string& request_body,
+ QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+ void CloseBackendResponseStream(
+ QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+ WebTransportResponse ProcessWebTransportRequest(
+ const spdy::Http2HeaderBlock& request_headers,
+ WebTransportSession* session) override;
+ bool SupportsWebTransport() override { return enable_webtransport_; }
+
+ private:
+ void AddResponseImpl(absl::string_view host,
+ absl::string_view path,
+ QuicBackendResponse::SpecialResponseType response_type,
+ spdy::Http2HeaderBlock response_headers,
+ absl::string_view response_body,
+ spdy::Http2HeaderBlock response_trailers,
+ const std::vector<spdy::Http2HeaderBlock>& early_hints);
+
+ std::string GetKey(absl::string_view host, absl::string_view path) const;
+
+ // Add some server push urls with given responses for specified
+ // request if these push resources are not associated with this request yet.
+ // TODO(b/171463363): Remove.
+ void MaybeAddServerPushResources(
+ absl::string_view request_host,
+ absl::string_view 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.
+ // TODO(b/171463363): Remove.
+ bool PushResourceExistsInCache(std::string original_request_url,
+ QuicBackendResponse::ServerPushInfo resource);
+
+ // Cached responses.
+ absl::flat_hash_map<std::string, std::unique_ptr<QuicBackendResponse>>
+ responses_ QUIC_GUARDED_BY(response_mutex_);
+
+ // The default response for cache misses, if set.
+ std::unique_ptr<QuicBackendResponse> default_response_
+ QUIC_GUARDED_BY(response_mutex_);
+
+ // The generate bytes response, if set.
+ std::unique_ptr<QuicBackendResponse> generate_bytes_response_
+ QUIC_GUARDED_BY(response_mutex_);
+
+ // A map from request URL to associated server push responses (if any).
+ // TODO(b/171463363): Remove.
+ std::multimap<std::string, QuicBackendResponse::ServerPushInfo>
+ server_push_resources_ QUIC_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_;
+
+ bool enable_webtransport_ = false;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
diff --git a/quiche/quic/tools/quic_memory_cache_backend_test.cc b/quiche/quic/tools/quic_memory_cache_backend_test.cc
new file mode 100644
index 0000000..bb5e675
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend_test.cc
@@ -0,0 +1,291 @@
+// 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 "quiche/quic/tools/quic_memory_cache_backend.h"
+
+#include <vector>
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/common/platform/api/quiche_file_utils.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+using Response = QuicBackendResponse;
+using ServerPushInfo = QuicBackendResponse::ServerPushInfo;
+} // namespace
+
+class QuicMemoryCacheBackendTest : public QuicTest {
+ protected:
+ void CreateRequest(std::string host, std::string path,
+ spdy::Http2HeaderBlock* 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::Http2HeaderBlock request_headers;
+ CreateRequest("www.google.com", "/", &request_headers);
+ const Response* response = cache_.GetResponse("www.google.com", "/");
+ ASSERT_TRUE(response);
+ ASSERT_TRUE(response->headers().contains(":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::Http2HeaderBlock response_headers;
+ response_headers[":status"] = "200";
+ response_headers["content-length"] = absl::StrCat(kResponseBody.size());
+
+ spdy::Http2HeaderBlock 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);
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDir DISABLED_ReadsCacheDir
+#else
+#define MAYBE_ReadsCacheDir ReadsCacheDir
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDir) {
+ cache_.InitializeBackend(CacheDirectory());
+ const Response* response =
+ cache_.GetResponse("test.example.com", "/index.html");
+ ASSERT_TRUE(response);
+ ASSERT_TRUE(response->headers().contains(":status"));
+ EXPECT_EQ("200", response->headers().find(":status")->second);
+ // Connection headers are not valid in HTTP/2.
+ EXPECT_FALSE(response->headers().contains("connection"));
+ EXPECT_LT(0U, response->body().length());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDirWithServerPushResource \
+ DISABLED_ReadsCacheDirWithServerPushResource
+#else
+#define MAYBE_ReadsCacheDirWithServerPushResource \
+ ReadsCacheDirWithServerPushResource
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDirWithServerPushResource) {
+ cache_.InitializeBackend(CacheDirectory() + "_with_push");
+ std::list<ServerPushInfo> resources =
+ cache_.GetServerPushResources("test.example.com/");
+ ASSERT_EQ(1UL, resources.size());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDirWithServerPushResources \
+ DISABLED_ReadsCacheDirWithServerPushResources
+#else
+#define MAYBE_ReadsCacheDirWithServerPushResources \
+ ReadsCacheDirWithServerPushResources
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDirWithServerPushResources) {
+ cache_.InitializeBackend(CacheDirectory() + "_with_push");
+ std::list<ServerPushInfo> resources =
+ cache_.GetServerPushResources("test.example.com/index2.html");
+ ASSERT_EQ(2UL, resources.size());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_UsesOriginalUrl DISABLED_UsesOriginalUrl
+#else
+#define MAYBE_UsesOriginalUrl UsesOriginalUrl
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_UsesOriginalUrl) {
+ cache_.InitializeBackend(CacheDirectory());
+ const Response* response =
+ cache_.GetResponse("test.example.com", "/site_map.html");
+ ASSERT_TRUE(response);
+ ASSERT_TRUE(response->headers().contains(":status"));
+ EXPECT_EQ("200", response->headers().find(":status")->second);
+ // Connection headers are not valid in HTTP/2.
+ EXPECT_FALSE(response->headers().contains("connection"));
+ EXPECT_LT(0U, response->body().length());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_UsesOriginalUrlOnly DISABLED_UsesOriginalUrlOnly
+#else
+#define MAYBE_UsesOriginalUrlOnly UsesOriginalUrlOnly
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_UsesOriginalUrlOnly) {
+ // Tests that if the URL cannot be inferred correctly from the path
+ // because the directory does not include the hostname, that the
+ // X-Original-Url header's value will be used.
+ std::string dir;
+ std::string path = "map.html";
+ std::vector<std::string> files;
+ ASSERT_TRUE(quiche::EnumerateDirectoryRecursively(CacheDirectory(), files));
+ for (const std::string& file : files) {
+ if (absl::EndsWithIgnoreCase(file, "map.html")) {
+ dir = file;
+ dir.erase(dir.length() - path.length() - 1);
+ break;
+ }
+ }
+ ASSERT_NE("", dir);
+
+ cache_.InitializeBackend(dir);
+ const Response* response =
+ cache_.GetResponse("test.example.com", "/site_map.html");
+ ASSERT_TRUE(response);
+ ASSERT_TRUE(response->headers().contains(":status"));
+ EXPECT_EQ("200", response->headers().find(":status")->second);
+ // Connection headers are not valid in HTTP/2.
+ EXPECT_FALSE(response->headers().contains("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::Http2HeaderBlock response_headers;
+ 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(response->headers().contains(":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(response->headers().contains(":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(response->headers().contains(":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 = absl::StrCat("/server_push_src", i);
+ std::string url = scheme + "://" + request_host + path;
+ QuicUrl resource_url(url);
+ std::string body =
+ absl::StrCat("This is server push response body for ", path);
+ spdy::Http2HeaderBlock response_headers;
+ response_headers[":status"] = "200";
+ response_headers["content-length"] = absl::StrCat(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 = absl::StrCat("/server_push_src", i);
+ std::string url = scheme + "://" + request_host + path;
+ QuicUrl resource_url(url);
+ std::string body = "This is server push response body for " + path;
+ spdy::Http2HeaderBlock response_headers;
+ response_headers[":status"] = push_response_status[i];
+ response_headers["content-length"] = absl::StrCat(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(response->headers().contains(":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/quiche/quic/tools/quic_packet_printer_bin.cc b/quiche/quic/tools/quic_packet_printer_bin.cc
new file mode 100644
index 0000000..8867e44
--- /dev/null
+++ b/quiche/quic/tools/quic_packet_printer_bin.cc
@@ -0,0 +1,283 @@
+// 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 "absl/strings/escaping.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_text_utils.h"
+
+DEFINE_QUICHE_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) 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";
+ }
+ void OnRetryPacket(QuicConnectionId /*original_connection_id*/,
+ QuicConnectionId /*new_connection_id*/,
+ absl::string_view /*retry_token*/,
+ absl::string_view /*retry_integrity_tag*/,
+ absl::string_view /*retry_without_tag*/) override {
+ std::cerr << "OnRetryPacket\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(size_t /*length*/, EncryptionLevel level) override {
+ // This only currently supports "decrypting" null encrypted packets.
+ QUICHE_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";
+ }
+ void OnUndecryptablePacket(const QuicEncryptedPacket& /*packet*/,
+ EncryptionLevel /*decryption_level*/,
+ bool /*has_decryption_key*/) override {
+ std::cerr << "OnUndecryptablePacket\n";
+ }
+ bool OnStreamFrame(const QuicStreamFrame& frame) override {
+ std::cerr << "OnStreamFrame: " << frame;
+ std::cerr << " data: { "
+ << absl::BytesToHexString(
+ absl::string_view(frame.data_buffer, frame.data_length))
+ << " }\n";
+ return true;
+ }
+ bool OnCryptoFrame(const QuicCryptoFrame& frame) override {
+ std::cerr << "OnCryptoFrame: " << frame;
+ std::cerr << " data: { "
+ << absl::BytesToHexString(
+ absl::string_view(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: " << frame;
+ 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 OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
+ std::cerr << "OnMaxStreamsFrame: " << frame;
+ return true;
+ }
+ bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
+ std::cerr << "OnStreamsBlockedFrame: " << 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;
+ }
+ bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override {
+ std::cerr << "OnHandshakeDoneFrame: " << frame;
+ return true;
+ }
+ bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override {
+ std::cerr << "OnAckFrequencyFrame: " << frame;
+ return true;
+ }
+ void OnPacketComplete() override { std::cerr << "OnPacketComplete\n"; }
+ bool IsValidStatelessResetToken(
+ const StatelessResetToken& /*token*/) const override {
+ std::cerr << "IsValidStatelessResetToken\n";
+ return false;
+ }
+ void OnAuthenticatedIetfStatelessResetPacket(
+ const QuicIetfStatelessResetPacket& /*packet*/) override {
+ std::cerr << "OnAuthenticatedIetfStatelessResetPacket\n";
+ }
+ void OnKeyUpdate(KeyUpdateReason reason) override {
+ std::cerr << "OnKeyUpdate: " << reason << "\n";
+ }
+ void OnDecryptedFirstPacketInKeyPhase() override {
+ std::cerr << "OnDecryptedFirstPacketInKeyPhase\n";
+ }
+ std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter()
+ override {
+ std::cerr << "AdvanceKeysAndCreateCurrentOneRttDecrypter\n";
+ return nullptr;
+ }
+ std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override {
+ std::cerr << "CreateCurrentOneRttEncrypter\n";
+ return nullptr;
+ }
+
+ 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 =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+
+ if (args.size() < 2) {
+ quiche::QuichePrintCommandLineFlagHelp(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;
+ quiche::QuichePrintCommandLineFlagHelp(usage);
+ return 1;
+ }
+ std::string hex = absl::HexStringToBytes(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);
+ const quic::ParsedQuicVersion& version =
+ quic::ParseQuicVersionString(GetQuicFlag(FLAGS_quic_version));
+ if (version != quic::ParsedQuicVersion::Unsupported()) {
+ 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/quiche/quic/tools/quic_reject_reason_decoder_bin.cc b/quiche/quic/tools/quic_reject_reason_decoder_bin.cc
new file mode 100644
index 0000000..661e3d9
--- /dev/null
+++ b/quiche/quic/tools/quic_reject_reason_decoder_bin.cc
@@ -0,0 +1,45 @@
+// 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 "absl/strings/numbers.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/crypto/crypto_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_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 =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+
+ if (args.size() != 1) {
+ std::cerr << usage << std::endl;
+ return 1;
+ }
+
+ uint32_t packed_error = 0;
+ if (!absl::SimpleAtoi(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/quiche/quic/tools/quic_server.cc b/quiche/quic/tools/quic_server.cc
new file mode 100644
index 0000000..aa521ef
--- /dev/null
+++ b/quiche/quic/tools/quic_server.cc
@@ -0,0 +1,221 @@
+// 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 "quiche/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 "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_clock.h"
+#include "quiche/quic/core/quic_crypto_stream.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_default_packet_writer.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_clock.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h"
+#include "quiche/quic/tools/quic_simple_dispatcher.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+
+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),
+ quic_simple_server_backend,
+ AllSupportedVersions()) {}
+
+QuicServer::QuicServer(std::unique_ptr<ProofSource> proof_source,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ const ParsedQuicVersionVector& supported_versions)
+ : QuicServer(std::move(proof_source),
+ QuicConfig(),
+ QuicCryptoServerConfig::ConfigOptions(),
+ supported_versions,
+ 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_server_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()),
+ crypto_config_options_(crypto_config_options),
+ version_manager_(supported_versions),
+ packet_reader_(new QuicPacketReader()),
+ quic_simple_server_backend_(quic_simple_server_backend),
+ expected_server_connection_id_length_(
+ expected_server_connection_id_length) {
+ QUICHE_DCHECK(quic_simple_server_backend_);
+ 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() ==
+ kDefaultFlowControlSendWindow) {
+ config_.SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindow);
+ }
+ if (config_.GetInitialSessionFlowControlWindowToSend() ==
+ kDefaultFlowControlSendWindow) {
+ 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) {
+ QuicUdpSocketApi socket_api;
+ fd_ = socket_api.Create(address.host().AddressFamilyToInt(),
+ /*receive_buffer_size =*/kDefaultSocketReceiveBuffer,
+ /*send_buffer_size =*/kDefaultSocketReceiveBuffer);
+ if (fd_ == kQuicInvalidSocketFd) {
+ QUIC_LOG(ERROR) << "CreateSocket() failed: " << strerror(errno);
+ return false;
+ }
+
+ overflow_supported_ = socket_api.EnableDroppedPacketCount(fd_);
+ socket_api.EnableReceiveTimestamp(fd_);
+
+ 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<QuicCryptoServerStreamBase::Helper>(
+ new QuicSimpleCryptoServerStreamHelper()),
+ std::unique_ptr<QuicEpollAlarmFactory>(
+ new QuicEpollAlarmFactory(&epoll_server_)),
+ quic_simple_server_backend_, expected_server_connection_id_length_);
+}
+
+void QuicServer::HandleEventsForever() {
+ while (true) {
+ WaitForEvents();
+ }
+}
+
+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) {
+ QUICHE_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;
+ }
+ }
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_server.h b/quiche/quic/tools/quic_server.h
new file mode 100644
index 0000000..9024289
--- /dev/null
+++ b/quiche/quic/tools/quic_server.h
@@ -0,0 +1,171 @@
+// 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 "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_packet_writer.h"
+#include "quiche/quic/core/quic_udp_socket.h"
+#include "quiche/quic/core/quic_version_manager.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_spdy_server_base.h"
+
+namespace quic {
+
+namespace test {
+class QuicServerPeer;
+} // namespace test
+
+class QuicDispatcher;
+class QuicPacketReader;
+
+class QuicServer : public QuicSpdyServerBase,
+ public QuicEpollCallbackInterface {
+ public:
+ QuicServer(std::unique_ptr<ProofSource> proof_source,
+ QuicSimpleServerBackend* quic_simple_server_backend);
+ QuicServer(std::unique_ptr<ProofSource> proof_source,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ const ParsedQuicVersionVector& supported_versions);
+ 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_server_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) override;
+ // Handles all events. Does not return.
+ void HandleEventsForever() override;
+
+ // 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(absl::string_view key) {
+ crypto_config_.set_pre_shared_key(key);
+ }
+
+ bool overflow_supported() { return overflow_supported_; }
+
+ QuicPacketCount packets_dropped() { return packets_dropped_; }
+
+ int port() { return port_; }
+
+ QuicEpollServer* epoll_server() { return &epoll_server_; }
+
+ protected:
+ virtual QuicPacketWriter* CreateWriter(int fd);
+
+ virtual QuicDispatcher* CreateQuicDispatcher();
+
+ const QuicConfig& config() const { return config_; }
+ const QuicCryptoServerConfig& crypto_config() const { return crypto_config_; }
+
+ 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_server_connection_id_length() {
+ return expected_server_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.
+ QuicUdpSocketFd 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_server_connection_id_length_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SERVER_H_
diff --git a/quiche/quic/tools/quic_server_bin.cc b/quiche/quic/tools/quic_server_bin.cc
new file mode 100644
index 0000000..b7e587b
--- /dev/null
+++ b/quiche/quic/tools/quic_server_bin.cc
@@ -0,0 +1,31 @@
+// 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 "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/tools/quic_epoll_server_factory.h"
+#include "quiche/quic/tools/quic_toy_server.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+int main(int argc, char* argv[]) {
+ quiche::QuicheSystemEventLoop event_loop("quic_server");
+ const char* usage = "Usage: quic_server [options]";
+ std::vector<std::string> non_option_args =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+ if (!non_option_args.empty()) {
+ quiche::QuichePrintCommandLineFlagHelp(usage);
+ exit(0);
+ }
+
+ quic::QuicToyServer::MemoryCacheBackendFactory backend_factory;
+ quic::QuicEpollServerFactory server_factory;
+ quic::QuicToyServer server(&backend_factory, &server_factory);
+ return server.Start();
+}
diff --git a/quiche/quic/tools/quic_server_test.cc b/quiche/quic/tools/quic_server_test.cc
new file mode 100644
index 0000000..5994689
--- /dev/null
+++ b/quiche/quic/tools/quic_server_test.cc
@@ -0,0 +1,210 @@
+// 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 "quiche/quic/tools/quic_server.h"
+
+#include "absl/base/macros.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/platform/api/quic_test_loopback.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/mock_quic_dispatcher.h"
+#include "quiche/quic/test_tools/quic_server_peer.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/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<QuicCryptoServerStreamBase::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_METHOD(void, OnCanWrite, (), (override));
+ MOCK_METHOD(bool, HasPendingWrites, (), (const, override));
+ MOCK_METHOD(bool, HasChlosBuffered, (), (const, override));
+ MOCK_METHOD(void, ProcessBufferedChlos, (size_t), (override));
+};
+
+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<QuicCryptoServerStreamBase::Helper>(
+ new QuicSimpleCryptoServerStreamHelper()),
+ 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() : server_address_(TestLoopback(), 0) {}
+
+ void StartListening() {
+ server_.CreateUDPSocketAndListen(server_address_);
+ server_address_ = QuicSocketAddress(server_address_.host(), server_.port());
+
+ ASSERT_TRUE(QuicServerPeer::SetSmallSocket(&server_));
+
+ if (!server_.overflow_supported()) {
+ QUIC_LOG(WARNING) << "Overflow not supported. Not testing.";
+ return;
+ }
+ }
+
+ protected:
+ 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();
+ QUICHE_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, ABSL_ARRAYSIZE(buf));
+ sockaddr_storage storage = server_address_.generic_address();
+ int rc = sendto(fd, buf, ABSL_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()),
+ version_manager_(AllSupportedVersions()),
+ dispatcher_(
+ &config_,
+ &crypto_config_,
+ &version_manager_,
+ std::unique_ptr<QuicEpollConnectionHelper>(
+ new QuicEpollConnectionHelper(&eps_,
+ QuicAllocator::BUFFER_POOL)),
+ std::unique_ptr<QuicCryptoServerStreamBase::Helper>(
+ new QuicSimpleCryptoServerStreamHelper()),
+ 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), ABSL_ARRAYSIZE(valid_packet),
+ QuicTime::Zero(), false);
+
+ EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _)).Times(1);
+ DispatchPacket(encrypted_valid_packet);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.cc b/quiche/quic/tools/quic_simple_client_session.cc
new file mode 100644
index 0000000..fa211ac
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_session.cc
@@ -0,0 +1,54 @@
+// 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 "quiche/quic/tools/quic_simple_client_session.h"
+
+#include <utility>
+
+namespace quic {
+
+QuicSimpleClientSession::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)
+ : QuicSimpleClientSession(config, supported_versions, connection, server_id,
+ crypto_config, push_promise_index,
+ drop_response_body,
+ /*enable_web_transport=*/false,
+ /*use_datagram_contexts=*/false) {}
+
+QuicSimpleClientSession::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,
+ bool enable_web_transport, bool use_datagram_contexts)
+ : QuicSpdyClientSession(config, supported_versions, connection, server_id,
+ crypto_config, push_promise_index),
+ drop_response_body_(drop_response_body),
+ enable_web_transport_(enable_web_transport),
+ use_datagram_contexts_(use_datagram_contexts) {}
+
+std::unique_ptr<QuicSpdyClientStream>
+QuicSimpleClientSession::CreateClientStream() {
+ return std::make_unique<QuicSimpleClientStream>(
+ GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL,
+ drop_response_body_);
+}
+
+bool QuicSimpleClientSession::ShouldNegotiateWebTransport() {
+ return enable_web_transport_;
+}
+
+bool QuicSimpleClientSession::ShouldNegotiateDatagramContexts() {
+ return use_datagram_contexts_;
+}
+
+HttpDatagramSupport QuicSimpleClientSession::LocalHttpDatagramSupport() {
+ return enable_web_transport_ ? HttpDatagramSupport::kDraft04
+ : HttpDatagramSupport::kNone;
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.h b/quiche/quic/tools/quic_simple_client_session.h
new file mode 100644
index 0000000..75ed195
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_session.h
@@ -0,0 +1,44 @@
+// 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 "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/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);
+ 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, bool enable_web_transport,
+ bool use_datagram_contexts);
+
+ std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override;
+ bool ShouldNegotiateWebTransport() override;
+ bool ShouldNegotiateDatagramContexts() override;
+ HttpDatagramSupport LocalHttpDatagramSupport() override;
+
+ private:
+ const bool drop_response_body_;
+ const bool enable_web_transport_;
+ const bool use_datagram_contexts_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_
diff --git a/quiche/quic/tools/quic_simple_client_stream.cc b/quiche/quic/tools/quic_simple_client_stream.cc
new file mode 100644
index 0000000..14de9f0
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_stream.cc
@@ -0,0 +1,29 @@
+// 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 "quiche/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();
+ }
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_stream.h b/quiche/quic/tools/quic_simple_client_stream.h
new file mode 100644
index 0000000..a68b7cb
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_stream.h
@@ -0,0 +1,28 @@
+// 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 "quiche/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) {}
+ void OnBodyAvailable() override;
+
+ private:
+ const bool drop_response_body_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_
diff --git a/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc
new file mode 100644
index 0000000..08d63b7
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc
@@ -0,0 +1,26 @@
+// 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 "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h"
+
+#include "quiche/quic/core/quic_utils.h"
+
+namespace quic {
+
+QuicSimpleCryptoServerStreamHelper::QuicSimpleCryptoServerStreamHelper() =
+ default;
+
+QuicSimpleCryptoServerStreamHelper::~QuicSimpleCryptoServerStreamHelper() =
+ default;
+
+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/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h
new file mode 100644
index 0000000..ba64226
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h
@@ -0,0 +1,31 @@
+// 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 "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_crypto_server_stream_base.h"
+
+namespace quic {
+
+// Simple helper for server crypto streams which generates a new random
+// connection ID for rejects.
+class QuicSimpleCryptoServerStreamHelper
+ : public QuicCryptoServerStreamBase::Helper {
+ public:
+ QuicSimpleCryptoServerStreamHelper();
+
+ ~QuicSimpleCryptoServerStreamHelper() override;
+
+ bool CanAcceptClientHello(const CryptoHandshakeMessage& message,
+ const QuicSocketAddress& client_address,
+ const QuicSocketAddress& peer_address,
+ const QuicSocketAddress& self_address,
+ std::string* error_details) const override;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_
diff --git a/quiche/quic/tools/quic_simple_dispatcher.cc b/quiche/quic/tools/quic_simple_dispatcher.cc
new file mode 100644
index 0000000..b820864
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_dispatcher.cc
@@ -0,0 +1,70 @@
+// 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 "quiche/quic/tools/quic_simple_dispatcher.h"
+
+#include "absl/strings/string_view.h"
+#include "quiche/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<QuicCryptoServerStreamBase::Helper> session_helper,
+ std::unique_ptr<QuicAlarmFactory> alarm_factory,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ uint8_t expected_server_connection_id_length)
+ : QuicDispatcher(config,
+ crypto_config,
+ version_manager,
+ std::move(helper),
+ std::move(session_helper),
+ std::move(alarm_factory),
+ expected_server_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++;
+ }
+}
+
+std::unique_ptr<QuicSession> QuicSimpleDispatcher::CreateQuicSession(
+ QuicConnectionId connection_id, const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address, absl::string_view /*alpn*/,
+ const ParsedQuicVersion& version,
+ const ParsedClientHello& /*parsed_chlo*/) {
+ // The QuicServerSessionBase takes ownership of |connection| below.
+ QuicConnection* connection =
+ new QuicConnection(connection_id, self_address, peer_address, helper(),
+ alarm_factory(), writer(),
+ /* owns_writer= */ false, Perspective::IS_SERVER,
+ ParsedQuicVersionVector{version});
+
+ auto session = std::make_unique<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/quiche/quic/tools/quic_simple_dispatcher.h b/quiche/quic/tools/quic_simple_dispatcher.h
new file mode 100644
index 0000000..ee80955
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_dispatcher.h
@@ -0,0 +1,53 @@
+// 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 "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_server_session_base.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/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<QuicCryptoServerStreamBase::Helper> session_helper,
+ std::unique_ptr<QuicAlarmFactory> alarm_factory,
+ QuicSimpleServerBackend* quic_simple_server_backend,
+ uint8_t expected_server_connection_id_length);
+
+ ~QuicSimpleDispatcher() override;
+
+ int GetRstErrorCount(QuicRstStreamErrorCode rst_error_code) const;
+
+ void OnRstStreamReceived(const QuicRstStreamFrame& frame) override;
+
+ protected:
+ std::unique_ptr<QuicSession> CreateQuicSession(
+ QuicConnectionId connection_id, const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address, absl::string_view alpn,
+ const ParsedQuicVersion& version,
+ const ParsedClientHello& parsed_chlo) 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/quiche/quic/tools/quic_simple_server_backend.h b/quiche/quic/tools/quic_simple_server_backend.h
new file mode 100644
index 0000000..b0e1d8f
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_backend.h
@@ -0,0 +1,76 @@
+// 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 <memory>
+
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/spdy/core/spdy_header_block.h"
+
+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) = 0;
+ };
+
+ struct WebTransportResponse {
+ spdy::Http2HeaderBlock response_headers;
+ std::unique_ptr<WebTransportVisitor> visitor;
+ };
+
+ 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::Http2HeaderBlock& 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;
+
+ virtual WebTransportResponse ProcessWebTransportRequest(
+ const spdy::Http2HeaderBlock& /*request_headers*/,
+ WebTransportSession* /*session*/) {
+ WebTransportResponse response;
+ response.response_headers[":status"] = "400";
+ return response;
+ }
+ virtual bool SupportsWebTransport() { return false; }
+ virtual bool UsesDatagramContexts() { return false; }
+ virtual bool SupportsExtendedConnect() { return true; }
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_
diff --git a/quiche/quic/tools/quic_simple_server_session.cc b/quiche/quic/tools/quic_simple_server_session.cc
new file mode 100644
index 0000000..15e43b9
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session.cc
@@ -0,0 +1,225 @@
+// 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 "quiche/quic/tools/quic_simple_server_session.h"
+
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "quiche/quic/core/http/quic_server_initiated_spdy_stream.h"
+#include "quiche/quic/core/http/quic_spdy_session.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_server_stream.h"
+
+namespace quic {
+
+QuicSimpleServerSession::QuicSimpleServerSession(
+ const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection, QuicSession::Visitor* visitor,
+ QuicCryptoServerStreamBase::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) {
+ QUICHE_DCHECK(quic_simple_server_backend_);
+}
+
+QuicSimpleServerSession::~QuicSimpleServerSession() { DeleteConnection(); }
+
+std::unique_ptr<QuicCryptoServerStreamBase>
+QuicSimpleServerSession::CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache) {
+ return CreateCryptoServerStream(crypto_config, compressed_certs_cache, this,
+ stream_helper());
+}
+
+void QuicSimpleServerSession::OnStreamFrame(const QuicStreamFrame& frame) {
+ if (!IsIncomingStream(frame.stream_id) && !WillNegotiateWebTransport()) {
+ 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);
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream(QuicStreamId id) {
+ if (!ShouldCreateIncomingStream(id)) {
+ return nullptr;
+ }
+
+ QuicSpdyStream* stream = new QuicSimpleServerStream(
+ id, this, BIDIRECTIONAL, quic_simple_server_backend_);
+ ActivateStream(absl::WrapUnique(stream));
+ return stream;
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream(
+ PendingStream* pending) {
+ QuicSpdyStream* stream =
+ new QuicSimpleServerStream(pending, this, quic_simple_server_backend_);
+ ActivateStream(absl::WrapUnique(stream));
+ return stream;
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateOutgoingBidirectionalStream() {
+ if (!WillNegotiateWebTransport()) {
+ QUIC_BUG(QuicSimpleServerSession CreateOutgoingBidirectionalStream without
+ WebTransport support)
+ << "QuicSimpleServerSession::CreateOutgoingBidirectionalStream called "
+ "in a session without WebTransport support.";
+ return nullptr;
+ }
+ if (!ShouldCreateOutgoingBidirectionalStream()) {
+ return nullptr;
+ }
+
+ QuicServerInitiatedSpdyStream* stream = new QuicServerInitiatedSpdyStream(
+ GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+ ActivateStream(absl::WrapUnique(stream));
+ return stream;
+}
+
+QuicSimpleServerStream*
+QuicSimpleServerSession::CreateOutgoingUnidirectionalStream() {
+ if (!ShouldCreateOutgoingUnidirectionalStream()) {
+ return nullptr;
+ }
+
+ QuicSimpleServerStream* stream = new QuicSimpleServerStream(
+ GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL,
+ quic_simple_server_backend_);
+ ActivateStream(absl::WrapUnique(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(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 ((!version().HasIetfQuicFrames() ||
+ !QuicUtils::IsBidirectionalStreamId(frame.stream_id, version())) &&
+ frame.stream_id >= next_stream_id) {
+ size_t index = (frame.stream_id - next_stream_id) /
+ QuicUtils::StreamIdDelta(transport_version());
+ if (index < promised_streams_.size()) {
+ promised_streams_[index].is_cancelled = true;
+ }
+ }
+ control_frame_manager().WriteOrBufferRstStream(
+ frame.stream_id,
+ QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), 0);
+ connection()->OnStreamReset(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT);
+ }
+}
+
+spdy::Http2HeaderBlock QuicSimpleServerSession::SynthesizePushRequestHeaders(
+ std::string request_url, QuicBackendResponse::ServerPushInfo resource,
+ const spdy::Http2HeaderBlock& original_request_headers) {
+ QuicUrl push_request_url = resource.request_url;
+
+ spdy::Http2HeaderBlock 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::Http2HeaderBlock 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();
+ QUICHE_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());
+ QUICHE_DCHECK_NE(promised_stream, nullptr);
+ QUICHE_DCHECK_EQ(promised_info.stream_id, promised_stream->id());
+ QUIC_DLOG(INFO) << "created server push stream " << promised_stream->id();
+
+ promised_stream->SetPriority(promised_info.precedence);
+
+ spdy::Http2HeaderBlock request_headers(
+ std::move(promised_info.request_headers));
+
+ promised_streams_.pop_front();
+ promised_stream->PushResponse(std::move(request_headers));
+ }
+}
+
+void QuicSimpleServerSession::OnCanCreateNewOutgoingStream(
+ bool unidirectional) {
+ QuicSpdySession::OnCanCreateNewOutgoingStream(unidirectional);
+ if (unidirectional) {
+ HandlePromisedPushRequests();
+ }
+}
+
+void QuicSimpleServerSession::MaybeInitializeHttp3UnidirectionalStreams() {
+ size_t previous_static_stream_count = num_static_streams();
+ QuicSpdySession::MaybeInitializeHttp3UnidirectionalStreams();
+ size_t current_static_stream_count = num_static_streams();
+ QUICHE_DCHECK_GE(current_static_stream_count, previous_static_stream_count);
+ highest_promised_stream_id_ +=
+ QuicUtils::StreamIdDelta(transport_version()) *
+ (current_static_stream_count - previous_static_stream_count);
+}
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_session.h b/quiche/quic/tools/quic_simple_server_session.h
new file mode 100644
index 0000000..950da18
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// 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 "quiche/quic/core/http/quic_server_session_base.h"
+#include "quiche/quic/core/http/quic_spdy_session.h"
+#include "quiche/quic/core/quic_crypto_server_stream_base.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/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::Http2HeaderBlock request_headers,
+ QuicStreamId stream_id,
+ const spdy::SpdyStreamPrecedence& precedence)
+ : request_headers(std::move(request_headers)),
+ stream_id(stream_id),
+ precedence(precedence),
+ is_cancelled(false) {}
+ spdy::Http2HeaderBlock request_headers;
+ QuicStreamId stream_id;
+ spdy::SpdyStreamPrecedence precedence;
+ bool is_cancelled;
+ };
+
+ // Takes ownership of |connection|.
+ QuicSimpleServerSession(const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ QuicCryptoServerStreamBase::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;
+
+ void OnCanCreateNewOutgoingStream(bool unidirectional) override;
+
+ bool ShouldNegotiateDatagramContexts() override {
+ return quic_simple_server_backend_->UsesDatagramContexts();
+ }
+
+ protected:
+ // QuicSession methods:
+ QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override;
+ QuicSpdyStream* CreateIncomingStream(PendingStream* pending) override;
+ QuicSpdyStream* 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:
+ std::unique_ptr<QuicCryptoServerStreamBase> CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache) override;
+
+ QuicSimpleServerBackend* server_backend() {
+ return quic_simple_server_backend_;
+ }
+
+ void MaybeInitializeHttp3UnidirectionalStreams() override;
+
+ bool ShouldNegotiateWebTransport() override {
+ return quic_simple_server_backend_->SupportsWebTransport();
+ }
+ HttpDatagramSupport LocalHttpDatagramSupport() override {
+ if (ShouldNegotiateWebTransport()) {
+ return HttpDatagramSupport::kDraft00And04;
+ }
+ return QuicServerSessionBase::LocalHttpDatagramSupport();
+ }
+
+ 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::Http2HeaderBlock SynthesizePushRequestHeaders(
+ std::string request_url,
+ QuicBackendResponse::ServerPushInfo resource,
+ const spdy::Http2HeaderBlock& original_request_headers);
+
+ // Send PUSH_PROMISE frame on headers stream.
+ void SendPushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ spdy::Http2HeaderBlock 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_.
+ quiche::QuicheCircularDeque<PromisedStreamInfo> promised_streams_;
+
+ QuicSimpleServerBackend* quic_simple_server_backend_; // Not owned.
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_
diff --git a/quiche/quic/tools/quic_simple_server_session_test.cc b/quiche/quic/tools/quic_simple_server_session_test.cc
new file mode 100644
index 0000000..2cff502
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session_test.cc
@@ -0,0 +1,595 @@
+// 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 "quiche/quic/tools/quic_simple_server_session.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/http_encoder.h"
+#include "quiche/quic/core/proto/cached_network_parameters_proto.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_crypto_server_stream.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/core/tls_server_handshaker.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/mock_quic_session_visitor.h"
+#include "quiche/quic/test_tools/quic_config_peer.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
+#include "quiche/quic/test_tools/quic_stream_peer.h"
+#include "quiche/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/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 {
+
+// Data to be sent on a request stream. In Google QUIC, this is interpreted as
+// DATA payload (there is no framing on request streams). In IETF QUIC, this is
+// interpreted as HEADERS frame (type 0x1) with payload length 122 ('z'). Since
+// no payload is included, QPACK decoder will not be invoked.
+const char* const kStreamData = "\1z";
+
+} // namespace
+
+class QuicSimpleServerSessionPeer {
+ public:
+ static void SetCryptoStream(QuicSimpleServerSession* s,
+ QuicCryptoServerStreamBase* crypto_stream) {
+ s->crypto_stream_.reset(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,
+ QuicSession* session,
+ QuicCryptoServerStreamBase::Helper* helper)
+ : QuicCryptoServerStream(crypto_config,
+ compressed_certs_cache,
+ session,
+ helper) {}
+ MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+ MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+ delete;
+ ~MockQuicCryptoServerStream() override {}
+
+ MOCK_METHOD(void,
+ SendServerConfigUpdate,
+ (const CachedNetworkParameters*),
+ (override));
+
+ bool encryption_established() const override { return true; }
+};
+
+class MockTlsServerHandshaker : public TlsServerHandshaker {
+ public:
+ explicit MockTlsServerHandshaker(QuicSession* session,
+ const QuicCryptoServerConfig* crypto_config)
+ : TlsServerHandshaker(session, crypto_config) {}
+ MockTlsServerHandshaker(const MockTlsServerHandshaker&) = delete;
+ MockTlsServerHandshaker& operator=(const MockTlsServerHandshaker&) = delete;
+ ~MockTlsServerHandshaker() override {}
+
+ MOCK_METHOD(void,
+ SendServerConfigUpdate,
+ (const CachedNetworkParameters*),
+ (override));
+
+ bool encryption_established() const override { return true; }
+};
+
+QuicCryptoServerStreamBase* CreateMockCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicSession* session,
+ QuicCryptoServerStreamBase::Helper* helper) {
+ switch (session->connection()->version().handshake_protocol) {
+ case PROTOCOL_QUIC_CRYPTO:
+ return new MockQuicCryptoServerStream(
+ crypto_config, compressed_certs_cache, session, helper);
+ case PROTOCOL_TLS1_3:
+ return new MockTlsServerHandshaker(session, crypto_config);
+ case PROTOCOL_UNSUPPORTED:
+ break;
+ }
+ QUIC_BUG(quic_bug_10933_1)
+ << "Unknown handshake protocol: "
+ << static_cast<int>(session->connection()->version().handshake_protocol);
+ return nullptr;
+}
+
+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_METHOD(QuicConsumedData,
+ SendStreamData,
+ (QuicStreamId id,
+ size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state),
+ (override));
+};
+
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
+ public:
+ MockQuicSimpleServerSession(
+ const QuicConfig& config,
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ QuicCryptoServerStreamBase::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 Http2HeaderBlock by value cannot be
+ // mocked directly.
+ void WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ spdy::Http2HeaderBlock headers) override {
+ return WritePushPromiseMock(original_stream_id, promised_stream_id,
+ headers);
+ }
+ MOCK_METHOD(void,
+ WritePushPromiseMock,
+ (QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const spdy::Http2HeaderBlock& headers),
+ ());
+
+ MOCK_METHOD(void, SendBlocked, (QuicStreamId), (override));
+ MOCK_METHOD(bool,
+ WriteControlFrame,
+ (const QuicFrame& frame, TransmissionType type),
+ (override));
+};
+
+class QuicSimpleServerSessionTest
+ : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+ // The function ensures that A) the MAX_STREAMS 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 ClearMaxStreamsControlFrame(const QuicFrame& frame) {
+ if (frame.type == MAX_STREAMS_FRAME) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ QuicSimpleServerSessionTest()
+ : crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance(),
+ crypto_test_utils::ProofSourceForTesting(),
+ KeyExchangeSource::Default()),
+ compressed_certs_cache_(
+ QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
+ config_.SetMaxBidirectionalStreamsToSend(kMaxStreamsForTest);
+ QuicConfigPeer::SetReceivedMaxBidirectionalStreams(&config_,
+ kMaxStreamsForTest);
+ config_.SetMaxUnidirectionalStreamsToSend(kMaxStreamsForTest);
+
+ config_.SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialMaxStreamDataBytesUnidirectionalToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+ if (VersionUsesHttp3(transport_version())) {
+ QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(
+ &config_, kMaxStreamsForTest + 3);
+ } else {
+ QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(&config_,
+ kMaxStreamsForTest);
+ }
+
+ ParsedQuicVersionVector supported_versions = SupportedVersions(version());
+ connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ connection_->SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<NullEncrypter>(connection_->perspective()));
+ session_ = std::make_unique<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();
+
+ if (VersionHasIetfQuicFrames(transport_version())) {
+ EXPECT_CALL(*session_, WriteControlFrame(_, _))
+ .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
+ }
+ session_->OnConfigNegotiated();
+ }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(transport_version(), n);
+ }
+
+ QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
+ return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
+ transport_version(), n);
+ }
+
+ ParsedQuicVersion version() const { return GetParam(); }
+
+ QuicTransportVersion transport_version() const {
+ return version().transport_version;
+ }
+
+ 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 (!VersionHasIetfQuicFrames(transport_version())) {
+ // Only needed for version 99/IETF QUIC.
+ return;
+ }
+ EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
+ QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_id,
+ 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_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSimpleServerSessionTest,
+ ::testing::ValuesIn(AllSupportedVersions()),
+ ::testing::PrintToStringParamName());
+
+TEST_P(QuicSimpleServerSessionTest, CloseStreamDueToReset) {
+ // Send some data open a stream, then reset it.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ kStreamData);
+ session_->OnStreamFrame(data1);
+ EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+ // 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(*session_, WriteControlFrame(_, _));
+
+ if (!VersionHasIetfQuicFrames(transport_version())) {
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ session_->OnRstStream(rst1);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+ EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+ // Send the same two bytes of payload in a new packet.
+ session_->OnStreamFrame(data1);
+
+ // The stream should not be re-opened.
+ EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+ 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 (!VersionHasIetfQuicFrames(transport_version())) {
+ EXPECT_CALL(*session_, WriteControlFrame(_, _));
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ session_->OnRstStream(rst1);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ kStreamData);
+ session_->OnStreamFrame(data1);
+
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, AcceptClosedStream) {
+ // Send some data to open two streams.
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ kStreamData);
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ kStreamData);
+ session_->OnStreamFrame(frame1);
+ session_->OnStreamFrame(frame2);
+ EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+ // 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 (!VersionHasIetfQuicFrames(transport_version())) {
+ EXPECT_CALL(*session_, WriteControlFrame(_, _));
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ session_->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,
+ kStreamData);
+ QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2,
+ kStreamData);
+ session_->OnStreamFrame(frame3);
+ session_->OnStreamFrame(frame4);
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateIncomingStreamDisconnected) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (version() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that incoming stream creation fails when connection is not connected.
+ size_t initial_num_open_stream =
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ EXPECT_QUIC_BUG(QuicSimpleServerSessionPeer::CreateIncomingStream(
+ session_.get(), GetNthClientInitiatedBidirectionalId(0)),
+ "ShouldCreateIncomingStream called when disconnected");
+ EXPECT_EQ(initial_num_open_stream,
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+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 (version() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that outgoing stream creation fails when connection is not connected.
+ size_t initial_num_open_stream =
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ EXPECT_QUIC_BUG(
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()),
+ "ShouldCreateOutgoingUnidirectionalStream called when disconnected");
+
+ EXPECT_EQ(initial_num_open_stream,
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUnencrypted) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (version() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that outgoing stream creation fails when encryption has not yet been
+ // established.
+ size_t initial_num_open_stream =
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+ EXPECT_QUIC_BUG(
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()),
+ "Encryption not established so no outgoing stream created.");
+ EXPECT_EQ(initial_num_open_stream,
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+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,
+ kStreamData);
+ session_->OnStreamFrame(data1);
+ EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+ EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+ /*incoming=*/1);
+
+ if (!VersionUsesHttp3(transport_version())) {
+ session_->UnregisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(transport_version()),
+ /*is_static=*/true);
+ }
+ // Assume encryption already established.
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr);
+ QuicCryptoServerStreamBase* crypto_stream =
+ CreateMockCryptoServerStream(&crypto_config_, &compressed_certs_cache_,
+ session_.get(), &stream_helper_);
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream);
+ if (!VersionUsesHttp3(transport_version())) {
+ session_->RegisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(transport_version()),
+ /*is_static=*/true,
+ spdy::SpdyStreamPrecedence(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());
+ if (VersionUsesHttp3(transport_version())) {
+ EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i + 3),
+ created_stream->id());
+ } else {
+ EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i), created_stream->id());
+ }
+ EXPECT_EQ(i + 1, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+ /*incoming=*/1);
+ }
+
+ // Continuing creating push stream would fail.
+ EXPECT_EQ(nullptr,
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()));
+ EXPECT_EQ(kMaxStreamsForTest,
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+ /*incoming=*/1);
+
+ // Create peer initiated stream should have no problem.
+ QuicStreamFrame data2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ kStreamData);
+ session_->OnStreamFrame(data2);
+ EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+ /*outcoming=*/kMaxStreamsForTest);
+}
+
+TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) {
+ QuicStreamFrame frame(GetNthServerInitiatedUnidirectionalId(0), false, 0,
+ kStreamData);
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Client sent data on server push stream", _));
+ session_->OnStreamFrame(frame);
+}
+
+// Tests that calling GetOrCreateStream() on an outgoing stream not promised yet
+// should result close connection.
+TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) {
+ const size_t initial_num_open_stream =
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+ const QuicErrorCode expected_error = VersionUsesHttp3(transport_version())
+ ? QUIC_HTTP_STREAM_WRONG_DIRECTION
+ : QUIC_INVALID_STREAM_ID;
+ EXPECT_CALL(*connection_, CloseConnection(expected_error,
+ "Data for nonexistent stream", _));
+ EXPECT_EQ(nullptr,
+ QuicSessionPeer::GetOrCreateStream(
+ session_.get(), GetNthServerInitiatedUnidirectionalId(3)));
+ EXPECT_EQ(initial_num_open_stream,
+ QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_stream.cc b/quiche/quic/tools/quic_simple_server_stream.cc
new file mode 100644
index 0000000..ab9622e
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream.cc
@@ -0,0 +1,429 @@
+// 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 "quiche/quic/tools/quic_simple_server_stream.h"
+
+#include <list>
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_spdy_stream.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/http/web_transport_http3.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_server_session.h"
+#include "quiche/spdy/core/spdy_protocol.h"
+
+using spdy::Http2HeaderBlock;
+
+namespace quic {
+
+QuicSimpleServerStream::QuicSimpleServerStream(
+ QuicStreamId id,
+ QuicSpdySession* session,
+ StreamType type,
+ QuicSimpleServerBackend* quic_simple_server_backend)
+ : QuicSpdyServerStreamBase(id, session, type),
+ content_length_(-1),
+ generate_bytes_length_(0),
+ quic_simple_server_backend_(quic_simple_server_backend) {
+ QUICHE_DCHECK(quic_simple_server_backend_);
+}
+
+QuicSimpleServerStream::QuicSimpleServerStream(
+ PendingStream* pending, QuicSpdySession* session,
+ QuicSimpleServerBackend* quic_simple_server_backend)
+ : QuicSpdyServerStreamBase(pending, session),
+ content_length_(-1),
+ generate_bytes_length_(0),
+ quic_simple_server_backend_(quic_simple_server_backend) {
+ QUICHE_DCHECK(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);
+ // QuicSpdyStream::OnInitialHeadersComplete() may have already sent error
+ // response.
+ if (!response_sent_ &&
+ !SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
+ &request_headers_)) {
+ QUIC_DVLOG(1) << "Invalid headers";
+ SendErrorResponse();
+ }
+ ConsumeHeaderList();
+ if (!fin && !response_sent_) {
+ // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) require
+ // sending the response right after parsing the headers even though the FIN
+ // bit has not been received on the request stream.
+ auto it = request_headers_.find(":method");
+ if (it != request_headers_.end() &&
+ absl::StartsWith(it->second, "CONNECT")) {
+ SendResponse();
+ }
+ }
+}
+
+void QuicSimpleServerStream::OnTrailingHeadersComplete(
+ bool /*fin*/,
+ size_t /*frame_len*/,
+ const QuicHeaderList& /*header_list*/) {
+ QUIC_BUG(quic_bug_10962_1) << "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(
+ Http2HeaderBlock push_request_headers) {
+ if (QuicUtils::IsClientInitiatedStreamId(session()->transport_version(),
+ id())) {
+ QUIC_BUG(quic_bug_10962_2)
+ << "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.";
+ QUICHE_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 (!request_headers_.contains(":authority")) {
+ QUIC_DVLOG(1) << "Request headers do not contain :authority.";
+ SendErrorResponse();
+ return;
+ }
+
+ if (!request_headers_.contains(":path")) {
+ // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) do not all
+ // require :path to be present.
+ auto it = request_headers_.find(":method");
+ if (it == request_headers_.end() ||
+ !absl::StartsWith(it->second, "CONNECT")) {
+ QUIC_DVLOG(1) << "Request headers do not contain :path.";
+ SendErrorResponse();
+ return;
+ }
+ }
+
+ if (quic_simple_server_backend_ == nullptr) {
+ QUIC_DVLOG(1) << "Backend is missing.";
+ SendErrorResponse();
+ return;
+ }
+
+ if (web_transport() != nullptr) {
+ QuicSimpleServerBackend::WebTransportResponse response =
+ quic_simple_server_backend_->ProcessWebTransportRequest(
+ request_headers_, web_transport());
+ if (response.response_headers[":status"] == "200") {
+ WriteHeaders(std::move(response.response_headers), false, nullptr);
+ if (response.visitor != nullptr) {
+ web_transport()->SetVisitor(std::move(response.visitor));
+ }
+ web_transport()->HeadersReceived(request_headers_);
+ } else {
+ WriteHeaders(std::move(response.response_headers), true, nullptr);
+ }
+ 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) {
+ if (response == nullptr) {
+ QUIC_DVLOG(1) << "Response not found in cache.";
+ SendNotFoundResponse();
+ return;
+ }
+
+ // Send Early Hints first.
+ for (const auto& headers : response->early_hints()) {
+ QUIC_DVLOG(1) << "Stream " << id() << " sending an Early Hints response: "
+ << headers.DebugString();
+ WriteHeaders(headers.Clone(), false, nullptr);
+ }
+
+ if (response->response_type() == QuicBackendResponse::CLOSE_CONNECTION) {
+ QUIC_DVLOG(1) << "Special response: closing connection.";
+ OnUnrecoverableError(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 Http2HeaderBlock& 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()->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 (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::GENERATE_BYTES) {
+ QUIC_DVLOG(1) << "Stream " << id() << " sending a generate bytes response.";
+ std::string path = request_headers_[":path"].as_string().substr(1);
+ if (!absl::SimpleAtoi(path, &generate_bytes_length_)) {
+ QUIC_LOG(ERROR) << "Path is not a number.";
+ SendNotFoundResponse();
+ return;
+ }
+ Http2HeaderBlock headers = response->headers().Clone();
+ headers["content-length"] = absl::StrCat(generate_bytes_length_);
+
+ WriteHeaders(std::move(headers), false, nullptr);
+ QUICHE_DCHECK(!response_sent_);
+ response_sent_ = true;
+
+ WriteGeneratedBytes();
+
+ return;
+ }
+
+ QUIC_DVLOG(1) << "Stream " << id() << " sending response.";
+ SendHeadersAndBodyAndTrailers(response->headers().Clone(), response->body(),
+ response->trailers().Clone());
+}
+
+void QuicSimpleServerStream::OnCanWrite() {
+ QuicSpdyStream::OnCanWrite();
+ WriteGeneratedBytes();
+}
+
+void QuicSimpleServerStream::WriteGeneratedBytes() {
+ static size_t kChunkSize = 1024;
+ while (!HasBufferedData() && generate_bytes_length_ > 0) {
+ size_t len = std::min<size_t>(kChunkSize, generate_bytes_length_);
+ std::string data(len, 'a');
+ generate_bytes_length_ -= len;
+ bool fin = generate_bytes_length_ == 0;
+ WriteOrBufferBody(data, fin);
+ }
+}
+
+void QuicSimpleServerStream::SendNotFoundResponse() {
+ QUIC_DVLOG(1) << "Stream " << id() << " sending not found response.";
+ Http2HeaderBlock headers;
+ headers[":status"] = "404";
+ headers["content-length"] = absl::StrCat(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.";
+ if (!reading_stopped()) {
+ StopReading();
+ }
+ Http2HeaderBlock headers;
+ if (resp_code <= 0) {
+ headers[":status"] = "500";
+ } else {
+ headers[":status"] = absl::StrCat(resp_code);
+ }
+ headers["content-length"] = absl::StrCat(strlen(kErrorResponseBody));
+ SendHeadersAndBody(std::move(headers), kErrorResponseBody);
+}
+
+void QuicSimpleServerStream::SendIncompleteResponse(
+ Http2HeaderBlock response_headers,
+ absl::string_view body) {
+ QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = false) : "
+ << response_headers.DebugString();
+ WriteHeaders(std::move(response_headers), /*fin=*/false, nullptr);
+ QUICHE_DCHECK(!response_sent_);
+ response_sent_ = true;
+
+ QUIC_DLOG(INFO) << "Stream " << id()
+ << " writing body (fin = false) with size: " << body.size();
+ if (!body.empty()) {
+ WriteOrBufferBody(body, /*fin=*/false);
+ }
+}
+
+void QuicSimpleServerStream::SendHeadersAndBody(
+ Http2HeaderBlock response_headers,
+ absl::string_view body) {
+ SendHeadersAndBodyAndTrailers(std::move(response_headers), body,
+ Http2HeaderBlock());
+}
+
+void QuicSimpleServerStream::SendHeadersAndBodyAndTrailers(
+ Http2HeaderBlock response_headers,
+ absl::string_view body,
+ Http2HeaderBlock 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);
+ QUICHE_DCHECK(!response_sent_);
+ response_sent_ = true;
+ 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);
+}
+
+void QuicSimpleServerStream::OnInvalidHeaders() {
+ QUIC_DVLOG(1) << "Invalid headers";
+ SendErrorResponse(400);
+}
+
+const char* const QuicSimpleServerStream::kErrorResponseBody = "bad";
+const char* const QuicSimpleServerStream::kNotFoundResponseBody =
+ "file not found";
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_stream.h b/quiche/quic/tools/quic_simple_server_stream.h
new file mode 100644
index 0000000..841437d
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream.h
@@ -0,0 +1,109 @@
+// 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 "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_spdy_server_stream_base.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/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,
+ 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;
+ void OnCanWrite() override;
+
+ // QuicStream implementation called by the sequencer when there is
+ // data (or a FIN) to be read.
+ void OnBodyAvailable() override;
+
+ void OnInvalidHeaders() 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::Http2HeaderBlock 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) 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();
+ virtual 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::Http2HeaderBlock response_headers,
+ absl::string_view body);
+
+ void SendHeadersAndBody(spdy::Http2HeaderBlock response_headers,
+ absl::string_view body);
+ void SendHeadersAndBodyAndTrailers(spdy::Http2HeaderBlock response_headers,
+ absl::string_view body,
+ spdy::Http2HeaderBlock response_trailers);
+
+ spdy::Http2HeaderBlock* request_headers() { return &request_headers_; }
+
+ const std::string& body() { return body_; }
+
+ // Writes the body bytes for the GENERATE_BYTES response type.
+ void WriteGeneratedBytes();
+
+ // The parsed headers received from the client.
+ spdy::Http2HeaderBlock request_headers_;
+ int64_t content_length_;
+ std::string body_;
+
+ private:
+ uint64_t generate_bytes_length_;
+ // Whether response headers have already been sent.
+ bool response_sent_ = false;
+
+ QuicSimpleServerBackend* quic_simple_server_backend_; // Not owned.
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_
diff --git a/quiche/quic/tools/quic_simple_server_stream_test.cc b/quiche/quic/tools/quic_simple_server_stream_test.cc
new file mode 100644
index 0000000..c2aa58e
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream_test.cc
@@ -0,0 +1,790 @@
+// 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 "quiche/quic/tools/quic_simple_server_stream.h"
+
+#include <list>
+#include <memory>
+#include <utility>
+
+#include "absl/base/macros.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/http/http_encoder.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_error_codes.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/quic_config_peer.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
+#include "quiche/quic/test_tools/quic_stream_peer.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/quic/tools/quic_simple_server_session.h"
+#include "quiche/common/simple_buffer_allocator.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Invoke;
+using testing::StrictMock;
+
+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_METHOD(void, WriteHeadersMock, (bool fin), ());
+ MOCK_METHOD(void, WriteEarlyHintsHeadersMock, (bool fin), ());
+
+ size_t WriteHeaders(
+ spdy::Http2HeaderBlock header_block, bool fin,
+ quiche::QuicheReferenceCountedPointer<QuicAckListenerInterface>
+ /*ack_listener*/) override {
+ if (header_block[":status"] == "103") {
+ WriteEarlyHintsHeadersMock(fin);
+ } else {
+ WriteHeadersMock(fin);
+ }
+ return 0;
+ }
+
+ // Expose protected QuicSimpleServerStream methods.
+ void DoSendResponse() { SendResponse(); }
+ void DoSendErrorResponse() { QuicSimpleServerStream::SendErrorResponse(); }
+
+ spdy::Http2HeaderBlock* 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_; }
+ bool send_response_was_called() const { return send_response_was_called_; }
+ bool send_error_response_was_called() const {
+ return send_error_response_was_called_;
+ }
+
+ absl::string_view GetHeader(absl::string_view key) const {
+ auto it = request_headers_.find(key);
+ QUICHE_DCHECK(it != request_headers_.end());
+ return it->second;
+ }
+
+ protected:
+ void SendResponse() override {
+ send_response_was_called_ = true;
+ QuicSimpleServerStream::SendResponse();
+ }
+
+ void SendErrorResponse(int resp_code) override {
+ send_error_response_was_called_ = true;
+ QuicSimpleServerStream::SendErrorResponse(resp_code);
+ }
+
+ private:
+ bool send_response_was_called_ = false;
+ bool send_error_response_was_called_ = false;
+};
+
+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) {
+ if (VersionHasIetfQuicFrames(connection->transport_version())) {
+ QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(
+ this, kMaxStreamsForTest);
+ QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(
+ this, kMaxStreamsForTest);
+ } else {
+ QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest);
+ QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest);
+ }
+ ON_CALL(*this, WritevData(_, _, _, _, _, _))
+ .WillByDefault(Invoke(this, &MockQuicSimpleServerSession::ConsumeData));
+ }
+
+ MockQuicSimpleServerSession(const MockQuicSimpleServerSession&) = delete;
+ MockQuicSimpleServerSession& operator=(const MockQuicSimpleServerSession&) =
+ delete;
+ ~MockQuicSimpleServerSession() override = default;
+
+ MOCK_METHOD(void, OnConnectionClosed,
+ (const QuicConnectionCloseFrame& frame,
+ ConnectionCloseSource source),
+ (override));
+ MOCK_METHOD(QuicSpdyStream*, CreateIncomingStream, (QuicStreamId id),
+ (override));
+ MOCK_METHOD(QuicConsumedData, WritevData,
+ (QuicStreamId id, size_t write_length, QuicStreamOffset offset,
+ StreamSendingState state, TransmissionType type,
+ EncryptionLevel level),
+ (override));
+ MOCK_METHOD(void, OnStreamHeaderList,
+ (QuicStreamId stream_id, bool fin, size_t frame_len,
+ const QuicHeaderList& header_list),
+ (override));
+ MOCK_METHOD(void, OnStreamHeadersPriority,
+ (QuicStreamId stream_id,
+ const spdy::SpdyStreamPrecedence& precedence),
+ (override));
+ MOCK_METHOD(void, MaybeSendRstStreamFrame,
+ (QuicStreamId stream_id, QuicResetStreamError error,
+ QuicStreamOffset bytes_written),
+ (override));
+ MOCK_METHOD(void, MaybeSendStopSendingFrame,
+ (QuicStreamId stream_id, QuicResetStreamError error), (override));
+
+ using QuicSession::ActivateStream;
+
+ QuicConsumedData ConsumeData(QuicStreamId id, size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state,
+ TransmissionType /*type*/,
+ absl::optional<EncryptionLevel> /*level*/) {
+ if (write_length > 0) {
+ auto buf = std::make_unique<char[]>(write_length);
+ QuicStream* stream = GetOrCreateStream(id);
+ QUICHE_DCHECK(stream);
+ QuicDataWriter writer(write_length, buf.get(), quiche::HOST_BYTE_ORDER);
+ stream->WriteStreamData(offset, write_length, &writer);
+ } else {
+ QUICHE_DCHECK(state != NO_FIN);
+ }
+ return QuicConsumedData(write_length, state != NO_FIN);
+ }
+
+ spdy::Http2HeaderBlock 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())),
+ 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(":scheme", "https");
+ 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);
+ session_.Initialize();
+ connection_->SetEncrypter(
+ quic::ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<quic::NullEncrypter>(connection_->perspective()));
+ if (connection_->version().SupportsAntiAmplificationLimit()) {
+ QuicConnectionPeer::SetAddressValidated(connection_);
+ }
+ 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(absl::WrapUnique(stream_));
+ QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+ session_.config(), kMinimumFlowControlSendWindow);
+ QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+ session_.config(), kMinimumFlowControlSendWindow);
+ QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+ session_.config(), kMinimumFlowControlSendWindow);
+ QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+ session_.config(), kMinimumFlowControlSendWindow);
+ QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(session_.config(), 10);
+ session_.OnConfigNegotiated();
+ 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 UsesHttp3() const {
+ return VersionUsesHttp3(connection_->transport_version());
+ }
+
+ spdy::Http2HeaderBlock 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_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests, QuicSimpleServerStreamTest,
+ ::testing::ValuesIn(AllSupportedVersions()),
+ ::testing::PrintToStringParamName());
+
+TEST_P(QuicSimpleServerStreamTest, TestFraming) {
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .WillRepeatedly(
+ Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+ stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body_.length(), quiche::SimpleBufferAllocator::Get());
+ std::string data =
+ UsesHttp3() ? absl::StrCat(header.AsStringView(), 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(&session_, &MockQuicSimpleServerSession::ConsumeData));
+
+ stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body_.length(), quiche::SimpleBufferAllocator::Get());
+ std::string data =
+ UsesHttp3() ? absl::StrCat(header.AsStringView(), 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(&session_, &MockQuicSimpleServerSession::ConsumeData));
+
+ EXPECT_FALSE(stream_->fin_received());
+ EXPECT_FALSE(stream_->rst_received());
+
+ QuicStreamPeer::SetFinSent(stream_);
+ stream_->CloseWriteSide();
+
+ if (session_.version().UsesHttp3()) {
+ EXPECT_CALL(session_,
+ MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+ QUIC_STREAM_NO_ERROR)))
+ .Times(1);
+ } else {
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(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 (UsesHttp3()) {
+ EXPECT_CALL(session_,
+ WritevData(_, kDataFrameHeaderLength, _, NO_FIN, _, _));
+ }
+ EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _));
+
+ stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body_.length(), quiche::SimpleBufferAllocator::Get());
+ std::string data =
+ UsesHttp3() ? absl::StrCat(header.AsStringView(), 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 = HttpEncoder::SerializeDataFrameHeader(
+ large_body.length(), quiche::SimpleBufferAllocator::Get());
+ std::string data2 = UsesHttp3()
+ ? absl::StrCat(header.AsStringView(), 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::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+ (*request_headers)[":path"] = "/bar";
+ (*request_headers)[":authority"] = "www.google.com";
+ (*request_headers)[":method"] = "GET";
+
+ // 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";
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body.length(), quiche::SimpleBufferAllocator::Get());
+
+ memory_cache_backend_.AddResponse("www.google.com", "/bar",
+ std::move(response_headers_), body);
+
+ QuicStreamPeer::SetFinReceived(stream_);
+
+ InSequence s;
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_, WritevData(_, header.size(), _, 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::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+ (*request_headers)[":path"] = "/bar";
+ (*request_headers)[":authority"] = "www.google.com";
+ (*request_headers)[":method"] = "GET";
+
+ // HTTP/2 only supports 3-digit-integer, so "+200" is illegal.
+ response_headers_[":status"] = "+200";
+ response_headers_["content-length"] = "5";
+ std::string body = "Yummm";
+
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body.length(), quiche::SimpleBufferAllocator::Get());
+
+ memory_cache_backend_.AddResponse("www.google.com", "/bar",
+ std::move(response_headers_), body);
+
+ QuicStreamPeer::SetFinReceived(stream_);
+
+ InSequence s;
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_, WritevData(_, header.size(), _, 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(), 3),
+ &session_, WRITE_UNIDIRECTIONAL, &memory_cache_backend_);
+ session_.ActivateStream(absl::WrapUnique(promised_stream));
+
+ // Send a push response with response status 404, which will be regarded as
+ // invalid server push response.
+ spdy::Http2HeaderBlock* request_headers = promised_stream->mutable_headers();
+ (*request_headers)[":path"] = "/bar";
+ (*request_headers)[":authority"] = "www.google.com";
+ (*request_headers)[":method"] = "GET";
+
+ 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;
+ if (session_.version().UsesHttp3()) {
+ EXPECT_CALL(session_,
+ MaybeSendStopSendingFrame(
+ promised_stream->id(),
+ QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)));
+ }
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ promised_stream->id(),
+ QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), 0));
+
+ promised_stream->DoSendResponse();
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) {
+ // Add a request and response with valid headers.
+ spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+ (*request_headers)[":path"] = "/bar";
+ (*request_headers)[":authority"] = "www.google.com";
+ (*request_headers)[":method"] = "GET";
+
+ response_headers_[":status"] = "200";
+ response_headers_["content-length"] = "5";
+ std::string body = "Yummm";
+
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body.length(), quiche::SimpleBufferAllocator::Get());
+
+ memory_cache_backend_.AddResponse("www.google.com", "/bar",
+ std::move(response_headers_), body);
+ QuicStreamPeer::SetFinReceived(stream_);
+
+ InSequence s;
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_, WritevData(_, header.size(), _, 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, SendResponseWithEarlyHints) {
+ std::string host = "www.google.com";
+ std::string request_path = "/foo";
+ std::string body = "Yummm";
+
+ // Add a request and response with early hints.
+ spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+ (*request_headers)[":path"] = request_path;
+ (*request_headers)[":authority"] = host;
+ (*request_headers)[":method"] = "GET";
+
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body.length(), quiche::SimpleBufferAllocator::Get());
+ std::vector<spdy::Http2HeaderBlock> early_hints;
+ // Add two Early Hints.
+ const size_t kNumEarlyHintsResponses = 2;
+ for (size_t i = 0; i < kNumEarlyHintsResponses; ++i) {
+ spdy::Http2HeaderBlock hints;
+ hints["link"] = "</image.png>; rel=preload; as=image";
+ early_hints.push_back(std::move(hints));
+ }
+
+ response_headers_[":status"] = "200";
+ response_headers_["content-length"] = "5";
+ memory_cache_backend_.AddResponseWithEarlyHints(
+ host, request_path, std::move(response_headers_), body, early_hints);
+ QuicStreamPeer::SetFinReceived(stream_);
+
+ InSequence s;
+ for (size_t i = 0; i < kNumEarlyHintsResponses; ++i) {
+ EXPECT_CALL(*stream_, WriteEarlyHintsHeadersMock(false));
+ }
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_, WritevData(_, header.size(), _, 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, 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::Http2HeaderBlock()),
+ "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(), 3);
+ // 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(absl::WrapUnique(server_initiated_stream));
+
+ const std::string kHost = "www.foo.com";
+ const std::string kPath = "/bar";
+ spdy::Http2HeaderBlock headers;
+ headers[":path"] = kPath;
+ headers[":authority"] = kHost;
+ headers[":method"] = "GET";
+
+ response_headers_[":status"] = "200";
+ response_headers_["content-length"] = "5";
+ const std::string kBody = "Hello";
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body_.length(), quiche::SimpleBufferAllocator::Get());
+ 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 (UsesHttp3()) {
+ EXPECT_CALL(session_, WritevData(kServerInitiatedStreamId, header.size(), _,
+ 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) {
+ QuicStreamPeer::SetFinReceived(stream_);
+
+ InSequence s;
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ if (UsesHttp3()) {
+ 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) {
+ spdy::Http2HeaderBlock request_headers;
+ // \000 is a way to write the null byte when followed by a literal digit.
+ header_list_.OnHeader("content-length", absl::string_view("11\00012", 5));
+
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .WillRepeatedly(
+ Invoke(&session_, &MockQuicSimpleServerSession::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) {
+ spdy::Http2HeaderBlock request_headers;
+ // \000 is a way to write the null byte when followed by a literal digit.
+ header_list_.OnHeader("content-length", absl::string_view("\00012", 3));
+
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .WillRepeatedly(
+ Invoke(&session_, &MockQuicSimpleServerSession::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::Http2HeaderBlock request_headers;
+ // \000 is a way to write the null byte when followed by a literal digit.
+ header_list_.OnHeader("content-length", absl::string_view("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) {
+ EXPECT_FALSE(stream_->reading_stopped());
+
+ if (VersionUsesHttp3(connection_->transport_version())) {
+ // Unidirectional stream type and then a Stream Cancellation instruction is
+ // sent on the QPACK decoder stream. Ignore these writes without any
+ // assumption on their number or size.
+ auto* qpack_decoder_stream =
+ QuicSpdySessionPeer::GetQpackDecoderSendStream(&session_);
+ EXPECT_CALL(session_, WritevData(qpack_decoder_stream->id(), _, _, _, _, _))
+ .Times(AnyNumber());
+ }
+
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _,
+ session_.version().UsesHttp3()
+ ? QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)
+ : QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT),
+ _))
+ .Times(1);
+ QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+ QUIC_STREAM_CANCELLED, 1234);
+ stream_->OnStreamReset(rst_frame);
+ if (VersionHasIetfQuicFrames(connection_->transport_version())) {
+ EXPECT_CALL(session_owner_, OnStopSendingReceived(_));
+ // Create and inject a STOP SENDING frame to complete the close
+ // of the stream. This is only needed for version 99/IETF QUIC.
+ QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_->id(),
+ QUIC_STREAM_CANCELLED);
+ session_.OnStopSendingFrame(stop_sending);
+ }
+ 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
+ '\x96', 0x6f, 0x6e, 0x00, // <i(69)>on.
+ 0x00, 0x00, 0x08, 0x48, // ...H
+ 0x54, 0x54, 0x50, 0x2f, // TTP/
+ 0x31, 0x2e, 0x31, // 1.1
+ };
+ absl::string_view data(arr, ABSL_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);
+}
+
+TEST_P(QuicSimpleServerStreamTest, ConnectSendsResponseBeforeFinReceived) {
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .WillRepeatedly(
+ Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ header_list.OnHeaderBlockEnd(128, 128);
+ EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
+ stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
+ quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+ body_.length(), quiche::SimpleBufferAllocator::Get());
+ std::string data =
+ UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
+ stream_->OnStreamFrame(
+ QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+ EXPECT_EQ("CONNECT", StreamHeadersValue(":method"));
+ EXPECT_EQ(body_, StreamBody());
+ EXPECT_TRUE(stream_->send_response_was_called());
+ EXPECT_FALSE(stream_->send_error_response_was_called());
+}
+
+TEST_P(QuicSimpleServerStreamTest, ConnectWithInvalidHeader) {
+ EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+ .WillRepeatedly(
+ Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+ QuicHeaderList header_list;
+ header_list.OnHeaderBlockStart();
+ header_list.OnHeader(":authority", "www.google.com:4433");
+ header_list.OnHeader(":method", "CONNECT");
+ // QUIC requires lower-case header names.
+ header_list.OnHeader("InVaLiD-HeAdEr", "Well that's just wrong!");
+ header_list.OnHeaderBlockEnd(128, 128);
+
+ if (UsesHttp3()) {
+ EXPECT_CALL(session_,
+ MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+ QUIC_STREAM_NO_ERROR)))
+ .Times(1);
+ } else {
+ EXPECT_CALL(
+ session_,
+ MaybeSendRstStreamFrame(
+ _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
+ .Times(1);
+ }
+ EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
+ stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
+ EXPECT_FALSE(stream_->send_response_was_called());
+ EXPECT_TRUE(stream_->send_error_response_was_called());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/tools/quic_spdy_client_base.cc b/quiche/quic/tools/quic_spdy_client_base.cc
new file mode 100644
index 0000000..f61a26d
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_client_base.cc
@@ -0,0 +1,296 @@
+// 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 "quiche/quic/tools/quic_spdy_client_base.h"
+
+#include <utility>
+
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/quiche_text_utils.h"
+
+using spdy::Http2HeaderBlock;
+
+namespace quic {
+
+void QuicSpdyClientBase::ClientQuicDataToResend::Resend() {
+ client_->SendRequest(*headers_, body_, fin_);
+ headers_ = nullptr;
+}
+
+QuicSpdyClientBase::QuicDataToResend::QuicDataToResend(
+ std::unique_ptr<Http2HeaderBlock> headers,
+ absl::string_view 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,
+ std::unique_ptr<SessionCache> session_cache)
+ : QuicClientBase(server_id,
+ supported_versions,
+ config,
+ helper,
+ alarm_factory,
+ std::move(network_helper),
+ std::move(proof_verifier),
+ std::move(session_cache)),
+ 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());
+}
+
+const QuicSpdyClientSession* QuicSpdyClientBase::client_session() const {
+ return static_cast<const QuicSpdyClientSession*>(QuicClientBase::session());
+}
+
+void QuicSpdyClientBase::InitializeSession() {
+ if (max_inbound_header_list_size_ > 0) {
+ client_session()->set_max_inbound_header_list_size(
+ max_inbound_header_list_size_);
+ }
+ client_session()->Initialize();
+ client_session()->CryptoConnect();
+}
+
+void QuicSpdyClientBase::OnClose(QuicSpdyStream* stream) {
+ QUICHE_DCHECK(stream != nullptr);
+ QuicSpdyClientStream* client_stream =
+ static_cast<QuicSpdyClientStream*>(stream);
+
+ const Http2HeaderBlock& 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()) {
+ QUIC_LOG(ERROR) << "Missing :status response header";
+ } else if (!absl::SimpleAtoi(status->second, &latest_response_code_)) {
+ QUIC_LOG(ERROR) << "Invalid :status response header: " << status->second;
+ }
+ 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 std::make_unique<QuicSpdyClientSession>(
+ *config(), supported_versions, connection, server_id(), crypto_config(),
+ &push_promise_index_);
+}
+
+void QuicSpdyClientBase::SendRequest(const Http2HeaderBlock& headers,
+ absl::string_view body,
+ bool fin) {
+ if (GetQuicFlag(FLAGS_quic_client_convert_http_header_name_to_lowercase)) {
+ QUIC_CODE_COUNT(quic_client_convert_http_header_name_to_lowercase);
+ Http2HeaderBlock sanitized_headers;
+ for (const auto& p : headers) {
+ sanitized_headers[quiche::QuicheTextUtils::ToLower(p.first)] = p.second;
+ }
+
+ SendRequestInternal(std::move(sanitized_headers), body, fin);
+ } else {
+ SendRequestInternal(headers.Clone(), body, fin);
+ }
+}
+
+void QuicSpdyClientBase::SendRequestInternal(Http2HeaderBlock sanitized_headers,
+ absl::string_view body,
+ bool fin) {
+ QuicClientPushPromiseIndex::TryHandle* handle;
+ QuicAsyncStatus rv =
+ push_promise_index()->Try(sanitized_headers, this, &handle);
+ if (rv == QUIC_SUCCESS)
+ return;
+
+ if (rv == QUIC_PENDING) {
+ // May need to retry request if asynchronous rendezvous fails.
+ AddPromiseDataToResend(sanitized_headers, body, fin);
+ return;
+ }
+
+ QuicSpdyClientStream* stream = CreateClientStream();
+ if (stream == nullptr) {
+ QUIC_BUG(quic_bug_10949_1) << "stream creation failed!";
+ return;
+ }
+ stream->SendRequest(std::move(sanitized_headers), body, fin);
+}
+
+void QuicSpdyClientBase::SendRequestAndWaitForResponse(
+ const Http2HeaderBlock& headers,
+ absl::string_view 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) {
+ Http2HeaderBlock headers;
+ if (!SpdyUtils::PopulateHeaderBlockFromUrl(url_list[i], &headers)) {
+ QUIC_BUG(quic_bug_10949_2) << "Unable to create request";
+ continue;
+ }
+ SendRequest(headers, "", true);
+ }
+ while (WaitForEvents()) {
+ }
+}
+
+QuicSpdyClientStream* QuicSpdyClientBase::CreateClientStream() {
+ if (!connected()) {
+ return nullptr;
+ }
+ if (VersionHasIetfQuicFrames(client_session()->transport_version())) {
+ // Process MAX_STREAMS from peer or wait for liveness testing succeeds.
+ while (!client_session()->CanOpenNextOutgoingBidirectionalStream()) {
+ network_helper()->RunEventLoop();
+ }
+ }
+ auto* stream = static_cast<QuicSpdyClientStream*>(
+ client_session()->CreateOutgoingBidirectionalStream());
+ if (stream) {
+ stream->set_visitor(this);
+ }
+ return stream;
+}
+
+bool QuicSpdyClientBase::goaway_received() const {
+ return client_session() && client_session()->goaway_received();
+}
+
+bool QuicSpdyClientBase::EarlyDataAccepted() {
+ return client_session()->EarlyDataAccepted();
+}
+
+bool QuicSpdyClientBase::ReceivedInchoateReject() {
+ return client_session()->ReceivedInchoateReject();
+}
+
+int QuicSpdyClientBase::GetNumSentClientHellosFromSession() {
+ return client_session()->GetNumSentClientHellos();
+}
+
+int QuicSpdyClientBase::GetNumReceivedServerConfigUpdatesFromSession() {
+ return client_session()->GetNumReceivedServerConfigUpdates();
+}
+
+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 Http2HeaderBlock& headers,
+ absl::string_view body,
+ bool fin) {
+ std::unique_ptr<Http2HeaderBlock> new_headers(
+ new Http2HeaderBlock(headers.Clone()));
+ push_promise_data_to_resend_.reset(
+ new ClientQuicDataToResend(std::move(new_headers), body, fin, this));
+}
+
+bool QuicSpdyClientBase::CheckVary(
+ const Http2HeaderBlock& /*client_request*/,
+ const Http2HeaderBlock& /*promise_request*/,
+ const Http2HeaderBlock& /*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();
+ }
+}
+
+int QuicSpdyClientBase::latest_response_code() const {
+ QUIC_BUG_IF(quic_bug_10949_3, !store_response_) << "Response not stored!";
+ return latest_response_code_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_headers() const {
+ QUIC_BUG_IF(quic_bug_10949_4, !store_response_) << "Response not stored!";
+ return latest_response_headers_;
+}
+
+const std::string& QuicSpdyClientBase::preliminary_response_headers() const {
+ QUIC_BUG_IF(quic_bug_10949_5, !store_response_) << "Response not stored!";
+ return preliminary_response_headers_;
+}
+
+const Http2HeaderBlock& QuicSpdyClientBase::latest_response_header_block()
+ const {
+ QUIC_BUG_IF(quic_bug_10949_6, !store_response_) << "Response not stored!";
+ return latest_response_header_block_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_body() const {
+ QUIC_BUG_IF(quic_bug_10949_7, !store_response_) << "Response not stored!";
+ return latest_response_body_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_trailers() const {
+ QUIC_BUG_IF(quic_bug_10949_8, !store_response_) << "Response not stored!";
+ return latest_response_trailers_;
+}
+
+bool QuicSpdyClientBase::HasActiveRequests() {
+ return client_session()->HasActiveRequestStreams();
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_spdy_client_base.h b/quiche/quic/tools/quic_spdy_client_base.h
new file mode 100644
index 0000000..921718a
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_client_base.h
@@ -0,0 +1,243 @@
+// 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 "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_client_base.h"
+
+namespace quic {
+
+class ProofVerifier;
+class QuicServerId;
+class SessionCache;
+
+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::Http2HeaderBlock& response_headers,
+ const std::string& response_body) = 0;
+ };
+
+ // A piece of data that can be sent multiple times. For example, it can be a
+ // HTTP request that is resent after a connect=>version negotiation=>reconnect
+ // sequence.
+ class QuicDataToResend {
+ public:
+ // |headers| may be null, since it's possible to send data without headers.
+ QuicDataToResend(std::unique_ptr<spdy::Http2HeaderBlock> headers,
+ absl::string_view 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::Http2HeaderBlock> headers_;
+ absl::string_view 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,
+ std::unique_ptr<SessionCache> session_cache);
+ 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::Http2HeaderBlock& headers,
+ absl::string_view body,
+ bool fin);
+
+ // Sends an HTTP request and waits for response before returning.
+ void SendRequestAndWaitForResponse(const spdy::Http2HeaderBlock& headers,
+ absl::string_view 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();
+ const QuicSpdyClientSession* client_session() const;
+
+ QuicClientPushPromiseIndex* push_promise_index() {
+ return &push_promise_index_;
+ }
+
+ bool CheckVary(const spdy::Http2HeaderBlock& client_request,
+ const spdy::Http2HeaderBlock& promise_request,
+ const spdy::Http2HeaderBlock& 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; }
+
+ int latest_response_code() const;
+ const std::string& latest_response_headers() const;
+ const std::string& preliminary_response_headers() const;
+ const spdy::Http2HeaderBlock& 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);
+ }
+
+ void set_drop_response_body(bool drop_response_body) {
+ drop_response_body_ = drop_response_body;
+ }
+ bool drop_response_body() const { return drop_response_body_; }
+
+ void set_enable_web_transport(bool enable_web_transport) {
+ enable_web_transport_ = enable_web_transport;
+ }
+ bool enable_web_transport() const { return enable_web_transport_; }
+
+ void set_use_datagram_contexts(bool use_datagram_contexts) {
+ use_datagram_contexts_ = use_datagram_contexts;
+ }
+ bool use_datagram_contexts() const { return use_datagram_contexts_; }
+
+ // QuicClientBase methods.
+ bool goaway_received() const override;
+ bool EarlyDataAccepted() override;
+ bool ReceivedInchoateReject() override;
+
+ void set_max_inbound_header_list_size(size_t size) {
+ max_inbound_header_list_size_ = size;
+ }
+
+ protected:
+ int GetNumSentClientHellosFromSession() override;
+ int GetNumReceivedServerConfigUpdatesFromSession() override;
+
+ // Takes ownership of |connection|.
+ std::unique_ptr<QuicSession> CreateQuicClientSession(
+ const quic::ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection) override;
+
+ void ClearDataToResend() override;
+
+ void ResendSavedData() override;
+
+ void AddPromiseDataToResend(const spdy::Http2HeaderBlock& headers,
+ absl::string_view 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::Http2HeaderBlock> headers,
+ absl::string_view body,
+ bool fin,
+ QuicSpdyClientBase* client)
+ : QuicDataToResend(std::move(headers), body, fin), client_(client) {
+ QUICHE_DCHECK(headers_);
+ QUICHE_DCHECK(client);
+ }
+
+ ClientQuicDataToResend(const ClientQuicDataToResend&) = delete;
+ ClientQuicDataToResend& operator=(const ClientQuicDataToResend&) = delete;
+ ~ClientQuicDataToResend() override {}
+
+ void Resend() override;
+
+ private:
+ QuicSpdyClientBase* client_;
+ };
+
+ void SendRequestInternal(spdy::Http2HeaderBlock sanitized_headers,
+ absl::string_view body,
+ bool fin);
+
+ // 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::Http2HeaderBlock 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_;
+
+ bool drop_response_body_ = false;
+ bool enable_web_transport_ = false;
+ bool use_datagram_contexts_ = false;
+ // If not zero, used to set client's max inbound header size before session
+ // initialize.
+ size_t max_inbound_header_list_size_ = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_
diff --git a/quiche/quic/tools/quic_spdy_server_base.h b/quiche/quic/tools/quic_spdy_server_base.h
new file mode 100644
index 0000000..39bde90
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_server_base.h
@@ -0,0 +1,30 @@
+// 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 connects to a specified port and sends QUIC
+// requests to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
+
+#include "quiche/quic/platform/api/quic_socket_address.h"
+
+namespace quic {
+
+// Base class for service instances to be used with QuicToyServer.
+class QuicSpdyServerBase {
+ public:
+ virtual ~QuicSpdyServerBase() = default;
+
+ // Creates a UDP socket and listens on |address|. Returns true on success
+ // and false otherwise.
+ virtual bool CreateUDPSocketAndListen(const QuicSocketAddress& address) = 0;
+
+ // Handles incoming requests. Does not return.
+ virtual void HandleEventsForever() = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
diff --git a/quiche/quic/tools/quic_tcp_like_trace_converter.cc b/quiche/quic/tools/quic_tcp_like_trace_converter.cc
new file mode 100644
index 0000000..f1ae0c1
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter.cc
@@ -0,0 +1,126 @@
+// 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 "quiche/quic/tools/quic_tcp_like_trace_converter.h"
+
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.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::OnCryptoFrameSent(
+ EncryptionLevel level,
+ QuicStreamOffset offset,
+ QuicByteCount data_length) {
+ if (level >= NUM_ENCRYPTION_LEVELS) {
+ QUIC_BUG(quic_bug_10907_1) << "Invalid encryption level";
+ return {};
+ }
+ return OnFrameSent(offset, data_length, /*fin=*/false,
+ &crypto_frames_info_[level]);
+}
+
+QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnStreamFrameSent(
+ QuicStreamId stream_id,
+ QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin) {
+ return OnFrameSent(
+ offset, data_length, fin,
+ &streams_info_.emplace(stream_id, StreamInfo()).first->second);
+}
+
+QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnFrameSent(
+ QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin,
+ StreamInfo* info) {
+ QuicIntervalSet<uint64_t> connection_offsets;
+ if (fin) {
+ // Stream fin consumes a connection offset.
+ ++data_length;
+ }
+ // Get connection offsets of retransmission data in this frame.
+ for (const auto& segment : 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 (info->fin) {
+ return connection_offsets;
+ }
+
+ // Get connection offsets of new data in this frame.
+ QuicStreamOffset least_unsent_offset =
+ info->segments.empty() ? 0 : 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 (!info->segments.empty() && new_data_offset == least_unsent_offset &&
+ connection_offset_ == info->segments.back().connection_offset +
+ info->segments.back().stream_data.Length()) {
+ // Extend the last segment if both stream and connection offsets are
+ // contiguous.
+ info->segments.back().stream_data.SetMax(new_data_offset + new_data_length);
+ } else {
+ info->segments.emplace_back(new_data_offset, connection_offset_,
+ new_data_length);
+ }
+ 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] = connection_offset;
+ 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/quiche/quic/tools/quic_tcp_like_trace_converter.h b/quiche/quic/tools/quic_tcp_like_trace_converter.h
new file mode 100644
index 0000000..78cbaf9
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter.h
@@ -0,0 +1,88 @@
+// 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 "absl/container/flat_hash_map.h"
+#include "quiche/quic/core/frames/quic_stream_frame.h"
+#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_interval_set.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/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 crypto frame is sent. Returns the corresponding connection
+ // offsets.
+ QuicIntervalSet<uint64_t> OnCryptoFrameSent(EncryptionLevel level,
+ QuicStreamOffset offset,
+ QuicByteCount data_length);
+
+ // 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;
+ };
+
+ // Called when frame with |offset|, |data_length| and |fin| has been sent.
+ // Update |info| and returns connection offsets.
+ QuicIntervalSet<uint64_t> OnFrameSent(QuicStreamOffset offset,
+ QuicByteCount data_length,
+ bool fin,
+ StreamInfo* info);
+
+ StreamInfo crypto_frames_info_[NUM_ENCRYPTION_LEVELS];
+ absl::flat_hash_map<QuicStreamId, StreamInfo> streams_info_;
+ absl::flat_hash_map<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/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc b/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc
new file mode 100644
index 0000000..287a319
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc
@@ -0,0 +1,124 @@
+// 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 "quiche/quic/tools/quic_tcp_like_trace_converter.h"
+
+#include "quiche/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 = {300, 450};
+ 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));
+}
+
+TEST(QuicTcpLikeTraceConverterTest, OnCryptoFrameSent) {
+ QuicTcpLikeTraceConverter converter;
+
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+ converter.OnCryptoFrameSent(ENCRYPTION_INITIAL, 0, 100));
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 200),
+ converter.OnStreamFrameSent(1, 0, 100, false));
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(200, 300),
+ converter.OnStreamFrameSent(1, 100, 100, false));
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(300, 400),
+ converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 0, 100));
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(400, 500),
+ converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 100, 100));
+
+ // Verify crypto frame retransmission works as intended.
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+ converter.OnCryptoFrameSent(ENCRYPTION_INITIAL, 0, 100));
+ EXPECT_EQ(QuicIntervalSet<uint64_t>(400, 500),
+ converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 100, 100));
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/tools/quic_toy_client.cc b/quiche/quic/tools/quic_toy_client.cc
new file mode 100644
index 0000000..9baccbf
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_client.cc
@@ -0,0 +1,501 @@
+// 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
+//
+// This tool is available as a built binary at:
+// /google/data/ro/teams/quic/tools/quic_client
+// After submitting changes to this file, you will need to follow the
+// instructions at go/quic_client_binary_update
+
+#include "quiche/quic/tools/quic_toy_client.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_client_session_cache.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_default_proof_providers.h"
+#include "quiche/quic/platform/api/quic_ip_address.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/fake_proof_verifier.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace {
+
+using quiche::QuicheTextUtils;
+
+} // namespace
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, ip_version_for_host_lookup, "",
+ "Only used if host address lookup is needed. "
+ "4=ipv4; 6=ipv6; otherwise=don't care.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, body, "",
+ "If set, send a POST with this body.");
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(
+ std::string, headers, "",
+ "A semicolon separated list of key:value pairs to "
+ "add to request headers.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quiet, false,
+ "Set to true for a quieter output experience.");
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(
+ std::string, connection_options, "",
+ "Connection options as ASCII tags separated by commas, "
+ "e.g. \"ABCD,EFGH\"");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, client_connection_options, "",
+ "Client connection options as ASCII tags separated by commas, "
+ "e.g. \"ABCD,EFGH\"");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false,
+ "Use the IETF draft version. This also enables "
+ "required internal QUIC flags.");
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(
+ bool, force_version_negotiation, false,
+ "If true, start by proposing a version that is reserved for version "
+ "negotiation.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ bool, multi_packet_chlo, false,
+ "If true, add a transport parameter to make the ClientHello span two "
+ "packets. Only works with QUIC+TLS.");
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(int32_t, initial_mtu, 0,
+ "Initial MTU of the connection.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ int32_t, num_requests, 1,
+ "How many sequential requests to make on a single connection.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ bool, disable_certificate_verification, false,
+ "If true, don't verify the server certificate.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, default_client_cert, "",
+ "The path to the file containing PEM-encoded client default certificate to "
+ "be sent to the server, if server requested client certs.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, default_client_cert_key, "",
+ "The path to the file containing PEM-encoded private key of the client's "
+ "default certificate for signing, if server requested client certs.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ bool, drop_response_body, false,
+ "If true, drop response body immediately after it is received.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ bool, disable_port_changes, false,
+ "If true, do not change local port after each request.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, one_connection_per_request, false,
+ "If true, close the connection after each "
+ "request. This allows testing 0-RTT.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, server_connection_id_length, -1,
+ "Length of the server connection ID used.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, client_connection_id_length, -1,
+ "Length of the client connection ID used.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, max_time_before_crypto_handshake_ms,
+ 10000,
+ "Max time to wait before handshake completes.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ int32_t, max_inbound_header_list_size, 128 * 1024,
+ "Max inbound header list size. 0 means default.");
+
+namespace quic {
+namespace {
+
+// Creates a ClientProofSource which only contains a default client certificate.
+// Return nullptr for failure.
+std::unique_ptr<ClientProofSource> CreateTestClientProofSource(
+ absl::string_view default_client_cert_file,
+ absl::string_view default_client_cert_key_file) {
+ std::ifstream cert_stream(std::string{default_client_cert_file},
+ std::ios::binary);
+ std::vector<std::string> certs =
+ CertificateView::LoadPemFromStream(&cert_stream);
+ if (certs.empty()) {
+ std::cerr << "Failed to load client certs." << std::endl;
+ return nullptr;
+ }
+
+ std::ifstream key_stream(std::string{default_client_cert_key_file},
+ std::ios::binary);
+ std::unique_ptr<CertificatePrivateKey> private_key =
+ CertificatePrivateKey::LoadPemFromStream(&key_stream);
+ if (private_key == nullptr) {
+ std::cerr << "Failed to load client cert key." << std::endl;
+ return nullptr;
+ }
+
+ auto proof_source = std::make_unique<DefaultClientProofSource>();
+ proof_source->AddCertAndKey(
+ {"*"},
+ quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
+ new ClientProofSource::Chain(certs)),
+ std::move(*private_key));
+
+ return proof_source;
+}
+
+} // namespace
+
+QuicToyClient::QuicToyClient(ClientFactory* client_factory)
+ : client_factory_(client_factory) {}
+
+int QuicToyClient::SendRequestsAndPrintResponses(
+ std::vector<std::string> urls) {
+ 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();
+ }
+
+ quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
+
+ if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
+ quic::QuicVersionInitializeSupportForIetfDraft();
+ versions = {};
+ for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+ if (version.HasIetfQuicFrames() &&
+ version.handshake_protocol == quic::PROTOCOL_TLS1_3) {
+ versions.push_back(version);
+ }
+ }
+ }
+
+ std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
+ if (!quic_version_string.empty()) {
+ versions = quic::ParseQuicVersionVectorString(quic_version_string);
+ }
+
+ if (versions.empty()) {
+ std::cerr << "No known version selected." << std::endl;
+ return 1;
+ }
+
+ for (const quic::ParsedQuicVersion& version : versions) {
+ quic::QuicEnableVersion(version);
+ }
+
+ if (GetQuicFlag(FLAGS_force_version_negotiation)) {
+ versions.insert(versions.begin(),
+ quic::QuicVersionReservedForNegotiation());
+ }
+
+ const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
+ std::unique_ptr<quic::ProofVerifier> proof_verifier;
+ if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
+ proof_verifier = std::make_unique<FakeProofVerifier>();
+ } else {
+ proof_verifier = quic::CreateDefaultProofVerifier(url.host());
+ }
+ std::unique_ptr<quic::SessionCache> session_cache;
+ if (num_requests > 1 && GetQuicFlag(FLAGS_one_connection_per_request)) {
+ session_cache = std::make_unique<QuicClientSessionCache>();
+ }
+
+ QuicConfig config;
+ std::string connection_options_string = GetQuicFlag(FLAGS_connection_options);
+ if (!connection_options_string.empty()) {
+ config.SetConnectionOptionsToSend(
+ ParseQuicTagVector(connection_options_string));
+ }
+ std::string client_connection_options_string =
+ GetQuicFlag(FLAGS_client_connection_options);
+ if (!client_connection_options_string.empty()) {
+ config.SetClientConnectionOptions(
+ ParseQuicTagVector(client_connection_options_string));
+ }
+ if (GetQuicFlag(FLAGS_multi_packet_chlo)) {
+ // Make the ClientHello span multiple packets by adding a custom transport
+ // parameter.
+ constexpr auto kCustomParameter =
+ static_cast<TransportParameters::TransportParameterId>(0x173E);
+ std::string custom_value(2000, '?');
+ config.custom_transport_parameters_to_send()[kCustomParameter] =
+ custom_value;
+ }
+ config.set_max_time_before_crypto_handshake(QuicTime::Delta::FromMilliseconds(
+ GetQuicFlag(FLAGS_max_time_before_crypto_handshake_ms)));
+
+ int address_family_for_lookup = AF_UNSPEC;
+ if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "4") {
+ address_family_for_lookup = AF_INET;
+ } else if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "6") {
+ address_family_for_lookup = AF_INET6;
+ }
+
+ // Build the client, and try to connect.
+ std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
+ url.host(), host, address_family_for_lookup, port, versions, config,
+ std::move(proof_verifier), std::move(session_cache));
+
+ if (client == nullptr) {
+ std::cerr << "Failed to create client." << std::endl;
+ return 1;
+ }
+
+ if (!GetQuicFlag(FLAGS_default_client_cert).empty() &&
+ !GetQuicFlag(FLAGS_default_client_cert_key).empty()) {
+ std::unique_ptr<ClientProofSource> proof_source =
+ CreateTestClientProofSource(GetQuicFlag(FLAGS_default_client_cert),
+ GetQuicFlag(FLAGS_default_client_cert_key));
+ if (proof_source == nullptr) {
+ std::cerr << "Failed to create client proof source." << std::endl;
+ return 1;
+ }
+ client->crypto_config()->set_proof_source(std::move(proof_source));
+ }
+
+ 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));
+ const int32_t server_connection_id_length =
+ GetQuicFlag(FLAGS_server_connection_id_length);
+ if (server_connection_id_length >= 0) {
+ client->set_server_connection_id_length(server_connection_id_length);
+ }
+ const int32_t client_connection_id_length =
+ GetQuicFlag(FLAGS_client_connection_id_length);
+ if (client_connection_id_length >= 0) {
+ client->set_client_connection_id_length(client_connection_id_length);
+ }
+ const size_t max_inbound_header_list_size =
+ GetQuicFlag(FLAGS_max_inbound_header_list_size);
+ if (max_inbound_header_list_size > 0) {
+ client->set_max_inbound_header_list_size(max_inbound_header_list_size);
+ }
+ 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 << "Failed to negotiate version with " << host << ":" << port
+ << ". " << client->session()->error_details() << 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 " << host << ":" << port << ". "
+ << quic::QuicErrorCodeToString(error) << " "
+ << client->session()->error_details() << std::endl;
+ return 1;
+ }
+ std::cerr << "Connected to " << host << ":" << port << std::endl;
+
+ // Construct the string body from flags, if provided.
+ std::string body = GetQuicFlag(FLAGS_body);
+ if (!GetQuicFlag(FLAGS_body_hex).empty()) {
+ QUICHE_DCHECK(GetQuicFlag(FLAGS_body).empty())
+ << "Only set one of --body and --body_hex.";
+ body = absl::HexStringToBytes(GetQuicFlag(FLAGS_body_hex));
+ }
+
+ // Construct a GET or POST request for supplied URL.
+ spdy::Http2HeaderBlock 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.
+ const std::string headers = GetQuicFlag(FLAGS_headers);
+ for (absl::string_view sp : absl::StrSplit(headers, ';')) {
+ QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
+ if (sp.empty()) {
+ continue;
+ }
+ std::vector<absl::string_view> kv =
+ absl::StrSplit(sp, absl::MaxSplits(':', 1));
+ QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
+ QuicheTextUtils::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"
+ << QuicheTextUtils::HexDump(
+ absl::HexStringToBytes(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"
+ << QuicheTextUtils::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;
+ }
+
+ int response_code = client->latest_response_code();
+ if (response_code >= 200 && response_code < 300) {
+ std::cout << "Request succeeded (" << response_code << ")." << std::endl;
+ } else if (response_code >= 300 && response_code < 400) {
+ if (GetQuicFlag(FLAGS_redirect_is_success)) {
+ std::cout << "Request succeeded (redirect " << response_code << ")."
+ << std::endl;
+ } else {
+ std::cout << "Request failed (redirect " << response_code << ")."
+ << std::endl;
+ return 1;
+ }
+ } else {
+ std::cout << "Request failed (" << response_code << ")." << std::endl;
+ return 1;
+ }
+
+ if (i + 1 < num_requests) { // There are more requests to perform.
+ if (GetQuicFlag(FLAGS_one_connection_per_request)) {
+ std::cout << "Disconnecting client between requests." << std::endl;
+ client->Disconnect();
+ if (!client->Initialize()) {
+ std::cerr << "Failed to reinitialize client between requests."
+ << std::endl;
+ return 1;
+ }
+ if (!client->Connect()) {
+ std::cerr << "Failed to reconnect client between requests."
+ << std::endl;
+ return 1;
+ }
+ } else if (!GetQuicFlag(FLAGS_disable_port_changes)) {
+ // Change the ephemeral port.
+ if (!client->ChangeEphemeralPort()) {
+ std::cerr << "Failed to change ephemeral port." << std::endl;
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_toy_client.h b/quiche/quic/tools/quic_toy_client.h
new file mode 100644
index 0000000..5f884f0
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_client.h
@@ -0,0 +1,51 @@
+// 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
+// requests to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
+
+#include "quiche/quic/tools/quic_spdy_client_base.h"
+
+namespace quic {
+
+class QuicToyClient {
+ public:
+ class ClientFactory {
+ public:
+ virtual ~ClientFactory() = default;
+
+ // Creates a new client configured to connect to |host_for_lookup:port|
+ // supporting |versions|, using |host_for_handshake| for handshake and
+ // |verifier| to verify proofs.
+ virtual std::unique_ptr<QuicSpdyClientBase> CreateClient(
+ std::string host_for_handshake,
+ std::string host_for_lookup,
+ // AF_INET, AF_INET6, or AF_UNSPEC(=don't care).
+ int address_family_for_lookup,
+ uint16_t port,
+ ParsedQuicVersionVector versions,
+ const QuicConfig& config,
+ std::unique_ptr<ProofVerifier> verifier,
+ std::unique_ptr<SessionCache> session_cache) = 0;
+ };
+
+ // Constructs a new toy client that will use |client_factory| to create the
+ // actual QuicSpdyClientBase instance.
+ QuicToyClient(ClientFactory* client_factory);
+
+ // Connects to the QUIC server based on the various flags defined in the
+ // .cc file, sends requests and prints the responses. Returns 0 on success
+ // and non-zero otherwise.
+ int SendRequestsAndPrintResponses(std::vector<std::string> urls);
+
+ private:
+ ClientFactory* client_factory_; // Unowned.
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
diff --git a/quiche/quic/tools/quic_toy_server.cc b/quiche/quic/tools/quic_toy_server.cc
new file mode 100644
index 0000000..03f910c
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_server.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/tools/quic_toy_server.h"
+
+#include <utility>
+#include <vector>
+
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_default_proof_providers.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 6121,
+ "The port the quic server will listen on.");
+
+DEFINE_QUICHE_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_QUICHE_COMMAND_LINE_FLAG(
+ bool, generate_dynamic_responses, false,
+ "If true, then URLs which have a numeric path will send a dynamically "
+ "generated response of that many bytes.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false,
+ "Only enable IETF draft versions. This also "
+ "enables required internal QUIC flags.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ std::string, quic_versions, "",
+ "QUIC versions to enable, e.g. \"h3-25,h3-27\". If not set, then all "
+ "available versions are enabled.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, enable_webtransport, false,
+ "If true, WebTransport support is enabled.");
+
+namespace quic {
+
+std::unique_ptr<quic::QuicSimpleServerBackend>
+QuicToyServer::MemoryCacheBackendFactory::CreateBackend() {
+ auto memory_cache_backend = std::make_unique<QuicMemoryCacheBackend>();
+ if (GetQuicFlag(FLAGS_generate_dynamic_responses)) {
+ memory_cache_backend->GenerateDynamicResponses();
+ }
+ if (!GetQuicFlag(FLAGS_quic_response_cache_dir).empty()) {
+ memory_cache_backend->InitializeBackend(
+ GetQuicFlag(FLAGS_quic_response_cache_dir));
+ }
+ if (GetQuicFlag(FLAGS_enable_webtransport)) {
+ memory_cache_backend->EnableWebTransport();
+ }
+ return memory_cache_backend;
+}
+
+QuicToyServer::QuicToyServer(BackendFactory* backend_factory,
+ ServerFactory* server_factory)
+ : backend_factory_(backend_factory), server_factory_(server_factory) {}
+
+int QuicToyServer::Start() {
+ ParsedQuicVersionVector supported_versions;
+ if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
+ QuicVersionInitializeSupportForIetfDraft();
+ for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+ // Add all versions that supports IETF QUIC.
+ if (version.HasIetfQuicFrames() &&
+ version.handshake_protocol == quic::PROTOCOL_TLS1_3) {
+ supported_versions.push_back(version);
+ }
+ }
+ } else {
+ supported_versions = AllSupportedVersions();
+ }
+ std::string versions_string = GetQuicFlag(FLAGS_quic_versions);
+ if (!versions_string.empty()) {
+ supported_versions = ParseQuicVersionVectorString(versions_string);
+ }
+ if (supported_versions.empty()) {
+ return 1;
+ }
+ for (const auto& version : supported_versions) {
+ QuicEnableVersion(version);
+ }
+ auto proof_source = quic::CreateDefaultProofSource();
+ auto backend = backend_factory_->CreateBackend();
+ auto server = server_factory_->CreateServer(
+ backend.get(), std::move(proof_source), supported_versions);
+
+ if (!server->CreateUDPSocketAndListen(quic::QuicSocketAddress(
+ quic::QuicIpAddress::Any6(), GetQuicFlag(FLAGS_port)))) {
+ return 1;
+ }
+
+ server->HandleEventsForever();
+ return 0;
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_toy_server.h b/quiche/quic/tools/quic_toy_server.h
new file mode 100644
index 0000000..fc82ff7
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_server.h
@@ -0,0 +1,63 @@
+// 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_TOY_SERVER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TOY_SERVER_H_
+
+#include "quiche/quic/core/crypto/proof_source.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_spdy_server_base.h"
+
+namespace quic {
+
+// A binary wrapper for QuicServer. It listens forever on --port
+// (default 6121) until it's killed or ctrl-cd to death.
+class QuicToyServer {
+ public:
+ // A factory for creating QuicSpdyServerBase instances.
+ class ServerFactory {
+ public:
+ virtual ~ServerFactory() = default;
+
+ // Creates a QuicSpdyServerBase instance using |backend| for generating
+ // responses, and |proof_source| for certificates.
+ virtual std::unique_ptr<QuicSpdyServerBase> CreateServer(
+ QuicSimpleServerBackend* backend,
+ std::unique_ptr<ProofSource> proof_source,
+ const ParsedQuicVersionVector& supported_versions) = 0;
+ };
+
+ // A facotry for creating QuicSimpleServerBackend instances.
+ class BackendFactory {
+ public:
+ virtual ~BackendFactory() = default;
+
+ // Creates a new backend.
+ virtual std::unique_ptr<QuicSimpleServerBackend> CreateBackend() = 0;
+ };
+
+ // A factory for creating QuicMemoryCacheBackend instances, configured
+ // to load files from disk, if necessary.
+ class MemoryCacheBackendFactory : public BackendFactory {
+ public:
+ std::unique_ptr<quic::QuicSimpleServerBackend> CreateBackend() override;
+ };
+
+ // Constructs a new toy server that will use |server_factory| to create the
+ // actual QuicSpdyServerBase instance.
+ QuicToyServer(BackendFactory* backend_factory, ServerFactory* server_factory);
+
+ // Connects to the QUIC server based on the various flags defined in the
+ // .cc file, listends for requests and sends the responses. Returns 1 on
+ // failure and does not return otherwise.
+ int Start();
+
+ private:
+ BackendFactory* backend_factory_; // Unowned.
+ ServerFactory* server_factory_; // Unowned.
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_TOY_SERVER_H_
diff --git a/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc b/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc
new file mode 100644
index 0000000..91c7816
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc
@@ -0,0 +1,56 @@
+// 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 "quiche/quic/tools/quic_transport_simple_server_dispatcher.h"
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+namespace quic {
+
+QuicTransportSimpleServerDispatcher::QuicTransportSimpleServerDispatcher(
+ const QuicConfig* config,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicVersionManager* version_manager,
+ std::unique_ptr<QuicConnectionHelperInterface> helper,
+ std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+ std::unique_ptr<QuicAlarmFactory> alarm_factory,
+ uint8_t expected_server_connection_id_length,
+ std::vector<url::Origin> accepted_origins)
+ : QuicDispatcher(config,
+ crypto_config,
+ version_manager,
+ std::move(helper),
+ std::move(session_helper),
+ std::move(alarm_factory),
+ expected_server_connection_id_length),
+ accepted_origins_(accepted_origins) {}
+
+std::unique_ptr<QuicSession>
+QuicTransportSimpleServerDispatcher::CreateQuicSession(
+ QuicConnectionId server_connection_id,
+ const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address, absl::string_view /*alpn*/,
+ const ParsedQuicVersion& version,
+ const ParsedClientHello& /*parsed_chlo*/) {
+ auto connection = std::make_unique<QuicConnection>(
+ server_connection_id, self_address, peer_address, helper(),
+ alarm_factory(), writer(),
+ /*owns_writer=*/false, Perspective::IS_SERVER,
+ ParsedQuicVersionVector{version});
+ auto session = std::make_unique<QuicTransportSimpleServerSession>(
+ connection.release(), /*owns_connection=*/true, this, config(),
+ GetSupportedVersions(), crypto_config(), compressed_certs_cache(),
+ accepted_origins_);
+ session->Initialize();
+ return session;
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_transport_simple_server_dispatcher.h b/quiche/quic/tools/quic_transport_simple_server_dispatcher.h
new file mode 100644
index 0000000..a70aae1
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_dispatcher.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
+
+#include "absl/strings/string_view.h"
+#include "url/origin.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+namespace quic {
+
+// Dispatcher that creates a QuicTransportSimpleServerSession for every incoming
+// connection.
+class QuicTransportSimpleServerDispatcher : public QuicDispatcher {
+ public:
+ QuicTransportSimpleServerDispatcher(
+ const QuicConfig* config,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicVersionManager* version_manager,
+ std::unique_ptr<QuicConnectionHelperInterface> helper,
+ std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+ std::unique_ptr<QuicAlarmFactory> alarm_factory,
+ uint8_t expected_server_connection_id_length,
+ std::vector<url::Origin> accepted_origins);
+
+ protected:
+ std::unique_ptr<QuicSession> CreateQuicSession(
+ QuicConnectionId server_connection_id,
+ const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address, absl::string_view alpn,
+ const ParsedQuicVersion& version,
+ const ParsedClientHello& parsed_chlo) override;
+
+ std::vector<url::Origin> accepted_origins_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
diff --git a/quiche/quic/tools/quic_transport_simple_server_session.cc b/quiche/quic/tools/quic_transport_simple_server_session.cc
new file mode 100644
index 0000000..722e131
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_session.cc
@@ -0,0 +1,176 @@
+// 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 "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+#include <memory>
+
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/quic_transport/quic_transport_protocol.h"
+#include "quiche/quic/quic_transport/quic_transport_stream.h"
+#include "quiche/quic/tools/web_transport_test_visitors.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+
+namespace quic {
+
+QuicTransportSimpleServerSession::QuicTransportSimpleServerSession(
+ QuicConnection* connection,
+ bool owns_connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ std::vector<url::Origin> accepted_origins)
+ : QuicTransportServerSession(connection,
+ owner,
+ config,
+ supported_versions,
+ crypto_config,
+ compressed_certs_cache,
+ this),
+ owns_connection_(owns_connection),
+ mode_(DISCARD),
+ accepted_origins_(accepted_origins) {}
+
+QuicTransportSimpleServerSession::~QuicTransportSimpleServerSession() {
+ if (owns_connection_) {
+ DeleteConnection();
+ }
+}
+
+void QuicTransportSimpleServerSession::OnIncomingDataStream(
+ QuicTransportStream* stream) {
+ switch (mode_) {
+ case DISCARD:
+ stream->SetVisitor(std::make_unique<WebTransportDiscardVisitor>(stream));
+ break;
+
+ case ECHO:
+ switch (stream->type()) {
+ case BIDIRECTIONAL:
+ QUIC_DVLOG(1) << "Opening bidirectional echo stream " << stream->id();
+ stream->SetVisitor(
+ std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+ break;
+ case READ_UNIDIRECTIONAL:
+ QUIC_DVLOG(1)
+ << "Started receiving data on unidirectional echo stream "
+ << stream->id();
+ stream->SetVisitor(
+ std::make_unique<WebTransportUnidirectionalEchoReadVisitor>(
+ stream,
+ [this](const std::string& s) { this->EchoStreamBack(s); }));
+ break;
+ default:
+ QUIC_NOTREACHED();
+ break;
+ }
+ break;
+
+ case OUTGOING_BIDIRECTIONAL:
+ stream->SetVisitor(std::make_unique<WebTransportDiscardVisitor>(stream));
+ ++pending_outgoing_bidirectional_streams_;
+ MaybeCreateOutgoingBidirectionalStream();
+ break;
+ }
+}
+
+void QuicTransportSimpleServerSession::OnCanCreateNewOutgoingStream(
+ bool unidirectional) {
+ if (mode_ == ECHO && unidirectional) {
+ MaybeEchoStreamsBack();
+ } else if (mode_ == OUTGOING_BIDIRECTIONAL && !unidirectional) {
+ MaybeCreateOutgoingBidirectionalStream();
+ }
+}
+
+bool QuicTransportSimpleServerSession::CheckOrigin(url::Origin origin) {
+ if (accepted_origins_.empty()) {
+ return true;
+ }
+
+ for (const url::Origin& accepted_origin : accepted_origins_) {
+ if (origin.IsSameOriginWith(accepted_origin)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QuicTransportSimpleServerSession::ProcessPath(const GURL& url) {
+ if (url.path() == "/discard") {
+ mode_ = DISCARD;
+ return true;
+ }
+ if (url.path() == "/echo") {
+ mode_ = ECHO;
+ return true;
+ }
+ if (url.path() == "/receive-bidirectional") {
+ mode_ = OUTGOING_BIDIRECTIONAL;
+ return true;
+ }
+
+ QUIC_DLOG(WARNING) << "Unknown path requested: " << url.path();
+ return false;
+}
+
+void QuicTransportSimpleServerSession::OnMessageReceived(
+ absl::string_view message) {
+ if (mode_ != ECHO) {
+ return;
+ }
+ datagram_queue()->SendOrQueueDatagram(
+ quiche::QuicheMemSlice(quiche::QuicheBuffer::Copy(
+ connection()->helper()->GetStreamSendBufferAllocator(), message)));
+}
+
+void QuicTransportSimpleServerSession::MaybeEchoStreamsBack() {
+ while (!streams_to_echo_back_.empty() &&
+ CanOpenNextOutgoingUnidirectionalStream()) {
+ // Remove the stream from the queue first, in order to avoid accidentally
+ // entering an infinite loop in case any of the following code calls
+ // OnCanCreateNewOutgoingStream().
+ std::string data = std::move(streams_to_echo_back_.front());
+ streams_to_echo_back_.pop_front();
+
+ auto stream_owned = std::make_unique<QuicTransportStream>(
+ GetNextOutgoingUnidirectionalStreamId(), this, this);
+ QuicTransportStream* stream = stream_owned.get();
+ ActivateStream(std::move(stream_owned));
+ QUIC_DVLOG(1) << "Opened echo response stream " << stream->id();
+
+ stream->SetVisitor(
+ std::make_unique<WebTransportUnidirectionalEchoWriteVisitor>(stream,
+ data));
+ stream->visitor()->OnCanWrite();
+ }
+}
+
+void QuicTransportSimpleServerSession::
+ MaybeCreateOutgoingBidirectionalStream() {
+ while (pending_outgoing_bidirectional_streams_ > 0 &&
+ CanOpenNextOutgoingBidirectionalStream()) {
+ auto stream_owned = std::make_unique<QuicTransportStream>(
+ GetNextOutgoingBidirectionalStreamId(), this, this);
+ QuicTransportStream* stream = stream_owned.get();
+ ActivateStream(std::move(stream_owned));
+ QUIC_DVLOG(1) << "Opened outgoing bidirectional stream " << stream->id();
+ stream->SetVisitor(
+ std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+ if (!stream->Write("hello")) {
+ QUIC_DVLOG(1) << "Write failed.";
+ }
+ --pending_outgoing_bidirectional_streams_;
+ }
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/quic_transport_simple_server_session.h b/quiche/quic/tools/quic_transport_simple_server_session.h
new file mode 100644
index 0000000..f5c3934
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_session.h
@@ -0,0 +1,81 @@
+// 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
+
+#include <memory>
+#include <vector>
+
+#include "url/origin.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/quic_transport/quic_transport_server_session.h"
+#include "quiche/quic/quic_transport/quic_transport_stream.h"
+
+namespace quic {
+
+// QuicTransport simple server is a non-production server that can be used for
+// testing QuicTransport. It has two modes that can be changed using the
+// command line flags, "echo" and "discard".
+class QuicTransportSimpleServerSession
+ : public QuicTransportServerSession,
+ QuicTransportServerSession::ServerVisitor {
+ public:
+ enum Mode {
+ // In DISCARD mode, any data on incoming streams is discarded and no
+ // outgoing streams are initiated.
+ DISCARD,
+ // In ECHO mode, any data sent on a bidirectional stream is echoed back.
+ // Any data sent on a unidirectional stream is buffered, and echoed back on
+ // a server-initiated unidirectional stream that is sent as soon as a FIN is
+ // received on the incoming stream.
+ ECHO,
+ // In OUTGOING_BIDIRECTIONAL mode, a server-originated bidirectional stream
+ // is created on receipt of a unidirectional stream. The contents of the
+ // unidirectional stream are disregarded. The bidirectional stream initially
+ // sends "hello", then any received data is echoed back.
+ // TODO(ricea): Maybe this should be replaced by a more general mechanism
+ // where commands on the unidirectional stream trigger different behaviour?
+ OUTGOING_BIDIRECTIONAL,
+ };
+
+ QuicTransportSimpleServerSession(
+ QuicConnection* connection,
+ bool owns_connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ std::vector<url::Origin> accepted_origins);
+ ~QuicTransportSimpleServerSession();
+
+ void OnIncomingDataStream(QuicTransportStream* stream) override;
+ void OnCanCreateNewOutgoingStream(bool unidirectional) override;
+ bool CheckOrigin(url::Origin origin) override;
+ bool ProcessPath(const GURL& url) override;
+ void OnMessageReceived(absl::string_view message) override;
+
+ void EchoStreamBack(const std::string& data) {
+ streams_to_echo_back_.push_back(data);
+ MaybeEchoStreamsBack();
+ }
+
+ private:
+ void MaybeEchoStreamsBack();
+ void MaybeCreateOutgoingBidirectionalStream();
+
+ const bool owns_connection_;
+ size_t pending_outgoing_bidirectional_streams_ = 0u;
+ Mode mode_;
+ std::vector<url::Origin> accepted_origins_;
+ quiche::QuicheCircularDeque<std::string> streams_to_echo_back_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
diff --git a/quiche/quic/tools/quic_url.cc b/quiche/quic/tools/quic_url.cc
new file mode 100644
index 0000000..db52720
--- /dev/null
+++ b/quiche/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 "quiche/quic/tools/quic_url.h"
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+namespace quic {
+
+static constexpr size_t kMaxHostNameLength = 256;
+
+QuicUrl::QuicUrl(absl::string_view url) : url_(static_cast<std::string>(url)) {}
+
+QuicUrl::QuicUrl(absl::string_view url, absl::string_view default_scheme)
+ : QuicUrl(url) {
+ if (url_.has_scheme()) {
+ return;
+ }
+
+ url_ = GURL(absl::StrCat(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 absl::StrCat(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/quiche/quic/tools/quic_url.h b/quiche/quic/tools/quic_url.h
new file mode 100644
index 0000000..78b21ec
--- /dev/null
+++ b/quiche/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 "absl/strings/string_view.h"
+#include "url/gurl.h"
+#include "quiche/quic/platform/api/quic_export.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(absl::string_view 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(absl::string_view url, absl::string_view 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/quiche/quic/tools/quic_url_test.cc b/quiche/quic/tools/quic_url_test.cc
new file mode 100644
index 0000000..8f26e01
--- /dev/null
+++ b/quiche/quic/tools/quic_url_test.cc
@@ -0,0 +1,156 @@
+// 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 "quiche/quic/tools/quic_url.h"
+
+#include <string>
+
+#include "quiche/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
diff --git a/quiche/quic/tools/simple_ticket_crypter.cc b/quiche/quic/tools/simple_ticket_crypter.cc
new file mode 100644
index 0000000..ef788d6
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 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 "quiche/quic/tools/simple_ticket_crypter.h"
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+
+namespace quic {
+
+namespace {
+
+constexpr QuicTime::Delta kTicketKeyLifetime =
+ QuicTime::Delta::FromSeconds(60 * 60 * 24 * 7);
+
+// The format of an encrypted ticket is 1 byte for the key epoch, followed by
+// 16 bytes of IV, followed by the output from the AES-GCM Seal operation. The
+// seal operation has an overhead of 16 bytes for its auth tag.
+constexpr size_t kEpochSize = 1;
+constexpr size_t kIVSize = 16;
+constexpr size_t kAuthTagSize = 16;
+
+// Offsets into the ciphertext to make message parsing easier.
+constexpr size_t kIVOffset = kEpochSize;
+constexpr size_t kMessageOffset = kIVOffset + kIVSize;
+
+} // namespace
+
+SimpleTicketCrypter::SimpleTicketCrypter(QuicClock* clock) : clock_(clock) {
+ RAND_bytes(&key_epoch_, 1);
+ current_key_ = NewKey();
+}
+
+SimpleTicketCrypter::~SimpleTicketCrypter() = default;
+
+size_t SimpleTicketCrypter::MaxOverhead() {
+ return kEpochSize + kIVSize + kAuthTagSize;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Encrypt(
+ absl::string_view in, absl::string_view encryption_key) {
+ // This class is only used in Chromium, in which the |encryption_key| argument
+ // will never be populated and an internally-cached key should be used for
+ // encrypting tickets.
+ QUICHE_DCHECK(encryption_key.empty());
+ MaybeRotateKeys();
+ std::vector<uint8_t> out(in.size() + MaxOverhead());
+ out[0] = key_epoch_;
+ RAND_bytes(out.data() + kIVOffset, kIVSize);
+ size_t out_len;
+ const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+ if (!EVP_AEAD_CTX_seal(ctx, out.data() + kMessageOffset, &out_len,
+ out.size() - kMessageOffset, out.data() + kIVOffset,
+ kIVSize, reinterpret_cast<const uint8_t*>(in.data()),
+ in.size(), nullptr, 0)) {
+ return std::vector<uint8_t>();
+ }
+ out.resize(out_len + kMessageOffset);
+ return out;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Decrypt(absl::string_view in) {
+ MaybeRotateKeys();
+ if (in.size() < kMessageOffset) {
+ return std::vector<uint8_t>();
+ }
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+ std::vector<uint8_t> out(in.size() - kMessageOffset);
+ size_t out_len;
+ const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+ if (input[0] != key_epoch_) {
+ if (input[0] == static_cast<uint8_t>(key_epoch_ - 1) && previous_key_) {
+ ctx = previous_key_->aead_ctx.get();
+ } else {
+ return std::vector<uint8_t>();
+ }
+ }
+ if (!EVP_AEAD_CTX_open(ctx, out.data(), &out_len, out.size(),
+ input + kIVOffset, kIVSize, input + kMessageOffset,
+ in.size() - kMessageOffset, nullptr, 0)) {
+ return std::vector<uint8_t>();
+ }
+ out.resize(out_len);
+ return out;
+}
+
+void SimpleTicketCrypter::Decrypt(
+ absl::string_view in,
+ std::unique_ptr<quic::ProofSource::DecryptCallback> callback) {
+ callback->Run(Decrypt(in));
+}
+
+void SimpleTicketCrypter::MaybeRotateKeys() {
+ QuicTime now = clock_->ApproximateNow();
+ if (current_key_->expiration < now) {
+ previous_key_ = std::move(current_key_);
+ current_key_ = NewKey();
+ key_epoch_++;
+ }
+}
+
+std::unique_ptr<SimpleTicketCrypter::Key> SimpleTicketCrypter::NewKey() {
+ auto key = std::make_unique<SimpleTicketCrypter::Key>();
+ RAND_bytes(key->key, kKeySize);
+ EVP_AEAD_CTX_init(key->aead_ctx.get(), EVP_aead_aes_128_gcm(), key->key,
+ kKeySize, EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr);
+ key->expiration = clock_->ApproximateNow() + kTicketKeyLifetime;
+ return key;
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/simple_ticket_crypter.h b/quiche/quic/tools/simple_ticket_crypter.h
new file mode 100644
index 0000000..518ea9b
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter.h
@@ -0,0 +1,56 @@
+// Copyright 2020 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_SIMPLE_TICKET_CRYPTER_H_
+#define QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "quiche/quic/core/crypto/proof_source.h"
+#include "quiche/quic/core/quic_clock.h"
+#include "quiche/quic/core/quic_time.h"
+
+namespace quic {
+
+// SimpleTicketCrypter implements the QUIC ProofSource::TicketCrypter interface.
+// It generates a random key at startup and every 7 days it rotates the key,
+// keeping track of the previous key used to facilitate decrypting older
+// tickets. This implementation is not suitable for server setups where multiple
+// servers need to share keys.
+class QUIC_NO_EXPORT SimpleTicketCrypter
+ : public quic::ProofSource::TicketCrypter {
+ public:
+ explicit SimpleTicketCrypter(QuicClock* clock);
+ ~SimpleTicketCrypter() override;
+
+ size_t MaxOverhead() override;
+ std::vector<uint8_t> Encrypt(absl::string_view in,
+ absl::string_view encryption_key) override;
+ void Decrypt(
+ absl::string_view in,
+ std::unique_ptr<quic::ProofSource::DecryptCallback> callback) override;
+
+ private:
+ std::vector<uint8_t> Decrypt(absl::string_view in);
+
+ void MaybeRotateKeys();
+
+ static constexpr size_t kKeySize = 16;
+
+ struct Key {
+ uint8_t key[kKeySize];
+ bssl::ScopedEVP_AEAD_CTX aead_ctx;
+ QuicTime expiration = QuicTime::Zero();
+ };
+
+ std::unique_ptr<Key> NewKey();
+
+ std::unique_ptr<Key> current_key_;
+ std::unique_ptr<Key> previous_key_;
+ uint8_t key_epoch_ = 0;
+ QuicClock* clock_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
diff --git a/quiche/quic/tools/simple_ticket_crypter_test.cc b/quiche/quic/tools/simple_ticket_crypter_test.cc
new file mode 100644
index 0000000..0399047
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2020 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 "quiche/quic/tools/simple_ticket_crypter.h"
+
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/mock_clock.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+
+constexpr QuicTime::Delta kOneDay = QuicTime::Delta::FromSeconds(60 * 60 * 24);
+
+} // namespace
+
+class DecryptCallback : public quic::ProofSource::DecryptCallback {
+ public:
+ explicit DecryptCallback(std::vector<uint8_t>* out) : out_(out) {}
+
+ void Run(std::vector<uint8_t> plaintext) override { *out_ = plaintext; }
+
+ private:
+ std::vector<uint8_t>* out_;
+};
+
+absl::string_view StringPiece(const std::vector<uint8_t>& in) {
+ return absl::string_view(reinterpret_cast<const char*>(in.data()), in.size());
+}
+
+class SimpleTicketCrypterTest : public QuicTest {
+ public:
+ SimpleTicketCrypterTest() : ticket_crypter_(&mock_clock_) {}
+
+ protected:
+ MockClock mock_clock_;
+ SimpleTicketCrypter ticket_crypter_;
+};
+
+TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) {
+ std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+ std::vector<uint8_t> ciphertext =
+ ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+ EXPECT_NE(plaintext, ciphertext);
+
+ std::vector<uint8_t> out_plaintext;
+ ticket_crypter_.Decrypt(StringPiece(ciphertext),
+ std::make_unique<DecryptCallback>(&out_plaintext));
+ EXPECT_EQ(out_plaintext, plaintext);
+}
+
+TEST_F(SimpleTicketCrypterTest, CiphertextsDiffer) {
+ std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+ std::vector<uint8_t> ciphertext1 =
+ ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+ std::vector<uint8_t> ciphertext2 =
+ ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+ EXPECT_NE(ciphertext1, ciphertext2);
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithModifiedCiphertext) {
+ std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+ std::vector<uint8_t> ciphertext =
+ ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+ EXPECT_NE(plaintext, ciphertext);
+
+ // Check that a bit flip in any byte will cause a decryption failure.
+ for (size_t i = 0; i < ciphertext.size(); i++) {
+ SCOPED_TRACE(i);
+ std::vector<uint8_t> munged_ciphertext = ciphertext;
+ munged_ciphertext[i] ^= 1;
+ std::vector<uint8_t> out_plaintext;
+ ticket_crypter_.Decrypt(StringPiece(munged_ciphertext),
+ std::make_unique<DecryptCallback>(&out_plaintext));
+ EXPECT_TRUE(out_plaintext.empty());
+ }
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithEmptyCiphertext) {
+ std::vector<uint8_t> out_plaintext;
+ ticket_crypter_.Decrypt(absl::string_view(),
+ std::make_unique<DecryptCallback>(&out_plaintext));
+ EXPECT_TRUE(out_plaintext.empty());
+}
+
+TEST_F(SimpleTicketCrypterTest, KeyRotation) {
+ std::vector<uint8_t> plaintext = {1, 2, 3};
+ std::vector<uint8_t> ciphertext =
+ ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+ EXPECT_FALSE(ciphertext.empty());
+
+ // Advance the clock 8 days, so the key used for |ciphertext| is now the
+ // previous key. Check that decryption still works.
+ mock_clock_.AdvanceTime(kOneDay * 8);
+ std::vector<uint8_t> out_plaintext;
+ ticket_crypter_.Decrypt(StringPiece(ciphertext),
+ std::make_unique<DecryptCallback>(&out_plaintext));
+ EXPECT_EQ(out_plaintext, plaintext);
+
+ // Advance the clock 8 more days. Now the original key should be expired and
+ // decryption should fail.
+ mock_clock_.AdvanceTime(kOneDay * 8);
+ ticket_crypter_.Decrypt(StringPiece(ciphertext),
+ std::make_unique<DecryptCallback>(&out_plaintext));
+ EXPECT_TRUE(out_plaintext.empty());
+}
+
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/tools/web_transport_test_visitors.h b/quiche/quic/tools/web_transport_test_visitors.h
new file mode 100644
index 0000000..07d3161
--- /dev/null
+++ b/quiche/quic/tools/web_transport_test_visitors.h
@@ -0,0 +1,263 @@
+// Copyright (c) 2021 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_WEB_TRANSPORT_TEST_VISITORS_H_
+#define QUICHE_QUIC_TOOLS_WEB_TRANSPORT_TEST_VISITORS_H_
+
+#include <string>
+
+#include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_circular_deque.h"
+#include "quiche/common/simple_buffer_allocator.h"
+
+namespace quic {
+
+// Discards any incoming data.
+class WebTransportDiscardVisitor : public WebTransportStreamVisitor {
+ public:
+ WebTransportDiscardVisitor(WebTransportStream* stream) : stream_(stream) {}
+
+ void OnCanRead() override {
+ std::string buffer;
+ WebTransportStream::ReadResult result = stream_->Read(&buffer);
+ QUIC_DVLOG(2) << "Read " << result.bytes_read
+ << " bytes from WebTransport stream "
+ << stream_->GetStreamId() << ", fin: " << result.fin;
+ }
+
+ void OnCanWrite() override {}
+
+ void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+ void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+ void OnWriteSideInDataRecvdState() override {}
+
+ private:
+ WebTransportStream* stream_;
+};
+
+// Echoes any incoming data back on the same stream.
+class WebTransportBidirectionalEchoVisitor : public WebTransportStreamVisitor {
+ public:
+ WebTransportBidirectionalEchoVisitor(WebTransportStream* stream)
+ : stream_(stream) {}
+
+ void OnCanRead() override {
+ WebTransportStream::ReadResult result = stream_->Read(&buffer_);
+ QUIC_DVLOG(1) << "Attempted reading on WebTransport bidirectional stream "
+ << stream_->GetStreamId()
+ << ", bytes read: " << result.bytes_read;
+ if (result.fin) {
+ send_fin_ = true;
+ }
+ OnCanWrite();
+ }
+
+ void OnCanWrite() override {
+ if (stop_sending_received_) {
+ return;
+ }
+
+ if (!buffer_.empty()) {
+ bool success = stream_->Write(buffer_);
+ QUIC_DVLOG(1) << "Attempted writing on WebTransport bidirectional stream "
+ << stream_->GetStreamId()
+ << ", success: " << (success ? "yes" : "no");
+ if (!success) {
+ return;
+ }
+
+ buffer_ = "";
+ }
+
+ if (send_fin_) {
+ bool success = stream_->SendFin();
+ QUICHE_DCHECK(success);
+ }
+ }
+
+ void OnResetStreamReceived(WebTransportStreamError /*error*/) override {
+ // Send FIN in response to a stream reset. We want to test that we can
+ // operate one side of the stream cleanly while the other is reset, thus
+ // replying with a FIN rather than a RESET_STREAM is more appropriate here.
+ send_fin_ = true;
+ OnCanWrite();
+ }
+ void OnStopSendingReceived(WebTransportStreamError /*error*/) override {
+ stop_sending_received_ = true;
+ }
+ void OnWriteSideInDataRecvdState() override {}
+
+ protected:
+ WebTransportStream* stream() { return stream_; }
+
+ private:
+ WebTransportStream* stream_;
+ std::string buffer_;
+ bool send_fin_ = false;
+ bool stop_sending_received_ = false;
+};
+
+// Buffers all of the data and calls |callback| with the entirety of the stream
+// data.
+class WebTransportUnidirectionalEchoReadVisitor
+ : public WebTransportStreamVisitor {
+ public:
+ using Callback = std::function<void(const std::string&)>;
+
+ WebTransportUnidirectionalEchoReadVisitor(WebTransportStream* stream,
+ Callback callback)
+ : stream_(stream), callback_(std::move(callback)) {}
+
+ void OnCanRead() override {
+ WebTransportStream::ReadResult result = stream_->Read(&buffer_);
+ QUIC_DVLOG(1) << "Attempted reading on WebTransport unidirectional stream "
+ << stream_->GetStreamId()
+ << ", bytes read: " << result.bytes_read;
+ if (result.fin) {
+ QUIC_DVLOG(1) << "Finished receiving data on a WebTransport stream "
+ << stream_->GetStreamId() << ", queueing up the echo";
+ callback_(buffer_);
+ }
+ }
+
+ void OnCanWrite() override { QUIC_NOTREACHED(); }
+
+ void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+ void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+ void OnWriteSideInDataRecvdState() override {}
+
+ private:
+ WebTransportStream* stream_;
+ std::string buffer_;
+ Callback callback_;
+};
+
+// Sends supplied data.
+class WebTransportUnidirectionalEchoWriteVisitor
+ : public WebTransportStreamVisitor {
+ public:
+ WebTransportUnidirectionalEchoWriteVisitor(WebTransportStream* stream,
+ const std::string& data)
+ : stream_(stream), data_(data) {}
+
+ void OnCanRead() override { QUIC_NOTREACHED(); }
+ void OnCanWrite() override {
+ if (data_.empty()) {
+ return;
+ }
+ if (!stream_->Write(data_)) {
+ return;
+ }
+ data_ = "";
+ bool fin_sent = stream_->SendFin();
+ QUICHE_DVLOG(1)
+ << "WebTransportUnidirectionalEchoWriteVisitor finished sending data.";
+ QUICHE_DCHECK(fin_sent);
+ }
+
+ void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+ void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+ void OnWriteSideInDataRecvdState() override {}
+
+ private:
+ WebTransportStream* stream_;
+ std::string data_;
+};
+
+// A session visitor which sets unidirectional or bidirectional stream visitors
+// to echo.
+class EchoWebTransportSessionVisitor : public WebTransportVisitor {
+ public:
+ EchoWebTransportSessionVisitor(WebTransportSession* session)
+ : session_(session) {}
+
+ void OnSessionReady(const spdy::SpdyHeaderBlock&) override {
+ if (session_->CanOpenNextOutgoingBidirectionalStream()) {
+ OnCanCreateNewOutgoingBidirectionalStream();
+ }
+ }
+
+ void OnSessionClosed(WebTransportSessionError /*error_code*/,
+ const std::string& /*error_message*/) override {}
+
+ void OnIncomingBidirectionalStreamAvailable() override {
+ while (true) {
+ WebTransportStream* stream =
+ session_->AcceptIncomingBidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ QUIC_DVLOG(1)
+ << "EchoWebTransportSessionVisitor received a bidirectional stream "
+ << stream->GetStreamId();
+ stream->SetVisitor(
+ std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+ stream->visitor()->OnCanRead();
+ }
+ }
+
+ void OnIncomingUnidirectionalStreamAvailable() override {
+ while (true) {
+ WebTransportStream* stream =
+ session_->AcceptIncomingUnidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ QUIC_DVLOG(1)
+ << "EchoWebTransportSessionVisitor received a unidirectional stream";
+ stream->SetVisitor(
+ std::make_unique<WebTransportUnidirectionalEchoReadVisitor>(
+ stream, [this](const std::string& data) {
+ streams_to_echo_back_.push_back(data);
+ TrySendingUnidirectionalStreams();
+ }));
+ stream->visitor()->OnCanRead();
+ }
+ }
+
+ void OnDatagramReceived(absl::string_view datagram) override {
+ quiche::QuicheMemSlice slice(
+ quiche::QuicheBuffer::Copy(&allocator_, datagram));
+ session_->SendOrQueueDatagram(std::move(slice));
+ }
+
+ void OnCanCreateNewOutgoingBidirectionalStream() override {
+ if (!echo_stream_opened_) {
+ WebTransportStream* stream = session_->OpenOutgoingBidirectionalStream();
+ stream->SetVisitor(
+ std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+ echo_stream_opened_ = true;
+ }
+ }
+ void OnCanCreateNewOutgoingUnidirectionalStream() override {
+ TrySendingUnidirectionalStreams();
+ }
+
+ void TrySendingUnidirectionalStreams() {
+ while (!streams_to_echo_back_.empty() &&
+ session_->CanOpenNextOutgoingUnidirectionalStream()) {
+ QUIC_DVLOG(1)
+ << "EchoWebTransportServer echoed a unidirectional stream back";
+ WebTransportStream* stream = session_->OpenOutgoingUnidirectionalStream();
+ stream->SetVisitor(
+ std::make_unique<WebTransportUnidirectionalEchoWriteVisitor>(
+ stream, streams_to_echo_back_.front()));
+ streams_to_echo_back_.pop_front();
+ stream->visitor()->OnCanWrite();
+ }
+ }
+
+ private:
+ WebTransportSession* session_;
+ quiche::SimpleBufferAllocator allocator_;
+ bool echo_stream_opened_ = false;
+
+ quiche::QuicheCircularDeque<std::string> streams_to_echo_back_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_WEB_TRANSPORT_TEST_VISITORS_H_