| // Copyright 2026 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 "quiche/quic/tools/web_transport_only_client.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/quic/core/crypto/quic_crypto_server_config.h" |
| #include "quiche/quic/core/crypto/quic_random.h" |
| #include "quiche/quic/core/deterministic_connection_id_generator.h" |
| #include "quiche/quic/core/http/web_transport_only_dispatcher.h" |
| #include "quiche/quic/core/http/web_transport_only_server_session.h" |
| #include "quiche/quic/core/io/quic_default_event_loop.h" |
| #include "quiche/quic/core/io/quic_event_loop.h" |
| #include "quiche/quic/core/io/quic_server_io_harness.h" |
| #include "quiche/quic/core/io/socket.h" |
| #include "quiche/quic/core/quic_config.h" |
| #include "quiche/quic/core/quic_connection_id.h" |
| #include "quiche/quic/core/quic_default_clock.h" |
| #include "quiche/quic/core/quic_default_connection_helper.h" |
| #include "quiche/quic/core/quic_version_manager.h" |
| #include "quiche/quic/core/quic_versions.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/crypto_test_utils.h" |
| #include "quiche/quic/tools/quic_event_loop_tools.h" |
| #include "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/quiche_ip_address.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| #include "quiche/web_transport/web_transport.h" |
| |
| namespace quic::test { |
| namespace { |
| |
| class TestSessionVisitor : public webtransport::SessionVisitor { |
| public: |
| explicit TestSessionVisitor(bool* established, bool* closed) |
| : established_(*established), closed_(*closed) {} |
| |
| void OnSessionReady() override { established_ = true; } |
| void OnSessionClosed(webtransport::SessionErrorCode /*error_code*/, |
| const std::string& /*error_message*/) override { |
| closed_ = true; |
| } |
| |
| void OnIncomingBidirectionalStreamAvailable() override {} |
| void OnIncomingUnidirectionalStreamAvailable() override {} |
| void OnDatagramReceived(absl::string_view /*datagram*/) override {} |
| void OnCanCreateNewOutgoingBidirectionalStream() override {} |
| void OnCanCreateNewOutgoingUnidirectionalStream() override {} |
| |
| private: |
| bool& established_; |
| bool& closed_; |
| }; |
| |
| class WebTransportOnlyTestServer { |
| public: |
| explicit WebTransportOnlyTestServer(bool* server_established, |
| bool* server_closed) |
| : crypto_config_("secret", QuicRandom::GetInstance(), |
| crypto_test_utils::ProofSourceForTesting(), |
| KeyExchangeSource::Default()), |
| version_manager_(CurrentSupportedVersionsWithTls()), |
| connection_id_generator_(kQuicDefaultConnectionIdLength), |
| event_loop_(GetDefaultEventLoop()->Create(QuicDefaultClock::Get())), |
| dispatcher_(&config_, &crypto_config_, &version_manager_, |
| std::make_unique<QuicDefaultConnectionHelper>(), |
| std::make_unique<QuicSimpleCryptoServerStreamHelper>(), |
| event_loop_->CreateAlarmFactory(), |
| kQuicDefaultConnectionIdLength, connection_id_generator_) { |
| dispatcher_.parameters().handler_factory = |
| [server_established, server_closed]( |
| webtransport::Session*, const WebTransportIncomingRequestDetails&) |
| -> absl::StatusOr<WebTransportConnectResponse> { |
| WebTransportConnectResponse response; |
| response.visitor = std::make_unique<TestSessionVisitor>( |
| server_established, server_closed); |
| return response; |
| }; |
| dispatcher_.parameters().subprotocol_callback = |
| [](absl::Span<const absl::string_view> subprotocols) { |
| return subprotocols.empty() ? -1 : 0; |
| }; |
| |
| QuicSocketAddress address(quiche::QuicheIpAddress::Loopback6(), /*port=*/0); |
| absl::StatusOr<OwnedSocketFd> fd = CreateAndBindServerSocket(address); |
| QUICHE_CHECK(fd.ok()); |
| fd_ = std::move(*fd); |
| |
| absl::StatusOr<std::unique_ptr<QuicServerIoHarness>> io = |
| QuicServerIoHarness::Create(event_loop_.get(), &dispatcher_, *fd_); |
| QUICHE_CHECK(io.ok()); |
| io_ = std::move(*io); |
| io_->InitializeWriter(); |
| } |
| |
| QuicSocketAddress server_address() const { return io_->local_address(); } |
| QuicEventLoop* event_loop() { return event_loop_.get(); } |
| |
| private: |
| QuicConfig config_; |
| QuicCryptoServerConfig crypto_config_; |
| QuicVersionManager version_manager_; |
| DeterministicConnectionIdGenerator connection_id_generator_; |
| std::unique_ptr<QuicEventLoop> event_loop_; |
| WebTransportOnlyDispatcher dispatcher_; |
| OwnedSocketFd fd_; |
| std::unique_ptr<QuicServerIoHarness> io_; |
| }; |
| |
| class WebTransportOnlyClientTest : public QuicTest {}; |
| |
| TEST_F(WebTransportOnlyClientTest, SuccessfulConnection) { |
| bool server_established = false; |
| bool server_closed = false; |
| WebTransportOnlyTestServer server(&server_established, &server_closed); |
| |
| bool client_established = false; |
| bool client_closed = false; |
| |
| auto client = std::make_unique<WebTransportOnlyClient>( |
| server.server_address(), QuicServerId("test.example.com", 443), |
| CurrentSupportedVersionsWithTls(), QuicConfig(), server.event_loop(), |
| /*network_helper=*/nullptr, crypto_test_utils::ProofVerifierForTesting(), |
| /*session_cache=*/nullptr); |
| |
| // Note: the `ConnectSync` call below implicitly drives both the client and |
| // the server, since both use the same event loop. |
| absl::Status status = |
| client->ConnectSync("/test", [&](webtransport::Session*) { |
| return std::make_unique<TestSessionVisitor>(&client_established, |
| &client_closed); |
| }); |
| QUICHE_ASSERT_OK(status); |
| |
| bool handshake_success = ProcessEventsUntil(server.event_loop(), [&] { |
| return client_established && server_established; |
| }); |
| EXPECT_TRUE(handshake_success); |
| EXPECT_FALSE(client_closed); |
| EXPECT_FALSE(server_closed); |
| |
| client->Disconnect(); |
| EXPECT_TRUE(client_closed); |
| |
| bool server_close_success = |
| ProcessEventsUntil(server.event_loop(), [&] { return server_closed; }); |
| EXPECT_TRUE(server_close_success); |
| EXPECT_TRUE(server_closed); |
| } |
| |
| } // namespace |
| } // namespace quic::test |