| // Copyright 2019 The Chromium Authors |
| // 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/containers/span.h" |
| #include "base/files/file_util.h" |
| #include "base/rand_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.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/quic_context.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/third_party/quiche/src/quiche/quic/core/crypto/proof_source_x509.h" |
| #include "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.h" |
| #include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_backend.h" |
| #include "net/tools/quic/quic_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/cpp/features.h" |
| #include "services/network/public/mojom/client_security_state.mojom.h" |
| #include "services/network/public/mojom/ip_address_space.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/test/client_security_state_builder.h" |
| #include "services/network/test/fake_test_cert_verifier_params_factory.h" |
| #include "services/network/test/test_url_loader_network_observer.h" |
| #include "services/network/url_request_context_builder_mojo.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/boringssl/src/pki/pem.h" |
| |
| namespace network { |
| namespace { |
| |
| class HostResolverFactory final : public net::HostResolver::Factory { |
| public: |
| explicit HostResolverFactory(std::unique_ptr<net::HostResolver> resolver) |
| : resolver_(std::move(resolver)) {} |
| |
| std::unique_ptr<net::HostResolver> CreateResolver( |
| net::HostResolverManager* manager, |
| std::string_view host_mapping_rules, |
| bool enable_caching, |
| bool enable_stale) override { |
| DCHECK(resolver_); |
| return std::move(resolver_); |
| } |
| |
| // See HostResolver::CreateStandaloneResolver. |
| std::unique_ptr<net::HostResolver> CreateStandaloneResolver( |
| net::NetLog* net_log, |
| const net::HostResolver::ManagerOptions& options, |
| std::string_view host_mapping_rules, |
| bool enable_caching, |
| bool enable_stale) override { |
| NOTREACHED(); |
| } |
| |
| private: |
| std::unique_ptr<net::HostResolver> resolver_; |
| }; |
| |
| // 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(); |
| } |
| quiche::QuicheBufferAllocator* GetStreamSendBufferAllocator() override { |
| return &allocator_; |
| } |
| |
| TestWallClock& clock() { return clock_; } |
| |
| private: |
| TestWallClock clock_; |
| quiche::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) { |
| std::string buffer(1024, '\0'); |
| size_t actually_read_bytes = 0; |
| MojoResult result = readable->ReadData(MOJO_READ_DATA_FLAG_NONE, |
| base::as_writable_byte_span(buffer), |
| actually_read_bytes); |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->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(std::string_view(buffer).substr(0, actually_read_bytes)); |
| } |
| } |
| |
| 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 OnBeforeConnect(const net::IPEndPoint& server_address) override {} |
| |
| void OnConnectionEstablished( |
| mojo::PendingRemote<mojom::WebTransport> transport, |
| mojo::PendingReceiver<mojom::WebTransportClient> client_receiver, |
| const scoped_refptr<net::HttpResponseHeaders>& response_headers, |
| const std::optional<std::string>& selected_application_protocol, |
| mojom::WebTransportStatsPtr initial_stats) override { |
| transport_ = std::move(transport); |
| client_receiver_ = std::move(client_receiver); |
| has_seen_connection_establishment_ = true; |
| receiver_.reset(); |
| selected_application_protocol_ = selected_application_protocol; |
| std::move(callback_).Run(); |
| } |
| |
| void OnHandshakeFailed( |
| const std::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(); |
| } |
| |
| void CloseReceiver() { receiver_.reset(); } |
| |
| 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_; |
| } |
| std::optional<net::WebTransportError> handshake_error() const { |
| return handshake_error_; |
| } |
| std::optional<std::string> selected_application_protocol() const { |
| return selected_application_protocol_; |
| } |
| |
| 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; |
| std::optional<net::WebTransportError> handshake_error_; |
| std::optional<std::string> selected_application_protocol_; |
| }; |
| |
| 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_.emplace_back(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 OnOutgoingStreamClosed(uint32_t stream_id) override { |
| closed_outgoing_streams_.insert(stream_id); |
| if (quit_closure_for_outgoing_stream_closure_) { |
| std::move(quit_closure_for_outgoing_stream_closure_).Run(); |
| } |
| } |
| void OnReceivedResetStream(uint32_t stream_id, uint32_t) override {} |
| void OnReceivedStopSending(uint32_t stream_id, uint32_t) override {} |
| void OnClosed(mojom::WebTransportCloseInfoPtr close_info, |
| mojom::WebTransportStatsPtr final_stats) override {} |
| |
| 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(); |
| } |
| } |
| |
| void WaitUntilOutgoingStreamIsClosed(uint32_t stream_id) { |
| while (!stream_is_closed_as_outgoing_stream(stream_id)) { |
| base::RunLoop run_loop; |
| |
| quit_closure_for_outgoing_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 stream_is_closed_as_outgoing_stream(uint32_t stream_id) { |
| return closed_outgoing_streams_.find(stream_id) != |
| closed_outgoing_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_; |
| base::OnceClosure quit_closure_for_outgoing_stream_closure_; |
| |
| std::vector<std::vector<uint8_t>> received_datagrams_; |
| std::map<uint32_t, bool> closed_incoming_streams_; |
| std::set<uint32_t> closed_outgoing_streams_; |
| bool has_seen_mojo_connection_error_ = false; |
| }; |
| |
| quic::ParsedQuicVersion GetTestVersion() { |
| quic::ParsedQuicVersion version = quic::ParsedQuicVersion::RFCv1(); |
| quic::QuicEnableVersion(version); |
| return version; |
| } |
| |
| class WebTransportTest : public testing::TestWithParam<std::string_view> { |
| 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()) { |
| auto host_resolver = std::make_unique<net::MockHostResolver>(); |
| host_resolver->rules()->AddRule("test.example.com", "127.0.0.1"); |
| network_service_->set_host_resolver_factory_for_testing( |
| std::make_unique<HostResolverFactory>(std::move(host_resolver))); |
| network_context_ = NetworkContext::CreateForTesting( |
| network_service_.get(), |
| network_context_remote_.BindNewPipeAndPassReceiver(), |
| CreateNetworkContextParams(), |
| base::BindOnce([](net::URLRequestContextBuilder* builder) { |
| auto cert_verifier = std::make_unique<net::MockCertVerifier>(); |
| cert_verifier->set_default_result(net::OK); |
| builder->SetCertVerifier(std::move(cert_verifier)); |
| })); |
| 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(quiche::QuicheIpAddress::Any6(), /*port=*/0)))); |
| |
| auto* quic_context = |
| network_context_->url_request_context()->quic_context(); |
| quic_context->params()->supported_versions.push_back(version_); |
| quic_context->params()->webtransport_developer_mode = true; |
| } |
| ~WebTransportTest() override = default; |
| |
| void CreateWebTransport( |
| const GURL& url, |
| const url::Origin& origin, |
| const net::NetworkAnonymizationKey& key, |
| std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints, |
| const std::vector<std::string>& application_protocols, |
| mojo::PendingRemote<mojom::WebTransportHandshakeClient> handshake_client, |
| mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver> |
| url_loader_network_observer, |
| mojom::ClientSecurityStatePtr client_security_state) { |
| network_context_->CreateWebTransport( |
| url, origin, key, std::move(fingerprints), application_protocols, |
| std::move(handshake_client), std::move(url_loader_network_observer), |
| std::move(client_security_state)); |
| } |
| |
| void CreateWebTransport( |
| const GURL& url, |
| const url::Origin& origin, |
| const net::NetworkAnonymizationKey& key, |
| std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints, |
| const std::vector<std::string>& application_protocols, |
| mojo::PendingRemote<mojom::WebTransportHandshakeClient> |
| handshake_client) { |
| CreateWebTransport(url, origin, key, std::move(fingerprints), |
| application_protocols, std::move(handshake_client), |
| url_loader_network_observer_.Bind(), |
| mojom::ClientSecurityState::New()); |
| } |
| |
| void CreateWebTransport( |
| const GURL& url, |
| const url::Origin& origin, |
| const net::NetworkAnonymizationKey& key, |
| std::vector<mojom::WebTransportCertificateFingerprintPtr> fingerprints, |
| mojo::PendingRemote<mojom::WebTransportHandshakeClient> |
| handshake_client) { |
| 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::NetworkAnonymizationKey(), {}, |
| 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::NetworkAnonymizationKey(), |
| std::move(fingerprints), std::move(handshake_client)); |
| } |
| |
| GURL GetURL(std::string_view suffix) { |
| int port = http_server_->server_address().port(); |
| return GURL(base::StrCat( |
| {"https://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::RecordingNetLogObserver& net_log_observer() { return net_log_observer_; } |
| |
| void RunPendingTasks() { |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| private: |
| quic::test::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::RecordingNetLogObserver net_log_observer_; |
| |
| std::unique_ptr<NetworkContext> network_context_; |
| |
| quic::test::QuicTestBackend backend_; |
| std::unique_ptr<net::QuicSimpleServer> http_server_; |
| |
| TestURLLoaderNetworkObserver url_loader_network_observer_; |
| }; |
| |
| TEST_F(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(test_handshake_client.selected_application_protocol(), |
| std::nullopt); |
| EXPECT_EQ(1u, network_context().NumOpenWebTransports()); |
| } |
| |
| TEST_F(WebTransportTest, ConnectWithCustomProtocol) { |
| 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("/selected-subprotocol"), origin(), |
| net::NetworkAnonymizationKey(), {}, |
| {"first", "second", "third"}, 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(test_handshake_client.selected_application_protocol(), "first"); |
| EXPECT_EQ(1u, network_context().NumOpenWebTransports()); |
| } |
| |
| TEST_F(WebTransportTest, ConnectHandles404) { |
| 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_F(WebTransportTest, ConnectToBannedPort) { |
| 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); |
| } |
| |
| class LNAPermissionURLLoaderNetworkObserver |
| : public TestURLLoaderNetworkObserver { |
| public: |
| void OnLocalNetworkAccessPermissionRequired( |
| OnLocalNetworkAccessPermissionRequiredCallback callback) override { |
| std::move(callback).Run(lna_permission_granted); |
| } |
| |
| bool lna_permission_granted = false; |
| }; |
| |
| TEST_F(WebTransportTest, ConnectLNAPermissionDenied) { |
| base::test::ScopedFeatureList scoped_features( |
| features::kLocalNetworkAccessChecksWebTransport); |
| LNAPermissionURLLoaderNetworkObserver url_loader_network_observer; |
| |
| 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(), net::NetworkAnonymizationKey(), |
| /*fingerprints=*/{}, |
| /*application_protocols=*/{}, std::move(handshake_client), |
| url_loader_network_observer.Bind(), |
| ClientSecurityStateBuilder() |
| .WithIsSecureContext(true) |
| .WithPrivateNetworkRequestPolicy( |
| mojom::PrivateNetworkRequestPolicy::kPermissionBlock) |
| .WithIPAddressSpace(mojom::IPAddressSpace::kPublic) |
| .Build()); |
| |
| 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_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS); |
| } |
| |
| TEST_F(WebTransportTest, ConnectLNAPermissionGranted) { |
| base::test::ScopedFeatureList scoped_features( |
| features::kLocalNetworkAccessChecksWebTransport); |
| |
| LNAPermissionURLLoaderNetworkObserver url_loader_network_observer; |
| url_loader_network_observer.lna_permission_granted = true; |
| |
| 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(), net::NetworkAnonymizationKey(), |
| /*fingerprints=*/{}, |
| /*application_protocols=*/{}, std::move(handshake_client), |
| url_loader_network_observer.Bind(), |
| ClientSecurityStateBuilder() |
| .WithIsSecureContext(true) |
| .WithPrivateNetworkRequestPolicy( |
| mojom::PrivateNetworkRequestPolicy::kPermissionBlock) |
| .WithIPAddressSpace(mojom::IPAddressSpace::kPublic) |
| .Build()); |
| |
| 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(test_handshake_client.selected_application_protocol(), |
| std::nullopt); |
| EXPECT_EQ(1u, network_context().NumOpenWebTransports()); |
| } |
| |
| TEST_F(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::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_F(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::span(data), |
| base::BindLambdaForTesting([&](bool r) { |
| result = r; |
| run_loop_for_datagram.Quit(); |
| })); |
| run_loop_for_datagram.Run(); |
| EXPECT_FALSE(result); |
| } |
| |
| TEST_F(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)); |
| size_t actually_written_bytes = 0; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| writable_for_outgoing->WriteData( |
| base::byte_span_from_cstring("hello"), |
| MOJO_WRITE_DATA_FLAG_NONE, actually_written_bytes)); |
| |
| 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); |
| |
| transport_remote->SendFin(stream_id); |
| writable_for_outgoing.reset(); |
| |
| client.WaitUntilOutgoingStreamIsClosed(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_observer().GetEntriesWithType( |
| net::NetLogEventType::QUIC_SESSION_RST_STREAM_FRAME_SENT); |
| EXPECT_EQ(0u, resets_sent.size()); |
| } |
| |
| TEST_F(WebTransportTest, DeleteClientWithStreamsOpen) { |
| 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()); |
| |
| constexpr int kNumStreams = 10; |
| auto writable_for_outgoing = |
| std::make_unique<mojo::ScopedDataPipeProducerHandle[]>(kNumStreams); |
| for (int i = 0; i < kNumStreams; i++) { |
| const MojoCreateDataPipeOptions options = { |
| sizeof(options), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, 4 * 1024}; |
| mojo::ScopedDataPipeConsumerHandle readable_for_outgoing; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| mojo::CreateDataPipe(&options, writable_for_outgoing[i], |
| readable_for_outgoing)); |
| base::RunLoop run_loop_for_stream_creation; |
| bool stream_created; |
| transport_remote->CreateStream( |
| std::move(readable_for_outgoing), |
| /*writable=*/{}, |
| base::BindLambdaForTesting([&](bool b, uint32_t /*id*/) { |
| stream_created = b; |
| run_loop_for_stream_creation.Quit(); |
| })); |
| run_loop_for_stream_creation.Run(); |
| ASSERT_TRUE(stream_created); |
| } |
| |
| // Keep the streams open so that they are closed via destructor. |
| } |
| |
| // crbug.com/1129847: disabled because it is flaky. |
| TEST_F(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)); |
| size_t actually_written_bytes = 0; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| writable_for_outgoing->WriteData( |
| base::byte_span_from_cstring("hello"), |
| MOJO_WRITE_DATA_FLAG_NONE, actually_written_bytes)); |
| |
| 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)); |
| } |
| |
| TEST_F(WebTransportTest, Stats) { |
| 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(); |
| 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()); |
| |
| base::test::TestFuture<mojom::WebTransportStatsPtr> future; |
| transport_remote->GetStats(future.GetCallback()); |
| mojom::WebTransportStatsPtr stats = future.Take(); |
| ASSERT_FALSE(stats.is_null()); |
| EXPECT_GT(stats->min_rtt, base::Microseconds(0)); |
| EXPECT_LT(stats->min_rtt, base::Seconds(5)); |
| } |
| |
| // Test that Dispose() handles properly when transport exists but session is |
| // null. This validates the transport_->session() check in Dispose(). |
| TEST_F(WebTransportTest, DisposeWithNullSession) { |
| 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)); |
| |
| RunPendingTasks(); |
| |
| // Close the handshake receiver immediately before session establishment. |
| // This simulates scenarios like: |
| // - Network context shutdown during early connection phase. |
| // - Tab close before QUIC session is fully established. |
| // - Process termination during handshake. |
| test_handshake_client.CloseReceiver(); |
| |
| // Should see no connection establishment due to early receiver closure. |
| EXPECT_FALSE(test_handshake_client.has_seen_connection_establishment()); |
| |
| // This is where Dispose() gets called with transport_ != null. |
| RunPendingTasks(); |
| |
| // Verify connection closed properly with clean shutdown. |
| EXPECT_EQ(0u, network_context().NumOpenWebTransports()); |
| } |
| |
| // Test that tab close scenario handles cleanup properly and shuts down |
| // cleanly. |
| TEST_F(WebTransportTest, TabCloseCleanShutdown) { |
| 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_EQ(1u, network_context().NumOpenWebTransports()); |
| |
| mojo::Remote<mojom::WebTransport> transport_remote( |
| test_handshake_client.PassTransport()); |
| TestClient client(test_handshake_client.PassClientReceiver()); |
| |
| // Simulate tab close by resetting the transport remote (disconnects the |
| // pipe). This triggers the disconnect handler which calls Dispose() directly. |
| transport_remote.reset(); |
| |
| // Wait for mojo connection error which should happen due to pipe disconnect. |
| client.WaitUntilMojoConnectionError(); |
| EXPECT_TRUE(client.has_seen_mojo_connection_error()); |
| |
| RunPendingTasks(); |
| |
| // Verify connection closed properly with clean shutdown. |
| EXPECT_EQ(0u, network_context().NumOpenWebTransports()); |
| } |
| |
| // This test verifies that calling WebTransport::Close() explicitly (e.g., |
| // user-initiated disconnect) and then performing internal cleanup through |
| // Dispose() does not result in multiple close frames being sent or undefined |
| // behavior. |
| TEST_F(WebTransportTest, ExplicitConnectionClose) { |
| 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_EQ(1u, network_context().NumOpenWebTransports()); |
| |
| mojo::Remote<mojom::WebTransport> transport_remote( |
| test_handshake_client.PassTransport()); |
| TestClient client(test_handshake_client.PassClientReceiver()); |
| |
| // Simulate explicit connection close. |
| auto close_info = mojom::WebTransportCloseInfo::New(); |
| close_info->code = 1000; |
| close_info->reason = "User exit"; |
| transport_remote->Close(std::move(close_info)); |
| |
| base::RunLoop run_loop_for_close; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop_for_close.QuitClosure(), base::Milliseconds(100)); |
| run_loop_for_close.Run(); |
| |
| // The torn_down_ flag should prevent double Close() when Dispose() is called. |
| EXPECT_EQ(0u, network_context().NumOpenWebTransports()); |
| } |
| |
| 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() { |
| base::FilePath certs_dir = net::GetTestCertsDirectory(); |
| base::FilePath cert_path = certs_dir.AppendASCII("quic-short-lived.pem"); |
| base::FilePath key_path = certs_dir.AppendASCII("quic-ecdsa-leaf.key"); |
| |
| std::string cert_pem, key_raw; |
| if (!base::ReadFileToString(cert_path, &cert_pem)) { |
| ADD_FAILURE() << "Failed to load the certificate from " << cert_path; |
| return nullptr; |
| } |
| if (!base::ReadFileToString(key_path, &key_raw)) { |
| ADD_FAILURE() << "Failed to load the private key from " << key_path; |
| return nullptr; |
| } |
| |
| bssl::PEMTokenizer pem_tokenizer(cert_pem, {"CERTIFICATE"}); |
| if (!pem_tokenizer.GetNext()) { |
| ADD_FAILURE() << "No certificates found in " << cert_path; |
| return nullptr; |
| } |
| auto chain = |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain>( |
| new quic::ProofSource::Chain( |
| std::vector<std::string>{pem_tokenizer.data()})); |
| std::unique_ptr<quic::CertificatePrivateKey> key = |
| quic::CertificatePrivateKey::LoadFromDer(key_raw); |
| if (!key) { |
| ADD_FAILURE() << "Failed to parse the key file " << key_path; |
| return nullptr; |
| } |
| |
| return quic::ProofSourceX509::Create(std::move(chain), std::move(*key)); |
| } |
| }; |
| |
| TEST_F(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", |
| "6E:8E:7B:43:2A:30:B2:A8:5F:59:56:85:64:C2:48:E9:35:" |
| "CB:63:B0:7A:E9:F5:CA:3C:35:6F:CB:CC:E8:8D:1B"); |
| 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_F(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 |