90.59% Lines (77/85) 100.00% Functions (28/28)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
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_UDP_SOCKET_HPP 10   #ifndef BOOST_COROSIO_UDP_SOCKET_HPP
11   #define BOOST_COROSIO_UDP_SOCKET_HPP 11   #define BOOST_COROSIO_UDP_SOCKET_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 16   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/detail/op_base.hpp> 17   #include <boost/corosio/detail/op_base.hpp>
18   #include <boost/corosio/io/io_object.hpp> 18   #include <boost/corosio/io/io_object.hpp>
19   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
20   #include <boost/corosio/detail/buffer_param.hpp> 20   #include <boost/corosio/detail/buffer_param.hpp>
21   #include <boost/corosio/endpoint.hpp> 21   #include <boost/corosio/endpoint.hpp>
22   #include <boost/corosio/message_flags.hpp> 22   #include <boost/corosio/message_flags.hpp>
23   #include <boost/corosio/udp.hpp> 23   #include <boost/corosio/udp.hpp>
  24 + #include <boost/corosio/wait_type.hpp>
24   #include <boost/capy/ex/executor_ref.hpp> 25   #include <boost/capy/ex/executor_ref.hpp>
25   #include <boost/capy/ex/execution_context.hpp> 26   #include <boost/capy/ex/execution_context.hpp>
26   #include <boost/capy/ex/io_env.hpp> 27   #include <boost/capy/ex/io_env.hpp>
27   #include <boost/capy/concept/executor.hpp> 28   #include <boost/capy/concept/executor.hpp>
28   29  
29   #include <system_error> 30   #include <system_error>
30   31  
31   #include <concepts> 32   #include <concepts>
32   #include <coroutine> 33   #include <coroutine>
33   #include <cstddef> 34   #include <cstddef>
34   #include <stop_token> 35   #include <stop_token>
35   #include <type_traits> 36   #include <type_traits>
36   37  
37   namespace boost::corosio { 38   namespace boost::corosio {
38   39  
39   /** An asynchronous UDP socket for coroutine I/O. 40   /** An asynchronous UDP socket for coroutine I/O.
40   41  
41   This class provides asynchronous UDP datagram operations that 42   This class provides asynchronous UDP datagram operations that
42   return awaitable types. Each operation participates in the affine 43   return awaitable types. Each operation participates in the affine
43   awaitable protocol, ensuring coroutines resume on the correct 44   awaitable protocol, ensuring coroutines resume on the correct
44   executor. 45   executor.
45   46  
46   Supports two modes of operation: 47   Supports two modes of operation:
47   48  
48   **Connectionless mode**: each `send_to` specifies a destination 49   **Connectionless mode**: each `send_to` specifies a destination
49   endpoint, and each `recv_from` captures the source endpoint. 50   endpoint, and each `recv_from` captures the source endpoint.
50   The socket must be opened (and optionally bound) before I/O. 51   The socket must be opened (and optionally bound) before I/O.
51   52  
52   **Connected mode**: call `connect()` to set a default peer, 53   **Connected mode**: call `connect()` to set a default peer,
53   then use `send()`/`recv()` without endpoint arguments. 54   then use `send()`/`recv()` without endpoint arguments.
54   The kernel filters incoming datagrams to those from the 55   The kernel filters incoming datagrams to those from the
55   connected peer. 56   connected peer.
56   57  
57   @par Thread Safety 58   @par Thread Safety
58   Distinct objects: Safe.@n 59   Distinct objects: Safe.@n
59   Shared objects: Unsafe. A socket must not have concurrent 60   Shared objects: Unsafe. A socket must not have concurrent
60   operations of the same type (e.g., two simultaneous recv_from). 61   operations of the same type (e.g., two simultaneous recv_from).
61   One send_to and one recv_from may be in flight simultaneously. 62   One send_to and one recv_from may be in flight simultaneously.
62   63  
63   @par Example 64   @par Example
64   @code 65   @code
65   // Connectionless mode 66   // Connectionless mode
66   io_context ioc; 67   io_context ioc;
67   udp_socket sock( ioc ); 68   udp_socket sock( ioc );
68   sock.open( udp::v4() ); 69   sock.open( udp::v4() );
69   sock.bind( endpoint( ipv4_address::any(), 9000 ) ); 70   sock.bind( endpoint( ipv4_address::any(), 9000 ) );
70   71  
71   char buf[1024]; 72   char buf[1024];
72   endpoint sender; 73   endpoint sender;
73   auto [ec, n] = co_await sock.recv_from( 74   auto [ec, n] = co_await sock.recv_from(
74   capy::mutable_buffer( buf, sizeof( buf ) ), sender ); 75   capy::mutable_buffer( buf, sizeof( buf ) ), sender );
75   if ( !ec ) 76   if ( !ec )
76   co_await sock.send_to( 77   co_await sock.send_to(
77   capy::const_buffer( buf, n ), sender ); 78   capy::const_buffer( buf, n ), sender );
78   79  
79   // Connected mode 80   // Connected mode
80   udp_socket csock( ioc ); 81   udp_socket csock( ioc );
81   auto [cec] = co_await csock.connect( 82   auto [cec] = co_await csock.connect(
82   endpoint( ipv4_address::loopback(), 9000 ) ); 83   endpoint( ipv4_address::loopback(), 9000 ) );
83   if ( !cec ) 84   if ( !cec )
84   co_await csock.send( 85   co_await csock.send(
85   capy::const_buffer( buf, n ) ); 86   capy::const_buffer( buf, n ) );
86   @endcode 87   @endcode
87   */ 88   */
88   class BOOST_COROSIO_DECL udp_socket : public io_object 89   class BOOST_COROSIO_DECL udp_socket : public io_object
89   { 90   {
90   public: 91   public:
91   /** Define backend hooks for UDP socket operations. 92   /** Define backend hooks for UDP socket operations.
92   93  
93   Platform backends (epoll, kqueue, select) derive from 94   Platform backends (epoll, kqueue, select) derive from
94   this to implement datagram I/O and option management. 95   this to implement datagram I/O and option management.
95   */ 96   */
96   struct implementation : io_object::implementation 97   struct implementation : io_object::implementation
97   { 98   {
98   /** Initiate an asynchronous send_to operation. 99   /** Initiate an asynchronous send_to operation.
99   100  
100   @param h Coroutine handle to resume on completion. 101   @param h Coroutine handle to resume on completion.
101   @param ex Executor for dispatching the completion. 102   @param ex Executor for dispatching the completion.
102   @param buf The buffer data to send. 103   @param buf The buffer data to send.
103   @param dest The destination endpoint. 104   @param dest The destination endpoint.
104   @param flags Platform message flags (e.g. `MSG_DONTWAIT`). 105   @param flags Platform message flags (e.g. `MSG_DONTWAIT`).
105   @param token Stop token for cancellation. 106   @param token Stop token for cancellation.
106   @param ec Output error code. 107   @param ec Output error code.
107   @param bytes_out Output bytes transferred. 108   @param bytes_out Output bytes transferred.
108   109  
109   @return Coroutine handle to resume immediately. 110   @return Coroutine handle to resume immediately.
110   */ 111   */
111   virtual std::coroutine_handle<> send_to( 112   virtual std::coroutine_handle<> send_to(
112   std::coroutine_handle<> h, 113   std::coroutine_handle<> h,
113   capy::executor_ref ex, 114   capy::executor_ref ex,
114   buffer_param buf, 115   buffer_param buf,
115   endpoint dest, 116   endpoint dest,
116   int flags, 117   int flags,
117   std::stop_token token, 118   std::stop_token token,
118   std::error_code* ec, 119   std::error_code* ec,
119   std::size_t* bytes_out) = 0; 120   std::size_t* bytes_out) = 0;
120   121  
121   /** Initiate an asynchronous recv_from operation. 122   /** Initiate an asynchronous recv_from operation.
122   123  
123   @param h Coroutine handle to resume on completion. 124   @param h Coroutine handle to resume on completion.
124   @param ex Executor for dispatching the completion. 125   @param ex Executor for dispatching the completion.
125   @param buf The buffer to receive into. 126   @param buf The buffer to receive into.
126   @param source Output endpoint for the sender's address. 127   @param source Output endpoint for the sender's address.
127   @param flags Platform message flags (e.g. `MSG_PEEK`). 128   @param flags Platform message flags (e.g. `MSG_PEEK`).
128   @param token Stop token for cancellation. 129   @param token Stop token for cancellation.
129   @param ec Output error code. 130   @param ec Output error code.
130   @param bytes_out Output bytes transferred. 131   @param bytes_out Output bytes transferred.
131   132  
132   @return Coroutine handle to resume immediately. 133   @return Coroutine handle to resume immediately.
133   */ 134   */
134   virtual std::coroutine_handle<> recv_from( 135   virtual std::coroutine_handle<> recv_from(
135   std::coroutine_handle<> h, 136   std::coroutine_handle<> h,
136   capy::executor_ref ex, 137   capy::executor_ref ex,
137   buffer_param buf, 138   buffer_param buf,
138   endpoint* source, 139   endpoint* source,
139   int flags, 140   int flags,
140   std::stop_token token, 141   std::stop_token token,
141   std::error_code* ec, 142   std::error_code* ec,
142   std::size_t* bytes_out) = 0; 143   std::size_t* bytes_out) = 0;
143   144  
144   /// Return the platform socket descriptor. 145   /// Return the platform socket descriptor.
145   virtual native_handle_type native_handle() const noexcept = 0; 146   virtual native_handle_type native_handle() const noexcept = 0;
146   147  
147   /** Request cancellation of pending asynchronous operations. 148   /** Request cancellation of pending asynchronous operations.
148   149  
149   All outstanding operations complete with operation_canceled 150   All outstanding operations complete with operation_canceled
150   error. Check `ec == cond::canceled` for portable comparison. 151   error. Check `ec == cond::canceled` for portable comparison.
151   */ 152   */
152   virtual void cancel() noexcept = 0; 153   virtual void cancel() noexcept = 0;
153   154  
154   /** Set a socket option. 155   /** Set a socket option.
155   156  
156   @param level The protocol level (e.g. `SOL_SOCKET`). 157   @param level The protocol level (e.g. `SOL_SOCKET`).
157   @param optname The option name. 158   @param optname The option name.
158   @param data Pointer to the option value. 159   @param data Pointer to the option value.
159   @param size Size of the option value in bytes. 160   @param size Size of the option value in bytes.
160   @return Error code on failure, empty on success. 161   @return Error code on failure, empty on success.
161   */ 162   */
162   virtual std::error_code set_option( 163   virtual std::error_code set_option(
163   int level, 164   int level,
164   int optname, 165   int optname,
165   void const* data, 166   void const* data,
166   std::size_t size) noexcept = 0; 167   std::size_t size) noexcept = 0;
167   168  
168   /** Get a socket option. 169   /** Get a socket option.
169   170  
170   @param level The protocol level (e.g. `SOL_SOCKET`). 171   @param level The protocol level (e.g. `SOL_SOCKET`).
171   @param optname The option name. 172   @param optname The option name.
172   @param data Pointer to receive the option value. 173   @param data Pointer to receive the option value.
173   @param size On entry, the size of the buffer. On exit, 174   @param size On entry, the size of the buffer. On exit,
174   the size of the option value. 175   the size of the option value.
175   @return Error code on failure, empty on success. 176   @return Error code on failure, empty on success.
176   */ 177   */
177   virtual std::error_code 178   virtual std::error_code
178   get_option(int level, int optname, void* data, std::size_t* size) 179   get_option(int level, int optname, void* data, std::size_t* size)
179   const noexcept = 0; 180   const noexcept = 0;
180   181  
181   /// Return the cached local endpoint. 182   /// Return the cached local endpoint.
182   virtual endpoint local_endpoint() const noexcept = 0; 183   virtual endpoint local_endpoint() const noexcept = 0;
183   184  
184   /// Return the cached remote endpoint (connected mode). 185   /// Return the cached remote endpoint (connected mode).
185   virtual endpoint remote_endpoint() const noexcept = 0; 186   virtual endpoint remote_endpoint() const noexcept = 0;
186   187  
187   /** Initiate an asynchronous connect to set the default peer. 188   /** Initiate an asynchronous connect to set the default peer.
188   189  
189   @param h Coroutine handle to resume on completion. 190   @param h Coroutine handle to resume on completion.
190   @param ex Executor for dispatching the completion. 191   @param ex Executor for dispatching the completion.
191   @param ep The remote endpoint to connect to. 192   @param ep The remote endpoint to connect to.
192   @param token Stop token for cancellation. 193   @param token Stop token for cancellation.
193   @param ec Output error code. 194   @param ec Output error code.
194   195  
195   @return Coroutine handle to resume immediately. 196   @return Coroutine handle to resume immediately.
196   */ 197   */
197   virtual std::coroutine_handle<> connect( 198   virtual std::coroutine_handle<> connect(
198   std::coroutine_handle<> h, 199   std::coroutine_handle<> h,
199   capy::executor_ref ex, 200   capy::executor_ref ex,
200   endpoint ep, 201   endpoint ep,
201   std::stop_token token, 202   std::stop_token token,
202   std::error_code* ec) = 0; 203   std::error_code* ec) = 0;
203   204  
204   /** Initiate an asynchronous connected send operation. 205   /** Initiate an asynchronous connected send operation.
205   206  
206   @param h Coroutine handle to resume on completion. 207   @param h Coroutine handle to resume on completion.
207   @param ex Executor for dispatching the completion. 208   @param ex Executor for dispatching the completion.
208   @param buf The buffer data to send. 209   @param buf The buffer data to send.
209   @param flags Platform message flags (e.g. `MSG_DONTWAIT`). 210   @param flags Platform message flags (e.g. `MSG_DONTWAIT`).
210   @param token Stop token for cancellation. 211   @param token Stop token for cancellation.
211   @param ec Output error code. 212   @param ec Output error code.
212   @param bytes_out Output bytes transferred. 213   @param bytes_out Output bytes transferred.
213   214  
214   @return Coroutine handle to resume immediately. 215   @return Coroutine handle to resume immediately.
215   */ 216   */
216   virtual std::coroutine_handle<> send( 217   virtual std::coroutine_handle<> send(
217   std::coroutine_handle<> h, 218   std::coroutine_handle<> h,
218   capy::executor_ref ex, 219   capy::executor_ref ex,
219   buffer_param buf, 220   buffer_param buf,
220   int flags, 221   int flags,
221   std::stop_token token, 222   std::stop_token token,
222   std::error_code* ec, 223   std::error_code* ec,
223   std::size_t* bytes_out) = 0; 224   std::size_t* bytes_out) = 0;
224   225  
225   /** Initiate an asynchronous connected recv operation. 226   /** Initiate an asynchronous connected recv operation.
226   227  
227   @param h Coroutine handle to resume on completion. 228   @param h Coroutine handle to resume on completion.
228   @param ex Executor for dispatching the completion. 229   @param ex Executor for dispatching the completion.
229   @param buf The buffer to receive into. 230   @param buf The buffer to receive into.
230   @param flags Platform message flags (e.g. `MSG_PEEK`). 231   @param flags Platform message flags (e.g. `MSG_PEEK`).
231   @param token Stop token for cancellation. 232   @param token Stop token for cancellation.
232   @param ec Output error code. 233   @param ec Output error code.
233   @param bytes_out Output bytes transferred. 234   @param bytes_out Output bytes transferred.
234   235  
235   @return Coroutine handle to resume immediately. 236   @return Coroutine handle to resume immediately.
236   */ 237   */
237   virtual std::coroutine_handle<> recv( 238   virtual std::coroutine_handle<> recv(
238   std::coroutine_handle<> h, 239   std::coroutine_handle<> h,
239   capy::executor_ref ex, 240   capy::executor_ref ex,
240   buffer_param buf, 241   buffer_param buf,
241   int flags, 242   int flags,
242   std::stop_token token, 243   std::stop_token token,
243   std::error_code* ec, 244   std::error_code* ec,
244   std::size_t* bytes_out) = 0; 245   std::size_t* bytes_out) = 0;
  246 +
  247 + /** Initiate an asynchronous wait for socket readiness.
  248 +
  249 + Completes when the socket becomes ready for the
  250 + specified direction, or an error condition is
  251 + reported. No bytes are transferred.
  252 +
  253 + @param h Coroutine handle to resume on completion.
  254 + @param ex Executor for dispatching the completion.
  255 + @param w The direction to wait on.
  256 + @param token Stop token for cancellation.
  257 + @param ec Output error code.
  258 +
  259 + @return Coroutine handle to resume immediately.
  260 + */
  261 + virtual std::coroutine_handle<> wait(
  262 + std::coroutine_handle<> h,
  263 + capy::executor_ref ex,
  264 + wait_type w,
  265 + std::stop_token token,
  266 + std::error_code* ec) = 0;
245   }; 267   };
246   268  
247   /** Represent the awaitable returned by @ref send_to. 269   /** Represent the awaitable returned by @ref send_to.
248   270  
249   Captures the destination endpoint and buffer, then dispatches 271   Captures the destination endpoint and buffer, then dispatches
250   to the backend implementation on suspension. 272   to the backend implementation on suspension.
251   */ 273   */
252   struct send_to_awaitable 274   struct send_to_awaitable
253   : detail::bytes_op_base<send_to_awaitable> 275   : detail::bytes_op_base<send_to_awaitable>
254   { 276   {
255   udp_socket& s_; 277   udp_socket& s_;
256   buffer_param buf_; 278   buffer_param buf_;
257   endpoint dest_; 279   endpoint dest_;
258   int flags_; 280   int flags_;
259   281  
HITCBC 260   22 send_to_awaitable( 282   24 send_to_awaitable(
261   udp_socket& s, buffer_param buf, 283   udp_socket& s, buffer_param buf,
262   endpoint dest, int flags = 0) noexcept 284   endpoint dest, int flags = 0) noexcept
HITCBC 263   22 : s_(s), buf_(buf), dest_(dest), flags_(flags) {} 285   24 : s_(s), buf_(buf), dest_(dest), flags_(flags) {}
264   286  
HITCBC 265   22 std::coroutine_handle<> dispatch( 287   24 std::coroutine_handle<> dispatch(
266   std::coroutine_handle<> h, capy::executor_ref ex) const 288   std::coroutine_handle<> h, capy::executor_ref ex) const
267   { 289   {
HITCBC 268   44 return s_.get().send_to( 290   48 return s_.get().send_to(
HITCBC 269   44 h, ex, buf_, dest_, flags_, token_, &ec_, &bytes_); 291   48 h, ex, buf_, dest_, flags_, token_, &ec_, &bytes_);
270   } 292   }
271   }; 293   };
272   294  
273   /** Represent the awaitable returned by @ref recv_from. 295   /** Represent the awaitable returned by @ref recv_from.
274   296  
275   Captures the source endpoint reference and buffer, then 297   Captures the source endpoint reference and buffer, then
276   dispatches to the backend implementation on suspension. 298   dispatches to the backend implementation on suspension.
277   */ 299   */
278   struct recv_from_awaitable 300   struct recv_from_awaitable
279   : detail::bytes_op_base<recv_from_awaitable> 301   : detail::bytes_op_base<recv_from_awaitable>
280   { 302   {
281   udp_socket& s_; 303   udp_socket& s_;
282   buffer_param buf_; 304   buffer_param buf_;
283   endpoint& source_; 305   endpoint& source_;
284   int flags_; 306   int flags_;
285   307  
HITCBC 286   32 recv_from_awaitable( 308   32 recv_from_awaitable(
287   udp_socket& s, buffer_param buf, 309   udp_socket& s, buffer_param buf,
288   endpoint& source, int flags = 0) noexcept 310   endpoint& source, int flags = 0) noexcept
HITCBC 289   32 : s_(s), buf_(buf), source_(source), flags_(flags) {} 311   32 : s_(s), buf_(buf), source_(source), flags_(flags) {}
290   312  
HITCBC 291   32 std::coroutine_handle<> dispatch( 313   32 std::coroutine_handle<> dispatch(
292   std::coroutine_handle<> h, capy::executor_ref ex) const 314   std::coroutine_handle<> h, capy::executor_ref ex) const
293   { 315   {
HITCBC 294   64 return s_.get().recv_from( 316   64 return s_.get().recv_from(
HITCBC 295   64 h, ex, buf_, &source_, flags_, token_, &ec_, &bytes_); 317   64 h, ex, buf_, &source_, flags_, token_, &ec_, &bytes_);
296   } 318   }
297   }; 319   };
298   320  
299   /// Represent the awaitable returned by @ref connect. 321   /// Represent the awaitable returned by @ref connect.
300   struct connect_awaitable 322   struct connect_awaitable
301   : detail::void_op_base<connect_awaitable> 323   : detail::void_op_base<connect_awaitable>
302   { 324   {
303   udp_socket& s_; 325   udp_socket& s_;
304   endpoint endpoint_; 326   endpoint endpoint_;
305   327  
HITCBC 306   12 connect_awaitable(udp_socket& s, endpoint ep) noexcept 328   12 connect_awaitable(udp_socket& s, endpoint ep) noexcept
HITCBC 307   12 : s_(s), endpoint_(ep) {} 329   12 : s_(s), endpoint_(ep) {}
308   330  
HITCBC 309   12 std::coroutine_handle<> dispatch( 331   12 std::coroutine_handle<> dispatch(
310   std::coroutine_handle<> h, capy::executor_ref ex) const 332   std::coroutine_handle<> h, capy::executor_ref ex) const
311   { 333   {
HITCBC 312   12 return s_.get().connect(h, ex, endpoint_, token_, &ec_); 334   12 return s_.get().connect(h, ex, endpoint_, token_, &ec_);
313   } 335   }
314   }; 336   };
315   337  
  338 + /// Represent the awaitable returned by @ref wait.
  339 + struct wait_awaitable
  340 + : detail::void_op_base<wait_awaitable>
  341 + {
  342 + udp_socket& s_;
  343 + wait_type w_;
  344 +
HITGNC   345 + 4 wait_awaitable(udp_socket& s, wait_type w) noexcept
HITGNC   346 + 4 : s_(s), w_(w) {}
  347 +
HITGNC   348 + 4 std::coroutine_handle<> dispatch(
  349 + std::coroutine_handle<> h, capy::executor_ref ex) const
  350 + {
HITGNC   351 + 4 return s_.get().wait(h, ex, w_, token_, &ec_);
  352 + }
  353 + };
  354 +
316   /// Represent the awaitable returned by @ref send. 355   /// Represent the awaitable returned by @ref send.
317   struct send_awaitable 356   struct send_awaitable
318   : detail::bytes_op_base<send_awaitable> 357   : detail::bytes_op_base<send_awaitable>
319   { 358   {
320   udp_socket& s_; 359   udp_socket& s_;
321   buffer_param buf_; 360   buffer_param buf_;
322   int flags_; 361   int flags_;
323   362  
HITCBC 324   6 send_awaitable( 363   6 send_awaitable(
325   udp_socket& s, buffer_param buf, 364   udp_socket& s, buffer_param buf,
326   int flags = 0) noexcept 365   int flags = 0) noexcept
HITCBC 327   6 : s_(s), buf_(buf), flags_(flags) {} 366   6 : s_(s), buf_(buf), flags_(flags) {}
328   367  
HITCBC 329   6 std::coroutine_handle<> dispatch( 368   6 std::coroutine_handle<> dispatch(
330   std::coroutine_handle<> h, capy::executor_ref ex) const 369   std::coroutine_handle<> h, capy::executor_ref ex) const
331   { 370   {
HITCBC 332   12 return s_.get().send( 371   12 return s_.get().send(
HITCBC 333   12 h, ex, buf_, flags_, token_, &ec_, &bytes_); 372   12 h, ex, buf_, flags_, token_, &ec_, &bytes_);
334   } 373   }
335   }; 374   };
336   375  
337   /// Represent the awaitable returned by @ref recv. 376   /// Represent the awaitable returned by @ref recv.
338   struct recv_awaitable 377   struct recv_awaitable
339   : detail::bytes_op_base<recv_awaitable> 378   : detail::bytes_op_base<recv_awaitable>
340   { 379   {
341   udp_socket& s_; 380   udp_socket& s_;
342   buffer_param buf_; 381   buffer_param buf_;
343   int flags_; 382   int flags_;
344   383  
HITCBC 345   4 recv_awaitable( 384   4 recv_awaitable(
346   udp_socket& s, buffer_param buf, 385   udp_socket& s, buffer_param buf,
347   int flags = 0) noexcept 386   int flags = 0) noexcept
HITCBC 348   4 : s_(s), buf_(buf), flags_(flags) {} 387   4 : s_(s), buf_(buf), flags_(flags) {}
349   388  
HITCBC 350   4 std::coroutine_handle<> dispatch( 389   4 std::coroutine_handle<> dispatch(
351   std::coroutine_handle<> h, capy::executor_ref ex) const 390   std::coroutine_handle<> h, capy::executor_ref ex) const
352   { 391   {
HITCBC 353   8 return s_.get().recv( 392   8 return s_.get().recv(
HITCBC 354   8 h, ex, buf_, flags_, token_, &ec_, &bytes_); 393   8 h, ex, buf_, flags_, token_, &ec_, &bytes_);
355   } 394   }
356   }; 395   };
357   396  
358   public: 397   public:
359   /** Destructor. 398   /** Destructor.
360   399  
361   Closes the socket if open, cancelling any pending operations. 400   Closes the socket if open, cancelling any pending operations.
362   */ 401   */
363   ~udp_socket() override; 402   ~udp_socket() override;
364   403  
365   /** Construct a socket from an execution context. 404   /** Construct a socket from an execution context.
366   405  
367   @param ctx The execution context that will own this socket. 406   @param ctx The execution context that will own this socket.
368   */ 407   */
369   explicit udp_socket(capy::execution_context& ctx); 408   explicit udp_socket(capy::execution_context& ctx);
370   409  
371   /** Construct a socket from an executor. 410   /** Construct a socket from an executor.
372   411  
373   The socket is associated with the executor's context. 412   The socket is associated with the executor's context.
374   413  
375   @param ex The executor whose context will own the socket. 414   @param ex The executor whose context will own the socket.
376   */ 415   */
377   template<class Ex> 416   template<class Ex>
378   requires(!std::same_as<std::remove_cvref_t<Ex>, udp_socket>) && 417   requires(!std::same_as<std::remove_cvref_t<Ex>, udp_socket>) &&
379   capy::Executor<Ex> 418   capy::Executor<Ex>
380   explicit udp_socket(Ex const& ex) : udp_socket(ex.context()) 419   explicit udp_socket(Ex const& ex) : udp_socket(ex.context())
381   { 420   {
382   } 421   }
383   422  
384   /** Move constructor. 423   /** Move constructor.
385   424  
386   Transfers ownership of the socket resources. 425   Transfers ownership of the socket resources.
387   426  
388   @param other The socket to move from. 427   @param other The socket to move from.
389   */ 428   */
HITCBC 390   2 udp_socket(udp_socket&& other) noexcept : io_object(std::move(other)) {} 429   2 udp_socket(udp_socket&& other) noexcept : io_object(std::move(other)) {}
391   430  
392   /** Move assignment operator. 431   /** Move assignment operator.
393   432  
394   Closes any existing socket and transfers ownership. 433   Closes any existing socket and transfers ownership.
395   434  
396   @param other The socket to move from. 435   @param other The socket to move from.
397   @return Reference to this socket. 436   @return Reference to this socket.
398   */ 437   */
HITCBC 399   2 udp_socket& operator=(udp_socket&& other) noexcept 438   2 udp_socket& operator=(udp_socket&& other) noexcept
400   { 439   {
HITCBC 401   2 if (this != &other) 440   2 if (this != &other)
402   { 441   {
HITCBC 403   2 close(); 442   2 close();
HITCBC 404   2 h_ = std::move(other.h_); 443   2 h_ = std::move(other.h_);
405   } 444   }
HITCBC 406   2 return *this; 445   2 return *this;
407   } 446   }
408   447  
409   udp_socket(udp_socket const&) = delete; 448   udp_socket(udp_socket const&) = delete;
410   udp_socket& operator=(udp_socket const&) = delete; 449   udp_socket& operator=(udp_socket const&) = delete;
411   450  
412   /** Open the socket. 451   /** Open the socket.
413   452  
414   Creates a UDP socket and associates it with the platform 453   Creates a UDP socket and associates it with the platform
415   reactor. 454   reactor.
416   455  
417   @param proto The protocol (IPv4 or IPv6). Defaults to 456   @param proto The protocol (IPv4 or IPv6). Defaults to
418   `udp::v4()`. 457   `udp::v4()`.
419   458  
420   @throws std::system_error on failure. 459   @throws std::system_error on failure.
421   */ 460   */
422   void open(udp proto = udp::v4()); 461   void open(udp proto = udp::v4());
423   462  
424   /** Close the socket. 463   /** Close the socket.
425   464  
426   Releases socket resources. Any pending operations complete 465   Releases socket resources. Any pending operations complete
427   with `errc::operation_canceled`. 466   with `errc::operation_canceled`.
428   */ 467   */
429   void close(); 468   void close();
430   469  
431   /** Check if the socket is open. 470   /** Check if the socket is open.
432   471  
433   @return `true` if the socket is open and ready for operations. 472   @return `true` if the socket is open and ready for operations.
434   */ 473   */
HITCBC 435   444 bool is_open() const noexcept 474   466 bool is_open() const noexcept
436   { 475   {
437   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 476   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
438   return h_ && get().native_handle() != ~native_handle_type(0); 477   return h_ && get().native_handle() != ~native_handle_type(0);
439   #else 478   #else
HITCBC 440   444 return h_ && get().native_handle() >= 0; 479   466 return h_ && get().native_handle() >= 0;
441   #endif 480   #endif
442   } 481   }
443   482  
444   /** Bind the socket to a local endpoint. 483   /** Bind the socket to a local endpoint.
445   484  
446   Associates the socket with a local address and port. 485   Associates the socket with a local address and port.
447   Required before calling `recv_from`. 486   Required before calling `recv_from`.
448   487  
449   @param ep The local endpoint to bind to. 488   @param ep The local endpoint to bind to.
450   489  
451   @return Error code on failure, empty on success. 490   @return Error code on failure, empty on success.
452   491  
453   @throws std::logic_error if the socket is not open. 492   @throws std::logic_error if the socket is not open.
454   */ 493   */
455   [[nodiscard]] std::error_code bind(endpoint ep); 494   [[nodiscard]] std::error_code bind(endpoint ep);
456   495  
457   /** Cancel any pending asynchronous operations. 496   /** Cancel any pending asynchronous operations.
458   497  
459   All outstanding operations complete with 498   All outstanding operations complete with
460   `errc::operation_canceled`. Check `ec == cond::canceled` 499   `errc::operation_canceled`. Check `ec == cond::canceled`
461   for portable comparison. 500   for portable comparison.
462   */ 501   */
463   void cancel(); 502   void cancel();
464   503  
465   /** Get the native socket handle. 504   /** Get the native socket handle.
466   505  
467   @return The native socket handle, or -1 if not open. 506   @return The native socket handle, or -1 if not open.
468   */ 507   */
469   native_handle_type native_handle() const noexcept; 508   native_handle_type native_handle() const noexcept;
470   509  
471   /** Set a socket option. 510   /** Set a socket option.
472   511  
473   @param opt The option to set. 512   @param opt The option to set.
474   513  
475   @throws std::logic_error if the socket is not open. 514   @throws std::logic_error if the socket is not open.
476   @throws std::system_error on failure. 515   @throws std::system_error on failure.
477   */ 516   */
478   template<class Option> 517   template<class Option>
HITCBC 479   20 void set_option(Option const& opt) 518   20 void set_option(Option const& opt)
480   { 519   {
HITCBC 481   20 if (!is_open()) 520   20 if (!is_open())
MISUBC 482   detail::throw_logic_error("set_option: socket not open"); 521   detail::throw_logic_error("set_option: socket not open");
HITCBC 483   20 std::error_code ec = get().set_option( 522   20 std::error_code ec = get().set_option(
484   Option::level(), Option::name(), opt.data(), opt.size()); 523   Option::level(), Option::name(), opt.data(), opt.size());
HITCBC 485   20 if (ec) 524   20 if (ec)
MISUBC 486   detail::throw_system_error(ec, "udp_socket::set_option"); 525   detail::throw_system_error(ec, "udp_socket::set_option");
HITCBC 487   20 } 526   20 }
488   527  
489   /** Get a socket option. 528   /** Get a socket option.
490   529  
491   @return The current option value. 530   @return The current option value.
492   531  
493   @throws std::logic_error if the socket is not open. 532   @throws std::logic_error if the socket is not open.
494   @throws std::system_error on failure. 533   @throws std::system_error on failure.
495   */ 534   */
496   template<class Option> 535   template<class Option>
HITCBC 497   16 Option get_option() const 536   16 Option get_option() const
498   { 537   {
HITCBC 499   16 if (!is_open()) 538   16 if (!is_open())
MISUBC 500   detail::throw_logic_error("get_option: socket not open"); 539   detail::throw_logic_error("get_option: socket not open");
HITCBC 501   16 Option opt{}; 540   16 Option opt{};
HITCBC 502   16 std::size_t sz = opt.size(); 541   16 std::size_t sz = opt.size();
503   std::error_code ec = 542   std::error_code ec =
HITCBC 504   16 get().get_option(Option::level(), Option::name(), opt.data(), &sz); 543   16 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
HITCBC 505   16 if (ec) 544   16 if (ec)
MISUBC 506   detail::throw_system_error(ec, "udp_socket::get_option"); 545   detail::throw_system_error(ec, "udp_socket::get_option");
HITCBC 507   16 opt.resize(sz); 546   16 opt.resize(sz);
HITCBC 508   16 return opt; 547   16 return opt;
509   } 548   }
510   549  
511   /** Get the local endpoint of the socket. 550   /** Get the local endpoint of the socket.
512   551  
513   @return The local endpoint, or a default endpoint if not bound. 552   @return The local endpoint, or a default endpoint if not bound.
514   */ 553   */
515   endpoint local_endpoint() const noexcept; 554   endpoint local_endpoint() const noexcept;
516   555  
517   /** Send a datagram to the specified destination. 556   /** Send a datagram to the specified destination.
518   557  
519   @param buf The buffer containing data to send. 558   @param buf The buffer containing data to send.
520   @param dest The destination endpoint. 559   @param dest The destination endpoint.
521   @param flags Message flags (e.g. message_flags::dont_route). 560   @param flags Message flags (e.g. message_flags::dont_route).
522   561  
523   @return An awaitable that completes with 562   @return An awaitable that completes with
524   `io_result<std::size_t>`. 563   `io_result<std::size_t>`.
525   564  
526   @throws std::logic_error if the socket is not open. 565   @throws std::logic_error if the socket is not open.
527   */ 566   */
528   template<capy::ConstBufferSequence Buffers> 567   template<capy::ConstBufferSequence Buffers>
HITCBC 529   22 auto send_to( 568   24 auto send_to(
530   Buffers const& buf, 569   Buffers const& buf,
531   endpoint dest, 570   endpoint dest,
532   corosio::message_flags flags) 571   corosio::message_flags flags)
533   { 572   {
HITCBC 534   22 if (!is_open()) 573   24 if (!is_open())
MISUBC 535   detail::throw_logic_error("send_to: socket not open"); 574   detail::throw_logic_error("send_to: socket not open");
536   return send_to_awaitable( 575   return send_to_awaitable(
HITCBC 537   22 *this, buf, dest, static_cast<int>(flags)); 576   24 *this, buf, dest, static_cast<int>(flags));
538   } 577   }
539   578  
540   /// @overload 579   /// @overload
541   template<capy::ConstBufferSequence Buffers> 580   template<capy::ConstBufferSequence Buffers>
HITCBC 542   22 auto send_to(Buffers const& buf, endpoint dest) 581   24 auto send_to(Buffers const& buf, endpoint dest)
543   { 582   {
HITCBC 544   22 return send_to(buf, dest, corosio::message_flags::none); 583   24 return send_to(buf, dest, corosio::message_flags::none);
545   } 584   }
546   585  
547   /** Receive a datagram and capture the sender's endpoint. 586   /** Receive a datagram and capture the sender's endpoint.
548   587  
549   @param buf The buffer to receive data into. 588   @param buf The buffer to receive data into.
550   @param source Reference to an endpoint that will be set to 589   @param source Reference to an endpoint that will be set to
551   the sender's address on successful completion. 590   the sender's address on successful completion.
552   @param flags Message flags (e.g. message_flags::peek). 591   @param flags Message flags (e.g. message_flags::peek).
553   592  
554   @return An awaitable that completes with 593   @return An awaitable that completes with
555   `io_result<std::size_t>`. 594   `io_result<std::size_t>`.
556   595  
557   @throws std::logic_error if the socket is not open. 596   @throws std::logic_error if the socket is not open.
558   */ 597   */
559   template<capy::MutableBufferSequence Buffers> 598   template<capy::MutableBufferSequence Buffers>
HITCBC 560   32 auto recv_from( 599   32 auto recv_from(
561   Buffers const& buf, 600   Buffers const& buf,
562   endpoint& source, 601   endpoint& source,
563   corosio::message_flags flags) 602   corosio::message_flags flags)
564   { 603   {
HITCBC 565   32 if (!is_open()) 604   32 if (!is_open())
MISUBC 566   detail::throw_logic_error("recv_from: socket not open"); 605   detail::throw_logic_error("recv_from: socket not open");
567   return recv_from_awaitable( 606   return recv_from_awaitable(
HITCBC 568   32 *this, buf, source, static_cast<int>(flags)); 607   32 *this, buf, source, static_cast<int>(flags));
569   } 608   }
570   609  
571   /// @overload 610   /// @overload
572   template<capy::MutableBufferSequence Buffers> 611   template<capy::MutableBufferSequence Buffers>
HITCBC 573   32 auto recv_from(Buffers const& buf, endpoint& source) 612   32 auto recv_from(Buffers const& buf, endpoint& source)
574   { 613   {
HITCBC 575   32 return recv_from(buf, source, corosio::message_flags::none); 614   32 return recv_from(buf, source, corosio::message_flags::none);
576   } 615   }
577   616  
578   /** Initiate an asynchronous connect to set the default peer. 617   /** Initiate an asynchronous connect to set the default peer.
579   618  
580   If the socket is not already open, it is opened automatically 619   If the socket is not already open, it is opened automatically
581   using the address family of @p ep. 620   using the address family of @p ep.
582   621  
583   @param ep The remote endpoint to connect to. 622   @param ep The remote endpoint to connect to.
584   623  
585   @return An awaitable that completes with `io_result<>`. 624   @return An awaitable that completes with `io_result<>`.
586   625  
587   @throws std::system_error if the socket needs to be opened 626   @throws std::system_error if the socket needs to be opened
588   and the open fails. 627   and the open fails.
589   */ 628   */
HITCBC 590   12 auto connect(endpoint ep) 629   12 auto connect(endpoint ep)
591   { 630   {
HITCBC 592   12 if (!is_open()) 631   12 if (!is_open())
HITCBC 593   8 open(ep.is_v6() ? udp::v6() : udp::v4()); 632   8 open(ep.is_v6() ? udp::v6() : udp::v4());
HITCBC 594   12 return connect_awaitable(*this, ep); 633   12 return connect_awaitable(*this, ep);
  634 + }
  635 +
  636 + /** Wait for the socket to become ready in a given direction.
  637 +
  638 + Suspends until the socket is ready for the requested
  639 + direction, or an error condition is reported. No bytes
  640 + are transferred.
  641 +
  642 + The operation supports cancellation via `std::stop_token`.
  643 +
  644 + @param w The wait direction (read, write, or error).
  645 +
  646 + @return An awaitable that completes with `io_result<>`.
  647 +
  648 + @par Preconditions
  649 + The socket must be open. This socket must outlive the
  650 + returned awaitable.
  651 + */
HITGNC   652 + 4 [[nodiscard]] auto wait(wait_type w)
  653 + {
HITGNC   654 + 4 return wait_awaitable(*this, w);
595   } 655   }
596   656  
597   /** Send a datagram to the connected peer. 657   /** Send a datagram to the connected peer.
598   658  
599   @param buf The buffer containing data to send. 659   @param buf The buffer containing data to send.
600   @param flags Message flags. 660   @param flags Message flags.
601   661  
602   @return An awaitable that completes with 662   @return An awaitable that completes with
603   `io_result<std::size_t>`. 663   `io_result<std::size_t>`.
604   664  
605   @throws std::logic_error if the socket is not open. 665   @throws std::logic_error if the socket is not open.
606   */ 666   */
607   template<capy::ConstBufferSequence Buffers> 667   template<capy::ConstBufferSequence Buffers>
HITCBC 608   6 auto send(Buffers const& buf, corosio::message_flags flags) 668   6 auto send(Buffers const& buf, corosio::message_flags flags)
609   { 669   {
HITCBC 610   6 if (!is_open()) 670   6 if (!is_open())
MISUBC 611   detail::throw_logic_error("send: socket not open"); 671   detail::throw_logic_error("send: socket not open");
612   return send_awaitable( 672   return send_awaitable(
HITCBC 613   6 *this, buf, static_cast<int>(flags)); 673   6 *this, buf, static_cast<int>(flags));
614   } 674   }
615   675  
616   /// @overload 676   /// @overload
617   template<capy::ConstBufferSequence Buffers> 677   template<capy::ConstBufferSequence Buffers>
HITCBC 618   6 auto send(Buffers const& buf) 678   6 auto send(Buffers const& buf)
619   { 679   {
HITCBC 620   6 return send(buf, corosio::message_flags::none); 680   6 return send(buf, corosio::message_flags::none);
621   } 681   }
622   682  
623   /** Receive a datagram from the connected peer. 683   /** Receive a datagram from the connected peer.
624   684  
625   @param buf The buffer to receive data into. 685   @param buf The buffer to receive data into.
626   @param flags Message flags (e.g. message_flags::peek). 686   @param flags Message flags (e.g. message_flags::peek).
627   687  
628   @return An awaitable that completes with 688   @return An awaitable that completes with
629   `io_result<std::size_t>`. 689   `io_result<std::size_t>`.
630   690  
631   @throws std::logic_error if the socket is not open. 691   @throws std::logic_error if the socket is not open.
632   */ 692   */
633   template<capy::MutableBufferSequence Buffers> 693   template<capy::MutableBufferSequence Buffers>
HITCBC 634   4 auto recv(Buffers const& buf, corosio::message_flags flags) 694   4 auto recv(Buffers const& buf, corosio::message_flags flags)
635   { 695   {
HITCBC 636   4 if (!is_open()) 696   4 if (!is_open())
MISUBC 637   detail::throw_logic_error("recv: socket not open"); 697   detail::throw_logic_error("recv: socket not open");
638   return recv_awaitable( 698   return recv_awaitable(
HITCBC 639   4 *this, buf, static_cast<int>(flags)); 699   4 *this, buf, static_cast<int>(flags));
640   } 700   }
641   701  
642   /// @overload 702   /// @overload
643   template<capy::MutableBufferSequence Buffers> 703   template<capy::MutableBufferSequence Buffers>
HITCBC 644   4 auto recv(Buffers const& buf) 704   4 auto recv(Buffers const& buf)
645   { 705   {
HITCBC 646   4 return recv(buf, corosio::message_flags::none); 706   4 return recv(buf, corosio::message_flags::none);
647   } 707   }
648   708  
649   /** Get the remote endpoint of the socket. 709   /** Get the remote endpoint of the socket.
650   710  
651   Returns the address and port of the connected peer. 711   Returns the address and port of the connected peer.
652   712  
653   @return The remote endpoint, or a default endpoint if 713   @return The remote endpoint, or a default endpoint if
654   not connected. 714   not connected.
655   */ 715   */
656   endpoint remote_endpoint() const noexcept; 716   endpoint remote_endpoint() const noexcept;
657   717  
658   protected: 718   protected:
659   /// Construct from a pre-built handle (for native_udp_socket). 719   /// Construct from a pre-built handle (for native_udp_socket).
660   explicit udp_socket(io_object::handle h) noexcept : io_object(std::move(h)) 720   explicit udp_socket(io_object::handle h) noexcept : io_object(std::move(h))
661   { 721   {
662   } 722   }
663   723  
664   private: 724   private:
665   /// Open the socket for the given protocol triple. 725   /// Open the socket for the given protocol triple.
666   void open_for_family(int family, int type, int protocol); 726   void open_for_family(int family, int type, int protocol);
667   727  
HITCBC 668   588 inline implementation& get() const noexcept 728   620 inline implementation& get() const noexcept
669   { 729   {
HITCBC 670   588 return *static_cast<implementation*>(h_.get()); 730   620 return *static_cast<implementation*>(h_.get());
671   } 731   }
672   }; 732   };
673   733  
674   } // namespace boost::corosio 734   } // namespace boost::corosio
675   735  
676   #endif // BOOST_COROSIO_UDP_SOCKET_HPP 736   #endif // BOOST_COROSIO_UDP_SOCKET_HPP