LCOV - code coverage report
Current view: top level - wirestead/transport/uds - uds_server.cc (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 77.4 % 452 350
Test Date: 2026-08-30 10:35:09 Functions: 80.0 % 55 44
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 56.6 % 258 146

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * Copyright 2025 Jinwoo Sung
       3                 :             :  *
       4                 :             :  * Licensed under the Apache License, Version 2.0 (the "License");
       5                 :             :  * you may not use this file except in compliance with the License.
       6                 :             :  * You may obtain a copy of the License at
       7                 :             :  *
       8                 :             :  *     http://www.apache.org/licenses/LICENSE-2.0
       9                 :             :  *
      10                 :             :  * Unless required by applicable law or agreed to in writing, software
      11                 :             :  * distributed under the License is distributed on an "AS IS" BASIS,
      12                 :             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      13                 :             :  * See the License for the specific language governing permissions and
      14                 :             :  * limitations under the License.
      15                 :             :  */
      16                 :             : 
      17                 :             : #include "wirestead/transport/uds/uds_server.hpp"
      18                 :             : 
      19                 :             : #include <spdlog/fmt/fmt.h>
      20                 :             : 
      21                 :             : #include <algorithm>
      22                 :             : #include <atomic>
      23                 :             : #include <boost/asio.hpp>
      24                 :             : #include <cerrno>
      25                 :             : #include <cstdio>
      26                 :             : #include <cstring>
      27                 :             : #include <future>
      28                 :             : #include <mutex>
      29                 :             : #include <stop_token>
      30                 :             : #include <string_view>
      31                 :             : #include <thread>
      32                 :             : #include <unordered_map>
      33                 :             : 
      34                 :             : #include "wirestead/base/platform.hpp"
      35                 :             : #include "wirestead/builder/auto_initializer.hpp"
      36                 :             : #include "wirestead/concurrency/io_context_manager.hpp"
      37                 :             : #include "wirestead/concurrency/io_thread_hook.hpp"
      38                 :             : #include "wirestead/concurrency/thread_safe_state.hpp"
      39                 :             : #include "wirestead/diagnostics/logger.hpp"
      40                 :             : #include "wirestead/diagnostics/runtime_stats_counter.hpp"
      41                 :             : #include "wirestead/interface/iuds_acceptor.hpp"
      42                 :             : #include "wirestead/transport/base/error_info_holder.hpp"
      43                 :             : #include "wirestead/transport/uds/boost_uds_acceptor.hpp"
      44                 :             : #include "wirestead/transport/uds/uds_server_session.hpp"
      45                 :             : 
      46                 :             : #if !defined(WIRESTEAD_PLATFORM_WINDOWS)
      47                 :             : #include <sys/socket.h>
      48                 :             : #include <sys/stat.h>
      49                 :             : #include <sys/un.h>
      50                 :             : #include <unistd.h>
      51                 :             : #endif
      52                 :             : 
      53                 :             : namespace wirestead {
      54                 :             : namespace transport {
      55                 :             : 
      56                 :             : namespace net = boost::asio;
      57                 :             : using uds = net::local::stream_protocol;
      58                 :             : 
      59                 :             : namespace {
      60                 :             : 
      61                 :             : // #438: an existing path at the configured socket location should only ever
      62                 :             : // be silently removed if it's a genuinely stale (no longer listened-on)
      63                 :             : // socket file - not a regular file/directory (misconfiguration) and not a
      64                 :             : // socket another live process is still listening on (which would otherwise
      65                 :             : // be silently hijacked instead of failing with a clear error). Returns a
      66                 :             : // non-empty reason string if bind should be refused; empty if it's safe to
      67                 :             : // remove the path (or there's nothing there) and proceed.
      68                 :          32 : std::string existing_uds_path_blocks_bind(const std::string& path) {
      69                 :             : #if defined(WIRESTEAD_PLATFORM_WINDOWS)
      70                 :             :   (void)path;
      71                 :             :   return {};
      72                 :             : #else
      73                 :          32 :   struct stat st {};
      74         [ +  + ]:          32 :   if (::stat(path.c_str(), &st) != 0) {
      75                 :          29 :     return {};  // nothing exists at this path - nothing to check
      76                 :             :   }
      77         [ +  + ]:           3 :   if (!S_ISSOCK(st.st_mode)) {
      78                 :           2 :     return "existing path is not a socket file";
      79                 :             :   }
      80                 :             : 
      81                 :           2 :   int probe_fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
      82         [ -  + ]:           2 :   if (probe_fd < 0) {
      83                 :           0 :     return {};  // can't probe - fall back to prior (remove-and-proceed) behavior
      84                 :             :   }
      85                 :           2 :   struct sockaddr_un addr {};
      86                 :           2 :   addr.sun_family = AF_UNIX;
      87                 :           2 :   std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
      88                 :           2 :   int rc = ::connect(probe_fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
      89                 :           2 :   ::close(probe_fd);
      90         [ +  + ]:           2 :   if (rc == 0) {
      91                 :           2 :     return "another process is already listening on this socket path";
      92                 :             :   }
      93                 :           1 :   return {};  // stale socket (nothing accepted the probe connect) - safe to remove
      94                 :             : #endif
      95                 :             : }
      96                 :             : 
      97                 :             : }  // namespace
      98                 :             : 
      99                 :             : struct UdsServer::Impl {
     100                 :             :   std::unique_ptr<net::io_context> owned_ioc_;
     101                 :             :   net::io_context* ioc_ = nullptr;
     102                 :             :   std::unique_ptr<net::executor_work_guard<net::io_context::executor_type>> work_guard_;
     103                 :             :   std::jthread ioc_thread_;
     104                 :             :   bool owns_ioc_ = true;
     105                 :             : 
     106                 :             :   std::atomic<bool> stopping_{false};
     107                 :             :   std::atomic<ClientId> next_client_id_{0};
     108                 :             :   // #438: only this instance's own successful bind() may remove the socket
     109                 :             :   // file on cleanup. Without this, a second UdsServer pointed at the same
     110                 :             :   // path whose start() failed before ever binding (e.g. because a live
     111                 :             :   // listener already owns that path) would still delete the first server's
     112                 :             :   // socket file the moment stop()/the destructor ran - the exact hijack
     113                 :             :   // #438 is about, just reached via stop() instead of start().
     114                 :             :   std::atomic<bool> bound_{false};
     115                 :             : 
     116                 :             :   std::unique_ptr<interface::UdsAcceptorInterface> acceptor_;
     117                 :             :   config::UdsServerConfig cfg_;
     118                 :             : 
     119                 :          34 :   concurrency::AtomicLinkState state_{base::LinkState::Idle};
     120                 :             :   // Shared snapshots for the handlers the io thread copies out per received
     121                 :             :   // chunk - a std::function copy allocates whenever the target outgrows its
     122                 :             :   // small-object buffer. See interface::SharedCallback. The connect/disconnect
     123                 :             :   // handlers stay plain: they fire once per connection, not per chunk.
     124                 :             :   interface::SharedCallback<OnBytes> on_bytes_;
     125                 :             :   interface::SharedCallback<OnState> on_state_;
     126                 :             :   interface::SharedCallback<OnBackpressure> on_bp_;
     127                 :             :   MultiClientConnectHandler on_multi_connect_;
     128                 :             :   interface::SharedCallback<MultiClientDataHandler> on_multi_data_;
     129                 :             :   MultiClientDisconnectHandler on_multi_disconnect_;
     130                 :             :   diagnostics::RuntimeStatsCounters stats_;
     131                 :             : 
     132                 :             :   mutable std::mutex sessions_mutex_;
     133                 :             :   std::unordered_map<ClientId, std::shared_ptr<UdsServerSession>> sessions_;
     134                 :             : 
     135                 :             :   ErrorInfoHolder error_info_holder_{"uds_server"};
     136                 :             : 
     137                 :          34 :   Impl(const config::UdsServerConfig& cfg, net::io_context* ioc_ptr)
     138         [ +  + ]:          34 :       : owned_ioc_(ioc_ptr ? nullptr : std::make_unique<net::io_context>()),
     139         [ +  + ]:          34 :         ioc_(ioc_ptr ? ioc_ptr : owned_ioc_.get()),
     140                 :          34 :         owns_ioc_(!ioc_ptr),
     141                 :          68 :         cfg_(cfg) {
     142                 :          34 :     cfg_.validate_and_clamp();
     143                 :          34 :     acceptor_ = std::make_unique<BoostUdsAcceptor>(*ioc_);
     144                 :          34 :   }
     145                 :          34 :   ~Impl() {
     146                 :          34 :     stopping_ = true;
     147         [ -  + ]:          34 :     if (work_guard_) {
     148                 :           0 :       work_guard_.reset();
     149                 :             :     }
     150   [ +  -  +  + ]:          34 :     if (ioc_ && owns_ioc_) {
     151         [ -  + ]:          23 :       if (ioc_thread_.joinable()) {
     152         [ #  # ]:           0 :         if (std::this_thread::get_id() == ioc_thread_.get_id()) {
     153                 :           0 :           ioc_thread_.detach();
     154                 :             :         } else {
     155                 :           0 :           ioc_thread_.request_stop();
     156                 :           0 :           ioc_thread_.join();
     157                 :             :         }
     158                 :             :       }
     159                 :             :     }
     160                 :             :     // UDS Cleanup: socket file should be removed, but only if this instance
     161                 :             :     // actually bound it (#438).
     162         [ -  + ]:          34 :     if (bound_.load()) {
     163                 :           0 :       std::remove(cfg_.socket_path.c_str());
     164                 :             :     }
     165                 :          34 :   }
     166                 :             :   void do_accept(std::shared_ptr<UdsServer> self);
     167                 :             :   void notify_state();
     168                 :             : 
     169                 :          34 :   void perform_cleanup() {
     170                 :             :     try {
     171                 :          34 :       boost::system::error_code ec;
     172         [ +  - ]:          34 :       if (acceptor_) {
     173                 :          34 :         acceptor_->close(ec);
     174                 :             :       }
     175                 :             : 
     176                 :          34 :       std::vector<std::shared_ptr<UdsServerSession>> sessions_to_stop;
     177                 :             :       {
     178                 :          34 :         std::lock_guard<std::mutex> lock(sessions_mutex_);
     179         [ +  + ]:          44 :         for (auto& pair : sessions_) {
     180                 :          10 :           sessions_to_stop.push_back(pair.second);
     181                 :             :         }
     182                 :          34 :         sessions_.clear();
     183                 :          34 :       }
     184                 :             : 
     185         [ +  + ]:          44 :       for (auto& session : sessions_to_stop) {
     186         [ +  - ]:          10 :         if (session) {
     187                 :          10 :           session->stop();
     188                 :             :         }
     189                 :             :       }
     190                 :             : 
     191         [ +  + ]:          34 :       if (bound_.exchange(false)) {
     192                 :          27 :         std::remove(cfg_.socket_path.c_str());
     193                 :             :       }
     194                 :             : 
     195                 :          34 :       state_.set(base::LinkState::Idle);
     196                 :          34 :       notify_state();
     197                 :          34 :     } catch (...) {
     198                 :           0 :     }
     199                 :          34 :   }
     200                 :             : 
     201                 :          34 :   void stop(std::shared_ptr<UdsServer> self) {
     202         [ -  + ]:          34 :     if (stopping_.exchange(true)) {
     203                 :           0 :       return;
     204                 :             :     }
     205                 :             : 
     206                 :             :     {
     207                 :          34 :       std::lock_guard<std::mutex> lock(sessions_mutex_);
     208                 :          34 :       on_bytes_ = nullptr;
     209                 :          34 :       on_state_ = nullptr;
     210                 :          34 :       on_bp_ = nullptr;
     211                 :          34 :       on_multi_connect_ = nullptr;
     212                 :          34 :       on_multi_data_ = nullptr;
     213                 :          34 :       on_multi_disconnect_ = nullptr;
     214                 :          34 :     }
     215                 :             : 
     216         [ -  + ]:          34 :     if (ioc_->get_executor().running_in_this_thread()) {
     217                 :           0 :       perform_cleanup();
     218         [ #  # ]:           0 :       if (owns_ioc_) {
     219                 :           0 :         work_guard_.reset();
     220                 :           0 :         ioc_->stop();
     221                 :             :       }
     222                 :           0 :       return;
     223                 :             :     }
     224                 :             : 
     225   [ +  +  +  + ]:          34 :     bool has_active_ioc = owns_ioc_ || !ioc_->stopped();
     226                 :             : 
     227   [ +  +  +  -  :          34 :     if (has_active_ioc && self) {
                   +  + ]
     228                 :          28 :       auto cleanup_promise = std::make_shared<std::promise<void>>();
     229                 :          28 :       auto cleanup_future = cleanup_promise->get_future();
     230                 :             : 
     231                 :          28 :       std::weak_ptr<UdsServer> weak_self = self;
     232                 :          28 :       net::dispatch(*ioc_, [weak_self, cleanup_promise]() {
     233         [ +  - ]:          23 :         if (auto shared_self = weak_self.lock()) {
     234                 :          23 :           auto* cleanup_impl = shared_self->get_impl();
     235                 :          23 :           cleanup_impl->perform_cleanup();
     236                 :          23 :         }
     237                 :          23 :         cleanup_promise->set_value();
     238                 :          23 :       });
     239                 :             : 
     240   [ +  -  +  + ]:          28 :       if (cleanup_future.wait_for(std::chrono::seconds(2)) == std::future_status::timeout) {
     241                 :           5 :         perform_cleanup();
     242                 :             :       }
     243                 :          28 :     } else {
     244                 :           6 :       perform_cleanup();
     245                 :             :     }
     246                 :             : 
     247         [ +  + ]:          34 :     if (owns_ioc_) {
     248                 :          23 :       work_guard_.reset();
     249                 :          23 :       ioc_->stop();
     250                 :             :     }
     251                 :             : 
     252   [ +  +  +  +  :          34 :     if (owns_ioc_ && ioc_thread_.joinable()) {
                   +  + ]
     253         [ +  - ]:          19 :       if (std::this_thread::get_id() != ioc_thread_.get_id()) {
     254                 :          19 :         ioc_thread_.join();
     255                 :             :       } else {
     256                 :           0 :         ioc_thread_.detach();
     257                 :             :       }
     258                 :          19 :       ioc_->restart();
     259                 :             :     }
     260                 :             :   }
     261                 :             : };
     262                 :             : 
     263                 :          23 : std::shared_ptr<UdsServer> UdsServer::create(const config::UdsServerConfig& cfg) {
     264                 :          23 :   return std::shared_ptr<UdsServer>(new UdsServer(cfg));
     265                 :             : }
     266                 :             : 
     267                 :          11 : std::shared_ptr<UdsServer> UdsServer::create(const config::UdsServerConfig& cfg,
     268                 :             :                                              std::unique_ptr<interface::UdsAcceptorInterface> acceptor,
     269                 :             :                                              net::io_context& ioc) {
     270                 :          11 :   return std::shared_ptr<UdsServer>(new UdsServer(cfg, std::move(acceptor), ioc));
     271                 :             : }
     272                 :             : 
     273                 :          23 : UdsServer::UdsServer(const config::UdsServerConfig& cfg) : impl_(std::make_unique<Impl>(cfg, nullptr)) {}
     274                 :          11 : UdsServer::UdsServer(const config::UdsServerConfig& cfg, std::unique_ptr<interface::UdsAcceptorInterface> acceptor,
     275                 :          11 :                      net::io_context& ioc)
     276                 :          11 :     : impl_(std::make_unique<Impl>(cfg, &ioc)) {
     277                 :          11 :   impl_->acceptor_ = std::move(acceptor);
     278                 :          11 : }
     279                 :             : 
     280                 :          68 : UdsServer::~UdsServer() {
     281   [ +  -  -  +  :          34 :   if (impl_ && impl_->state_.get() != base::LinkState::Idle) {
                   -  + ]
     282                 :           0 :     impl_->stop(nullptr);
     283                 :             :   }
     284                 :          68 : }
     285                 :             : 
     286                 :           0 : UdsServer::UdsServer(UdsServer&&) noexcept = default;
     287                 :           0 : UdsServer& UdsServer::operator=(UdsServer&&) noexcept = default;
     288                 :             : 
     289                 :          33 : void UdsServer::start() {
     290         [ -  + ]:          39 :   if (impl_->state_.get() == base::LinkState::Listening) return;
     291                 :             : 
     292                 :          33 :   impl_->stopping_ = false;
     293                 :             :   // Restart contract (#444): stats() resets on restart. The server-level
     294                 :             :   // counters now outlive the sessions that fed them, so clearing them here is
     295                 :             :   // what keeps that promise - before absorption they were empty and a restart
     296                 :             :   // zeroed the aggregate for free.
     297                 :          33 :   impl_->stats_.reset(0);
     298                 :             : 
     299   [ +  -  +  + ]:          33 :   if (!impl_->cfg_.is_valid()) {
     300                 :           1 :     WIRESTEAD_LOG_ERROR("uds_server", "start", "Invalid UDS server configuration or socket path");
     301                 :           1 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONFIGURATION,
     302                 :             :                                            "start", {}, "Invalid UDS server configuration or socket path", false, 0);
     303                 :           1 :     impl_->state_.set(base::LinkState::Error);
     304                 :           1 :     impl_->notify_state();
     305                 :           1 :     return;
     306                 :             :   }
     307                 :             : 
     308                 :             :   // #438: only remove an existing path at this location if it's actually a
     309                 :             :   // stale socket - never a regular file/directory (misconfiguration), and
     310                 :             :   // never a socket another live process is still listening on (that would
     311                 :             :   // otherwise be silently hijacked instead of failing loudly).
     312                 :          32 :   std::string block_reason = existing_uds_path_blocks_bind(impl_->cfg_.socket_path);
     313         [ +  + ]:          32 :   if (!block_reason.empty()) {
     314                 :           2 :     std::string msg = fmt::format("Refusing to bind {}: {}", impl_->cfg_.socket_path, block_reason);
     315                 :           2 :     WIRESTEAD_LOG_ERROR("uds_server", "start", msg);
     316                 :           4 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONNECTION,
     317                 :           2 :                                            "start", make_error_code(boost::system::errc::address_in_use), msg, false,
     318                 :             :                                            0);
     319                 :           2 :     impl_->state_.set(base::LinkState::Error);
     320                 :           2 :     impl_->notify_state();
     321                 :           2 :     return;
     322                 :           2 :   }
     323                 :          30 :   std::remove(impl_->cfg_.socket_path.c_str());
     324                 :             : 
     325                 :          30 :   boost::system::error_code ec;
     326                 :          30 :   impl_->acceptor_->open(uds(), ec);
     327         [ -  + ]:          30 :   if (ec) {
     328                 :           0 :     std::string msg = fmt::format("Failed to open acceptor: {}", ec.message());
     329                 :           0 :     WIRESTEAD_LOG_ERROR("uds_server", "start", msg);
     330                 :           0 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::SYSTEM, "open",
     331                 :             :                                            ec, msg, false, 0);
     332                 :           0 :     impl_->state_.set(base::LinkState::Error);
     333                 :           0 :     impl_->notify_state();
     334                 :           0 :     return;
     335                 :           0 :   }
     336                 :             : 
     337                 :          30 :   uds::endpoint endpoint;
     338                 :             :   try {
     339                 :          30 :     endpoint = uds::endpoint(impl_->cfg_.socket_path);
     340                 :           0 :   } catch (const std::exception& e) {
     341                 :           0 :     std::string msg = fmt::format("Invalid UDS endpoint: {}", e.what());
     342                 :           0 :     WIRESTEAD_LOG_ERROR("uds_server", "start", msg);
     343                 :           0 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONFIGURATION,
     344                 :           0 :                                            "start", make_error_code(boost::system::errc::filename_too_long), msg, false,
     345                 :             :                                            0);
     346                 :           0 :     impl_->state_.set(base::LinkState::Error);
     347                 :           0 :     impl_->notify_state();
     348                 :           0 :     return;
     349                 :           0 :   }
     350                 :             : 
     351                 :          30 :   impl_->acceptor_->bind(endpoint, ec);
     352         [ +  + ]:          30 :   if (ec) {
     353                 :           3 :     std::string msg = fmt::format("Failed to bind to {}: {}", impl_->cfg_.socket_path, ec.message());
     354                 :           3 :     WIRESTEAD_LOG_ERROR("uds_server", "start", msg);
     355                 :           3 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONNECTION,
     356                 :             :                                            "bind", ec, msg, false, 0);
     357                 :           3 :     impl_->state_.set(base::LinkState::Error);
     358                 :           3 :     impl_->notify_state();
     359                 :           3 :     return;
     360                 :           3 :   }
     361                 :          27 :   impl_->bound_.store(true);
     362                 :             : 
     363                 :          27 :   impl_->acceptor_->listen(net::socket_base::max_listen_connections, ec);
     364         [ -  + ]:          27 :   if (ec) {
     365                 :           0 :     std::string msg = fmt::format("Failed to listen: {}", ec.message());
     366                 :           0 :     WIRESTEAD_LOG_ERROR("uds_server", "start", msg);
     367                 :           0 :     impl_->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONNECTION,
     368                 :             :                                            "listen", ec, msg, false, 0);
     369                 :           0 :     impl_->state_.set(base::LinkState::Error);
     370                 :           0 :     impl_->notify_state();
     371                 :           0 :     return;
     372                 :           0 :   }
     373                 :             : 
     374                 :             :   // #438: restrict local access to the socket file if requested. Best-effort
     375                 :             :   // - a chmod failure is logged but does not fail startup, since the socket
     376                 :             :   // is already bound and listening at this point.
     377         [ +  + ]:          27 :   if (impl_->cfg_.socket_permissions != -1) {
     378                 :             : #if !defined(WIRESTEAD_PLATFORM_WINDOWS)
     379         [ -  + ]:           1 :     if (::chmod(impl_->cfg_.socket_path.c_str(), static_cast<mode_t>(impl_->cfg_.socket_permissions)) != 0) {
     380                 :           0 :       WIRESTEAD_LOG_WARNING("uds_server", "start",
     381                 :             :                             fmt::format("Failed to chmod socket {}: {}", impl_->cfg_.socket_path, strerror(errno)));
     382                 :             :     }
     383                 :             : #else
     384                 :             :     WIRESTEAD_LOG_WARNING("uds_server", "start", "socket_permissions is ignored on Windows");
     385                 :             : #endif
     386                 :             :   }
     387                 :             : 
     388                 :          27 :   impl_->state_.set(base::LinkState::Listening);
     389                 :          27 :   impl_->notify_state();
     390                 :             : 
     391   [ +  +  +  -  :          27 :   if (impl_->owns_ioc_ && !impl_->ioc_thread_.joinable()) {
                   +  + ]
     392   [ +  -  -  + ]:          19 :     if (impl_->ioc_->stopped()) {
     393                 :           0 :       impl_->ioc_->restart();
     394                 :             :     }
     395                 :          19 :     impl_->work_guard_ =
     396                 :          38 :         std::make_unique<net::executor_work_guard<net::io_context::executor_type>>(net::make_work_guard(*impl_->ioc_));
     397                 :          38 :     impl_->ioc_thread_ = std::jthread([impl = impl_.get()](std::stop_token st) {
     398                 :          19 :       wirestead::concurrency::run_io_thread_init();
     399                 :             :       try {
     400                 :          19 :         std::stop_callback cb(st, [impl] { impl->ioc_->stop(); });
     401                 :          19 :         impl->ioc_->run();
     402                 :          19 :       } catch (...) {
     403                 :           0 :       }
     404                 :          38 :     });
     405                 :             :   }
     406                 :             : 
     407                 :          54 :   net::post(impl_->ioc_->get_executor(), [self = shared_from_this()]() { self->impl_->do_accept(self); });
     408                 :          32 : }
     409                 :             : 
     410                 :          34 : void UdsServer::stop() { impl_->stop(shared_from_this()); }
     411                 :             : 
     412                 :           3 : bool UdsServer::is_connected() const { return impl_->state_.get() == base::LinkState::Listening; }
     413                 :           0 : bool UdsServer::is_backpressure_active() const { return false; }
     414                 :             : 
     415                 :           0 : bool UdsServer::is_backpressure_active(ClientId client_id) const {
     416                 :           0 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     417                 :           0 :   auto it = impl_->sessions_.find(client_id);
     418   [ #  #  #  #  :           0 :   if (it != impl_->sessions_.end() && it->second) {
                   #  # ]
     419                 :           0 :     return it->second->is_backpressure_active();
     420                 :             :   }
     421                 :           0 :   return false;
     422                 :           0 : }
     423                 :             : 
     424                 :          17 : boost::asio::any_io_executor UdsServer::get_executor() { return impl_->ioc_->get_executor(); }
     425                 :             : 
     426                 :        1879 : wrapper::RuntimeStats UdsServer::stats() const {
     427                 :        1879 :   auto aggregate = impl_->stats_.snapshot(0, 0, false);
     428                 :        1879 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     429         [ +  + ]:        1885 :   for (const auto& pair : impl_->sessions_) {
     430         [ -  + ]:           6 :     if (!pair.second) continue;
     431                 :           6 :     const auto session_stats = pair.second->stats();
     432                 :           6 :     aggregate.bytes_accepted += session_stats.bytes_accepted;
     433                 :           6 :     aggregate.messages_accepted += session_stats.messages_accepted;
     434                 :           6 :     aggregate.bytes_sent += session_stats.bytes_sent;
     435                 :           6 :     aggregate.messages_sent += session_stats.messages_sent;
     436                 :           6 :     aggregate.bytes_received += session_stats.bytes_received;
     437                 :           6 :     aggregate.messages_received += session_stats.messages_received;
     438                 :           6 :     aggregate.failed_sends += session_stats.failed_sends;
     439                 :           6 :     aggregate.dropped_messages += session_stats.dropped_messages;
     440                 :           6 :     aggregate.dropped_bytes += session_stats.dropped_bytes;
     441                 :           6 :     aggregate.backpressure_events += session_stats.backpressure_events;
     442                 :           6 :     aggregate.queued_bytes += session_stats.queued_bytes;
     443                 :           6 :     aggregate.pending_bytes += session_stats.pending_bytes;
     444                 :             :     // Peak, not a total: summing per-session peaks would report a depth no
     445                 :             :     // session ever reached, because the peaks need not have been simultaneous.
     446                 :           6 :     aggregate.max_queued_bytes = std::max(aggregate.max_queued_bytes, session_stats.max_queued_bytes);
     447   [ +  -  -  + ]:           6 :     aggregate.backpressure_active = aggregate.backpressure_active || session_stats.backpressure_active;
     448                 :             :   }
     449                 :        3758 :   return aggregate;
     450                 :        1879 : }
     451                 :             : 
     452                 :           0 : void UdsServer::reset_stats() {
     453                 :           0 :   impl_->stats_.reset(0);
     454                 :           0 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     455         [ #  # ]:           0 :   for (const auto& pair : impl_->sessions_) {
     456   [ #  #  #  # ]:           0 :     if (pair.second) pair.second->reset_stats();
     457                 :             :   }
     458                 :           0 : }
     459                 :             : 
     460                 :           3 : std::optional<diagnostics::ErrorInfo> UdsServer::last_error_info() const {
     461                 :           3 :   return impl_->error_info_holder_.last_error_info();
     462                 :             : }
     463                 :             : 
     464                 :           1 : bool UdsServer::async_write_copy(memory::ConstByteSpan data) {
     465                 :           1 :   auto shared_data = std::make_shared<const std::vector<uint8_t>>(data.begin(), data.end());
     466                 :           2 :   return async_write_shared(shared_data);
     467                 :           1 : }
     468                 :             : 
     469                 :           1 : bool UdsServer::async_write_move(std::vector<uint8_t>&& data) {
     470                 :           1 :   auto shared_data = std::make_shared<const std::vector<uint8_t>>(std::move(data));
     471                 :           2 :   return async_write_shared(shared_data);
     472                 :           1 : }
     473                 :             : 
     474                 :           3 : bool UdsServer::async_write_shared(std::shared_ptr<const std::vector<uint8_t>> data) {
     475   [ +  -  +  -  :           3 :   if (impl_->stopping_.load() || !data || data->empty()) {
             -  +  -  + ]
     476                 :           0 :     impl_->stats_.record_failed_send();
     477                 :           0 :     return false;
     478                 :             :   }
     479                 :           3 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     480                 :           3 :   bool sent = false;
     481                 :           3 :   bool attempted = false;
     482         [ -  + ]:           3 :   for (auto& pair : impl_->sessions_) {
     483   [ #  #  #  #  :           0 :     if (pair.second && pair.second->alive() && pair.second->async_write_shared(data)) {
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     484                 :           0 :       attempted = true;
     485                 :           0 :       sent = true;
     486   [ #  #  #  #  :           0 :     } else if (pair.second && pair.second->alive()) {
             #  #  #  # ]
     487                 :           0 :       attempted = true;
     488                 :             :     }
     489                 :             :   }
     490         [ +  - ]:           3 :   if (!attempted) impl_->stats_.record_failed_send();
     491                 :           3 :   return sent;
     492                 :           3 : }
     493                 :             : 
     494                 :           0 : bool UdsServer::async_try_write_copy(memory::ConstByteSpan data) {
     495                 :           0 :   auto shared_data = std::make_shared<const std::vector<uint8_t>>(data.begin(), data.end());
     496                 :           0 :   return async_try_write_shared(shared_data);
     497                 :           0 : }
     498                 :             : 
     499                 :           0 : bool UdsServer::async_try_write_move(std::vector<uint8_t>&& data) {
     500                 :           0 :   auto shared_data = std::make_shared<const std::vector<uint8_t>>(std::move(data));
     501                 :           0 :   return async_try_write_shared(shared_data);
     502                 :           0 : }
     503                 :             : 
     504                 :          39 : bool UdsServer::async_try_write_shared(std::shared_ptr<const std::vector<uint8_t>> data) {
     505   [ +  -  +  -  :          39 :   if (impl_->stopping_.load() || !data || data->empty()) {
             -  +  -  + ]
     506                 :           0 :     impl_->stats_.record_failed_send();
     507                 :           0 :     return false;
     508                 :             :   }
     509                 :          39 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     510                 :          39 :   bool sent = false;
     511                 :          39 :   bool attempted = false;
     512         [ +  + ]:         108 :   for (auto& pair : impl_->sessions_) {
     513   [ +  -  +  -  :          69 :     if (pair.second && pair.second->alive()) {
             +  -  +  - ]
     514                 :          69 :       attempted = true;
     515   [ +  -  +  - ]:          69 :       if (pair.second->async_try_write_shared(data)) sent = true;
     516                 :             :     }
     517                 :             :   }
     518         [ +  + ]:          39 :   if (!attempted) impl_->stats_.record_failed_send();
     519                 :          39 :   return sent;
     520                 :          39 : }
     521                 :             : 
     522                 :          17 : void UdsServer::on_bytes(OnBytes cb) {
     523                 :          17 :   auto shared = interface::share_callback(std::move(cb));
     524                 :          17 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     525                 :          17 :   impl_->on_bytes_ = std::move(shared);
     526                 :          17 : }
     527                 :             : 
     528                 :          37 : void UdsServer::on_state(OnState cb) {
     529                 :          37 :   auto shared = interface::share_callback(std::move(cb));
     530                 :          37 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     531                 :          37 :   impl_->on_state_ = std::move(shared);
     532                 :          37 : }
     533                 :             : 
     534                 :          34 : void UdsServer::on_backpressure(OnBackpressure cb) {
     535                 :          34 :   auto shared = interface::share_callback(std::move(cb));
     536                 :          34 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     537                 :          34 :   impl_->on_bp_ = std::move(shared);
     538                 :          34 : }
     539                 :             : 
     540                 :          38 : bool UdsServer::broadcast(std::string_view message) {
     541                 :             :   auto data =
     542                 :           0 :       std::make_shared<const std::vector<uint8_t>>(reinterpret_cast<const uint8_t*>(message.data()),
     543                 :          38 :                                                    reinterpret_cast<const uint8_t*>(message.data()) + message.size());
     544                 :          76 :   return async_try_write_shared(data);
     545                 :          38 : }
     546                 :             : 
     547                 :           1 : bool UdsServer::broadcast(memory::ConstByteSpan data) {
     548                 :           1 :   auto shared_data = std::make_shared<const std::vector<uint8_t>>(data.begin(), data.end());
     549                 :           2 :   return async_try_write_shared(shared_data);
     550                 :           1 : }
     551                 :             : 
     552                 :           0 : bool UdsServer::send_to_client(ClientId client_id, std::string_view message) {
     553                 :           0 :   return send_to_client(client_id,
     554                 :           0 :                         memory::ConstByteSpan(reinterpret_cast<const uint8_t*>(message.data()), message.size()));
     555                 :             : }
     556                 :             : 
     557                 :           0 : bool UdsServer::send_to_client(ClientId client_id, memory::ConstByteSpan data) {
     558                 :           0 :   std::shared_ptr<UdsServerSession> session;
     559                 :             :   {
     560                 :           0 :     std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     561                 :           0 :     auto it = impl_->sessions_.find(client_id);
     562         [ #  # ]:           0 :     if (it != impl_->sessions_.end()) session = it->second;
     563                 :           0 :   }
     564         [ #  # ]:           0 :   if (session) {
     565                 :           0 :     return session->async_write_copy(data);
     566                 :             :   }
     567                 :           0 :   impl_->stats_.record_failed_send();
     568                 :           0 :   return false;
     569                 :           0 : }
     570                 :             : 
     571                 :           2 : bool UdsServer::try_send_to_client(ClientId client_id, std::string_view message) {
     572                 :           6 :   return try_send_to_client(client_id,
     573                 :           6 :                             memory::ConstByteSpan(reinterpret_cast<const uint8_t*>(message.data()), message.size()));
     574                 :             : }
     575                 :             : 
     576                 :           2 : bool UdsServer::try_send_to_client(ClientId client_id, memory::ConstByteSpan data) {
     577                 :           2 :   std::shared_ptr<UdsServerSession> session;
     578                 :             :   {
     579                 :           2 :     std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     580                 :           2 :     auto it = impl_->sessions_.find(client_id);
     581         [ +  - ]:           2 :     if (it != impl_->sessions_.end()) session = it->second;
     582                 :           2 :   }
     583         [ +  - ]:           2 :   if (session) {
     584                 :           2 :     return session->async_try_write_copy(data);
     585                 :             :   }
     586                 :           0 :   impl_->stats_.record_failed_send();
     587                 :           0 :   return false;
     588                 :           2 : }
     589                 :             : 
     590                 :          21 : size_t UdsServer::client_count() const {
     591                 :          21 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     592                 :          42 :   return impl_->sessions_.size();
     593                 :          21 : }
     594                 :             : 
     595                 :           2 : std::vector<ClientId> UdsServer::connected_clients() const {
     596                 :           2 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     597                 :           2 :   std::vector<ClientId> ids;
     598   [ +  -  +  + ]:           4 :   for (const auto& pair : impl_->sessions_) ids.push_back(pair.first);
     599                 :           4 :   return ids;
     600                 :           2 : }
     601                 :             : 
     602                 :           5 : std::optional<wrapper::RuntimeStats> UdsServer::client_stats(ClientId client_id) const {
     603                 :           5 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     604                 :           5 :   auto it = impl_->sessions_.find(client_id);
     605   [ +  +  -  +  :           5 :   if (it == impl_->sessions_.end() || !it->second) return std::nullopt;
                   +  + ]
     606                 :           3 :   return it->second->stats();
     607                 :           5 : }
     608                 :             : 
     609                 :           0 : void UdsServer::set_client_limit(size_t max_clients) {
     610                 :           0 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     611                 :           0 :   impl_->cfg_.max_connections =
     612                 :           0 :       static_cast<int>(std::min(max_clients, static_cast<size_t>(base::constants::MAX_MAX_CONNECTIONS)));
     613                 :           0 : }
     614                 :             : 
     615                 :          17 : void UdsServer::on_multi_connect(MultiClientConnectHandler handler) {
     616                 :          17 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     617                 :          17 :   impl_->on_multi_connect_ = std::move(handler);
     618                 :          17 : }
     619                 :             : 
     620                 :          17 : void UdsServer::on_multi_data(MultiClientDataHandler handler) {
     621                 :          17 :   auto shared = interface::share_callback(std::move(handler));
     622                 :          17 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     623                 :          17 :   impl_->on_multi_data_ = std::move(shared);
     624                 :          17 : }
     625                 :             : 
     626                 :          17 : void UdsServer::on_multi_disconnect(MultiClientDisconnectHandler handler) {
     627                 :          17 :   std::lock_guard<std::mutex> lock(impl_->sessions_mutex_);
     628                 :          17 :   impl_->on_multi_disconnect_ = std::move(handler);
     629                 :          17 : }
     630                 :             : 
     631                 :          14 : base::LinkState UdsServer::state() const { return impl_->state_.get(); }
     632                 :             : 
     633                 :          51 : void UdsServer::Impl::do_accept(std::shared_ptr<UdsServer> self) {
     634                 :          51 :   acceptor_->async_accept([self](const boost::system::error_code& ec, uds::socket socket) {
     635   [ +  -  +  +  :          47 :     if (!self || self->impl_->stopping_) return;
                   +  + ]
     636                 :             : 
     637         [ +  + ]:          24 :     if (!ec) {
     638                 :             :       ClientId client_id;
     639                 :             :       {
     640                 :          23 :         std::lock_guard<std::mutex> lock(self->impl_->sessions_mutex_);
     641   [ +  +  -  + ]:          31 :         if (self->impl_->cfg_.max_connections > 0 &&
     642         [ -  + ]:           8 :             self->impl_->sessions_.size() >= static_cast<size_t>(self->impl_->cfg_.max_connections)) {
     643                 :           0 :           boost::system::error_code ignored;
     644                 :           0 :           socket.close(ignored);
     645                 :           0 :           auto* impl = self->impl_.get();
     646                 :           0 :           impl->do_accept(self);
     647                 :           0 :           return;
     648                 :             :         }
     649                 :          23 :         client_id = self->impl_->next_client_id_++;
     650                 :          23 :       }
     651                 :             : 
     652                 :             :       auto session = std::make_shared<UdsServerSession>(
     653                 :          23 :           *self->impl_->ioc_, std::move(socket), self->impl_->cfg_.backpressure_threshold,
     654                 :          23 :           self->impl_->cfg_.idle_timeout_ms, self->impl_->cfg_.backpressure_strategy,
     655                 :          46 :           self->impl_->cfg_.enable_memory_pool, self->impl_->cfg_.read_buffer_size);
     656                 :             : 
     657                 :          23 :       std::weak_ptr<UdsServer> weak_self = self;
     658                 :          23 :       session->on_bytes([weak_self, client_id](memory::ConstByteSpan data) {
     659                 :          19 :         auto s = weak_self.lock();
     660         [ -  + ]:          19 :         if (!s) return;
     661                 :          19 :         interface::SharedCallback<MultiClientDataHandler> data_handler;
     662                 :          19 :         interface::SharedCallback<OnBytes> bytes_handler;
     663                 :             :         {
     664                 :          19 :           std::lock_guard<std::mutex> lock(s->impl_->sessions_mutex_);
     665                 :          19 :           data_handler = s->impl_->on_multi_data_;
     666                 :          19 :           bytes_handler = s->impl_->on_bytes_;
     667                 :          19 :         }
     668   [ +  +  +  - ]:          19 :         if (data_handler) (*data_handler)(client_id, data);
     669   [ -  +  -  - ]:          19 :         if (bytes_handler) (*bytes_handler)(data);
     670                 :          19 :       });
     671                 :             : 
     672                 :          23 :       session->on_close([weak_self, client_id]() {
     673                 :          22 :         auto s = weak_self.lock();
     674   [ +  -  +  +  :          22 :         if (!s || s->impl_->stopping_) return;
                   +  + ]
     675                 :             : 
     676                 :          13 :         MultiClientDisconnectHandler disconnect_handler;
     677                 :             :         {
     678                 :          13 :           std::lock_guard<std::mutex> lock(s->impl_->sessions_mutex_);
     679         [ -  + ]:          13 :           if (s->impl_->stopping_) return;  // Double check inside lock
     680                 :             :           // Carry the session's totals over to the server before it goes away,
     681                 :             :           // so stats() keeps reporting what this connection did. Tied to the
     682                 :             :           // erase below, which makes it exactly once even if on_close re-fires.
     683                 :          13 :           auto it = s->impl_->sessions_.find(client_id);
     684   [ +  -  +  -  :          13 :           if (it != s->impl_->sessions_.end() && it->second) {
                   +  - ]
     685                 :          13 :             s->impl_->stats_.absorb(it->second->stats());
     686                 :             :           }
     687                 :          13 :           s->impl_->sessions_.erase(client_id);
     688                 :          13 :           disconnect_handler = s->impl_->on_multi_disconnect_;
     689                 :          13 :         }
     690   [ +  +  +  - ]:          13 :         if (disconnect_handler) disconnect_handler(client_id);
     691                 :          22 :       });
     692                 :             : 
     693                 :             :       // alive_ must be true before the session enters sessions_, so that
     694                 :             :       // broadcast() callers who observe client_count() >= 1 are guaranteed
     695                 :             :       // to pass the alive() check inside async_try_write_shared().
     696                 :          23 :       session->start();
     697                 :             : 
     698                 :             :       {
     699                 :          23 :         std::lock_guard<std::mutex> lock(self->impl_->sessions_mutex_);
     700                 :          23 :         self->impl_->sessions_[client_id] = session;
     701                 :          23 :       }
     702                 :             : 
     703                 :          23 :       MultiClientConnectHandler connect_handler;
     704                 :             :       {
     705                 :          23 :         std::lock_guard<std::mutex> lock(self->impl_->sessions_mutex_);
     706                 :          23 :         connect_handler = self->impl_->on_multi_connect_;
     707                 :          23 :       }
     708   [ +  +  +  -  :          55 :       if (connect_handler) connect_handler(client_id, "UDS Client");
                   +  - ]
     709                 :             : 
     710                 :             :       // Continue accepting
     711                 :          23 :       auto* impl = self->impl_.get();
     712                 :          23 :       impl->do_accept(self);
     713                 :          23 :     } else {
     714                 :           1 :       auto* impl = self->impl_.get();
     715         [ -  + ]:           1 :       if (impl->stopping_.load()) return;
     716                 :             : 
     717                 :             :       // Log only real errors, not operation_aborted
     718         [ +  - ]:           1 :       if (ec != boost::asio::error::operation_aborted) {
     719                 :           1 :         std::string msg = fmt::format("Accept failed: {}", ec.message());
     720                 :           1 :         WIRESTEAD_LOG_ERROR("uds_server", "accept", msg);
     721                 :           1 :         impl->error_info_holder_.record_error(diagnostics::ErrorLevel::ERROR, diagnostics::ErrorCategory::CONNECTION,
     722                 :             :                                               "accept", ec, msg, true, 0);
     723                 :           1 :         impl->state_.set(base::LinkState::Error);
     724                 :           1 :         impl->notify_state();
     725                 :           1 :       }
     726                 :             : 
     727         [ +  - ]:           1 :       if (!impl->stopping_.load()) {
     728                 :           1 :         auto timer = std::make_shared<net::steady_timer>(*impl->ioc_);
     729                 :           1 :         timer->expires_after(std::chrono::milliseconds(100));
     730                 :           1 :         timer->async_wait([self, timer](const boost::system::error_code&) {
     731                 :           1 :           auto* retry_impl = self->impl_.get();
     732         [ +  - ]:           1 :           if (!retry_impl->stopping_.load()) {
     733                 :           1 :             retry_impl->do_accept(self);
     734                 :             :           }
     735                 :           1 :         });
     736                 :           1 :       }
     737                 :             :     }
     738                 :             :   });
     739                 :          51 : }
     740                 :             : 
     741                 :          68 : void UdsServer::Impl::notify_state() {
     742                 :          68 :   interface::SharedCallback<OnState> cb;
     743                 :             :   {
     744                 :          68 :     std::lock_guard<std::mutex> lock(sessions_mutex_);
     745                 :          68 :     cb = on_state_;
     746                 :          68 :   }
     747   [ +  +  +  - ]:          68 :   if (cb) (*cb)(state_.get());
     748                 :          68 : }
     749                 :             : 
     750                 :             : }  // namespace transport
     751                 :             : }  // namespace wirestead
        

Generated by: LCOV version 2.0-1