|  | // Copyright 2023 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/moqt/tools/moqt_client.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "absl/status/status.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "quiche/quic/core/crypto/proof_verifier.h" | 
|  | #include "quiche/quic/core/http/quic_spdy_client_stream.h" | 
|  | #include "quiche/quic/core/http/web_transport_http3.h" | 
|  | #include "quiche/quic/core/io/quic_event_loop.h" | 
|  | #include "quiche/quic/core/quic_server_id.h" | 
|  | #include "quiche/quic/core/quic_types.h" | 
|  | #include "quiche/quic/moqt/moqt_messages.h" | 
|  | #include "quiche/quic/moqt/moqt_session.h" | 
|  | #include "quiche/quic/platform/api/quic_socket_address.h" | 
|  | #include "quiche/quic/tools/quic_default_client.h" | 
|  | #include "quiche/quic/tools/quic_event_loop_tools.h" | 
|  | #include "quiche/quic/tools/quic_name_lookup.h" | 
|  | #include "quiche/common/http/http_header_block.h" | 
|  | #include "quiche/common/platform/api/quiche_logging.h" | 
|  |  | 
|  | namespace moqt { | 
|  |  | 
|  | MoqtClient::MoqtClient(quic::QuicSocketAddress peer_address, | 
|  | const quic::QuicServerId& server_id, | 
|  | std::unique_ptr<quic::ProofVerifier> proof_verifier, | 
|  | quic::QuicEventLoop* event_loop) | 
|  | : spdy_client_(peer_address, server_id, GetMoqtSupportedQuicVersions(), | 
|  | event_loop, std::move(proof_verifier)) { | 
|  | spdy_client_.set_enable_web_transport(true); | 
|  | } | 
|  |  | 
|  | void MoqtClient::Connect(std::string path, MoqtSessionCallbacks callbacks) { | 
|  | absl::Status status = ConnectInner(std::move(path), callbacks); | 
|  | if (!status.ok()) { | 
|  | std::move(callbacks.session_terminated_callback)(status.message()); | 
|  | } | 
|  | } | 
|  |  | 
|  | absl::Status MoqtClient::ConnectInner(std::string path, | 
|  | MoqtSessionCallbacks& callbacks) { | 
|  | if (!spdy_client_.Initialize()) { | 
|  | return absl::InternalError("Initialization failed"); | 
|  | } | 
|  | if (!spdy_client_.Connect()) { | 
|  | return absl::UnavailableError("Failed to establish a QUIC connection"); | 
|  | } | 
|  | bool settings_received = quic::ProcessEventsUntil( | 
|  | spdy_client_.default_network_helper()->event_loop(), | 
|  | [&] { return spdy_client_.client_session()->settings_received(); }); | 
|  | if (!settings_received) { | 
|  | return absl::UnavailableError( | 
|  | "Timed out while waiting for server SETTINGS"); | 
|  | } | 
|  | if (!spdy_client_.client_session()->SupportsWebTransport()) { | 
|  | QUICHE_DLOG(INFO) << "session: SupportsWebTransport = " | 
|  | << spdy_client_.client_session()->SupportsWebTransport() | 
|  | << ", SupportsH3Datagram = " | 
|  | << spdy_client_.client_session()->SupportsH3Datagram() | 
|  | << ", OneRttKeysAvailable = " | 
|  | << spdy_client_.client_session()->OneRttKeysAvailable(); | 
|  | return absl::FailedPreconditionError( | 
|  | "Server does not support WebTransport"); | 
|  | } | 
|  | auto* stream = static_cast<quic::QuicSpdyClientStream*>( | 
|  | spdy_client_.client_session()->CreateOutgoingBidirectionalStream()); | 
|  | if (!stream) { | 
|  | return absl::InternalError("Could not open a CONNECT stream"); | 
|  | } | 
|  | spdy_client_.set_store_response(true); | 
|  |  | 
|  | quiche::HttpHeaderBlock headers; | 
|  | headers[":scheme"] = "https"; | 
|  | headers[":authority"] = spdy_client_.server_id().host(); | 
|  | headers[":path"] = path; | 
|  | headers[":method"] = "CONNECT"; | 
|  | headers[":protocol"] = "webtransport"; | 
|  | stream->SendRequest(std::move(headers), "", false); | 
|  |  | 
|  | quic::WebTransportHttp3* web_transport = stream->web_transport(); | 
|  | if (web_transport == nullptr) { | 
|  | return absl::InternalError("Failed to initialize WebTransport session"); | 
|  | } | 
|  |  | 
|  | MoqtSessionParameters parameters(quic::Perspective::IS_CLIENT); | 
|  |  | 
|  | // Ensure that we never have a dangling pointer to the session. | 
|  | MoqtSessionDeletedCallback deleted_callback = | 
|  | std::move(callbacks.session_deleted_callback); | 
|  | callbacks.session_deleted_callback = | 
|  | [this, old = std::move(deleted_callback)]() mutable { | 
|  | session_ = nullptr; | 
|  | std::move(old)(); | 
|  | }; | 
|  |  | 
|  | auto session = std::make_unique<MoqtSession>( | 
|  | web_transport, parameters, | 
|  | spdy_client_.default_network_helper()->event_loop()->CreateAlarmFactory(), | 
|  | std::move(callbacks)); | 
|  | session_ = session.get(); | 
|  | web_transport->SetVisitor(std::move(session)); | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | }  // namespace moqt |