blob: 7bb8479af3130e5e5e4f3e3580e241f4eb9e47b5 [file] [log] [blame]
// 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.
// An integration test that covers interactions between QuicTransport client and
// server sessions.
#include <memory>
#include <vector>
#include "absl/strings/string_view.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "quic/core/crypto/quic_crypto_client_config.h"
#include "quic/core/crypto/quic_crypto_server_config.h"
#include "quic/core/quic_buffer_allocator.h"
#include "quic/core/quic_connection.h"
#include "quic/core/quic_error_codes.h"
#include "quic/core/quic_types.h"
#include "quic/core/quic_versions.h"
#include "quic/platform/api/quic_flags.h"
#include "quic/platform/api/quic_test.h"
#include "quic/quic_transport/quic_transport_client_session.h"
#include "quic/quic_transport/quic_transport_server_session.h"
#include "quic/quic_transport/quic_transport_stream.h"
#include "quic/test_tools/crypto_test_utils.h"
#include "quic/test_tools/quic_session_peer.h"
#include "quic/test_tools/quic_test_utils.h"
#include "quic/test_tools/quic_transport_test_tools.h"
#include "quic/test_tools/simulator/link.h"
#include "quic/test_tools/simulator/quic_endpoint_base.h"
#include "quic/test_tools/simulator/simulator.h"
#include "quic/test_tools/simulator/switch.h"
#include "quic/tools/quic_transport_simple_server_session.h"
namespace quic {
namespace test {
namespace {
using simulator::QuicEndpointBase;
using simulator::Simulator;
using testing::_;
using testing::Assign;
using testing::Eq;
url::Origin GetTestOrigin() {
constexpr char kTestOrigin[] = "https://test-origin.test";
GURL origin_url(kTestOrigin);
return url::Origin::Create(origin_url);
}
ParsedQuicVersionVector GetVersions() {
return {DefaultVersionForQuicTransport()};
}
class QuicTransportEndpointBase : public QuicEndpointBase {
public:
QuicTransportEndpointBase(Simulator* simulator,
const std::string& name,
const std::string& peer_name,
Perspective perspective)
: QuicEndpointBase(simulator, name, peer_name) {
QuicEnableVersion(DefaultVersionForQuicTransport());
connection_ = std::make_unique<QuicConnection>(
TestConnectionId(0x10), simulator::GetAddressFromName(name),
simulator::GetAddressFromName(peer_name), simulator,
simulator->GetAlarmFactory(), &writer_,
/*owns_writer=*/false, perspective, GetVersions());
connection_->SetSelfAddress(simulator::GetAddressFromName(name));
}
};
class QuicTransportClientEndpoint : public QuicTransportEndpointBase {
public:
QuicTransportClientEndpoint(Simulator* simulator,
const std::string& name,
const std::string& peer_name,
const QuicConfig& config,
url::Origin origin,
const std::string& path)
: QuicTransportEndpointBase(simulator,
name,
peer_name,
Perspective::IS_CLIENT),
crypto_config_(crypto_test_utils::ProofVerifierForTesting()),
session_(connection_.get(),
nullptr,
config,
GetVersions(),
GURL("quic-transport://test.example.com:50000" + path),
&crypto_config_,
origin,
&visitor_,
/*datagram_observer=*/nullptr) {
session_.Initialize();
}
QuicTransportClientSession* session() { return &session_; }
MockClientVisitor* visitor() { return &visitor_; }
private:
QuicCryptoClientConfig crypto_config_;
MockClientVisitor visitor_;
QuicTransportClientSession session_;
};
class QuicTransportServerEndpoint : public QuicTransportEndpointBase {
public:
QuicTransportServerEndpoint(Simulator* simulator,
const std::string& name,
const std::string& peer_name,
const QuicConfig& config,
std::vector<url::Origin> accepted_origins)
: QuicTransportEndpointBase(simulator,
name,
peer_name,
Perspective::IS_SERVER),
crypto_config_(QuicCryptoServerConfig::TESTING,
QuicRandom::GetInstance(),
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default()),
compressed_certs_cache_(
QuicCompressedCertsCache::kQuicCompressedCertsCacheSize),
session_(connection_.get(),
/*owns_connection=*/false,
nullptr,
config,
GetVersions(),
&crypto_config_,
&compressed_certs_cache_,
accepted_origins) {
session_.Initialize();
}
QuicTransportServerSession* session() { return &session_; }
private:
QuicCryptoServerConfig crypto_config_;
QuicCompressedCertsCache compressed_certs_cache_;
QuicTransportSimpleServerSession session_;
};
constexpr QuicBandwidth kClientBandwidth =
QuicBandwidth::FromKBitsPerSecond(10000);
constexpr QuicTime::Delta kClientPropagationDelay =
QuicTime::Delta::FromMilliseconds(2);
constexpr QuicBandwidth kServerBandwidth =
QuicBandwidth::FromKBitsPerSecond(4000);
constexpr QuicTime::Delta kServerPropagationDelay =
QuicTime::Delta::FromMilliseconds(50);
const QuicTime::Delta kTransferTime =
kClientBandwidth.TransferTime(kMaxOutgoingPacketSize) +
kServerBandwidth.TransferTime(kMaxOutgoingPacketSize);
const QuicTime::Delta kRtt =
(kClientPropagationDelay + kServerPropagationDelay + kTransferTime) * 2;
const QuicByteCount kBdp = kRtt * kServerBandwidth;
constexpr QuicTime::Delta kDefaultTimeout = QuicTime::Delta::FromSeconds(3);
class QuicTransportIntegrationTest : public QuicTest {
public:
QuicTransportIntegrationTest()
: switch_(&simulator_, "Switch", 8, 2 * kBdp) {}
void CreateDefaultEndpoints(const std::string& path) {
client_ = std::make_unique<QuicTransportClientEndpoint>(
&simulator_, "Client", "Server", client_config_, GetTestOrigin(), path);
server_ = std::make_unique<QuicTransportServerEndpoint>(
&simulator_, "Server", "Client", server_config_, accepted_origins_);
}
void WireUpEndpoints() {
client_link_ = std::make_unique<simulator::SymmetricLink>(
client_.get(), switch_.port(1), kClientBandwidth,
kClientPropagationDelay);
server_link_ = std::make_unique<simulator::SymmetricLink>(
server_.get(), switch_.port(2), kServerBandwidth,
kServerPropagationDelay);
}
void RunHandshake() {
client_->session()->CryptoConnect();
bool result = simulator_.RunUntilOrTimeout(
[this]() {
return IsHandshakeDone(client_->session()) &&
IsHandshakeDone(server_->session());
},
kDefaultTimeout);
EXPECT_TRUE(result);
}
protected:
template <class Session>
static bool IsHandshakeDone(const Session* session) {
return session->IsSessionReady() || session->error() != QUIC_NO_ERROR;
}
QuicConfig client_config_ = DefaultQuicConfig();
QuicConfig server_config_ = DefaultQuicConfig();
Simulator simulator_;
simulator::Switch switch_;
std::unique_ptr<simulator::SymmetricLink> client_link_;
std::unique_ptr<simulator::SymmetricLink> server_link_;
std::unique_ptr<QuicTransportClientEndpoint> client_;
std::unique_ptr<QuicTransportServerEndpoint> server_;
std::vector<url::Origin> accepted_origins_ = {GetTestOrigin()};
};
TEST_F(QuicTransportIntegrationTest, SuccessfulHandshake) {
CreateDefaultEndpoints("/discard");
WireUpEndpoints();
EXPECT_CALL(*client_->visitor(), OnSessionReady(_));
RunHandshake();
EXPECT_TRUE(client_->session()->IsSessionReady());
EXPECT_TRUE(server_->session()->IsSessionReady());
}
TEST_F(QuicTransportIntegrationTest, OriginMismatch) {
accepted_origins_ = {url::Origin::Create(GURL{"https://wrong-origin.test"})};
CreateDefaultEndpoints("/discard");
WireUpEndpoints();
RunHandshake();
// Wait until the client receives CONNECTION_CLOSE.
simulator_.RunUntilOrTimeout(
[this]() { return !client_->session()->connection()->connected(); },
kDefaultTimeout);
EXPECT_TRUE(client_->session()->IsSessionReady());
EXPECT_FALSE(server_->session()->IsSessionReady());
EXPECT_FALSE(client_->session()->connection()->connected());
EXPECT_FALSE(server_->session()->connection()->connected());
EXPECT_THAT(client_->session()->error(),
IsError(QUIC_TRANSPORT_INVALID_CLIENT_INDICATION));
EXPECT_THAT(server_->session()->error(),
IsError(QUIC_TRANSPORT_INVALID_CLIENT_INDICATION));
}
TEST_F(QuicTransportIntegrationTest, SendOutgoingStreams) {
CreateDefaultEndpoints("/discard");
WireUpEndpoints();
RunHandshake();
std::vector<QuicTransportStream*> streams;
for (int i = 0; i < 10; i++) {
QuicTransportStream* stream =
client_->session()->OpenOutgoingUnidirectionalStream();
ASSERT_TRUE(stream->Write("test"));
streams.push_back(stream);
}
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) ==
10;
},
kDefaultTimeout));
for (QuicTransportStream* stream : streams) {
ASSERT_TRUE(stream->SendFin());
}
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) ==
0;
},
kDefaultTimeout));
}
TEST_F(QuicTransportIntegrationTest, EchoBidirectionalStreams) {
CreateDefaultEndpoints("/echo");
WireUpEndpoints();
RunHandshake();
QuicTransportStream* stream =
client_->session()->OpenOutgoingBidirectionalStream();
EXPECT_TRUE(stream->Write("Hello!"));
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[stream]() { return stream->ReadableBytes() == strlen("Hello!"); },
kDefaultTimeout));
std::string received;
WebTransportStream::ReadResult result = stream->Read(&received);
EXPECT_EQ(result.bytes_read, strlen("Hello!"));
EXPECT_FALSE(result.fin);
EXPECT_EQ(received, "Hello!");
EXPECT_TRUE(stream->SendFin());
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) ==
0;
},
kDefaultTimeout));
}
TEST_F(QuicTransportIntegrationTest, EchoUnidirectionalStreams) {
CreateDefaultEndpoints("/echo");
WireUpEndpoints();
RunHandshake();
// Send two streams, but only send FIN on the second one.
QuicTransportStream* stream1 =
client_->session()->OpenOutgoingUnidirectionalStream();
EXPECT_TRUE(stream1->Write("Stream One"));
QuicTransportStream* stream2 =
client_->session()->OpenOutgoingUnidirectionalStream();
EXPECT_TRUE(stream2->Write("Stream Two"));
EXPECT_TRUE(stream2->SendFin());
// Wait until a stream is received.
bool stream_received = false;
EXPECT_CALL(*client_->visitor(), OnIncomingUnidirectionalStreamAvailable())
.Times(2)
.WillRepeatedly(Assign(&stream_received, true));
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[&stream_received]() { return stream_received; }, kDefaultTimeout));
// Receive a reply stream and expect it to be the second one.
QuicTransportStream* reply =
client_->session()->AcceptIncomingUnidirectionalStream();
ASSERT_TRUE(reply != nullptr);
std::string buffer;
WebTransportStream::ReadResult result = reply->Read(&buffer);
EXPECT_GT(result.bytes_read, 0u);
EXPECT_TRUE(result.fin);
EXPECT_EQ(buffer, "Stream Two");
// Reset reply-related variables.
stream_received = false;
buffer = "";
// Send FIN on the first stream, and expect to receive it back.
EXPECT_TRUE(stream1->SendFin());
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[&stream_received]() { return stream_received; }, kDefaultTimeout));
reply = client_->session()->AcceptIncomingUnidirectionalStream();
ASSERT_TRUE(reply != nullptr);
result = reply->Read(&buffer);
EXPECT_GT(result.bytes_read, 0u);
EXPECT_TRUE(result.fin);
EXPECT_EQ(buffer, "Stream One");
}
TEST_F(QuicTransportIntegrationTest, EchoDatagram) {
CreateDefaultEndpoints("/echo");
WireUpEndpoints();
RunHandshake();
client_->session()->SendOrQueueDatagram(MemSliceFromString("test"));
bool datagram_received = false;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(Eq("test")))
.WillOnce(Assign(&datagram_received, true));
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[&datagram_received]() { return datagram_received; }, kDefaultTimeout));
}
// This test sets the datagram queue to an nearly-infinte queueing time, and
// then sends 1000 datagrams. We expect to receive most of them back, since the
// datagrams would be paced out by the congestion controller.
TEST_F(QuicTransportIntegrationTest, EchoALotOfDatagrams) {
CreateDefaultEndpoints("/echo");
WireUpEndpoints();
RunHandshake();
// Set the datagrams to effectively never expire.
client_->session()->SetDatagramMaxTimeInQueue(10000 * kRtt);
for (int i = 0; i < 1000; i++) {
client_->session()->SendOrQueueDatagram(MemSliceFromString(std::string(
client_->session()->GetGuaranteedLargestMessagePayload(), 'a')));
}
size_t received = 0;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
.WillRepeatedly(
[&received](absl::string_view /*datagram*/) { received++; });
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[this]() { return client_->session()->datagram_queue()->empty(); },
3 * kServerBandwidth.TransferTime(1000 * kMaxOutgoingPacketSize)));
// Allow extra round-trips for the final flight of datagrams to arrive back.
simulator_.RunFor(2 * kRtt);
EXPECT_GT(received, 500u);
EXPECT_LT(received, 1000u);
}
TEST_F(QuicTransportIntegrationTest, OutgoingStreamFlowControlBlocked) {
server_config_.SetMaxUnidirectionalStreamsToSend(4);
CreateDefaultEndpoints("/discard");
WireUpEndpoints();
RunHandshake();
QuicTransportStream* stream;
// Note that since we've already used one stream for client indication, we can
// only send three streams at once.
for (int i = 0; i < 3; i++) {
ASSERT_TRUE(client_->session()->CanOpenNextOutgoingUnidirectionalStream());
stream = client_->session()->OpenOutgoingUnidirectionalStream();
ASSERT_TRUE(stream != nullptr);
ASSERT_TRUE(stream->SendFin());
}
EXPECT_FALSE(client_->session()->CanOpenNextOutgoingUnidirectionalStream());
// Receiving FINs for the streams we've just opened will cause the server to
// let us open more streams.
bool can_create_new_stream = false;
EXPECT_CALL(*client_->visitor(), OnCanCreateNewOutgoingUnidirectionalStream())
.WillOnce(Assign(&can_create_new_stream, true));
ASSERT_TRUE(simulator_.RunUntilOrTimeout(
[&can_create_new_stream]() { return can_create_new_stream; },
kDefaultTimeout));
EXPECT_TRUE(client_->session()->CanOpenNextOutgoingUnidirectionalStream());
}
} // namespace
} // namespace test
} // namespace quic