1 // Copyright 2023 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/moqt/tools/moqt_client.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "absl/status/status.h"
12 #include "absl/strings/string_view.h"
13 #include "quiche/quic/core/crypto/proof_verifier.h"
14 #include "quiche/quic/core/http/quic_spdy_client_stream.h"
15 #include "quiche/quic/core/http/web_transport_http3.h"
16 #include "quiche/quic/core/io/quic_event_loop.h"
17 #include "quiche/quic/core/quic_server_id.h"
18 #include "quiche/quic/core/quic_types.h"
19 #include "quiche/quic/moqt/moqt_messages.h"
20 #include "quiche/quic/moqt/moqt_session.h"
21 #include "quiche/quic/platform/api/quic_socket_address.h"
22 #include "quiche/quic/tools/quic_default_client.h"
23 #include "quiche/quic/tools/quic_event_loop_tools.h"
24 #include "quiche/quic/tools/quic_name_lookup.h"
25 #include "quiche/common/platform/api/quiche_logging.h"
26 #include "quiche/spdy/core/http2_header_block.h"
27
28 namespace moqt {
29
MoqtClient(quic::QuicSocketAddress peer_address,const quic::QuicServerId & server_id,std::unique_ptr<quic::ProofVerifier> proof_verifier,quic::QuicEventLoop * event_loop)30 MoqtClient::MoqtClient(quic::QuicSocketAddress peer_address,
31 const quic::QuicServerId& server_id,
32 std::unique_ptr<quic::ProofVerifier> proof_verifier,
33 quic::QuicEventLoop* event_loop)
34 : spdy_client_(peer_address, server_id, GetMoqtSupportedQuicVersions(),
35 event_loop, std::move(proof_verifier)) {
36 spdy_client_.set_enable_web_transport(true);
37 }
38
Connect(std::string path,MoqtSessionCallbacks callbacks)39 void MoqtClient::Connect(std::string path, MoqtSessionCallbacks callbacks) {
40 absl::Status status = ConnectInner(std::move(path), callbacks);
41 if (!status.ok()) {
42 std::move(callbacks.session_terminated_callback)(status.message());
43 }
44 }
45
ConnectInner(std::string path,MoqtSessionCallbacks & callbacks)46 absl::Status MoqtClient::ConnectInner(std::string path,
47 MoqtSessionCallbacks& callbacks) {
48 if (!spdy_client_.Initialize()) {
49 return absl::InternalError("Initialization failed");
50 }
51 if (!spdy_client_.Connect()) {
52 return absl::UnavailableError("Failed to establish a QUIC connection");
53 }
54 bool settings_received = quic::ProcessEventsUntil(
55 spdy_client_.default_network_helper()->event_loop(),
56 [&] { return spdy_client_.client_session()->settings_received(); });
57 if (!settings_received) {
58 return absl::UnavailableError(
59 "Timed out while waiting for server SETTINGS");
60 }
61 if (!spdy_client_.client_session()->SupportsWebTransport()) {
62 QUICHE_DLOG(INFO) << "session: SupportsWebTransport = "
63 << spdy_client_.client_session()->SupportsWebTransport()
64 << ", SupportsH3Datagram = "
65 << spdy_client_.client_session()->SupportsH3Datagram()
66 << ", OneRttKeysAvailable = "
67 << spdy_client_.client_session()->OneRttKeysAvailable();
68 return absl::FailedPreconditionError(
69 "Server does not support WebTransport");
70 }
71 auto* stream = static_cast<quic::QuicSpdyClientStream*>(
72 spdy_client_.client_session()->CreateOutgoingBidirectionalStream());
73 if (!stream) {
74 return absl::InternalError("Could not open a CONNECT stream");
75 }
76 spdy_client_.set_store_response(true);
77
78 spdy::Http2HeaderBlock headers;
79 headers[":scheme"] = "https";
80 headers[":authority"] = spdy_client_.server_id().host();
81 headers[":path"] = path;
82 headers[":method"] = "CONNECT";
83 headers[":protocol"] = "webtransport";
84 stream->SendRequest(std::move(headers), "", false);
85
86 quic::WebTransportHttp3* web_transport = stream->web_transport();
87 if (web_transport == nullptr) {
88 return absl::InternalError("Failed to initialize WebTransport session");
89 }
90
91 MoqtSessionParameters parameters;
92 parameters.version = MoqtVersion::kDraft03;
93 parameters.perspective = quic::Perspective::IS_CLIENT,
94 parameters.using_webtrans = true;
95 parameters.path = "";
96 parameters.deliver_partial_objects = false;
97
98 // Ensure that we never have a dangling pointer to the session.
99 MoqtSessionDeletedCallback deleted_callback =
100 std::move(callbacks.session_deleted_callback);
101 callbacks.session_deleted_callback =
102 [this, old = std::move(deleted_callback)]() mutable {
103 session_ = nullptr;
104 std::move(old)();
105 };
106
107 auto session = std::make_unique<MoqtSession>(web_transport, parameters,
108 std::move(callbacks));
109 session_ = session.get();
110 web_transport->SetVisitor(std::move(session));
111 return absl::OkStatus();
112 }
113
114 } // namespace moqt
115