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

Generated by: LCOV version 2.3