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