LCOV - code coverage report
Current view: top level - corosio - connect.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 11 11
Test Date: 2026-09-25 21:36:35 Functions: 75.0 % 12 9 3

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_CONNECT_HPP
      11                 : #define BOOST_COROSIO_CONNECT_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/config.hpp>
      14                 : 
      15                 : #include <boost/capy/cond.hpp>
      16                 : #include <boost/capy/io_result.hpp>
      17                 : #include <boost/capy/task.hpp>
      18                 : 
      19                 : #include <concepts>
      20                 : #include <iterator>
      21                 : #include <ranges>
      22                 : #include <system_error>
      23                 : #include <utility>
      24                 : 
      25                 : /*
      26                 :   Range-based composed connect operation.
      27                 : 
      28                 :   These free functions try each endpoint in a range (or iterator pair)
      29                 :   in order, returning on the first successful connect. Between attempts
      30                 :   the socket is closed so that the next attempt can auto-open with the
      31                 :   correct address family (e.g. going from IPv4 to IPv6 candidates).
      32                 : 
      33                 :   The iteration semantics follow Boost.Asio's range/iterator async_connect:
      34                 :   on success, the successful endpoint (or its iterator) is returned; on
      35                 :   all-fail, the last attempt's error code is returned; on an empty range
      36                 :   (or when a connect_condition rejects every candidate),
      37                 :   std::errc::no_such_device_or_address is returned, matching the error
      38                 :   the resolver uses for "no results" in posix_resolver_service.
      39                 : 
      40                 :   The operation is a plain coroutine; cancellation is propagated to the
      41                 :   inner per-endpoint connect via the affine awaitable protocol on io_env.
      42                 : */
      43                 : 
      44                 : namespace boost::corosio {
      45                 : 
      46                 : namespace detail {
      47                 : 
      48                 : /* Always-true connect condition used by the overloads that take no
      49                 :    user-supplied predicate. Kept at namespace-detail scope so it has a
      50                 :    stable linkage name across translation units. */
      51                 : struct default_connect_condition
      52                 : {
      53                 :     template<class Endpoint>
      54 HIT          20 :     bool operator()(std::error_code const&, Endpoint const&) const noexcept
      55                 :     {
      56              20 :         return true;
      57                 :     }
      58                 : };
      59                 : 
      60                 : } // namespace detail
      61                 : 
      62                 : /* Forward declarations so the non-condition overloads can delegate
      63                 :    to the condition overloads via qualified lookup (qualified calls
      64                 :    bind to the overload set visible at definition, not instantiation). */
      65                 : 
      66                 : template<class Socket, std::ranges::input_range Range, class ConnectCondition>
      67                 :     requires std::convertible_to<
      68                 :                  std::ranges::range_reference_t<Range>,
      69                 :                  typename Socket::endpoint_type> &&
      70                 :     std::predicate<
      71                 :                  ConnectCondition&,
      72                 :                  std::error_code const&,
      73                 :                  typename Socket::endpoint_type const&>
      74                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
      75                 : connect(Socket& s, Range endpoints, ConnectCondition cond);
      76                 : 
      77                 : template<class Socket, std::input_iterator Iter, class ConnectCondition>
      78                 :     requires std::convertible_to<
      79                 :                  std::iter_reference_t<Iter>,
      80                 :                  typename Socket::endpoint_type> &&
      81                 :     std::predicate<
      82                 :                  ConnectCondition&,
      83                 :                  std::error_code const&,
      84                 :                  typename Socket::endpoint_type const&>
      85                 : capy::task<capy::io_result<Iter>>
      86                 : connect(Socket& s, Iter begin, Iter end, ConnectCondition cond);
      87                 : 
      88                 : /** Asynchronously connect a socket by trying each endpoint in a range.
      89                 : 
      90                 :     Each candidate is tried in order. Before each attempt the socket is
      91                 :     closed (so the next `connect` auto-opens with the candidate's
      92                 :     address family). On first successful connect, the operation
      93                 :     completes with the connected endpoint.
      94                 : 
      95                 :     @par Cancellation
      96                 :     Supports cancellation via the affine awaitable protocol. If a
      97                 :     per-endpoint connect completes with `capy::cond::canceled` the
      98                 :     operation completes immediately with that error and does not try
      99                 :     further endpoints.
     100                 : 
     101                 :     @param s The socket to connect. Must have a `connect(endpoint)`
     102                 :         member returning an awaitable, plus `close()` and `is_open()`.
     103                 :         If the socket is already open, it will be closed before the
     104                 :         first attempt.
     105                 :     @param endpoints A range of candidate endpoints. Taken by value
     106                 :         so temporaries (e.g. the `std::vector<endpoint>` returned from
     107                 :         `resolver::resolve`) remain alive for the coroutine's lifetime.
     108                 : 
     109                 :     @return An awaitable completing with
     110                 :         `capy::io_result<typename Socket::endpoint_type>`:
     111                 :         - on success: default error_code and the connected endpoint;
     112                 :         - on failure of all attempts: the error from the last attempt
     113                 :           and a default-constructed endpoint;
     114                 :         - on empty range: `std::errc::no_such_device_or_address` and a
     115                 :           default-constructed endpoint.
     116                 : 
     117                 :     @note The socket is closed and re-opened before each attempt, so
     118                 :         any socket options set by the caller (e.g. `no_delay`,
     119                 :         `reuse_address`) are lost. Apply options after this operation
     120                 :         completes.
     121                 : 
     122                 :     If auto-opening the socket fails during an attempt, that attempt
     123                 :     completes with the open error (inherits the contract of
     124                 :     `Socket::connect`).
     125                 : 
     126                 :     @par Example
     127                 :     @par !example connect
     128                 : */
     129                 : template<class Socket, std::ranges::input_range Range>
     130                 :     requires std::convertible_to<
     131                 :         std::ranges::range_reference_t<Range>,
     132                 :         typename Socket::endpoint_type>
     133                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
     134              12 : connect(Socket& s, Range endpoints)
     135                 : {
     136                 :     detail::default_connect_condition cond;
     137              12 :     return corosio::connect(s, std::move(endpoints), cond);
     138                 : }
     139                 : 
     140                 : /** Asynchronously connect a socket by trying each endpoint in a range,
     141                 :     filtered by a user-supplied condition.
     142                 : 
     143                 :     For each candidate the condition is invoked as
     144                 :     `cond(last_ec, ep)` where `last_ec` is the error from the most
     145                 :     recent attempt (default-constructed before the first attempt). If
     146                 :     the condition returns `false` the candidate is skipped; otherwise a
     147                 :     connect is attempted.
     148                 : 
     149                 :     @param s The socket to connect. See the non-condition overload for
     150                 :         requirements.
     151                 :     @param endpoints A range of candidate endpoints, taken by value.
     152                 :     @param cond A predicate invocable with
     153                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`
     154                 :         returning a value contextually convertible to `bool`.
     155                 : 
     156                 :     @return Same as the non-condition overload. If every candidate is
     157                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     158                 : 
     159                 :     If auto-opening the socket fails, the attempt completes with the
     160                 :     open error.
     161                 : */
     162                 : template<class Socket, std::ranges::input_range Range, class ConnectCondition>
     163                 :     requires std::convertible_to<
     164                 :                  std::ranges::range_reference_t<Range>,
     165                 :                  typename Socket::endpoint_type> &&
     166                 :     std::predicate<
     167                 :                  ConnectCondition&,
     168                 :                  std::error_code const&,
     169                 :                  typename Socket::endpoint_type const&>
     170                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
     171              16 : connect(Socket& s, Range endpoints, ConnectCondition cond)
     172                 : {
     173                 :     using endpoint_type = typename Socket::endpoint_type;
     174                 : 
     175                 :     std::error_code last_ec;
     176                 : 
     177                 :     for (auto&& e : endpoints)
     178                 :     {
     179                 :         endpoint_type ep = e;
     180                 : 
     181                 :         if (!cond(
     182                 :                 static_cast<std::error_code const&>(last_ec),
     183                 :                 static_cast<endpoint_type const&>(ep)))
     184                 :             continue;
     185                 : 
     186                 :         if (s.is_open())
     187                 :             s.close();
     188                 : 
     189                 :         auto [ec] = co_await s.connect(ep);
     190                 : 
     191                 :         if (!ec)
     192                 :             co_return {std::error_code{}, std::move(ep)};
     193                 : 
     194                 :         if (ec == capy::cond::canceled)
     195                 :             co_return {ec, endpoint_type{}};
     196                 : 
     197                 :         last_ec = ec;
     198                 :     }
     199                 : 
     200                 :     if (!last_ec)
     201                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     202                 : 
     203                 :     co_return {last_ec, endpoint_type{}};
     204              32 : }
     205                 : 
     206                 : /** Asynchronously connect a socket by trying each endpoint in an
     207                 :     iterator range.
     208                 : 
     209                 :     Behaves like the range overload, except the return value carries
     210                 :     the iterator to the successfully connected endpoint on success, or
     211                 :     `end` on failure. This mirrors Boost.Asio's iterator-based
     212                 :     `async_connect`.
     213                 : 
     214                 :     @param s The socket to connect.
     215                 :     @param begin The first candidate.
     216                 :     @param end One past the last candidate.
     217                 : 
     218                 :     @return An awaitable completing with `capy::io_result<Iter>`:
     219                 :         - on success: default error_code and the iterator of the
     220                 :           successful endpoint;
     221                 :         - on failure of all attempts: the error from the last attempt
     222                 :           and `end`;
     223                 :         - on empty range: `std::errc::no_such_device_or_address` and
     224                 :           `end`.
     225                 : 
     226                 :     If auto-opening the socket fails, the attempt completes with the
     227                 :     open error.
     228                 : */
     229                 : template<class Socket, std::input_iterator Iter>
     230                 :     requires std::convertible_to<
     231                 :         std::iter_reference_t<Iter>,
     232                 :         typename Socket::endpoint_type>
     233                 : capy::task<capy::io_result<Iter>>
     234               4 : connect(Socket& s, Iter begin, Iter end)
     235                 : {
     236                 :     return corosio::connect(
     237               4 :         s, std::move(begin), std::move(end),
     238               4 :         detail::default_connect_condition{});
     239                 : }
     240                 : 
     241                 : /** Asynchronously connect a socket by trying each endpoint in an
     242                 :     iterator range, filtered by a user-supplied condition.
     243                 : 
     244                 :     @param s The socket to connect.
     245                 :     @param begin The first candidate.
     246                 :     @param end One past the last candidate.
     247                 :     @param cond A predicate invocable with
     248                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`.
     249                 : 
     250                 :     @return Same as the plain iterator overload. If every candidate is
     251                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     252                 : 
     253                 :     If auto-opening the socket fails, the attempt completes with the
     254                 :     open error.
     255                 : */
     256                 : template<class Socket, std::input_iterator Iter, class ConnectCondition>
     257                 :     requires std::convertible_to<
     258                 :                  std::iter_reference_t<Iter>,
     259                 :                  typename Socket::endpoint_type> &&
     260                 :     std::predicate<
     261                 :                  ConnectCondition&,
     262                 :                  std::error_code const&,
     263                 :                  typename Socket::endpoint_type const&>
     264                 : capy::task<capy::io_result<Iter>>
     265               4 : connect(Socket& s, Iter begin, Iter end, ConnectCondition cond)
     266                 : {
     267                 :     using endpoint_type = typename Socket::endpoint_type;
     268                 : 
     269                 :     std::error_code last_ec;
     270                 : 
     271                 :     for (Iter it = begin; it != end; ++it)
     272                 :     {
     273                 :         endpoint_type ep = *it;
     274                 : 
     275                 :         if (!cond(
     276                 :                 static_cast<std::error_code const&>(last_ec),
     277                 :                 static_cast<endpoint_type const&>(ep)))
     278                 :             continue;
     279                 : 
     280                 :         if (s.is_open())
     281                 :             s.close();
     282                 : 
     283                 :         auto [ec] = co_await s.connect(ep);
     284                 : 
     285                 :         if (!ec)
     286                 :             co_return {std::error_code{}, std::move(it)};
     287                 : 
     288                 :         if (ec == capy::cond::canceled)
     289                 :             co_return {ec, std::move(end)};
     290                 : 
     291                 :         last_ec = ec;
     292                 :     }
     293                 : 
     294                 :     if (!last_ec)
     295                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     296                 : 
     297                 :     co_return {last_ec, std::move(end)};
     298               8 : }
     299                 : 
     300                 : } // namespace boost::corosio
     301                 : 
     302                 : #endif
        

Generated by: LCOV version 2.3