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