blob: 1c02da12001499d0a6509ae71e59e3dd189b06e1 [file] [log] [blame]
// Copyright 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 "services/network/web_transport.h"
#include <set>
#include <vector>
#include "base/containers/contains.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/dns/mock_host_resolver.h"
#include "net/log/test_net_log.h"
#include "net/quic/crypto/proof_source_chromium.h"
#include "net/test/test_data_directory.h"
#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_backend.h"
#include "net/tools/quic/quic_simple_server.h"
#include "net/tools/quic/quic_transport_simple_server.h"
#include "net/url_request/url_request_context.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/test/fake_test_cert_verifier_params_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
// A clock that only mocks out WallNow(), but uses real Now() and
// ApproximateNow(). Useful for certificate verification.
class TestWallClock : public quic::QuicClock {
public:
quic::QuicTime Now() const override {
return quic::QuicChromiumClock::GetInstance()->Now();
}
quic::QuicTime ApproximateNow() const override {
return quic::QuicChromiumClock::GetInstance()->ApproximateNow();
}
quic::QuicWallTime WallNow() const override { return wall_now_; }
void set_wall_now(quic::QuicWallTime now) { wall_now_ = now; }
private:
quic::QuicWallTime wall_now_ = quic::QuicWallTime::Zero();
};
class TestConnectionHelper : public quic::QuicConnectionHelperInterface {
public:
const quic::QuicClock* GetClock() const override { return &clock_; }
quic::QuicRandom* GetRandomGenerator() override {
return quic::QuicRandom::GetInstance();
}
quic::QuicBufferAllocator* GetStreamSendBufferAllocator() override {
return &allocator_;
}
TestWallClock& clock() { return clock_; }
private:
TestWallClock clock_;
quic::SimpleBufferAllocator allocator_;
};
mojom::NetworkContextParamsPtr CreateNetworkContextParams() {
auto context_params = mojom::NetworkContextParams::New();
// Use a dummy CertVerifier that always passes cert verification, since
// these unittests don't need to test CertVerifier behavior.
context_params->cert_verifier_params =
FakeTestCertVerifierParamsFactory::GetCertVerifierParams();
return context_params;
}
// We don't use mojo::BlockingCopyToString because it leads to deadlocks.
std::string Read(mojo::ScopedDataPipeConsumerHandle readable) {
std::string output;
while (true) {
char buffer[1024];
uint32_t size = sizeof(buffer);
MojoResult result =
readable->ReadData(buffer, &size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop run_loop;
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
continue;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
return output;
}
DCHECK_EQ(result, MOJO_RESULT_OK);
output.append(buffer, size);
}
}
class TestHandshakeClient final : public mojom::WebTransportHandshakeClient {
public:
TestHandshakeClient(mojo::PendingReceiver<mojom::WebTransportHandshakeClient>
pending_receiver,
base::OnceClosure callback)
: receiver_(this, std::move(pending_receiver)),
callback_(std::move(callback)) {
receiver_.set_disconnect_handler(base::BindOnce(
&TestHandshakeClient::OnMojoConnectionError, base::Unretained(this)));
}
~TestHandshakeClient() override = default;
void OnConnectionEstablished(
mojo::PendingRemote<mojom::WebTransport> transport,
mojo::PendingReceiver<mojom::WebTransportClient> client_receiver)
override {
transport_ = std::move(transport);
client_receiver_ = std::move(client_receiver);
has_seen_connection_establishment_ = true;
receiver_.reset();
std::move(callback_).Run();
}
void OnHandshakeFailed(
const absl::optional<net::WebTransportError>& error) override {
has_seen_handshake_failure_ = true;
handshake_error_ = error;
receiver_.reset();
std::move(callback_).Run();
}
void OnMojoConnectionError() {
has_seen_handshake_failure_ = true;
std::move(callback_).Run();
}
mojo::PendingRemote<mojom::WebTransport> PassTransport() {
return std::move(transport_);
}
mojo::PendingReceiver<mojom::WebTransportClient> PassClientReceiver() {
return std::move(client_receiver_);
}
bool has_seen_connection_establishment() const {
return has_seen_connection_establishment_;
}
bool has_seen_handshake_failure() const {
return has_seen_handshake_failure_;
}
bool has_seen_mojo_connection_error() const {
return has_seen_mojo_connection_error_;
}
absl::optional<net::WebTransportError> handshake_error() const {
return handshake_error_;
}
private:
mojo::Receiver<mojom::WebTransportHandshakeClient> receiver_;
mojo::PendingRemote<mojom::WebTransport> transport_;
mojo::PendingReceiver<mojom::WebTransportClient> client_receiver_;
base::OnceClosure callback_;
bool has_seen_connection_establishment_ = false;
bool has_seen_handshake_failure_ = false;
bool has_seen_mojo_connection_error_ = false;
absl::optional<net::WebTransportError> handshake_error_;
};
class TestClient final : public mojom::WebTransportClient {
public:
explicit TestClient(
mojo::PendingReceiver<mojom::WebTransportClient> pending_receiver)
: receiver_(this, std::move(pending_receiver)) {
receiver_.set_disconnect_handler(base::BindOnce(
&TestClient::OnMojoConnectionError, base::Unretained(this)));
}
// mojom::WebTransportClient implementation.
void OnDatagramReceived(base::span<const uint8_t> data) override {
received_datagrams_.push_back(
std::vector<uint8_t>(data.begin(), data.end()));
}
void OnIncomingStreamClosed(uint32_t stream_id, bool fin_received) override {
closed_incoming_streams_.insert(std::make_pair(stream_id, fin_received));
if (quit_closure_for_incoming_stream_closure_) {
std::move(quit_closure_for_incoming_stream_closure_).Run();
}
}
void WaitUntilMojoConnectionError() {
base::RunLoop run_loop;
quit_closure_for_mojo_connection_error_ = run_loop.QuitClosure();
run_loop.Run();
}
void WaitUntilIncomingStreamIsClosed(uint32_t stream_id) {
while (!stream_is_closed_as_incoming_stream(stream_id)) {
base::RunLoop run_loop;
quit_closure_for_incoming_stream_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
const std::vector<std::vector<uint8_t>>& received_datagrams() const {
return received_datagrams_;
}
bool has_received_fin_for(uint32_t stream_id) {
auto it = closed_incoming_streams_.find(stream_id);
return it != closed_incoming_streams_.end() && it->second;
}
bool stream_is_closed_as_incoming_stream(uint32_t stream_id) {
return closed_incoming_streams_.find(stream_id) !=
closed_incoming_streams_.end();
}
bool has_seen_mojo_connection_error() const {
return has_seen_mojo_connection_error_;
}
private:
void OnMojoConnectionError() {
has_seen_mojo_connection_error_ = true;
if (quit_closure_for_mojo_connection_error_) {
std::move(quit_closure_for_mojo_connection_error_).Run();
}
}
mojo::Receiver<mojom::WebTransportClient> receiver_;
base::OnceClosure quit_closure_for_mojo_connection_error_;
base::OnceClosure quit_closure_for_incoming_stream_closure_;
std::vector<std::vector<uint8_t>> received_datagrams_;
std::map<uint32_t, bool> closed_incoming_streams_;
bool has_seen_mojo_connection_error_ = false;
};
quic::ParsedQuicVersion GetTestVersion() {
quic::ParsedQuicVersion version = quic::DefaultVersionForQuicTransport();
quic::QuicEnableVersion(version);
return version;
}
class WebTransportTest : public testing::TestWithParam<base::StringPiece> {
public:
WebTransportTest()
: WebTransportTest(
quic::test::crypto_test_utils::ProofSourceForTesting()) {}
explicit WebTransportTest(std::unique_ptr<quic::ProofSource> proof_source)
: version_(GetTestVersion()),
origin_(url::Origin::Create(GURL("https://example.org/"))),
task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
network_service_(NetworkService::CreateForTesting()),
network_context_remote_(mojo::NullRemote()),
network_context_(network_service_.get(),
network_context_remote_.BindNewPipeAndPassReceiver(),
CreateNetworkContextParams()) {
if (GetParam() == "https") {
backend_.set_enable_webtransport(true);
http_server_ = std::make_unique<net::QuicSimpleServer>(
std::move(proof_source), quic::QuicConfig(),
quic::QuicCryptoServerConfig::ConfigOptions(),
quic::AllSupportedVersions(), &backend_);
EXPECT_TRUE(http_server_->CreateUDPSocketAndListen(
quic::QuicSocketAddress(quic::QuicSocketAddress(
quic::QuicIpAddress::Any6(), /*port=*/0))));
} else {
quic_transport_server_ = std::make_unique<net::QuicTransportSimpleServer>(
/* port= */ 0, std::vector<url::Origin>({origin_}),
std::move(proof_source));
EXPECT_EQ(EXIT_SUCCESS, quic_transport_server_->Start());
}
cert_verifier_.set_default_result(net::OK);
host_resolver_.rules()->AddRule("test.example.com", "127.0.0.1");
network_context_.url_request_context()->set_cert_verifier(&cert_verifier_);
network_context_.url_request_context()->set_host_resolver(&host_resolver_);
network_context_.url_request_context()->set_net_log(&net_log_);
auto* quic_context = network_context_.url_request_context()->quic_context();
quic_context->params()->supported_versions.push_back(version_);
quic_context->params()->origins_to_force_quic_on.insert(
net::HostPortPair("test.example.com", 0));
}
~WebTransportTest() override = default;
void CreateWebTransport(
const GURL& url,
const url::Origin& origin,
const net::NetworkIsolationKey& key,
std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints,
mojo::PendingRemote<mojom::WebTransportHandshakeClient>
handshake_client) {
network_context_.CreateWebTransport(
url, origin, key, std::move(fingerprints), std::move(handshake_client));
}
void CreateWebTransport(
const GURL& url,
const url::Origin& origin,
mojo::PendingRemote<mojom::WebTransportHandshakeClient>
handshake_client) {
CreateWebTransport(url, origin, net::NetworkIsolationKey(), {},
std::move(handshake_client));
}
void CreateWebTransport(
const GURL& url,
const url::Origin& origin,
std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints,
mojo::PendingRemote<mojom::WebTransportHandshakeClient>
handshake_client) {
CreateWebTransport(url, origin, net::NetworkIsolationKey(),
std::move(fingerprints), std::move(handshake_client));
}
GURL GetURL(base::StringPiece suffix) {
int port = GetParam() == "https"
? http_server_->server_address().port()
: quic_transport_server_->server_address().port();
return GURL(base::StrCat({GetParam(), "://test.example.com:",
base::NumberToString(port), suffix}));
}
const url::Origin& origin() const { return origin_; }
const NetworkContext& network_context() const { return network_context_; }
NetworkContext& mutable_network_context() { return network_context_; }
net::RecordingTestNetLog& net_log() { return net_log_; }
void RunPendingTasks() {
base::RunLoop run_loop;
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
private:
QuicFlagSaver flags_; // Save/restore all QUIC flag values.
quic::ParsedQuicVersion version_;
const url::Origin origin_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<NetworkService> network_service_;
mojo::Remote<mojom::NetworkContext> network_context_remote_;
net::MockCertVerifier cert_verifier_;
net::MockHostResolver host_resolver_;
net::RecordingTestNetLog net_log_;
NetworkContext network_context_;
std::unique_ptr<net::QuicTransportSimpleServer> quic_transport_server_;
std::unique_ptr<net::QuicSimpleServer> http_server_;
quic::test::QuicTestBackend backend_;
};
struct PrintStringPiece {
std::string operator()(
const testing::TestParamInfo<base::StringPiece>& info) const {
std::string output;
base::ReplaceChars(info.param, "-", "_", &output);
return output;
}
};
INSTANTIATE_TEST_SUITE_P(WebTransportTests,
WebTransportTest,
testing::Values("quic-transport", "https"),
PrintStringPiece());
TEST_P(WebTransportTest, ConnectSuccessfully) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"), origin(), std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_TRUE(test_handshake_client.has_seen_connection_establishment());
EXPECT_FALSE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
EXPECT_EQ(1u, network_context().NumOpenWebTransports());
}
TEST_P(WebTransportTest, ConnectWithWrongOrigin) {
// HTTP/3 test server currently does not do the origin check.
// TODO(vasilvv): fix this.
if (GetParam() == "https") {
return;
}
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"),
url::Origin::Create(GURL("https://evil.com")),
std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_TRUE(test_handshake_client.has_seen_connection_establishment());
EXPECT_FALSE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
// Server resets the connection due to origin mismatch.
TestClient client(test_handshake_client.PassClientReceiver());
client.WaitUntilMojoConnectionError();
EXPECT_EQ(0u, network_context().NumOpenWebTransports());
}
TEST_P(WebTransportTest, ConnectHandles404) {
if (GetParam() == "quic-transport") {
return;
}
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/does_not_exist"), origin(),
std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_FALSE(test_handshake_client.has_seen_connection_establishment());
EXPECT_TRUE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
EXPECT_EQ(0u, network_context().NumOpenWebTransports());
}
TEST_P(WebTransportTest, ConnectToBannedPort) {
if (GetParam() == "quic-transport") {
return;
}
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GURL("https://test.example.com:5060/echo"), origin(),
std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_FALSE(test_handshake_client.has_seen_connection_establishment());
EXPECT_TRUE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
EXPECT_EQ(0u, network_context().NumOpenWebTransports());
ASSERT_TRUE(test_handshake_client.handshake_error().has_value());
EXPECT_EQ(test_handshake_client.handshake_error()->net_error,
net::ERR_UNSAFE_PORT);
}
TEST_P(WebTransportTest, SendDatagram) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"),
url::Origin::Create(GURL("https://example.org/")),
std::move(handshake_client));
run_loop_for_handshake.Run();
mojo::Remote<mojom::WebTransport> transport_remote(
test_handshake_client.PassTransport());
TestClient client(test_handshake_client.PassClientReceiver());
std::set<std::vector<uint8_t>> sent_data;
// Both sending and receiving datagrams are flaky due to lack of
// retransmission, and we cannot expect a specific message to be echoed back.
// Instead, we expect one of sent messages to be echoed back.
while (client.received_datagrams().empty()) {
base::RunLoop run_loop_for_datagram;
bool result;
std::vector<uint8_t> data = {
static_cast<uint8_t>(base::RandInt(0, 255)),
static_cast<uint8_t>(base::RandInt(0, 255)),
static_cast<uint8_t>(base::RandInt(0, 255)),
static_cast<uint8_t>(base::RandInt(0, 255)),
};
transport_remote->SendDatagram(base::make_span(data),
base::BindLambdaForTesting([&](bool r) {
result = r;
run_loop_for_datagram.Quit();
}));
run_loop_for_datagram.Run();
if (sent_data.empty()) {
// We expect that the first data went to the network successfully.
ASSERT_TRUE(result);
}
sent_data.insert(std::move(data));
}
EXPECT_TRUE(base::Contains(sent_data, client.received_datagrams()[0]));
}
TEST_P(WebTransportTest, SendToolargeDatagram) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"),
url::Origin::Create(GURL("https://example.org/")),
std::move(handshake_client));
run_loop_for_handshake.Run();
base::RunLoop run_loop_for_datagram;
bool result;
// The actual upper limit for one datagram is platform specific, but
// 786kb should be large enough for any platform.
std::vector<uint8_t> data(786 * 1024, 99);
mojo::Remote<mojom::WebTransport> transport_remote(
test_handshake_client.PassTransport());
transport_remote->SendDatagram(base::make_span(data),
base::BindLambdaForTesting([&](bool r) {
result = r;
run_loop_for_datagram.Quit();
}));
run_loop_for_datagram.Run();
EXPECT_FALSE(result);
}
TEST_P(WebTransportTest, EchoOnUnidirectionalStreams) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"),
url::Origin::Create(GURL("https://example.org/")),
std::move(handshake_client));
run_loop_for_handshake.Run();
ASSERT_TRUE(test_handshake_client.has_seen_connection_establishment());
TestClient client(test_handshake_client.PassClientReceiver());
mojo::Remote<mojom::WebTransport> transport_remote(
test_handshake_client.PassTransport());
mojo::ScopedDataPipeConsumerHandle readable_for_outgoing;
mojo::ScopedDataPipeProducerHandle writable_for_outgoing;
const MojoCreateDataPipeOptions options = {
sizeof(options), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, 4 * 1024};
ASSERT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&options, writable_for_outgoing,
readable_for_outgoing));
uint32_t size = 5;
ASSERT_EQ(MOJO_RESULT_OK, writable_for_outgoing->WriteData(
"hello", &size, MOJO_WRITE_DATA_FLAG_NONE));
base::RunLoop run_loop_for_stream_creation;
uint32_t stream_id;
bool stream_created;
transport_remote->CreateStream(
std::move(readable_for_outgoing),
/*writable=*/{}, base::BindLambdaForTesting([&](bool b, uint32_t id) {
stream_created = b;
stream_id = id;
run_loop_for_stream_creation.Quit();
}));
run_loop_for_stream_creation.Run();
ASSERT_TRUE(stream_created);
// Signal the end-of-data.
writable_for_outgoing.reset();
transport_remote->SendFin(stream_id);
mojo::ScopedDataPipeConsumerHandle readable_for_incoming;
uint32_t incoming_stream_id = stream_id;
base::RunLoop run_loop_for_incoming_stream;
transport_remote->AcceptUnidirectionalStream(base::BindLambdaForTesting(
[&](uint32_t id, mojo::ScopedDataPipeConsumerHandle readable) {
incoming_stream_id = id;
readable_for_incoming = std::move(readable);
run_loop_for_incoming_stream.Quit();
}));
run_loop_for_incoming_stream.Run();
ASSERT_TRUE(readable_for_incoming);
EXPECT_NE(stream_id, incoming_stream_id);
std::string echo_back = Read(std::move(readable_for_incoming));
EXPECT_EQ("hello", echo_back);
client.WaitUntilIncomingStreamIsClosed(incoming_stream_id);
EXPECT_FALSE(client.has_received_fin_for(stream_id));
EXPECT_TRUE(client.has_received_fin_for(incoming_stream_id));
EXPECT_FALSE(client.has_seen_mojo_connection_error());
std::vector<net::NetLogEntry> resets_sent = net_log().GetEntriesWithType(
net::NetLogEventType::QUIC_SESSION_RST_STREAM_FRAME_SENT);
EXPECT_EQ(0u, resets_sent.size());
}
// crbug.com/1129847: disabled because it is flaky.
TEST_P(WebTransportTest, DISABLED_EchoOnBidirectionalStream) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
CreateWebTransport(GetURL("/echo"),
url::Origin::Create(GURL("https://example.org/")),
std::move(handshake_client));
run_loop_for_handshake.Run();
ASSERT_TRUE(test_handshake_client.has_seen_connection_establishment());
TestClient client(test_handshake_client.PassClientReceiver());
mojo::Remote<mojom::WebTransport> transport_remote(
test_handshake_client.PassTransport());
mojo::ScopedDataPipeConsumerHandle readable_for_outgoing;
mojo::ScopedDataPipeProducerHandle writable_for_outgoing;
mojo::ScopedDataPipeConsumerHandle readable_for_incoming;
mojo::ScopedDataPipeProducerHandle writable_for_incoming;
const MojoCreateDataPipeOptions options = {
sizeof(options), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, 4 * 1024};
ASSERT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&options, writable_for_outgoing,
readable_for_outgoing));
ASSERT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&options, writable_for_incoming,
readable_for_incoming));
uint32_t size = 5;
ASSERT_EQ(MOJO_RESULT_OK, writable_for_outgoing->WriteData(
"hello", &size, MOJO_WRITE_DATA_FLAG_NONE));
base::RunLoop run_loop_for_stream_creation;
uint32_t stream_id;
bool stream_created;
transport_remote->CreateStream(
std::move(readable_for_outgoing), std::move(writable_for_incoming),
base::BindLambdaForTesting([&](bool b, uint32_t id) {
stream_created = b;
stream_id = id;
run_loop_for_stream_creation.Quit();
}));
run_loop_for_stream_creation.Run();
ASSERT_TRUE(stream_created);
// Signal the end-of-data.
writable_for_outgoing.reset();
transport_remote->SendFin(stream_id);
std::string echo_back = Read(std::move(readable_for_incoming));
EXPECT_EQ("hello", echo_back);
client.WaitUntilIncomingStreamIsClosed(stream_id);
EXPECT_FALSE(client.has_seen_mojo_connection_error());
EXPECT_TRUE(client.has_received_fin_for(stream_id));
EXPECT_TRUE(client.stream_is_closed_as_incoming_stream(stream_id));
}
class WebTransportWithCustomCertificateTest : public WebTransportTest {
public:
WebTransportWithCustomCertificateTest()
: WebTransportTest(CreateProofSource()) {
auto helper = std::make_unique<TestConnectionHelper>();
// Set clock to a time in which quic-short-lived.pem is valid
// (2020-06-05T20:35:00.000Z).
helper->clock().set_wall_now(
quic::QuicWallTime::FromUNIXSeconds(1591389300));
mutable_network_context()
.url_request_context()
->quic_context()
->SetHelperForTesting(std::move(helper));
}
~WebTransportWithCustomCertificateTest() override = default;
static std::unique_ptr<quic::ProofSource> CreateProofSource() {
auto proof_source = std::make_unique<net::ProofSourceChromium>();
base::FilePath certs_dir = net::GetTestCertsDirectory();
EXPECT_TRUE(proof_source->Initialize(
certs_dir.AppendASCII("quic-short-lived.pem"),
certs_dir.AppendASCII("quic-leaf-cert.key"),
certs_dir.AppendASCII("quic-leaf-cert.key.sct")));
return proof_source;
}
};
INSTANTIATE_TEST_SUITE_P(WebTransportWithCustomCertificateTests,
WebTransportWithCustomCertificateTest,
testing::Values("quic-transport", "https"),
PrintStringPiece());
TEST_P(WebTransportWithCustomCertificateTest, WithValidFingerprint) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
auto fingerprint = mojom::WebTransportCertificateFingerprint::New(
"sha-256",
"ED:3D:D7:C3:67:10:94:68:D1:DC:D1:26:5C:B2:74:D7:1C:"
"A2:63:3E:94:94:C0:84:39:D6:64:FA:08:B9:77:37");
std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints;
fingerprints.push_back(std::move(fingerprint));
CreateWebTransport(GetURL("/echo"), origin(), std::move(fingerprints),
std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_TRUE(test_handshake_client.has_seen_connection_establishment());
EXPECT_FALSE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
EXPECT_EQ(1u, network_context().NumOpenWebTransports());
}
TEST_P(WebTransportWithCustomCertificateTest, WithInvalidFingerprint) {
base::RunLoop run_loop_for_handshake;
mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client;
TestHandshakeClient test_handshake_client(
handshake_client.InitWithNewPipeAndPassReceiver(),
run_loop_for_handshake.QuitClosure());
auto fingerprint = network::mojom::WebTransportCertificateFingerprint::New(
"sha-256",
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:"
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00");
std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints;
fingerprints.push_back(std::move(fingerprint));
CreateWebTransport(GetURL("/echo"), origin(), std::move(fingerprints),
std::move(handshake_client));
run_loop_for_handshake.Run();
EXPECT_FALSE(test_handshake_client.has_seen_connection_establishment());
EXPECT_TRUE(test_handshake_client.has_seen_handshake_failure());
EXPECT_FALSE(test_handshake_client.has_seen_mojo_connection_error());
EXPECT_EQ(0u, network_context().NumOpenWebTransports());
}
} // namespace
} // namespace network