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-09 02:31:18 Functions: 64.3 % 14 9 5

           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                 :         Because the range is owned by the coroutine, passing an lvalue
     109                 :         copies it; since `resolver_results` is a
     110                 :         `std::vector<resolver_entry>`, that is a deep copy of every entry.
     111                 :         Pass an rvalue (`std::move(results)`) or use the iterator overload
     112                 :         (`connect(s, results.begin(), results.end())`) to avoid the copy.
     113                 : 
     114                 :     @return An awaitable completing with
     115                 :         `capy::io_result<typename Socket::endpoint_type>`:
     116                 :         - on success: default error_code and the connected endpoint;
     117                 :         - on failure of all attempts: the error from the last attempt
     118                 :           and a default-constructed endpoint;
     119                 :         - on empty range: `std::errc::no_such_device_or_address` and a
     120                 :           default-constructed endpoint.
     121                 : 
     122                 :     @note The socket is closed and re-opened before each attempt, so
     123                 :         any socket options set by the caller (e.g. `no_delay`,
     124                 :         `reuse_address`) are lost. Apply options after this operation
     125                 :         completes.
     126                 : 
     127                 :     If auto-opening the socket fails during an attempt, that attempt
     128                 :     completes with the open error (inherits the contract of
     129                 :     `Socket::connect`).
     130                 : 
     131                 :     @par Example
     132                 :     @par !example connect
     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                 :     detail::default_connect_condition cond;
     142              12 :     return corosio::connect(s, std::move(endpoints), cond);
     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, taken by value. See
     157                 :         the non-condition overload for the deep-copy caveat when passing
     158                 :         an lvalue `resolver_results`.
     159                 :     @param cond A predicate invocable with
     160                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`
     161                 :         returning a value contextually convertible to `bool`.
     162                 : 
     163                 :     @return Same as the non-condition overload. If every candidate is
     164                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     165                 : 
     166                 :     If auto-opening the socket fails, the attempt completes with the
     167                 :     open error.
     168                 : */
     169                 : template<class Socket, std::ranges::input_range Range, class ConnectCondition>
     170                 :     requires std::convertible_to<
     171                 :                  std::ranges::range_reference_t<Range>,
     172                 :                  typename Socket::endpoint_type> &&
     173                 :     std::predicate<
     174                 :                  ConnectCondition&,
     175                 :                  std::error_code const&,
     176                 :                  typename Socket::endpoint_type const&>
     177                 : capy::task<capy::io_result<typename Socket::endpoint_type>>
     178              16 : connect(Socket& s, Range endpoints, ConnectCondition cond)
     179                 : {
     180                 :     using endpoint_type = typename Socket::endpoint_type;
     181                 : 
     182                 :     std::error_code last_ec;
     183                 : 
     184                 :     for (auto&& e : endpoints)
     185                 :     {
     186                 :         endpoint_type ep = e;
     187                 : 
     188                 :         if (!cond(
     189                 :                 static_cast<std::error_code const&>(last_ec),
     190                 :                 static_cast<endpoint_type const&>(ep)))
     191                 :             continue;
     192                 : 
     193                 :         if (s.is_open())
     194                 :             s.close();
     195                 : 
     196                 :         auto [ec] = co_await s.connect(ep);
     197                 : 
     198                 :         if (!ec)
     199                 :             co_return {std::error_code{}, std::move(ep)};
     200                 : 
     201                 :         if (ec == capy::cond::canceled)
     202                 :             co_return {ec, endpoint_type{}};
     203                 : 
     204                 :         last_ec = ec;
     205                 :     }
     206                 : 
     207                 :     if (!last_ec)
     208                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     209                 : 
     210                 :     co_return {last_ec, endpoint_type{}};
     211              32 : }
     212                 : 
     213                 : /** Asynchronously connect a socket by trying each endpoint in an
     214                 :     iterator range.
     215                 : 
     216                 :     Behaves like the range overload, except the return value carries
     217                 :     the iterator to the successfully connected endpoint on success, or
     218                 :     `end` on failure. This mirrors Boost.Asio's iterator-based
     219                 :     `async_connect`.
     220                 : 
     221                 :     @param s The socket to connect.
     222                 :     @param begin The first candidate.
     223                 :     @param end One past the last candidate.
     224                 : 
     225                 :     @return An awaitable completing with `capy::io_result<Iter>`:
     226                 :         - on success: default error_code and the iterator of the
     227                 :           successful endpoint;
     228                 :         - on failure of all attempts: the error from the last attempt
     229                 :           and `end`;
     230                 :         - on empty range: `std::errc::no_such_device_or_address` and
     231                 :           `end`.
     232                 : 
     233                 :     If auto-opening the socket fails, the attempt completes with the
     234                 :     open error.
     235                 : */
     236                 : template<class Socket, std::input_iterator Iter>
     237                 :     requires std::convertible_to<
     238                 :         std::iter_reference_t<Iter>,
     239                 :         typename Socket::endpoint_type>
     240                 : capy::task<capy::io_result<Iter>>
     241               4 : connect(Socket& s, Iter begin, Iter end)
     242                 : {
     243                 :     return corosio::connect(
     244               4 :         s, std::move(begin), std::move(end),
     245               4 :         detail::default_connect_condition{});
     246                 : }
     247                 : 
     248                 : /** Asynchronously connect a socket by trying each endpoint in an
     249                 :     iterator range, filtered by a user-supplied condition.
     250                 : 
     251                 :     @param s The socket to connect.
     252                 :     @param begin The first candidate.
     253                 :     @param end One past the last candidate.
     254                 :     @param cond A predicate invocable with
     255                 :         `(std::error_code const&, typename Socket::endpoint_type const&)`.
     256                 : 
     257                 :     @return Same as the plain iterator overload. If every candidate is
     258                 :         rejected, completes with `std::errc::no_such_device_or_address`.
     259                 : 
     260                 :     If auto-opening the socket fails, the attempt completes with the
     261                 :     open error.
     262                 : */
     263                 : template<class Socket, std::input_iterator Iter, class ConnectCondition>
     264                 :     requires std::convertible_to<
     265                 :                  std::iter_reference_t<Iter>,
     266                 :                  typename Socket::endpoint_type> &&
     267                 :     std::predicate<
     268                 :                  ConnectCondition&,
     269                 :                  std::error_code const&,
     270                 :                  typename Socket::endpoint_type const&>
     271                 : capy::task<capy::io_result<Iter>>
     272               4 : connect(Socket& s, Iter begin, Iter end, ConnectCondition cond)
     273                 : {
     274                 :     using endpoint_type = typename Socket::endpoint_type;
     275                 : 
     276                 :     std::error_code last_ec;
     277                 : 
     278                 :     for (Iter it = begin; it != end; ++it)
     279                 :     {
     280                 :         endpoint_type ep = *it;
     281                 : 
     282                 :         if (!cond(
     283                 :                 static_cast<std::error_code const&>(last_ec),
     284                 :                 static_cast<endpoint_type const&>(ep)))
     285                 :             continue;
     286                 : 
     287                 :         if (s.is_open())
     288                 :             s.close();
     289                 : 
     290                 :         auto [ec] = co_await s.connect(ep);
     291                 : 
     292                 :         if (!ec)
     293                 :             co_return {std::error_code{}, std::move(it)};
     294                 : 
     295                 :         if (ec == capy::cond::canceled)
     296                 :             co_return {ec, std::move(end)};
     297                 : 
     298                 :         last_ec = ec;
     299                 :     }
     300                 : 
     301                 :     if (!last_ec)
     302                 :         last_ec = std::make_error_code(std::errc::no_such_device_or_address);
     303                 : 
     304                 :     co_return {last_ec, std::move(end)};
     305               8 : }
     306                 : 
     307                 : } // namespace boost::corosio
     308                 : 
     309                 : #endif
        

Generated by: LCOV version 2.3