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