LCOV - code coverage report
Current view: top level - corosio - connect.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 12 12
Test Date: 2026-04-17 16:46:32 Functions: 100.0 % 9 9

           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. `resolver_results` 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                 :     @throws std::system_error if auto-opening the socket fails during
     123                 :         an attempt (inherits the contract of `Socket::connect`).
     124                 : 
     125                 :     @par Example
     126                 :     @code
     127                 :     resolver r(ioc);
     128                 :     auto [rec, results] = co_await r.resolve("www.boost.org", "80");
     129                 :     if (rec) co_return;
     130                 :     tcp_socket s(ioc);
     131                 :     auto [cec, ep] = co_await corosio::connect(s, results);
     132                 :     @endcode
     133                 : */
     134                 : template<class Socket, std::ranges::input_range Range>
     135                 :     requires std::convertible_to<
     136                 :         std::ranges::range_reference_t<Range>,
     137                 :         typename Socket::endpoint_type>
     138                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
     139              12 : connect(Socket& s, Range endpoints)
     140                 : {
     141                 :     return corosio::connect(
     142              12 :         s, std::move(endpoints), detail::default_connect_condition{});
     143                 : }
     144                 : 
     145                 : /** Asynchronously connect a socket by trying each endpoint in a range,
     146                 :     filtered by a user-supplied condition.
     147                 : 
     148                 :     For each candidate the condition is invoked as
     149                 :     `cond(last_ec, ep)` where `last_ec` is the error from the most
     150                 :     recent attempt (default-constructed before the first attempt). If
     151                 :     the condition returns `false` the candidate is skipped; otherwise a
     152                 :     connect is attempted.
     153                 : 
     154                 :     @param s The socket to connect. See the non-condition overload for
     155                 :         requirements.
     156                 :     @param endpoints A range of candidate endpoints.
     157                 :     @param cond A predicate invocable with
     158                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`
     159                 :         returning a value contextually convertible to `bool`.
     160                 : 
     161                 :     @return Same as the non-condition overload. If every candidate is
     162                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     163                 : 
     164                 :     @throws std::system_error if auto-opening the socket fails.
     165                 : */
     166                 : template<class Socket, std::ranges::input_range Range, class ConnectCondition>
     167                 :     requires std::convertible_to<
     168                 :                  std::ranges::range_reference_t<Range>,
     169                 :                  typename Socket::endpoint_type> &&
     170                 :     std::predicate<
     171                 :                  ConnectCondition&,
     172                 :                  std::error_code const&,
     173                 :                  typename Socket::endpoint_type const&>
     174                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
     175              16 : connect(Socket& s, Range endpoints, ConnectCondition cond)
     176                 : {
     177                 :     using endpoint_type = typename Socket::endpoint_type;
     178                 : 
     179                 :     std::error_code last_ec;
     180                 : 
     181                 :     for (auto&& e : endpoints)
     182                 :     {
     183                 :         endpoint_type ep = e;
     184                 : 
     185                 :         if (!cond(static_cast<std::error_code const&>(last_ec),
     186                 :                   static_cast<endpoint_type const&>(ep)))
     187                 :             continue;
     188                 : 
     189                 :         if (s.is_open())
     190                 :             s.close();
     191                 : 
     192                 :         auto [ec] = co_await s.connect(ep);
     193                 : 
     194                 :         if (!ec)
     195                 :             co_return {std::error_code{}, std::move(ep)};
     196                 : 
     197                 :         if (ec == capy::cond::canceled)
     198                 :             co_return {ec, endpoint_type{}};
     199                 : 
     200                 :         last_ec = ec;
     201                 :     }
     202                 : 
     203                 :     if (!last_ec)
     204                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     205                 : 
     206                 :     co_return {last_ec, endpoint_type{}};
     207              32 : }
     208                 : 
     209                 : /** Asynchronously connect a socket by trying each endpoint in an
     210                 :     iterator range.
     211                 : 
     212                 :     Behaves like the range overload, except the return value carries
     213                 :     the iterator to the successfully connected endpoint on success, or
     214                 :     `end` on failure. This mirrors Boost.Asio's iterator-based
     215                 :     `async_connect`.
     216                 : 
     217                 :     @param s The socket to connect.
     218                 :     @param begin The first candidate.
     219                 :     @param end One past the last candidate.
     220                 : 
     221                 :     @return An awaitable completing with `capy::io_result<Iter>`:
     222                 :         - on success: default error_code and the iterator of the
     223                 :           successful endpoint;
     224                 :         - on failure of all attempts: the error from the last attempt
     225                 :           and `end`;
     226                 :         - on empty range: `std::errc::no_such_device_or_address` and
     227                 :           `end`.
     228                 : 
     229                 :     @throws std::system_error if auto-opening the socket fails.
     230                 : */
     231                 : template<class Socket, std::input_iterator Iter>
     232                 :     requires std::convertible_to<
     233                 :         std::iter_reference_t<Iter>,
     234                 :         typename Socket::endpoint_type>
     235                 : capy::task<capy::io_result<Iter>>
     236               4 : connect(Socket& s, Iter begin, Iter end)
     237                 : {
     238                 :     return corosio::connect(
     239                 :         s,
     240               4 :         std::move(begin),
     241               4 :         std::move(end),
     242               4 :         detail::default_connect_condition{});
     243                 : }
     244                 : 
     245                 : /** Asynchronously connect a socket by trying each endpoint in an
     246                 :     iterator range, filtered by a user-supplied condition.
     247                 : 
     248                 :     @param s The socket to connect.
     249                 :     @param begin The first candidate.
     250                 :     @param end One past the last candidate.
     251                 :     @param cond A predicate invocable with
     252                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`.
     253                 : 
     254                 :     @return Same as the plain iterator overload. If every candidate is
     255                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     256                 : 
     257                 :     @throws std::system_error if auto-opening the socket fails.
     258                 : */
     259                 : template<class Socket, std::input_iterator Iter, class ConnectCondition>
     260                 :     requires std::convertible_to<
     261                 :                  std::iter_reference_t<Iter>,
     262                 :                  typename Socket::endpoint_type> &&
     263                 :     std::predicate<
     264                 :                  ConnectCondition&,
     265                 :                  std::error_code const&,
     266                 :                  typename Socket::endpoint_type const&>
     267                 : capy::task<capy::io_result<Iter>>
     268               4 : connect(Socket& s, Iter begin, Iter end, ConnectCondition cond)
     269                 : {
     270                 :     using endpoint_type = typename Socket::endpoint_type;
     271                 : 
     272                 :     std::error_code last_ec;
     273                 : 
     274                 :     for (Iter it = begin; it != end; ++it)
     275                 :     {
     276                 :         endpoint_type ep = *it;
     277                 : 
     278                 :         if (!cond(static_cast<std::error_code const&>(last_ec),
     279                 :                   static_cast<endpoint_type const&>(ep)))
     280                 :             continue;
     281                 : 
     282                 :         if (s.is_open())
     283                 :             s.close();
     284                 : 
     285                 :         auto [ec] = co_await s.connect(ep);
     286                 : 
     287                 :         if (!ec)
     288                 :             co_return {std::error_code{}, std::move(it)};
     289                 : 
     290                 :         if (ec == capy::cond::canceled)
     291                 :             co_return {ec, std::move(end)};
     292                 : 
     293                 :         last_ec = ec;
     294                 :     }
     295                 : 
     296                 :     if (!last_ec)
     297                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     298                 : 
     299                 :     co_return {last_ec, std::move(end)};
     300               8 : }
     301                 : 
     302                 : } // namespace boost::corosio
     303                 : 
     304                 : #endif
        

Generated by: LCOV version 2.3