| // 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/base/casts.h" |
| #include "absl/base/nullability.h" |
| #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/proof_verifier.h" |
| #include "quiche/quic/core/crypto/quic_crypto_client_config.h" |
| #include "quiche/quic/core/http/quic_connection_migration_manager.h" |
| #include "quiche/quic/core/http/quic_spdy_client_session.h" |
| #include "quiche/quic/core/http/quic_spdy_client_stream.h" |
| #include "quiche/quic/core/http/quic_spdy_session.h" |
| #include "quiche/quic/core/http/web_transport_http3.h" |
| #include "quiche/quic/core/io/quic_event_loop.h" |
| #include "quiche/quic/core/quic_config.h" |
| #include "quiche/quic/core/quic_connection.h" |
| #include "quiche/quic/core/quic_default_connection_helper.h" |
| #include "quiche/quic/core/quic_packet_writer.h" |
| #include "quiche/quic/core/quic_path_validator.h" |
| #include "quiche/quic/core/quic_server_id.h" |
| #include "quiche/quic/core/quic_session.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_versions.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/tools/quic_client_base.h" |
| #include "quiche/quic/tools/quic_client_default_network_helper.h" |
| #include "quiche/quic/tools/quic_event_loop_tools.h" |
| #include "quiche/common/http/http_header_block.h" |
| #include "quiche/web_transport/web_transport_headers.h" |
| |
| namespace quic { |
| |
| WebTransportOnlyClient::Session::Session( |
| const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, |
| QuicConnection* connection, QuicClientBase::NetworkHelper* network_helper, |
| const QuicServerId& server_id, QuicCryptoClientConfig* crypto_config) |
| : QuicSpdyClientSession( |
| config, supported_versions, connection, |
| /*visitor=*/nullptr, /*writer=*/nullptr, |
| /*migration_helper=*/nullptr, |
| QuicConnectionMigrationConfig{.allow_server_preferred_address = |
| false}, |
| server_id, crypto_config, QuicPriorityType::kWebTransport), |
| network_helper_(network_helper) {} |
| |
| WebTransportHttp3VersionSet |
| WebTransportOnlyClient::Session::LocallySupportedWebTransportVersions() const { |
| return kDefaultSupportedWebTransportVersions; |
| } |
| |
| HttpDatagramSupport |
| WebTransportOnlyClient::Session::LocalHttpDatagramSupport() { |
| return HttpDatagramSupport::kRfcAndDraft04; |
| } |
| |
| // TODO(vasilvv): the connection migration code below comes from |
| // QuicSimpleClientSession; should it live in QuicClientBase? |
| void WebTransportOnlyClient::Session::CreateContextForMultiPortPath( |
| std::unique_ptr<MultiPortPathContextObserver> context_observer) { |
| if (!network_helper_ || connection()->multi_port_stats() == nullptr) { |
| return; |
| } |
| QuicSocketAddress self_address = connection()->self_address(); |
| QuicSocketAddress server_address = connection()->peer_address(); |
| if (!network_helper_->CreateUDPSocketAndBind( |
| server_address, self_address.host(), self_address.port() + 1)) { |
| return; |
| } |
| QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); |
| if (writer == nullptr) { |
| return; |
| } |
| context_observer->OnMultiPortPathContextAvailable( |
| std::make_unique<PathMigrationContext>( |
| std::unique_ptr<QuicPacketWriter>(writer), |
| network_helper_->GetLatestClientAddress(), peer_address())); |
| } |
| |
| void WebTransportOnlyClient::Session::MigrateToMultiPortPath( |
| std::unique_ptr<QuicPathValidationContext> context) { |
| auto* path_migration_context = |
| static_cast<PathMigrationContext*>(context.get()); |
| MigratePath(path_migration_context->self_address(), |
| path_migration_context->peer_address(), |
| path_migration_context->ReleaseWriter(), /*owns_writer=*/true); |
| } |
| |
| WebTransportOnlyClient::WebTransportOnlyClient( |
| QuicSocketAddress server_address, const QuicServerId& server_id, |
| const ParsedQuicVersionVector& supported_versions, const QuicConfig& config, |
| QuicEventLoop* event_loop, |
| std::unique_ptr<QuicClientDefaultNetworkHelper> absl_nullable |
| network_helper, |
| std::unique_ptr<ProofVerifier> absl_nonnull proof_verifier, |
| std::unique_ptr<SessionCache> absl_nullable session_cache) |
| : QuicClientBase(server_id, supported_versions, config, |
| new QuicDefaultConnectionHelper(), |
| event_loop->CreateAlarmFactory().release(), |
| network_helper != nullptr |
| ? std::move(network_helper) |
| : std::make_unique<QuicClientDefaultNetworkHelper>( |
| event_loop, this), |
| std::move(proof_verifier), std::move(session_cache)) { |
| set_server_address(server_address); |
| } |
| |
| WebTransportOnlyClient::~WebTransportOnlyClient() { ResetSession(); } |
| |
| absl::Status WebTransportOnlyClient::ConnectSync( |
| std::string path, VisitorFactory visitor_factory, |
| absl::Span<const std::string> subprotocols, |
| const quiche::HttpHeaderBlock& extra_headers) { |
| if (!Initialize()) { |
| return absl::InternalError("Failed to initialize the client"); |
| } |
| if (!QuicClientBase::Connect()) { |
| return absl::InternalError("Failed to establish a QUIC connection"); |
| } |
| bool settings_received = |
| ProcessEventsUntil(default_network_helper()->event_loop(), |
| [&] { return client_session()->settings_received(); }); |
| if (!settings_received) { |
| return absl::DeadlineExceededError( |
| "Timed out while waiting for HTTP/3 SETTINGS"); |
| } |
| if (!client_session()->SupportsWebTransport()) { |
| return absl::FailedPreconditionError( |
| "QUIC server does not support WebTransport"); |
| } |
| auto* stream = absl::down_cast<QuicSpdyClientStream*>( |
| client_session()->CreateOutgoingBidirectionalStream()); |
| if (!stream) { |
| return absl::InternalError("Failed to create a CONNECT request stream"); |
| } |
| |
| quiche::HttpHeaderBlock headers = extra_headers.Clone(); |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_id().host(); |
| headers[":path"] = std::move(path); |
| headers[":method"] = "CONNECT"; |
| headers[":protocol"] = "webtransport"; |
| if (!subprotocols.empty()) { |
| absl::StatusOr<std::string> serialized = |
| webtransport::SerializeSubprotocolRequestHeader(subprotocols); |
| if (!serialized.ok()) { |
| return absl::InvalidArgumentError("Invalid subprotocol name supplied"); |
| } |
| headers["wt-available-protocols"] = *serialized; |
| } |
| stream->SendRequest(std::move(headers), "", false); |
| |
| WebTransportHttp3* web_transport = stream->web_transport(); |
| if (!web_transport) { |
| return absl::InternalError( |
| "Failed to associate a WebTransport session with an HTTP request"); |
| } |
| web_transport->SetVisitor(std::move(visitor_factory)(web_transport)); |
| return absl::OkStatus(); |
| } |
| |
| QuicClientDefaultNetworkHelper* |
| WebTransportOnlyClient::default_network_helper() { |
| return absl::down_cast<QuicClientDefaultNetworkHelper*>(network_helper()); |
| } |
| |
| WebTransportOnlyClient::Session* WebTransportOnlyClient::client_session() { |
| return absl::down_cast<Session*>(QuicClientBase::session()); |
| } |
| |
| void WebTransportOnlyClient::InitializeSession() { |
| client_session()->Initialize(); |
| client_session()->CryptoConnect(); |
| } |
| |
| bool WebTransportOnlyClient::EarlyDataAccepted() { |
| return client_session()->EarlyDataAccepted(); |
| } |
| |
| bool WebTransportOnlyClient::ReceivedInchoateReject() { |
| return client_session()->ReceivedInchoateReject(); |
| } |
| |
| int WebTransportOnlyClient::GetNumSentClientHellosFromSession() { |
| return client_session()->GetNumSentClientHellos(); |
| } |
| |
| int WebTransportOnlyClient::GetNumReceivedServerConfigUpdatesFromSession() { |
| return client_session()->GetNumReceivedServerConfigUpdates(); |
| } |
| |
| std::unique_ptr<QuicSession> WebTransportOnlyClient::CreateQuicClientSession( |
| const ParsedQuicVersionVector& supported_versions, |
| QuicConnection* connection) { |
| return std::make_unique<Session>(*config(), supported_versions, connection, |
| network_helper(), server_id(), |
| crypto_config()); |
| } |
| |
| bool WebTransportOnlyClient::HasActiveRequests() { |
| return client_session()->HasActiveRequestStreams(); |
| } |
| |
| } // namespace quic |