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