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

Generated by: LCOV version 2.3