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