Implement MoqtClient and MoqtServer Based on cl/578655843 PiperOrigin-RevId: 582112382
diff --git a/build/source_list.bzl b/build/source_list.bzl index 1038ec4..0ba5220 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -696,6 +696,7 @@ "quic/tools/quic_backend_response.h", "quic/tools/quic_client_base.h", "quic/tools/quic_client_factory.h", + "quic/tools/quic_event_loop_tools.h", "quic/tools/quic_memory_cache_backend.h", "quic/tools/quic_name_lookup.h", "quic/tools/quic_simple_client_session.h", @@ -710,6 +711,7 @@ "quic/tools/quic_tcp_like_trace_converter.h", "quic/tools/quic_url.h", "quic/tools/simple_ticket_crypter.h", + "quic/tools/web_transport_only_backend.h", "quic/tools/web_transport_test_visitors.h", ] quiche_tool_support_srcs = [ @@ -733,6 +735,7 @@ "quic/tools/quic_tcp_like_trace_converter.cc", "quic/tools/quic_url.cc", "quic/tools/simple_ticket_crypter.cc", + "quic/tools/web_transport_only_backend.cc", ] quiche_test_support_hdrs = [ "common/platform/api/quiche_expect_bug.h", @@ -1470,6 +1473,8 @@ "quic/moqt/moqt_parser.h", "quic/moqt/moqt_session.h", "quic/moqt/test_tools/moqt_test_message.h", + "quic/moqt/tools/moqt_client.h", + "quic/moqt/tools/moqt_server.h", ] moqt_srcs = [ "quic/moqt/moqt_framer.cc", @@ -1479,6 +1484,9 @@ "quic/moqt/moqt_parser.cc", "quic/moqt/moqt_parser_test.cc", "quic/moqt/moqt_session.cc", + "quic/moqt/tools/moqt_client.cc", + "quic/moqt/tools/moqt_end_to_end_test.cc", + "quic/moqt/tools/moqt_server.cc", ] binary_http_hdrs = [ "binary_http/binary_http_message.h",
diff --git a/build/source_list.gni b/build/source_list.gni index ad82d4d..69e5f27 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -696,6 +696,7 @@ "src/quiche/quic/tools/quic_backend_response.h", "src/quiche/quic/tools/quic_client_base.h", "src/quiche/quic/tools/quic_client_factory.h", + "src/quiche/quic/tools/quic_event_loop_tools.h", "src/quiche/quic/tools/quic_memory_cache_backend.h", "src/quiche/quic/tools/quic_name_lookup.h", "src/quiche/quic/tools/quic_simple_client_session.h", @@ -710,6 +711,7 @@ "src/quiche/quic/tools/quic_tcp_like_trace_converter.h", "src/quiche/quic/tools/quic_url.h", "src/quiche/quic/tools/simple_ticket_crypter.h", + "src/quiche/quic/tools/web_transport_only_backend.h", "src/quiche/quic/tools/web_transport_test_visitors.h", ] quiche_tool_support_srcs = [ @@ -733,6 +735,7 @@ "src/quiche/quic/tools/quic_tcp_like_trace_converter.cc", "src/quiche/quic/tools/quic_url.cc", "src/quiche/quic/tools/simple_ticket_crypter.cc", + "src/quiche/quic/tools/web_transport_only_backend.cc", ] quiche_test_support_hdrs = [ "src/quiche/common/platform/api/quiche_expect_bug.h", @@ -1474,6 +1477,8 @@ "src/quiche/quic/moqt/moqt_parser.h", "src/quiche/quic/moqt/moqt_session.h", "src/quiche/quic/moqt/test_tools/moqt_test_message.h", + "src/quiche/quic/moqt/tools/moqt_client.h", + "src/quiche/quic/moqt/tools/moqt_server.h", ] moqt_srcs = [ "src/quiche/quic/moqt/moqt_framer.cc", @@ -1483,6 +1488,9 @@ "src/quiche/quic/moqt/moqt_parser.cc", "src/quiche/quic/moqt/moqt_parser_test.cc", "src/quiche/quic/moqt/moqt_session.cc", + "src/quiche/quic/moqt/tools/moqt_client.cc", + "src/quiche/quic/moqt/tools/moqt_end_to_end_test.cc", + "src/quiche/quic/moqt/tools/moqt_server.cc", ] binary_http_hdrs = [ "src/quiche/binary_http/binary_http_message.h",
diff --git a/build/source_list.json b/build/source_list.json index 3e2d6f3..165f594 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -695,6 +695,7 @@ "quiche/quic/tools/quic_backend_response.h", "quiche/quic/tools/quic_client_base.h", "quiche/quic/tools/quic_client_factory.h", + "quiche/quic/tools/quic_event_loop_tools.h", "quiche/quic/tools/quic_memory_cache_backend.h", "quiche/quic/tools/quic_name_lookup.h", "quiche/quic/tools/quic_simple_client_session.h", @@ -709,6 +710,7 @@ "quiche/quic/tools/quic_tcp_like_trace_converter.h", "quiche/quic/tools/quic_url.h", "quiche/quic/tools/simple_ticket_crypter.h", + "quiche/quic/tools/web_transport_only_backend.h", "quiche/quic/tools/web_transport_test_visitors.h" ], "quiche_tool_support_srcs": [ @@ -731,7 +733,8 @@ "quiche/quic/tools/quic_spdy_client_base.cc", "quiche/quic/tools/quic_tcp_like_trace_converter.cc", "quiche/quic/tools/quic_url.cc", - "quiche/quic/tools/simple_ticket_crypter.cc" + "quiche/quic/tools/simple_ticket_crypter.cc", + "quiche/quic/tools/web_transport_only_backend.cc" ], "quiche_test_support_hdrs": [ "quiche/common/platform/api/quiche_expect_bug.h", @@ -1472,7 +1475,9 @@ "quiche/quic/moqt/moqt_messages.h", "quiche/quic/moqt/moqt_parser.h", "quiche/quic/moqt/moqt_session.h", - "quiche/quic/moqt/test_tools/moqt_test_message.h" + "quiche/quic/moqt/test_tools/moqt_test_message.h", + "quiche/quic/moqt/tools/moqt_client.h", + "quiche/quic/moqt/tools/moqt_server.h" ], "moqt_srcs": [ "quiche/quic/moqt/moqt_framer.cc", @@ -1481,7 +1486,10 @@ "quiche/quic/moqt/moqt_messages.cc", "quiche/quic/moqt/moqt_parser.cc", "quiche/quic/moqt/moqt_parser_test.cc", - "quiche/quic/moqt/moqt_session.cc" + "quiche/quic/moqt/moqt_session.cc", + "quiche/quic/moqt/tools/moqt_client.cc", + "quiche/quic/moqt/tools/moqt_end_to_end_test.cc", + "quiche/quic/moqt/tools/moqt_server.cc" ], "binary_http_hdrs": [ "quiche/binary_http/binary_http_message.h"
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h index 290b640..bf6d82b 100644 --- a/quiche/quic/core/http/quic_spdy_session.h +++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -469,6 +469,9 @@ void OnConfigNegotiated() override; + // Returns true if the SETTINGS frame has been received from the peer. + bool settings_received() const { return settings_received_; } + protected: // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
diff --git a/quiche/quic/moqt/moqt_integration_test.cc b/quiche/quic/moqt/moqt_integration_test.cc index 0fa4245..8efefec 100644 --- a/quiche/quic/moqt/moqt_integration_test.cc +++ b/quiche/quic/moqt/moqt_integration_test.cc
@@ -27,6 +27,18 @@ using ::testing::_; using ::testing::Assign; +struct MockSessionCallbacks { + testing::MockFunction<void()> session_established_callback; + testing::MockFunction<void(absl::string_view)> session_terminated_callback; + testing::MockFunction<void()> session_deleted_callback; + + MoqtSessionCallbacks AsSessionCallbacks() { + return MoqtSessionCallbacks{session_established_callback.AsStdFunction(), + session_terminated_callback.AsStdFunction(), + session_deleted_callback.AsStdFunction()}; + } +}; + class ClientEndpoint : public quic::simulator::QuicEndpointWithConnection { public: ClientEndpoint(Simulator* simulator, const std::string& name, @@ -44,23 +56,21 @@ MoqtSessionParameters{.version = version, .perspective = quic::Perspective::IS_CLIENT, .using_webtrans = false}, - established_callback_.AsStdFunction(), - terminated_callback_.AsStdFunction()) { + callbacks_.AsSessionCallbacks()) { quic_session_.Initialize(); } MoqtSession* session() { return &session_; } quic::QuicGenericClientSession* quic_session() { return &quic_session_; } testing::MockFunction<void()>& established_callback() { - return established_callback_; + return callbacks_.session_established_callback; } testing::MockFunction<void(absl::string_view)>& terminated_callback() { - return terminated_callback_; + return callbacks_.session_terminated_callback; } private: - testing::MockFunction<void()> established_callback_; - testing::MockFunction<void(absl::string_view)> terminated_callback_; + MockSessionCallbacks callbacks_; quic::QuicCryptoClientConfig crypto_config_; quic::QuicGenericClientSession quic_session_; MoqtSession session_; @@ -88,22 +98,20 @@ MoqtSessionParameters{.version = version, .perspective = quic::Perspective::IS_SERVER, .using_webtrans = false}, - established_callback_.AsStdFunction(), - terminated_callback_.AsStdFunction()) { + callbacks_.AsSessionCallbacks()) { quic_session_.Initialize(); } MoqtSession* session() { return &session_; } testing::MockFunction<void()>& established_callback() { - return established_callback_; + return callbacks_.session_established_callback; } testing::MockFunction<void(absl::string_view)>& terminated_callback() { - return terminated_callback_; + return callbacks_.session_terminated_callback; } private: - testing::MockFunction<void()> established_callback_; - testing::MockFunction<void(absl::string_view)> terminated_callback_; + MockSessionCallbacks callbacks_; quic::QuicCompressedCertsCache compressed_certs_cache_; quic::QuicCryptoServerConfig crypto_config_; quic::QuicGenericServerSession quic_session_;
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h index 15948a1..3586a22 100644 --- a/quiche/quic/moqt/moqt_messages.h +++ b/quiche/quic/moqt/moqt_messages.h
@@ -16,10 +16,15 @@ #include "absl/strings/string_view.h" #include "quiche/quic/core/quic_time.h" #include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_versions.h" #include "quiche/common/platform/api/quiche_export.h" namespace moqt { +inline constexpr quic::ParsedQuicVersionVector GetMoqtSupportedQuicVersions() { + return quic::ParsedQuicVersionVector{quic::ParsedQuicVersion::RFCv1()}; +} + enum class MoqtVersion : uint64_t { kDraft01 = 0xff000001, kUnrecognizedVersionForTests = 0xfe0000ff,
diff --git a/quiche/quic/moqt/moqt_session.h b/quiche/quic/moqt/moqt_session.h index f33c753..a13c14d 100644 --- a/quiche/quic/moqt/moqt_session.h +++ b/quiche/quic/moqt/moqt_session.h
@@ -24,18 +24,31 @@ using MoqtSessionEstablishedCallback = quiche::SingleUseCallback<void()>; using MoqtSessionTerminatedCallback = quiche::SingleUseCallback<void(absl::string_view error_message)>; +using MoqtSessionDeletedCallback = quiche::SingleUseCallback<void()>; + +// Callbacks for session-level events. +struct MoqtSessionCallbacks { + MoqtSessionEstablishedCallback session_established_callback = +[] {}; + MoqtSessionTerminatedCallback session_terminated_callback = + +[](absl::string_view) {}; + MoqtSessionDeletedCallback session_deleted_callback = +[] {}; +}; class QUICHE_EXPORT MoqtSession : public webtransport::SessionVisitor { public: MoqtSession(webtransport::Session* session, MoqtSessionParameters parameters, - MoqtSessionEstablishedCallback session_established_callback, - MoqtSessionTerminatedCallback session_terminated_callback) + MoqtSessionCallbacks callbacks) : session_(session), parameters_(parameters), - session_established_callback_(std::move(session_established_callback)), - session_terminated_callback_(std::move(session_terminated_callback)), + session_established_callback_( + std::move(callbacks.session_established_callback)), + session_terminated_callback_( + std::move(callbacks.session_terminated_callback)), + session_deleted_callback_( + std::move(callbacks.session_deleted_callback)), framer_(quiche::SimpleBufferAllocator::Get(), parameters.using_webtrans) {} + ~MoqtSession() { std::move(session_deleted_callback_)(); } // webtransport::SessionVisitor implementation. void OnSessionReady() override; @@ -109,6 +122,7 @@ MoqtSessionParameters parameters_; MoqtSessionEstablishedCallback session_established_callback_; MoqtSessionTerminatedCallback session_terminated_callback_; + MoqtSessionDeletedCallback session_deleted_callback_; MoqtFramer framer_; std::optional<webtransport::StreamId> control_stream_;
diff --git a/quiche/quic/moqt/tools/moqt_client.cc b/quiche/quic/moqt/tools/moqt_client.cc new file mode 100644 index 0000000..2ed51ec --- /dev/null +++ b/quiche/quic/moqt/tools/moqt_client.cc
@@ -0,0 +1,111 @@ +// 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/platform/api/quiche_logging.h" +#include "quiche/spdy/core/http2_header_block.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); + + spdy::Http2HeaderBlock 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; + parameters.version = MoqtVersion::kDraft01; + parameters.perspective = quic::Perspective::IS_CLIENT, + parameters.using_webtrans = true; + parameters.path = ""; + + // 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)(); + }; + + web_transport->SetVisitor(std::make_unique<MoqtSession>( + web_transport, parameters, std::move(callbacks))); + return absl::OkStatus(); +} + +} // namespace moqt
diff --git a/quiche/quic/moqt/tools/moqt_client.h b/quiche/quic/moqt/tools/moqt_client.h new file mode 100644 index 0000000..7959bf3 --- /dev/null +++ b/quiche/quic/moqt/tools/moqt_client.h
@@ -0,0 +1,45 @@ +// 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. + +#ifndef QUICHE_QUIC_MOQT_TOOLS_MOQT_CLIENT_H_ +#define QUICHE_QUIC_MOQT_TOOLS_MOQT_CLIENT_H_ + +#include <memory> +#include <string> + +#include "absl/status/status.h" +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/http/quic_spdy_client_session.h" +#include "quiche/quic/core/io/quic_event_loop.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/common/platform/api/quiche_export.h" + +namespace moqt { + +// A synchronous MoQT client based on QuicDefaultClient. +class MoqtClient { + public: + MoqtClient(quic::QuicSocketAddress peer_address, + const quic::QuicServerId& server_id, + std::unique_ptr<quic::ProofVerifier> proof_verifier, + quic::QuicEventLoop* event_loop); + + // Establishes the connection to the specified endpoint. The errors are + // returned via the session termination callback. + void Connect(std::string path, MoqtSessionCallbacks callbacks); + + MoqtSession* session() { return session_; } + + private: + absl::Status ConnectInner(std::string path, MoqtSessionCallbacks& callbacks); + + quic::QuicDefaultClient spdy_client_; + MoqtSession* session_ = nullptr; +}; + +} // namespace moqt + +#endif // QUICHE_QUIC_MOQT_TOOLS_MOQT_CLIENT_H_
diff --git a/quiche/quic/moqt/tools/moqt_end_to_end_test.cc b/quiche/quic/moqt/tools/moqt_end_to_end_test.cc new file mode 100644 index 0000000..0763085 --- /dev/null +++ b/quiche/quic/moqt/tools/moqt_end_to_end_test.cc
@@ -0,0 +1,121 @@ +// 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. + +// End-to-end test for MoqtClient/MoqtServer. +// +// IMPORTANT NOTE: +// This test mostly exists to test the two classes mentioned above. When +// possible, moqt_integration_test should be used instead, as it does not use +// real clocks or I/O and thus has less overhead. + +#include <memory> +#include <string> +#include <utility> + +#include "absl/functional/bind_front.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/io/quic_event_loop.h" +#include "quiche/quic/core/quic_server_id.h" +#include "quiche/quic/moqt/moqt_session.h" +#include "quiche/quic/moqt/tools/moqt_client.h" +#include "quiche/quic/moqt/tools/moqt_server.h" +#include "quiche/quic/platform/api/quic_ip_address.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/quic/platform/api/quic_test_loopback.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/tools/quic_event_loop_tools.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_callbacks.h" + +namespace moqt::test { +namespace { + +constexpr absl::string_view kNotFoundPath = "/not-found"; + +void UnexpectedClose(absl::string_view reason) { + ADD_FAILURE() << "Unexpected close of MoQT session with reason: " << reason; +} + +class MoqtEndToEndTest : public quiche::test::QuicheTest { + public: + MoqtEndToEndTest() + : server_(quic::test::crypto_test_utils::ProofSourceForTesting(), + absl::bind_front(&MoqtEndToEndTest::ServerBackend, this)) { + quic::QuicIpAddress host = quic::TestLoopback(); + bool success = server_.quic_server().CreateUDPSocketAndListen( + quic::QuicSocketAddress(host, /*port=*/0)); + QUICHE_CHECK(success); + server_address_ = + quic::QuicSocketAddress(host, server_.quic_server().port()); + event_loop_ = server_.quic_server().event_loop(); + } + + absl::StatusOr<MoqtSessionCallbacks> ServerBackend(absl::string_view path) { + QUICHE_LOG(INFO) << "Server: Received a request for path " << path; + if (path == kNotFoundPath) { + return absl::NotFoundError("404 test endpoint"); + } + MoqtSessionCallbacks callbacks; + callbacks.session_established_callback = []() { + QUICHE_LOG(INFO) << "Server: session established"; + }; + callbacks.session_terminated_callback = [](absl::string_view reason) { + QUICHE_LOG(INFO) << "Server: session terminated with reason: " << reason; + }; + return std::move(callbacks); + } + + std::unique_ptr<MoqtClient> CreateClient() { + return std::make_unique<MoqtClient>( + server_address_, quic::QuicServerId("test.example.com", 443), + quic::test::crypto_test_utils::ProofVerifierForTesting(), event_loop_); + } + + bool RunEventsUntil(quiche::UnretainedCallback<bool()> callback) { + return quic::ProcessEventsUntil(event_loop_, callback); + } + + private: + MoqtServer server_; + quic::QuicEventLoop* event_loop_; + quic::QuicSocketAddress server_address_; +}; + +TEST_F(MoqtEndToEndTest, SuccessfulHandshake) { + MoqtSessionCallbacks callbacks; + bool established = false; + bool deleted = false; + callbacks.session_established_callback = [&] { established = true; }; + callbacks.session_terminated_callback = UnexpectedClose; + callbacks.session_deleted_callback = [&] { deleted = true; }; + std::unique_ptr<MoqtClient> client = CreateClient(); + client->Connect("/test", std::move(callbacks)); + bool success = RunEventsUntil([&] { return established; }); + EXPECT_TRUE(success); + EXPECT_FALSE(deleted); + client.reset(); + EXPECT_TRUE(deleted); +} + +TEST_F(MoqtEndToEndTest, HandshakeFailed404) { + MoqtSessionCallbacks callbacks; + bool resolved = false; + callbacks.session_established_callback = [&] { + ADD_FAILURE() << "Established session when 404 expected"; + resolved = true; + }; + callbacks.session_terminated_callback = [&](absl::string_view error) { + resolved = true; + }; + std::unique_ptr<MoqtClient> client = CreateClient(); + client->Connect(std::string(kNotFoundPath), std::move(callbacks)); + bool success = RunEventsUntil([&] { return resolved; }); + EXPECT_TRUE(success); +} + +} // namespace +} // namespace moqt::test
diff --git a/quiche/quic/moqt/tools/moqt_server.cc b/quiche/quic/moqt/tools/moqt_server.cc new file mode 100644 index 0000000..c2ec5fb --- /dev/null +++ b/quiche/quic/moqt/tools/moqt_server.cc
@@ -0,0 +1,48 @@ +// 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_server.h" + +#include <memory> +#include <utility> + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/proof_source.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/tools/quic_server.h" +#include "quiche/quic/tools/web_transport_only_backend.h" +#include "quiche/web_transport/web_transport.h" + +namespace moqt { + +namespace { +quic::WebTransportRequestCallback CreateWebTransportCallback( + MoqtIncomingSessionCallback callback) { + return [callback = std::move(callback)](absl::string_view path, + webtransport::Session* session) + -> absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> { + absl::StatusOr<MoqtSessionCallbacks> callbacks = callback(path); + if (!callbacks.ok()) { + return callbacks.status(); + } + MoqtSessionParameters parameters; + parameters.perspective = quic::Perspective::IS_SERVER; + parameters.path = path; + parameters.using_webtrans = true; + parameters.version = MoqtVersion::kDraft01; + return std::make_unique<MoqtSession>(session, parameters, + *std::move(callbacks)); + }; +} +} // namespace + +MoqtServer::MoqtServer(std::unique_ptr<quic::ProofSource> proof_source, + MoqtIncomingSessionCallback callback) + : backend_(CreateWebTransportCallback(std::move(callback))), + server_(std::move(proof_source), &backend_) {} + +} // namespace moqt
diff --git a/quiche/quic/moqt/tools/moqt_server.h b/quiche/quic/moqt/tools/moqt_server.h new file mode 100644 index 0000000..44c8fb8 --- /dev/null +++ b/quiche/quic/moqt/tools/moqt_server.h
@@ -0,0 +1,42 @@ +// 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. + +#ifndef QUICHE_QUIC_MOQT_TOOLS_MOQT_SERVER_H_ +#define QUICHE_QUIC_MOQT_TOOLS_MOQT_SERVER_H_ + +#include <memory> + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/crypto/proof_source.h" +#include "quiche/quic/core/io/quic_event_loop.h" +#include "quiche/quic/moqt/moqt_session.h" +#include "quiche/quic/tools/quic_server.h" +#include "quiche/quic/tools/web_transport_only_backend.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/quiche_callbacks.h" + +namespace moqt { + +// A callback to provide MoQT handler based on the path in the request. +using MoqtIncomingSessionCallback = + quiche::MultiUseCallback<absl::StatusOr<MoqtSessionCallbacks>( + absl::string_view path)>; + +// A simple MoQT server. +class MoqtServer { + public: + explicit MoqtServer(std::unique_ptr<quic::ProofSource> proof_source, + MoqtIncomingSessionCallback callback); + + quic::QuicServer& quic_server() { return server_; } + + private: + quic::WebTransportOnlyBackend backend_; + quic::QuicServer server_; +}; + +} // namespace moqt + +#endif // QUICHE_QUIC_MOQT_TOOLS_MOQT_SERVER_H_
diff --git a/quiche/quic/tools/quic_event_loop_tools.h b/quiche/quic/tools/quic_event_loop_tools.h new file mode 100644 index 0000000..1bf1d28 --- /dev/null +++ b/quiche/quic/tools/quic_event_loop_tools.h
@@ -0,0 +1,40 @@ +// 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. + +#ifndef QUICHE_QUIC_TOOLS_QUIC_EVENT_LOOP_TOOLS_H_ +#define QUICHE_QUIC_TOOLS_QUIC_EVENT_LOOP_TOOLS_H_ + +#include "absl/base/attributes.h" +#include "quiche/quic/core/io/quic_event_loop.h" +#include "quiche/quic/core/quic_clock.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/common/quiche_callbacks.h" + +namespace quic { + +inline constexpr QuicTimeDelta kDefaultTimeoutForTools = + QuicTimeDelta::FromSeconds(3); +inline constexpr QuicTimeDelta kDefaultEventLoopTimeoutForTools = + QuicTimeDelta::FromMilliseconds(50); + +// Runs the event loop until the specified callback returns true, or until the +// timeout occurs. Returns true if callback returned true at least once. +ABSL_MUST_USE_RESULT inline bool ProcessEventsUntil( + QuicEventLoop* event_loop, quiche::UnretainedCallback<bool()> callback, + QuicTimeDelta timeout = kDefaultTimeoutForTools) { + const QuicClock* clock = event_loop->GetClock(); + QuicTime start = clock->Now(); + while (!callback()) { + event_loop->RunEventLoopOnce(kDefaultEventLoopTimeoutForTools); + QuicTimeDelta elapsed = clock->Now() - start; + if (elapsed >= timeout) { + return false; + } + } + return true; +} + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_QUIC_EVENT_LOOP_TOOLS_H_
diff --git a/quiche/quic/tools/web_transport_only_backend.cc b/quiche/quic/tools/web_transport_only_backend.cc new file mode 100644 index 0000000..d908de3 --- /dev/null +++ b/quiche/quic/tools/web_transport_only_backend.cc
@@ -0,0 +1,68 @@ +// 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/tools/web_transport_only_backend.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "quiche/quic/tools/quic_backend_response.h" +#include "quiche/common/http/http_header_block.h" +#include "quiche/web_transport/web_transport.h" + +namespace quic { + +void WebTransportOnlyBackend::FetchResponseFromBackend( + const quiche::HttpHeaderBlock&, const std::string&, + RequestHandler* request_handler) { + static QuicBackendResponse* response = []() { + quiche::HttpHeaderBlock headers; + headers[":status"] = "405"; // 405 Method Not Allowed + headers["content-type"] = "text/plain"; + auto response = std::make_unique<QuicBackendResponse>(); + response->set_headers(std::move(headers)); + response->set_body("This endpoint only accepts WebTransport requests"); + return response.release(); + }(); + request_handler->OnResponseBackendComplete(response); +} + +WebTransportOnlyBackend::WebTransportResponse +WebTransportOnlyBackend::ProcessWebTransportRequest( + const quiche::HttpHeaderBlock& request_headers, + webtransport::Session* session) { + WebTransportResponse response; + + auto path = request_headers.find(":path"); + if (path == request_headers.end()) { + response.response_headers[":status"] = "400"; + return response; + } + + absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> processed = + callback_(path->second, session); + switch (processed.status().code()) { + case absl::StatusCode::kOk: + response.response_headers[":status"] = "200"; + response.visitor = *std::move(processed); + return response; + case absl::StatusCode::kNotFound: + response.response_headers[":status"] = "404"; + return response; + case absl::StatusCode::kInvalidArgument: + response.response_headers[":status"] = "400"; + return response; + case absl::StatusCode::kResourceExhausted: + response.response_headers[":status"] = "429"; + return response; + default: + response.response_headers[":status"] = "500"; + return response; + } +} + +} // namespace quic
diff --git a/quiche/quic/tools/web_transport_only_backend.h b/quiche/quic/tools/web_transport_only_backend.h new file mode 100644 index 0000000..cd37672 --- /dev/null +++ b/quiche/quic/tools/web_transport_only_backend.h
@@ -0,0 +1,51 @@ +// 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. + +#ifndef QUICHE_QUIC_TOOLS_WEB_TRANSPORT_ONLY_BACKEND_H_ +#define QUICHE_QUIC_TOOLS_WEB_TRANSPORT_ONLY_BACKEND_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "quiche/quic/core/web_transport_interface.h" +#include "quiche/quic/tools/quic_simple_server_backend.h" +#include "quiche/common/quiche_callbacks.h" +#include "quiche/web_transport/web_transport.h" +#include "quiche/spdy/core/http2_header_block.h" + +namespace quic { + +// A callback to create a WebTransport session visitor for a given path and the +// session object. The path includes both the path and the query. +using WebTransportRequestCallback = quiche::MultiUseCallback< + absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>>( + absl::string_view path, WebTransportSession* session)>; + +class WebTransportOnlyBackend : public QuicSimpleServerBackend { + public: + explicit WebTransportOnlyBackend(WebTransportRequestCallback callback) + : callback_(std::move(callback)) {} + + // QuicSimpleServerBackend implementation. + bool InitializeBackend(const std::string&) override { return true; } + bool IsBackendInitialized() const override { return true; } + void FetchResponseFromBackend(const spdy::Http2HeaderBlock&, + const std::string&, + RequestHandler* request_handler) override; + void CloseBackendResponseStream(RequestHandler*) override {} + bool SupportsWebTransport() override { return true; } + WebTransportResponse ProcessWebTransportRequest( + const spdy::Http2HeaderBlock& request_headers, + WebTransportSession* session) override; + + private: + WebTransportRequestCallback callback_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_TOOLS_WEB_TRANSPORT_ONLY_BACKEND_H_
diff --git a/quiche/quic/tools/web_transport_test_server.cc b/quiche/quic/tools/web_transport_test_server.cc index 3ba29a3..a7c1794 100644 --- a/quiche/quic/tools/web_transport_test_server.cc +++ b/quiche/quic/tools/web_transport_test_server.cc
@@ -8,12 +8,11 @@ #include "absl/status/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" -#include "quiche/quic/core/quic_error_codes.h" #include "quiche/quic/core/web_transport_interface.h" #include "quiche/quic/platform/api/quic_socket_address.h" #include "quiche/quic/tools/devious_baton.h" #include "quiche/quic/tools/quic_server.h" -#include "quiche/quic/tools/quic_simple_server_backend.h" +#include "quiche/quic/tools/web_transport_only_backend.h" #include "quiche/quic/tools/web_transport_test_visitors.h" #include "quiche/common/platform/api/quiche_command_line_flags.h" #include "quiche/common/platform/api/quiche_default_proof_providers.h" @@ -30,7 +29,12 @@ namespace { absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> ProcessRequest( - const GURL& url, WebTransportSession* session) { + absl::string_view path, WebTransportSession* session) { + GURL url(absl::StrCat("https://localhost", path)); + if (!url.is_valid()) { + return absl::InvalidArgumentError("Unable to parse the :path"); + } + if (url.path() == "/webtransport/echo") { return std::make_unique<EchoWebTransportSessionVisitor>(session); } @@ -70,59 +74,13 @@ return absl::NotFoundError("Path not found"); } -class WebTransportTestBackend : public QuicSimpleServerBackend { - public: - bool InitializeBackend(const std::string&) override { return true; } - bool IsBackendInitialized() const override { return true; } - void FetchResponseFromBackend(const spdy::Http2HeaderBlock&, - const std::string&, - RequestHandler* request_handler) override { - request_handler->TerminateStreamWithError( - QuicResetStreamError::FromInternal(QUIC_STREAM_INTERNAL_ERROR)); - } - void CloseBackendResponseStream(RequestHandler*) override {} - bool SupportsWebTransport() override { return true; } - WebTransportResponse ProcessWebTransportRequest( - const spdy::Http2HeaderBlock& request_headers, - WebTransportSession* session) override { - WebTransportResponse response; - response.response_headers[":status"] = "400"; - - auto path = request_headers.find(":path"); - if (path == request_headers.end()) { - return response; - } - GURL url(absl::StrCat("https://localhost", path->second)); - if (!url.is_valid()) { - return response; - } - absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> processed = - ProcessRequest(url, session); - switch (processed.status().code()) { - case absl::StatusCode::kOk: - response.response_headers[":status"] = "200"; - response.visitor = *std::move(processed); - return response; - case absl::StatusCode::kNotFound: - response.response_headers[":status"] = "404"; - return response; - case absl::StatusCode::kInvalidArgument: - response.response_headers[":status"] = "400"; - return response; - default: - response.response_headers[":status"] = "500"; - return response; - } - } -}; - int Main(int argc, char** argv) { quiche::QuicheSystemEventLoop event_loop("web_transport_test_server"); const char* usage = "Usage: web_transport_test_server [options]"; std::vector<std::string> non_option_args = quiche::QuicheParseCommandLineFlags(usage, argc, argv); - WebTransportTestBackend backend; + WebTransportOnlyBackend backend(ProcessRequest); QuicServer server(quiche::CreateDefaultProofSource(), &backend); quic::QuicSocketAddress addr(quic::QuicIpAddress::Any6(), quiche::GetQuicheCommandLineFlag(FLAGS_port));