blob: 56fe71247a3bd9a0ef7244f21ce4f776d86746e7 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/tools/quic/test_tools/quic_test_client.h"
#include <memory>
#include <utility>
#include <vector>
#include "net/quic/core/crypto/proof_verifier.h"
#include "net/quic/core/quic_flags.h"
#include "net/quic/core/quic_server_id.h"
#include "net/quic/core/quic_utils.h"
#include "net/quic/core/spdy_utils.h"
#include "net/quic/platform/api/quic_logging.h"
#include "net/quic/platform/api/quic_ptr_util.h"
#include "net/quic/platform/api/quic_stack_trace.h"
#include "net/quic/platform/api/quic_text_utils.h"
#include "net/quic/platform/api/quic_url.h"
#include "net/quic/test_tools/crypto_test_utils.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_spdy_session_peer.h"
#include "net/quic/test_tools/quic_stream_peer.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/tools/quic/quic_epoll_connection_helper.h"
#include "net/tools/quic/quic_packet_writer_wrapper.h"
#include "net/tools/quic/quic_spdy_client_stream.h"
#include "net/tools/quic/test_tools/quic_client_peer.h"
#include "third_party/boringssl/src/include/openssl/x509.h"
using base::StringPiece;
using std::string;
using testing::_;
using testing::Invoke;
namespace net {
namespace test {
namespace {
// RecordingProofVerifier accepts any certificate chain and records the common
// name of the leaf and then delegates the actual verfication to an actual
// verifier. If no optional verifier is provided, then VerifyProof will return
// success.
class RecordingProofVerifier : public ProofVerifier {
public:
explicit RecordingProofVerifier(std::unique_ptr<ProofVerifier> verifier)
: verifier_(std::move(verifier)) {}
// ProofVerifier interface.
QuicAsyncStatus VerifyProof(
const string& hostname,
const uint16_t port,
const string& server_config,
QuicVersion quic_version,
StringPiece chlo_hash,
const std::vector<string>& certs,
const string& cert_sct,
const string& signature,
const ProofVerifyContext* context,
string* error_details,
std::unique_ptr<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback) override {
common_name_.clear();
if (certs.empty()) {
return QUIC_FAILURE;
}
// Convert certs to X509Certificate.
std::vector<StringPiece> cert_pieces(certs.size());
for (unsigned i = 0; i < certs.size(); i++) {
cert_pieces[i] = StringPiece(certs[i]);
}
// TODO(rtenneti): Fix after adding support for real certs. Currently,
// cert_pieces are "leaf" and "intermediate" and CreateFromDERCertChain
// fails to return cert from these cert_pieces.
// bssl::UniquePtr<X509> cert(d2i_X509(nullptr, &data, certs[0].size()));
// if (!cert.get()) {
// return QUIC_FAILURE;
// }
//
// common_name_ = cert->subject().GetDisplayName();
cert_sct_ = cert_sct;
if (!verifier_) {
return QUIC_SUCCESS;
}
return verifier_->VerifyProof(
hostname, port, server_config, quic_version, chlo_hash, certs, cert_sct,
signature, context, error_details, details, std::move(callback));
}
QuicAsyncStatus VerifyCertChain(
const std::string& hostname,
const std::vector<std::string>& certs,
const ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback) override {
return QUIC_SUCCESS;
}
const string& common_name() const { return common_name_; }
const string& cert_sct() const { return cert_sct_; }
private:
std::unique_ptr<ProofVerifier> verifier_;
string common_name_;
string cert_sct_;
};
} // anonymous namespace
MockableQuicClient::MockableQuicClient(
QuicSocketAddress server_address,
const QuicServerId& server_id,
const QuicVersionVector& supported_versions,
EpollServer* epoll_server)
: MockableQuicClient(server_address,
server_id,
QuicConfig(),
supported_versions,
epoll_server) {}
MockableQuicClient::MockableQuicClient(
QuicSocketAddress server_address,
const QuicServerId& server_id,
const QuicConfig& config,
const QuicVersionVector& supported_versions,
EpollServer* epoll_server)
: MockableQuicClient(server_address,
server_id,
config,
supported_versions,
epoll_server,
nullptr) {}
MockableQuicClient::MockableQuicClient(
QuicSocketAddress server_address,
const QuicServerId& server_id,
const QuicConfig& config,
const QuicVersionVector& supported_versions,
EpollServer* epoll_server,
std::unique_ptr<ProofVerifier> proof_verifier)
: QuicClient(server_address,
server_id,
supported_versions,
config,
epoll_server,
QuicWrapUnique(
new RecordingProofVerifier(std::move(proof_verifier)))),
override_connection_id_(0),
test_writer_(nullptr),
track_last_incoming_packet_(false) {}
void MockableQuicClient::ProcessPacket(const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicReceivedPacket& packet) {
QuicClient::ProcessPacket(self_address, peer_address, packet);
if (track_last_incoming_packet_) {
last_incoming_packet_ = packet.Clone();
}
}
MockableQuicClient::~MockableQuicClient() {
if (connected()) {
Disconnect();
}
}
QuicPacketWriter* MockableQuicClient::CreateQuicPacketWriter() {
QuicPacketWriter* writer = QuicClient::CreateQuicPacketWriter();
if (!test_writer_) {
return writer;
}
test_writer_->set_writer(writer);
return test_writer_;
}
QuicConnectionId MockableQuicClient::GenerateNewConnectionId() {
return override_connection_id_ ? override_connection_id_
: QuicClient::GenerateNewConnectionId();
}
// Takes ownership of writer.
void MockableQuicClient::UseWriter(QuicPacketWriterWrapper* writer) {
CHECK(test_writer_ == nullptr);
test_writer_ = writer;
}
void MockableQuicClient::UseConnectionId(QuicConnectionId connection_id) {
override_connection_id_ = connection_id;
}
QuicTestClient::QuicTestClient(QuicSocketAddress server_address,
const string& server_hostname,
const QuicVersionVector& supported_versions)
: QuicTestClient(server_address,
server_hostname,
QuicConfig(),
supported_versions) {}
QuicTestClient::QuicTestClient(QuicSocketAddress server_address,
const string& server_hostname,
const QuicConfig& config,
const QuicVersionVector& supported_versions)
: client_(new MockableQuicClient(server_address,
QuicServerId(server_hostname,
server_address.port(),
PRIVACY_MODE_DISABLED),
config,
supported_versions,
&epoll_server_)),
response_complete_(false),
allow_bidirectional_data_(false) {
Initialize();
}
QuicTestClient::QuicTestClient(QuicSocketAddress server_address,
const string& server_hostname,
const QuicConfig& config,
const QuicVersionVector& supported_versions,
std::unique_ptr<ProofVerifier> proof_verifier)
: client_(new MockableQuicClient(server_address,
QuicServerId(server_hostname,
server_address.port(),
PRIVACY_MODE_DISABLED),
config,
supported_versions,
&epoll_server_,
std::move(proof_verifier))),
response_complete_(false),
allow_bidirectional_data_(false) {
Initialize();
}
QuicTestClient::QuicTestClient()
: response_complete_(false), allow_bidirectional_data_(false) {}
QuicTestClient::~QuicTestClient() {
if (stream_) {
stream_->set_visitor(nullptr);
}
client_->Disconnect();
}
void QuicTestClient::Initialize() {
priority_ = 3;
connect_attempted_ = false;
auto_reconnect_ = false;
buffer_body_ = true;
num_requests_ = 0;
num_responses_ = 0;
ClearPerRequestState();
// As chrome will generally do this, we want it to be the default when it's
// not overridden.
if (!client_->config()->HasSetBytesForConnectionIdToSend()) {
client_->config()->SetBytesForConnectionIdToSend(0);
}
}
void QuicTestClient::SetUserAgentID(const string& user_agent_id) {
client_->SetUserAgentID(user_agent_id);
}
ssize_t QuicTestClient::SendRequest(const string& uri) {
SpdyHeaderBlock headers;
if (!PopulateHeaderBlockFromUrl(uri, &headers)) {
return 0;
}
return SendMessage(headers, "");
}
void QuicTestClient::SendRequestsAndWaitForResponses(
const std::vector<string>& url_list) {
for (const string& url : url_list) {
SendRequest(url);
}
while (client()->WaitForEvents()) {
}
return;
}
ssize_t QuicTestClient::GetOrCreateStreamAndSendRequest(
const SpdyHeaderBlock* headers,
StringPiece body,
bool fin,
QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
if (headers) {
QuicClientPushPromiseIndex::TryHandle* handle;
QuicAsyncStatus rv =
client()->push_promise_index()->Try(*headers, this, &handle);
if (rv == QUIC_SUCCESS)
return 1;
if (rv == QUIC_PENDING) {
// May need to retry request if asynchronous rendezvous fails.
std::unique_ptr<SpdyHeaderBlock> new_headers(
new SpdyHeaderBlock(headers->Clone()));
push_promise_data_to_resend_.reset(new TestClientDataToResend(
std::move(new_headers), body, fin, this, std::move(ack_listener)));
return 1;
}
}
// Maybe it's better just to overload this. it's just that we need
// for the GetOrCreateStream function to call something else...which
// is icky and complicated, but maybe not worse than this.
QuicSpdyClientStream* stream = GetOrCreateStream();
if (stream == nullptr) {
return 0;
}
ssize_t ret = 0;
if (headers != nullptr) {
SpdyHeaderBlock spdy_headers(headers->Clone());
if (spdy_headers[":authority"].as_string().empty()) {
spdy_headers[":authority"] = client_->server_id().host();
}
ret = stream->SendRequest(std::move(spdy_headers), body, fin);
++num_requests_;
} else {
stream->WriteOrBufferBody(body.as_string(), fin, ack_listener);
ret = body.length();
}
if (FLAGS_quic_reloadable_flag_enable_quic_stateless_reject_support) {
std::unique_ptr<SpdyHeaderBlock> new_headers;
if (headers) {
new_headers.reset(new SpdyHeaderBlock(headers->Clone()));
}
std::unique_ptr<QuicClientBase::QuicDataToResend> data_to_resend(
new TestClientDataToResend(std::move(new_headers), body, fin, this,
ack_listener));
client()->MaybeAddQuicDataToResend(std::move(data_to_resend));
}
return ret;
}
ssize_t QuicTestClient::SendMessage(const SpdyHeaderBlock& headers,
StringPiece body) {
return SendMessage(headers, body, /*fin=*/true);
}
ssize_t QuicTestClient::SendMessage(const SpdyHeaderBlock& headers,
StringPiece body,
bool fin) {
stream_ = nullptr; // Always force creation of a stream for SendMessage.
// Any response we might have received for a previous request would no longer
// be valid. TODO(jeffpiazza): There's probably additional client state that
// should be reset here, too, if we were being more careful.
response_complete_ = false;
// If we're not connected, try to find an sni hostname.
if (!connected()) {
QuicUrl url(SpdyUtils::GetUrlFromHeaderBlock(headers));
if (override_sni_set_) {
client_->set_server_id(
QuicServerId(override_sni_, url.port(), PRIVACY_MODE_DISABLED));
}
}
ssize_t ret = GetOrCreateStreamAndSendRequest(&headers, body, fin, nullptr);
WaitForWriteToFlush();
return ret;
}
ssize_t QuicTestClient::SendData(const string& data, bool last_data) {
return SendData(data, last_data, nullptr);
}
ssize_t QuicTestClient::SendData(
const string& data,
bool last_data,
QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
return GetOrCreateStreamAndSendRequest(nullptr, StringPiece(data), last_data,
std::move(ack_listener));
}
bool QuicTestClient::response_complete() const {
return response_complete_;
}
int64_t QuicTestClient::response_body_size() const {
return response_body_size_;
}
bool QuicTestClient::buffer_body() const {
return buffer_body_;
}
void QuicTestClient::set_buffer_body(bool buffer_body) {
buffer_body_ = buffer_body;
}
const string& QuicTestClient::response_body() {
return response_;
}
string QuicTestClient::SendCustomSynchronousRequest(
const SpdyHeaderBlock& headers,
const string& body) {
if (SendMessage(headers, body) == 0) {
QUIC_DLOG(ERROR) << "Failed the request for: " << headers.DebugString();
// Set the response_ explicitly. Otherwise response_ will contain the
// response from the previously successful request.
response_ = "";
} else {
WaitForResponse();
}
return response_;
}
string QuicTestClient::SendSynchronousRequest(const string& uri) {
SpdyHeaderBlock headers;
if (!PopulateHeaderBlockFromUrl(uri, &headers)) {
return "";
}
return SendCustomSynchronousRequest(headers, "");
}
void QuicTestClient::SetStream(QuicSpdyClientStream* stream) {
stream_ = stream;
if (stream_ != nullptr) {
response_complete_ = false;
stream_->set_visitor(this);
}
}
QuicSpdyClientStream* QuicTestClient::GetOrCreateStream() {
if (!connect_attempted_ || auto_reconnect_) {
if (!connected()) {
Connect();
}
if (!connected()) {
return nullptr;
}
}
if (!stream_) {
SetStream(client_->CreateClientStream());
if (stream_) {
stream_->SetPriority(priority_);
stream_->set_allow_bidirectional_data(allow_bidirectional_data_);
}
}
return stream_;
}
QuicErrorCode QuicTestClient::connection_error() {
return client()->connection_error();
}
MockableQuicClient* QuicTestClient::client() {
return client_.get();
}
const string& QuicTestClient::cert_common_name() const {
return reinterpret_cast<RecordingProofVerifier*>(client_->proof_verifier())
->common_name();
}
const string& QuicTestClient::cert_sct() const {
return reinterpret_cast<RecordingProofVerifier*>(client_->proof_verifier())
->cert_sct();
}
QuicTagValueMap QuicTestClient::GetServerConfig() const {
QuicCryptoClientConfig* config = client_->crypto_config();
QuicCryptoClientConfig::CachedState* state =
config->LookupOrCreate(client_->server_id());
const CryptoHandshakeMessage* handshake_msg = state->GetServerConfig();
if (handshake_msg != nullptr) {
return handshake_msg->tag_value_map();
} else {
return QuicTagValueMap();
}
}
bool QuicTestClient::connected() const {
return client_->connected();
}
void QuicTestClient::Connect() {
DCHECK(!connected());
if (!connect_attempted_) {
client_->Initialize();
}
client_->Connect();
connect_attempted_ = true;
}
void QuicTestClient::ResetConnection() {
Disconnect();
Connect();
}
void QuicTestClient::Disconnect() {
client_->Disconnect();
connect_attempted_ = false;
}
QuicSocketAddress QuicTestClient::local_address() const {
return client_->GetLatestClientAddress();
}
void QuicTestClient::ClearPerRequestState() {
stream_error_ = QUIC_STREAM_NO_ERROR;
stream_ = nullptr;
response_ = "";
response_complete_ = false;
response_headers_complete_ = false;
response_headers_.clear();
bytes_read_ = 0;
bytes_written_ = 0;
response_body_size_ = 0;
}
bool QuicTestClient::HaveActiveStream() {
return push_promise_data_to_resend_.get() ||
(stream_ != nullptr &&
!client_->session()->IsClosedStream(stream_->id()));
}
bool QuicTestClient::WaitUntil(int timeout_ms, std::function<bool()> trigger) {
int64_t timeout_us = timeout_ms * base::Time::kMicrosecondsPerMillisecond;
int64_t old_timeout_us = epoll_server()->timeout_in_us();
if (timeout_us > 0) {
epoll_server()->set_timeout_in_us(timeout_us);
}
const QuicClock* clock =
QuicConnectionPeer::GetHelper(client()->session()->connection())
->GetClock();
QuicTime end_waiting_time =
clock->Now() + QuicTime::Delta::FromMicroseconds(timeout_us);
while (HaveActiveStream() && !(trigger && trigger()) &&
(timeout_us < 0 || clock->Now() < end_waiting_time)) {
client_->WaitForEvents();
}
if (timeout_us > 0) {
epoll_server()->set_timeout_in_us(old_timeout_us);
}
if (trigger && !trigger()) {
VLOG(1) << "Client WaitUntil returning with trigger returning false."
<< QuicStackTrace();
return false;
}
return true;
}
ssize_t QuicTestClient::Send(const void* buffer, size_t size) {
return SendData(string(static_cast<const char*>(buffer), size), false);
}
bool QuicTestClient::response_headers_complete() const {
if (stream_ != nullptr) {
return stream_->headers_decompressed();
}
return response_headers_complete_;
}
const SpdyHeaderBlock* QuicTestClient::response_headers() const {
if (stream_ != nullptr) {
response_headers_ = stream_->response_headers().Clone();
}
return &response_headers_;
}
const SpdyHeaderBlock* QuicTestClient::preliminary_headers() const {
if (stream_ != nullptr) {
preliminary_headers_ = stream_->preliminary_headers().Clone();
}
return &preliminary_headers_;
}
const SpdyHeaderBlock& QuicTestClient::response_trailers() const {
return response_trailers_;
}
int64_t QuicTestClient::response_size() const {
return bytes_read();
}
size_t QuicTestClient::bytes_read() const {
// While stream_ is available, its member functions provide more accurate
// information. bytes_read_ is updated only when stream_ becomes null.
if (stream_) {
return stream_->stream_bytes_read() + stream_->header_bytes_read();
} else {
return bytes_read_;
}
}
size_t QuicTestClient::bytes_written() const {
// While stream_ is available, its member functions provide more accurate
// information. bytes_written_ is updated only when stream_ becomes null.
if (stream_) {
return stream_->stream_bytes_written() + stream_->header_bytes_written();
} else {
return bytes_written_;
}
}
void QuicTestClient::OnClose(QuicSpdyStream* stream) {
if (stream != nullptr) {
// Always close the stream, regardless of whether it was the last stream
// written.
client()->OnClose(stream);
++num_responses_;
}
if (stream_ != stream) {
return;
}
if (buffer_body()) {
// TODO(fnk): The stream still buffers the whole thing. Fix that.
response_ = stream_->data();
}
response_complete_ = true;
response_headers_complete_ = stream_->headers_decompressed();
response_headers_ = stream_->response_headers().Clone();
response_trailers_ = stream_->received_trailers().Clone();
preliminary_headers_ = stream_->preliminary_headers().Clone();
stream_error_ = stream_->stream_error();
bytes_read_ = stream_->stream_bytes_read() + stream_->header_bytes_read();
bytes_written_ =
stream_->stream_bytes_written() + stream_->header_bytes_written();
response_body_size_ = stream_->data().size();
stream_ = nullptr;
}
bool QuicTestClient::CheckVary(const SpdyHeaderBlock& client_request,
const SpdyHeaderBlock& promise_request,
const SpdyHeaderBlock& promise_response) {
return true;
}
void QuicTestClient::OnRendezvousResult(QuicSpdyStream* stream) {
std::unique_ptr<TestClientDataToResend> data_to_resend =
std::move(push_promise_data_to_resend_);
SetStream(static_cast<QuicSpdyClientStream*>(stream));
if (stream) {
stream->OnDataAvailable();
} else if (data_to_resend.get()) {
data_to_resend->Resend();
}
}
void QuicTestClient::UseWriter(QuicPacketWriterWrapper* writer) {
client_->UseWriter(writer);
}
void QuicTestClient::UseConnectionId(QuicConnectionId connection_id) {
DCHECK(!connected());
client_->UseConnectionId(connection_id);
}
void QuicTestClient::MigrateSocket(const QuicIpAddress& new_host) {
client_->MigrateSocket(new_host);
}
QuicIpAddress QuicTestClient::bind_to_address() const {
return client_->bind_to_address();
}
void QuicTestClient::set_bind_to_address(QuicIpAddress address) {
client_->set_bind_to_address(address);
}
const QuicSocketAddress& QuicTestClient::address() const {
return client_->server_address();
}
void QuicTestClient::WaitForWriteToFlush() {
while (connected() && client()->session()->HasDataToWrite()) {
client_->WaitForEvents();
}
}
QuicTestClient::TestClientDataToResend::TestClientDataToResend(
std::unique_ptr<SpdyHeaderBlock> headers,
base::StringPiece body,
bool fin,
QuicTestClient* test_client,
QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener)
: QuicClient::QuicDataToResend(std::move(headers), body, fin),
test_client_(test_client),
ack_listener_(std::move(ack_listener)) {}
QuicTestClient::TestClientDataToResend::~TestClientDataToResend() {}
void QuicTestClient::TestClientDataToResend::Resend() {
test_client_->GetOrCreateStreamAndSendRequest(headers_.get(), body_, fin_,
ack_listener_);
headers_.reset();
}
bool QuicTestClient::PopulateHeaderBlockFromUrl(const string& uri,
SpdyHeaderBlock* headers) {
string url;
if (QuicTextUtils::StartsWith(uri, "https://") ||
QuicTextUtils::StartsWith(uri, "http://")) {
url = uri;
} else if (uri[0] == '/') {
url = "https://" + client_->server_id().host() + uri;
} else {
url = "https://" + uri;
}
return SpdyUtils::PopulateHeaderBlockFromUrl(url, headers);
}
} // namespace test
} // namespace net