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