| // Copyright 2026 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/qbone/test_tools/basic_quic_server.h" | |
| #include <array> | |
| #include <cstddef> | |
| #include <memory> | |
| #include <queue> | |
| #include <string> | |
| #include <thread> // NOLINT (for open-sourceable thread ID) | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/base/attributes.h" | |
| #include "absl/base/nullability.h" | |
| #include "absl/base/thread_annotations.h" | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include "absl/synchronization/notification.h" | |
| #include "absl/types/span.h" | |
| #include "quiche/quic/core/connection_id_generator.h" | |
| #include "quiche/quic/core/crypto/proof_source.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/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_alarm_factory.h" | |
| #include "quiche/quic/core/quic_connection.h" | |
| #include "quiche/quic/core/quic_connection_id.h" | |
| #include "quiche/quic/core/quic_crypto_server_stream_base.h" | |
| #include "quiche/quic/core/quic_default_clock.h" | |
| #include "quiche/quic/core/quic_default_connection_helper.h" | |
| #include "quiche/quic/core/quic_dispatcher.h" | |
| #include "quiche/quic/core/quic_session.h" | |
| #include "quiche/quic/core/quic_stream.h" | |
| #include "quiche/quic/core/quic_stream_sequencer.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/core/quic_utils.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/tools/quic_simple_crypto_server_stream_helper.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #include "quiche/common/platform/api/quiche_thread.h" | |
| #include "quiche/common/quiche_callbacks.h" | |
| #include "quiche/common/quiche_mem_slice.h" | |
| #include "quiche/common/quiche_socket_address.h" | |
| #include "quiche/common/quiche_status_utils.h" | |
| namespace quic::test { | |
| namespace { | |
| using ::quiche::QuicheMemSlice; | |
| using ::quiche::QuicheSocketAddress; | |
| using ::quiche::QuicheThread; | |
| using ::quiche::SingleUseCallback; | |
| constexpr auto kSupportedQuicVersions = std::to_array<ParsedQuicVersion>({ | |
| ParsedQuicVersion::RFCv1(), | |
| }); | |
| class Stream : public QuicStream { | |
| public: | |
| Stream(QuicStreamId id, | |
| QuicSession* absl_nonnull session ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| BasicQuicServer::Handler* absl_nonnull handler | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND) | |
| : QuicStream(id, session, /*is_static=*/false, | |
| QuicUtils::GetStreamType( | |
| id, session->connection()->perspective(), | |
| session->IsIncomingStream(id), session->version())), | |
| handler_(handler) { | |
| // This stream allows leaving data unconsumed in the sequencer, so need to | |
| // use level-triggered reading to notify the handler on any increase in | |
| // readable bytes. | |
| sequencer()->set_level_triggered(true); | |
| } | |
| void OnDataAvailable() override { | |
| int consumed = handler_->OnStreamDataAvailable(session()->connection_id(), | |
| id(), *sequencer()); | |
| if (consumed > 0) { | |
| sequencer()->MarkConsumed(consumed); | |
| } | |
| } | |
| private: | |
| BasicQuicServer::Handler* absl_nonnull const handler_; | |
| }; | |
| class Session : public QuicSession { | |
| public: | |
| class DestructionObserver { | |
| public: | |
| virtual ~DestructionObserver() = default; | |
| virtual void OnSessionDestroyed(Session* session) = 0; | |
| }; | |
| Session(std::unique_ptr<QuicConnection> absl_nonnull connection, | |
| Visitor* visitor, const QuicConfig& config, | |
| const QuicCryptoServerConfig* crypto_config, | |
| BasicQuicServer::Handler* absl_nonnull handler | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND) | |
| : QuicSession(connection.get(), visitor, config, | |
| connection->supported_versions(), | |
| /*num_expected_unidirectional_static_streams=*/0), | |
| connection_(std::move(connection)), | |
| handler_(handler) { | |
| crypto_stream_ = handler_->OnNewSession(this, crypto_config); | |
| Initialize(); | |
| } | |
| ~Session() override { | |
| handler_->OnSessionEnd(connection_id()); | |
| if (destruction_observer_) { | |
| destruction_observer_->OnSessionDestroyed(this); | |
| } | |
| } | |
| void RegisterDestructionObserver(DestructionObserver* absl_nonnull observer) { | |
| QUICHE_CHECK(!destruction_observer_); | |
| destruction_observer_ = observer; | |
| } | |
| void UnregisterDestructionObserver() { | |
| QUICHE_CHECK(destruction_observer_); | |
| destruction_observer_ = nullptr; | |
| } | |
| // QuicSession: | |
| const QuicCryptoStream* GetCryptoStream() const override { | |
| return crypto_stream_.get(); | |
| } | |
| std::vector<absl::string_view>::const_iterator SelectAlpn( | |
| const std::vector<absl::string_view>& alpns) const override { | |
| return handler_->SelectAlpn(connection_id(), alpns); | |
| } | |
| protected: | |
| QuicStream* CreateIncomingStream(QuicStreamId id) override { | |
| if (handler_->OnNewStream(connection_id(), id)) { | |
| auto stream = std::make_unique<Stream>(id, this, handler_); | |
| Stream* stream_ptr = stream.get(); | |
| ActivateStream(std::move(stream)); | |
| return stream_ptr; | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| QuicCryptoStream* GetMutableCryptoStream() override { | |
| return crypto_stream_.get(); | |
| } | |
| bool ShouldKeepConnectionAlive() const override { return true; } | |
| void OnDatagramReceived(absl::string_view datagram) override { | |
| absl::Span<const std::byte> datagram_bytes( | |
| reinterpret_cast<const std::byte*>(datagram.data()), datagram.size()); | |
| handler_->OnDatagramReceived(connection_id(), datagram_bytes); | |
| } | |
| private: | |
| std::unique_ptr<QuicConnection> connection_; | |
| std::unique_ptr<QuicCryptoStream> crypto_stream_; | |
| DestructionObserver* absl_nullable destruction_observer_ = nullptr; | |
| BasicQuicServer::Handler* absl_nonnull const handler_; | |
| }; | |
| class Dispatcher : public QuicDispatcher, public Session::DestructionObserver { | |
| public: | |
| Dispatcher( | |
| const QuicConfig* config ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| const QuicCryptoServerConfig* crypto_config ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| QuicVersionManager* version_manager ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| std::unique_ptr<QuicAlarmFactory> alarm_factory, | |
| ConnectionIdGeneratorInterface* absl_nonnull connection_id_generator | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| BasicQuicServer::Handler* absl_nonnull handler | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND) | |
| : QuicDispatcher(config, crypto_config, version_manager, | |
| std::make_unique<QuicDefaultConnectionHelper>(), | |
| std::make_unique<QuicSimpleCryptoServerStreamHelper>(), | |
| std::move(alarm_factory), kQuicDefaultConnectionIdLength, | |
| *connection_id_generator), | |
| handler_(handler) {} | |
| ~Dispatcher() override { | |
| // Stop sessions from notifying back to this dispatcher on destruction. | |
| for (auto& [connection_id, session] : sessions_) { | |
| session->UnregisterDestructionObserver(); | |
| } | |
| } | |
| absl::StatusOr<Session*> GetSession(QuicConnectionId connection_id) { | |
| auto it = sessions_.find(connection_id); | |
| if (it == sessions_.end()) { | |
| return absl::NotFoundError("Session not found."); | |
| } else { | |
| return it->second; | |
| } | |
| } | |
| // Session::DestructionObserver | |
| void OnSessionDestroyed(Session* session) override { | |
| QUICHE_CHECK_EQ(sessions_.erase(session->connection_id()), 1); | |
| } | |
| protected: | |
| std::unique_ptr<QuicSession> CreateQuicSession( | |
| QuicConnectionId server_connection_id, | |
| const QuicSocketAddress& self_address, | |
| const QuicSocketAddress& peer_address, absl::string_view alpn, | |
| const ParsedQuicVersion& version, const ParsedClientHello& parsed_chlo, | |
| ConnectionIdGeneratorInterface& connection_id_generator) override { | |
| QUICHE_LOG(INFO) << "Creating QuicSession for connection " | |
| << server_connection_id << " with peer " | |
| << peer_address.ToString() << " and ALPN " << alpn; | |
| auto connection = std::make_unique<QuicConnection>( | |
| server_connection_id, self_address, peer_address, helper(), | |
| alarm_factory(), writer(), | |
| /*owns_writer=*/false, Perspective::IS_SERVER, | |
| ParsedQuicVersionVector{version}, connection_id_generator); | |
| auto session = std::make_unique<Session>( | |
| std::move(connection), this, config(), crypto_config(), handler_); | |
| auto [it, inserted] = | |
| sessions_.emplace(server_connection_id, session.get()); | |
| QUICHE_CHECK(inserted); | |
| session->RegisterDestructionObserver(this); | |
| return session; | |
| } | |
| private: | |
| // Non-refed Session pointers to allow destruction, but expect the Session to | |
| // call OnSessionDestroyed to remove itself. | |
| absl::flat_hash_map<QuicConnectionId, Session*, QuicConnectionIdHash> | |
| sessions_; | |
| BasicQuicServer::Handler* absl_nonnull const handler_; | |
| }; | |
| } // namespace | |
| class BasicQuicServer::ServerThread : public QuicheThread { | |
| public: | |
| ServerThread(QuicheSocketAddress socket_address, | |
| const QuicConfig* absl_nonnull config | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| const QuicCryptoServerConfig* absl_nonnull crypto_config | |
| ABSL_ATTRIBUTE_LIFETIME_BOUND, | |
| std::unique_ptr<BasicQuicServer::Handler> absl_nonnull handler) | |
| : QuicheThread("BasicQuicServerThread"), | |
| socket_address_(std::move(socket_address)), | |
| config_(config), | |
| crypto_config_(crypto_config), | |
| handler_(std::move(handler)) {} | |
| ~ServerThread() override { | |
| // Expect/require server to be stopped before destruction. | |
| QUICHE_CHECK(!running_notification_.HasBeenNotified() || | |
| shutdown_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!dispatcher_); | |
| } | |
| void Run() override { | |
| QUICHE_CHECK(!running_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!shutdown_notification_.HasBeenNotified()); | |
| thread_id_ = std::this_thread::get_id(); | |
| QUICHE_CHECK(absl::IsUnknown(initialization_status_)); | |
| initialization_status_ = Initialize(); | |
| if (!initialization_status_.ok()) { | |
| QUICHE_LOG(ERROR) << "Failed to initialize BasicQuicServer: " | |
| << initialization_status_; | |
| running_notification_.Notify(); | |
| return; | |
| } | |
| bound_address_ = io_harness_->local_address(); | |
| QUICHE_LOG(INFO) << "Starting BasicQuicServer on " << bound_address_; | |
| running_notification_.Notify(); | |
| while (!shutdown_notification_.HasBeenNotified()) { | |
| ExecuteScheduledActions(); | |
| event_loop_->RunEventLoopOnce(QuicTimeDelta::FromMilliseconds(50)); | |
| } | |
| thread_id_ = std::thread::id(); | |
| bound_address_ = QuicheSocketAddress(); | |
| dispatcher_->Shutdown(); | |
| io_harness_.reset(); | |
| socket_fd_.reset(); | |
| dispatcher_.reset(); | |
| // Don't reset `event_loop_`, as off-thread callers may attempt to use it | |
| // to call WakeUp(). | |
| } | |
| absl::Status WaitForRunning() { | |
| running_notification_.WaitForNotification(); | |
| QUICHE_CHECK(!absl::IsUnknown(initialization_status_)); | |
| return initialization_status_; | |
| } | |
| void Shutdown() { | |
| QUICHE_CHECK(running_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!shutdown_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!IsRunningOnThread()); | |
| shutdown_notification_.Notify(); | |
| if (event_loop_->SupportsWakeUp()) { | |
| event_loop_->WakeUp(); | |
| } | |
| Join(); | |
| } | |
| absl::StatusOr<QuicheSocketAddress> bound_address() const { | |
| QUICHE_CHECK(IsRunningOnThread()); | |
| return bound_address_; | |
| } | |
| absl::StatusOr<int> SendStreamData(QuicConnectionId server_connection_id, | |
| QuicStreamId stream_id, | |
| absl::Span<QuicheMemSlice> data, | |
| bool fin) { | |
| QUICHE_CHECK(IsRunningOnThread()); | |
| QUICHE_CHECK(dispatcher_); | |
| QUICHE_ASSIGN_OR_RETURN(QuicSession * session, | |
| dispatcher_->GetSession(server_connection_id)); | |
| QuicStream* stream = session->GetActiveStream(stream_id); | |
| if (!stream) { | |
| return absl::NotFoundError("Stream not found."); | |
| } | |
| return stream->WriteMemSlices(data, fin).bytes_consumed; | |
| } | |
| absl::Status SendDatagram(QuicConnectionId server_connection_id, | |
| absl::Span<QuicheMemSlice> data) { | |
| QUICHE_CHECK(IsRunningOnThread()); | |
| QUICHE_CHECK(dispatcher_); | |
| QUICHE_ASSIGN_OR_RETURN(QuicSession * session, | |
| dispatcher_->GetSession(server_connection_id)); | |
| if (!session->connection() || !session->connection()->connected()) { | |
| return absl::FailedPreconditionError("Session is not connected."); | |
| } | |
| DatagramResult result = session->SendDatagram(data); | |
| if (result.status == DATAGRAM_STATUS_SUCCESS) { | |
| return absl::OkStatus(); | |
| } else { | |
| return absl::InternalError(absl::StrCat( | |
| "Failed to send datagram: ", DatagramStatusToString(result.status))); | |
| } | |
| } | |
| void Schedule(quiche::SingleUseCallback<void()> callback) { | |
| QUICHE_CHECK(running_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!shutdown_notification_.HasBeenNotified()); | |
| { | |
| absl::MutexLock lock(scheduled_actions_mutex_); | |
| scheduled_actions_.push(std::move(callback)); | |
| } | |
| if (IsRunningOnThread()) { | |
| // Run all scheduled actions immediately if already on the server thread. | |
| // This is important to avoid deadlocks if the caller of Schedule() waits | |
| // on signals contained in the callback. | |
| ExecuteScheduledActions(); | |
| } else if (event_loop_->SupportsWakeUp()) { | |
| event_loop_->WakeUp(); | |
| } | |
| } | |
| void ScheduleAndWaitForCompletion( | |
| quiche::SingleUseCallback<void()> callback) { | |
| QUICHE_CHECK(running_notification_.HasBeenNotified()); | |
| QUICHE_CHECK(!shutdown_notification_.HasBeenNotified()); | |
| if (IsRunningOnThread()) { | |
| ExecuteScheduledActions(); | |
| std::move(callback)(); | |
| } else { | |
| absl::Notification done; | |
| Schedule([&callback, &done]() { | |
| std::move(callback)(); | |
| done.Notify(); | |
| }); | |
| done.WaitForNotification(); | |
| } | |
| } | |
| bool IsRunningOnThread() const { | |
| return running_notification_.HasBeenNotified() && | |
| thread_id_ == std::this_thread::get_id(); | |
| } | |
| private: | |
| absl::Status Initialize() { | |
| event_loop_ = GetDefaultEventLoop()->Create(QuicDefaultClock::Get()); | |
| dispatcher_ = | |
| std::make_unique<Dispatcher>(config_, crypto_config_, &version_manager_, | |
| event_loop_->CreateAlarmFactory(), | |
| &connection_id_generator_, handler_.get()); | |
| QUICHE_ASSIGN_OR_RETURN(socket_fd_, | |
| CreateAndBindServerSocket(socket_address_)); | |
| QUICHE_ASSIGN_OR_RETURN( | |
| io_harness_, | |
| QuicServerIoHarness::Create(event_loop_.get(), dispatcher_.get(), | |
| socket_fd_.get())); | |
| io_harness_->InitializeWriter(); | |
| return absl::OkStatus(); | |
| } | |
| void ExecuteScheduledActions() { | |
| QUICHE_CHECK(IsRunningOnThread()); | |
| std::queue<quiche::SingleUseCallback<void()>> actions; | |
| { | |
| absl::MutexLock lock(scheduled_actions_mutex_); | |
| actions.swap(scheduled_actions_); | |
| } | |
| while (!actions.empty() && !shutdown_notification_.HasBeenNotified()) { | |
| std::move(actions.front())(); | |
| actions.pop(); | |
| } | |
| } | |
| const QuicheSocketAddress socket_address_; | |
| const QuicConfig* absl_nonnull const config_; | |
| const QuicCryptoServerConfig* absl_nonnull const crypto_config_; | |
| const std::unique_ptr<BasicQuicServer::Handler> absl_nonnull handler_; | |
| // Must be set before `running_notification_` is notified and not changed | |
| // after that. | |
| absl::Status initialization_status_ = absl::UnknownError("Not initialized."); | |
| std::thread::id thread_id_; | |
| absl::Notification running_notification_; | |
| absl::Notification shutdown_notification_; | |
| absl::Mutex scheduled_actions_mutex_; | |
| std::queue<quiche::SingleUseCallback<void()>> scheduled_actions_ | |
| ABSL_GUARDED_BY(scheduled_actions_mutex_); | |
| QuicVersionManager version_manager_{ParsedQuicVersionVector( | |
| kSupportedQuicVersions.begin(), kSupportedQuicVersions.end())}; | |
| DeterministicConnectionIdGenerator connection_id_generator_{ | |
| kQuicDefaultConnectionIdLength}; | |
| std::unique_ptr<QuicEventLoop> event_loop_; | |
| std::unique_ptr<Dispatcher> dispatcher_; | |
| OwnedSocketFd socket_fd_; | |
| std::unique_ptr<QuicServerIoHarness> io_harness_; | |
| QuicheSocketAddress bound_address_; | |
| }; | |
| BasicQuicServer::BasicQuicServer( | |
| QuicheSocketAddress socket_address, | |
| std::unique_ptr<ProofSource> absl_nonnull proof_source) | |
| : socket_address_(std::move(socket_address)), | |
| crypto_config_(std::make_unique<QuicCryptoServerConfig>( | |
| QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(), | |
| std::move(proof_source), KeyExchangeSource::Default())) {} | |
| BasicQuicServer::~BasicQuicServer() { | |
| if (server_thread_) { | |
| QUICHE_LOG(WARNING) << "BasicQuicServer not stopped before destruction."; | |
| QUICHE_CHECK_OK(Stop()); | |
| } | |
| } | |
| absl::Status BasicQuicServer::Start( | |
| std::unique_ptr<Handler> absl_nonnull handler) { | |
| QUICHE_CHECK(!server_thread_) << "Server already running."; | |
| server_thread_ = std::make_unique<ServerThread>( | |
| socket_address_, &config_, crypto_config_.get(), std::move(handler)); | |
| server_thread_->Start(); | |
| absl::Status status = server_thread_->WaitForRunning(); | |
| if (!status.ok()) { | |
| server_thread_->Shutdown(); | |
| server_thread_.reset(); | |
| return status; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status BasicQuicServer::Stop() { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| server_thread_->Shutdown(); | |
| server_thread_.reset(); | |
| return absl::OkStatus(); | |
| } | |
| void BasicQuicServer::Schedule(quiche::SingleUseCallback<void()> callback) { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| server_thread_->Schedule(std::move(callback)); | |
| } | |
| void BasicQuicServer::ScheduleAndWaitForCompletion( | |
| quiche::SingleUseCallback<void()> callback) { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| server_thread_->ScheduleAndWaitForCompletion(std::move(callback)); | |
| } | |
| absl::StatusOr<QuicheSocketAddress> BasicQuicServer::bound_address() { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| absl::StatusOr<QuicheSocketAddress> result; | |
| const ServerThread* server_thread = server_thread_.get(); | |
| ScheduleAndWaitForCompletion( | |
| [&result, server_thread]() { result = server_thread->bound_address(); }); | |
| return result; | |
| } | |
| absl::StatusOr<int> BasicQuicServer::SendStreamData( | |
| QuicConnectionId server_connection_id, QuicStreamId stream_id, | |
| absl::Span<const std::byte> data, bool fin) { | |
| auto mem_slice = QuicheMemSlice::Copy(absl::string_view( | |
| reinterpret_cast<const char*>(data.data()), data.size())); | |
| return SendStreamData(server_connection_id, stream_id, | |
| absl::MakeSpan(&mem_slice, 1), fin); | |
| } | |
| absl::StatusOr<int> BasicQuicServer::SendStreamData( | |
| QuicConnectionId server_connection_id, QuicStreamId stream_id, | |
| absl::Span<quiche::QuicheMemSlice> data, bool fin) { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| ServerThread* server_thread = server_thread_.get(); | |
| absl::StatusOr<int> result; | |
| ScheduleAndWaitForCompletion( | |
| [&result, server_thread, server_connection_id, stream_id, data, fin]() { | |
| result = server_thread->SendStreamData(server_connection_id, stream_id, | |
| data, fin); | |
| }); | |
| return result; | |
| } | |
| absl::Status BasicQuicServer::SendDatagram( | |
| QuicConnectionId server_connection_id, absl::Span<const std::byte> data) { | |
| auto mem_slice = QuicheMemSlice::Copy(absl::string_view( | |
| reinterpret_cast<const char*>(data.data()), data.size())); | |
| return SendDatagram(server_connection_id, absl::MakeSpan(&mem_slice, 1)); | |
| } | |
| absl::Status BasicQuicServer::SendDatagram( | |
| QuicConnectionId server_connection_id, | |
| absl::Span<quiche::QuicheMemSlice> data) { | |
| QUICHE_CHECK(server_thread_) << "Server not running."; | |
| ServerThread* server_thread = server_thread_.get(); | |
| absl::Status result; | |
| ScheduleAndWaitForCompletion( | |
| [&result, server_thread, server_connection_id, data]() { | |
| result = server_thread->SendDatagram(server_connection_id, data); | |
| }); | |
| return result; | |
| } | |
| } // namespace quic::test |