blob: a1b9d97e007c174b40ca8835bbd3806f6fe5a5a7 [file] [log] [blame] [edit]
// 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 QuicGenericSession
// client and server sessions.
#include "quiche/quic/core/quic_generic_session.h"
#include <cstddef>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/crypto/quic_compressed_certs_cache.h"
#include "quiche/quic/core/crypto/quic_crypto_client_config.h"
#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/core/quic_connection.h"
#include "quiche/quic/core/quic_constants.h"
#include "quiche/quic/core/quic_datagram_queue.h"
#include "quiche/quic/core/quic_error_codes.h"
#include "quiche/quic/core/quic_stream.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/web_transport_interface.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/crypto_test_utils.h"
#include "quiche/quic/test_tools/quic_session_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/quic/test_tools/simulator/simulator.h"
#include "quiche/quic/test_tools/simulator/test_harness.h"
#include "quiche/quic/test_tools/web_transport_test_tools.h"
#include "quiche/quic/tools/web_transport_test_visitors.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
#include "quiche/web_transport/web_transport.h"
namespace quic::test {
namespace {
enum ServerType { kDiscardServer, kEchoServer };
using quiche::test::StatusIs;
using simulator::Simulator;
using testing::_;
using testing::Assign;
using testing::AtMost;
using testing::Eq;
class CountingDatagramObserver : public QuicDatagramQueue::Observer {
public:
CountingDatagramObserver(int& total) : total_(total) {}
void OnDatagramProcessed(std::optional<MessageStatus>) { ++total_; }
private:
int& total_;
};
class ClientEndpoint : public simulator::QuicEndpointWithConnection {
public:
ClientEndpoint(Simulator* simulator, const std::string& name,
const std::string& peer_name, const QuicConfig& config)
: QuicEndpointWithConnection(simulator, name, peer_name,
Perspective::IS_CLIENT,
GetQuicVersionsForGenericSession()),
crypto_config_(crypto_test_utils::ProofVerifierForTesting()),
session_(connection_.get(), false, nullptr, config, "test.example.com",
443, "example_alpn", &visitor_, /*visitor_owned=*/false,
std::make_unique<CountingDatagramObserver>(
total_datagrams_processed_),
&crypto_config_) {
session_.Initialize();
session_.connection()->sent_packet_manager().SetSendAlgorithm(
CongestionControlType::kBBRv2);
EXPECT_CALL(visitor_, OnSessionReady())
.Times(AtMost(1))
.WillOnce(Assign(&session_ready_, true));
}
QuicGenericClientSession* session() { return &session_; }
MockWebTransportSessionVisitor* visitor() { return &visitor_; }
bool session_ready() const { return session_ready_; }
int total_datagrams_processed() const { return total_datagrams_processed_; }
private:
QuicCryptoClientConfig crypto_config_;
MockWebTransportSessionVisitor visitor_;
QuicGenericClientSession session_;
bool session_ready_ = false;
int total_datagrams_processed_ = 0;
};
class ServerEndpoint : public simulator::QuicEndpointWithConnection {
public:
ServerEndpoint(Simulator* simulator, const std::string& name,
const std::string& peer_name, const QuicConfig& config,
ServerType type)
: QuicEndpointWithConnection(simulator, name, peer_name,
Perspective::IS_SERVER,
GetQuicVersionsForGenericSession()),
crypto_config_(QuicCryptoServerConfig::TESTING,
QuicRandom::GetInstance(),
crypto_test_utils::ProofSourceForTesting(),
KeyExchangeSource::Default()),
compressed_certs_cache_(
QuicCompressedCertsCache::kQuicCompressedCertsCacheSize),
session_(connection_.get(), false, nullptr, config, "example_alpn",
type == kEchoServer
? static_cast<webtransport::SessionVisitor*>(
new EchoWebTransportSessionVisitor(
&session_,
/*open_server_initiated_echo_stream=*/false))
: static_cast<webtransport::SessionVisitor*>(
new DiscardWebTransportSessionVisitor(&session_)),
/*owns_visitor=*/true,
/*datagram_observer=*/nullptr, &crypto_config_,
&compressed_certs_cache_) {
session_.Initialize();
session_.connection()->sent_packet_manager().SetSendAlgorithm(
CongestionControlType::kBBRv2);
}
QuicGenericServerSession* session() { return &session_; }
private:
QuicCryptoServerConfig crypto_config_;
QuicCompressedCertsCache compressed_certs_cache_;
QuicGenericServerSession session_;
};
class QuicGenericSessionTest : public QuicTest {
public:
void CreateDefaultEndpoints(ServerType server_type) {
client_ = std::make_unique<ClientEndpoint>(
&test_harness_.simulator(), "Client", "Server", client_config_);
server_ =
std::make_unique<ServerEndpoint>(&test_harness_.simulator(), "Server",
"Client", server_config_, server_type);
test_harness_.set_client(client_.get());
test_harness_.set_server(server_.get());
}
void WireUpEndpoints() { test_harness_.WireUpEndpoints(); }
void RunHandshake() {
client_->session()->CryptoConnect();
bool result = test_harness_.RunUntilWithDefaultTimeout([this]() {
return client_->session_ready() ||
client_->session()->error() != QUIC_NO_ERROR;
});
EXPECT_TRUE(result);
}
protected:
QuicConfig client_config_ = DefaultQuicConfig();
QuicConfig server_config_ = DefaultQuicConfig();
simulator::TestHarness test_harness_;
std::unique_ptr<ClientEndpoint> client_;
std::unique_ptr<ServerEndpoint> server_;
};
TEST_F(QuicGenericSessionTest, SuccessfulHandshake) {
CreateDefaultEndpoints(kDiscardServer);
WireUpEndpoints();
RunHandshake();
EXPECT_TRUE(client_->session_ready());
}
TEST_F(QuicGenericSessionTest, SendOutgoingStreams) {
CreateDefaultEndpoints(kDiscardServer);
WireUpEndpoints();
RunHandshake();
std::vector<webtransport::Stream*> streams;
for (int i = 0; i < 10; i++) {
webtransport::Stream* stream =
client_->session()->OpenOutgoingUnidirectionalStream();
ASSERT_TRUE(stream->Write("test"));
streams.push_back(stream);
}
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout([this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) == 10;
}));
for (webtransport::Stream* stream : streams) {
ASSERT_TRUE(stream->SendFin());
}
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout([this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) == 0;
}));
}
TEST_F(QuicGenericSessionTest, EchoBidirectionalStreams) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
webtransport::Stream* stream =
client_->session()->OpenOutgoingBidirectionalStream();
EXPECT_TRUE(stream->Write("Hello!"));
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout(
[stream]() { return stream->ReadableBytes() == strlen("Hello!"); }));
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(test_harness_.RunUntilWithDefaultTimeout([this]() {
return QuicSessionPeer::GetNumOpenDynamicStreams(server_->session()) == 0;
}));
}
TEST_F(QuicGenericSessionTest, EchoUnidirectionalStreams) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
// Send two streams, but only send FIN on the second one.
webtransport::Stream* stream1 =
client_->session()->OpenOutgoingUnidirectionalStream();
EXPECT_TRUE(stream1->Write("Stream One"));
webtransport::Stream* 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(test_harness_.RunUntilWithDefaultTimeout(
[&stream_received]() { return stream_received; }));
// Receive a reply stream and expect it to be the second one.
webtransport::Stream* 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(test_harness_.RunUntilWithDefaultTimeout(
[&stream_received]() { return stream_received; }));
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(QuicGenericSessionTest, EchoStreamsUsingPeekApi) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
// Send two streams, a bidirectional and a unidirectional one, but only send
// FIN on the second one.
webtransport::Stream* stream1 =
client_->session()->OpenOutgoingBidirectionalStream();
EXPECT_TRUE(stream1->Write("Stream One"));
webtransport::Stream* stream2 =
client_->session()->OpenOutgoingUnidirectionalStream();
EXPECT_TRUE(stream2->Write("Stream Two"));
EXPECT_TRUE(stream2->SendFin());
// Wait until the unidirectional stream is received back.
bool stream_received_unidi = false;
EXPECT_CALL(*client_->visitor(), OnIncomingUnidirectionalStreamAvailable())
.WillOnce(Assign(&stream_received_unidi, true));
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout(
[&]() { return stream_received_unidi; }));
// Receive the unidirectional echo reply.
webtransport::Stream* reply =
client_->session()->AcceptIncomingUnidirectionalStream();
ASSERT_TRUE(reply != nullptr);
std::string buffer;
quiche::ReadStream::PeekResult peek_result = reply->PeekNextReadableRegion();
EXPECT_EQ(peek_result.peeked_data, "Stream Two");
EXPECT_EQ(peek_result.fin_next, false);
EXPECT_EQ(peek_result.all_data_received, true);
bool fin_received =
quiche::ProcessAllReadableRegions(*reply, [&](absl::string_view chunk) {
buffer.append(chunk.data(), chunk.size());
return true;
});
EXPECT_TRUE(fin_received);
EXPECT_EQ(buffer, "Stream Two");
// Receive the bidirectional stream reply without a FIN.
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout(
[&]() { return stream1->PeekNextReadableRegion().has_data(); }));
peek_result = stream1->PeekNextReadableRegion();
EXPECT_EQ(peek_result.peeked_data, "Stream One");
EXPECT_EQ(peek_result.fin_next, false);
EXPECT_EQ(peek_result.all_data_received, false);
fin_received = stream1->SkipBytes(strlen("Stream One"));
EXPECT_FALSE(fin_received);
peek_result = stream1->PeekNextReadableRegion();
EXPECT_EQ(peek_result.peeked_data, "");
EXPECT_EQ(peek_result.fin_next, false);
EXPECT_EQ(peek_result.all_data_received, false);
// Send FIN on the first stream, and expect to receive it back.
EXPECT_TRUE(stream1->SendFin());
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout(
[&]() { return stream1->PeekNextReadableRegion().all_data_received; }));
peek_result = stream1->PeekNextReadableRegion();
EXPECT_EQ(peek_result.peeked_data, "");
EXPECT_EQ(peek_result.fin_next, true);
EXPECT_EQ(peek_result.all_data_received, true);
// Read FIN and expect the stream to get garbage collected.
webtransport::StreamId id = stream1->GetStreamId();
EXPECT_TRUE(client_->session()->GetStreamById(id) != nullptr);
fin_received = stream1->SkipBytes(0);
EXPECT_TRUE(fin_received);
EXPECT_TRUE(client_->session()->GetStreamById(id) == nullptr);
}
TEST_F(QuicGenericSessionTest, EchoDatagram) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
client_->session()->SendOrQueueDatagram("test");
bool datagram_received = false;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(Eq("test")))
.WillOnce(Assign(&datagram_received, true));
ASSERT_TRUE(test_harness_.RunUntilWithDefaultTimeout(
[&datagram_received]() { return datagram_received; }));
}
// This test sets the datagram queue to an nearly-infinite 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(QuicGenericSessionTest, EchoALotOfDatagrams) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
// Set the datagrams to effectively never expire.
client_->session()->SetDatagramMaxTimeInQueue(
(10000 * simulator::TestHarness::kRtt).ToAbsl());
for (int i = 0; i < 1000; i++) {
client_->session()->SendOrQueueDatagram(std::string(
client_->session()->GetGuaranteedLargestMessagePayload(), 'a'));
}
size_t received = 0;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
.WillRepeatedly(
[&received](absl::string_view /*datagram*/) { received++; });
ASSERT_TRUE(test_harness_.simulator().RunUntilOrTimeout(
[this]() { return client_->total_datagrams_processed() >= 1000; },
3 * simulator::TestHarness::kServerBandwidth.TransferTime(
1000 * kMaxOutgoingPacketSize)));
// Allow extra round-trips for the final flight of datagrams to arrive back.
test_harness_.simulator().RunFor(2 * simulator::TestHarness::kRtt);
EXPECT_GT(received, 500u);
EXPECT_LT(received, 1000u);
}
TEST_F(QuicGenericSessionTest, OutgoingStreamFlowControlBlocked) {
server_config_.SetMaxUnidirectionalStreamsToSend(4);
CreateDefaultEndpoints(kDiscardServer);
WireUpEndpoints();
RunHandshake();
webtransport::Stream* stream;
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(test_harness_.RunUntilWithDefaultTimeout(
[&can_create_new_stream]() { return can_create_new_stream; }));
EXPECT_TRUE(client_->session()->CanOpenNextOutgoingUnidirectionalStream());
}
TEST_F(QuicGenericSessionTest, ExpireDatagrams) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
// Set the datagrams to expire very soon.
client_->session()->SetDatagramMaxTimeInQueue(
(0.2 * simulator::TestHarness::kRtt).ToAbsl());
for (int i = 0; i < 1000; i++) {
client_->session()->SendOrQueueDatagram(std::string(
client_->session()->GetGuaranteedLargestMessagePayload(), 'a'));
}
size_t received = 0;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
.WillRepeatedly(
[&received](absl::string_view /*datagram*/) { received++; });
ASSERT_TRUE(test_harness_.simulator().RunUntilOrTimeout(
[this]() { return client_->total_datagrams_processed() >= 1000; },
3 * simulator::TestHarness::kServerBandwidth.TransferTime(
1000 * kMaxOutgoingPacketSize)));
// Allow extra round-trips for the final flight of datagrams to arrive back.
test_harness_.simulator().RunFor(2 * simulator::TestHarness::kRtt);
EXPECT_LT(received, 500);
EXPECT_EQ(received + client_->session()->GetDatagramStats().expired_outgoing,
1000);
}
TEST_F(QuicGenericSessionTest, LoseDatagrams) {
CreateDefaultEndpoints(kEchoServer);
test_harness_.WireUpEndpointsWithLoss(/*lose_every_n=*/4);
RunHandshake();
// Set the datagrams to effectively never expire.
client_->session()->SetDatagramMaxTimeInQueue(
(10000 * simulator::TestHarness::kRtt).ToAbsl());
for (int i = 0; i < 1000; i++) {
client_->session()->SendOrQueueDatagram(std::string(
client_->session()->GetGuaranteedLargestMessagePayload(), 'a'));
}
size_t received = 0;
EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
.WillRepeatedly(
[&received](absl::string_view /*datagram*/) { received++; });
ASSERT_TRUE(test_harness_.simulator().RunUntilOrTimeout(
[this]() { return client_->total_datagrams_processed() >= 1000; },
4 * simulator::TestHarness::kServerBandwidth.TransferTime(
1000 * kMaxOutgoingPacketSize)));
// Allow extra round-trips for the final flight of datagrams to arrive back.
test_harness_.simulator().RunFor(16 * simulator::TestHarness::kRtt);
QuicPacketCount client_lost =
client_->session()->GetDatagramStats().lost_outgoing;
QuicPacketCount server_lost =
server_->session()->GetDatagramStats().lost_outgoing;
EXPECT_LT(received, 800u);
EXPECT_GT(client_lost, 100u);
EXPECT_GT(server_lost, 100u);
EXPECT_EQ(received + client_lost + server_lost, 1000u);
}
TEST_F(QuicGenericSessionTest, WriteWhenBufferFull) {
CreateDefaultEndpoints(kEchoServer);
WireUpEndpoints();
RunHandshake();
const std::string buffer(64 * 1024 + 1, 'q');
webtransport::Stream* stream =
client_->session()->OpenOutgoingBidirectionalStream();
ASSERT_TRUE(stream != nullptr);
ASSERT_TRUE(stream->CanWrite());
absl::Status status = quiche::WriteIntoStream(*stream, buffer);
QUICHE_EXPECT_OK(status);
EXPECT_FALSE(stream->CanWrite());
status = quiche::WriteIntoStream(*stream, buffer);
EXPECT_THAT(status, StatusIs(absl::StatusCode::kUnavailable));
quiche::StreamWriteOptions options;
options.set_buffer_unconditionally(true);
options.set_send_fin(true);
status = quiche::WriteIntoStream(*stream, buffer, options);
QUICHE_EXPECT_OK(status);
EXPECT_FALSE(stream->CanWrite());
QuicByteCount total_received = 0;
for (;;) {
test_harness_.RunUntilWithDefaultTimeout(
[&] { return stream->PeekNextReadableRegion().has_data(); });
quiche::ReadStream::PeekResult result = stream->PeekNextReadableRegion();
total_received += result.peeked_data.size();
bool fin_consumed = stream->SkipBytes(result.peeked_data.size());
if (fin_consumed) {
break;
}
}
EXPECT_EQ(total_received, 128u * 1024u + 2);
}
} // namespace
} // namespace quic::test