95.56% Lines (129/135) 100.00% Functions (37/37)
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_NATIVE_NATIVE_UDP_SOCKET_HPP 10   #ifndef BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP
11   #define BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP 11   #define BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP
12   12  
13   #include <boost/corosio/udp_socket.hpp> 13   #include <boost/corosio/udp_socket.hpp>
14   #include <boost/corosio/backend.hpp> 14   #include <boost/corosio/backend.hpp>
15   15  
16   #ifndef BOOST_COROSIO_MRDOCS 16   #ifndef BOOST_COROSIO_MRDOCS
17   #if BOOST_COROSIO_HAS_EPOLL 17   #if BOOST_COROSIO_HAS_EPOLL
18   #include <boost/corosio/native/detail/epoll/epoll_types.hpp> 18   #include <boost/corosio/native/detail/epoll/epoll_types.hpp>
19   #endif 19   #endif
20   20  
21   #if BOOST_COROSIO_HAS_SELECT 21   #if BOOST_COROSIO_HAS_SELECT
22   #include <boost/corosio/native/detail/select/select_types.hpp> 22   #include <boost/corosio/native/detail/select/select_types.hpp>
23   #endif 23   #endif
24   24  
25   #if BOOST_COROSIO_HAS_KQUEUE 25   #if BOOST_COROSIO_HAS_KQUEUE
26   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp> 26   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp>
27   #endif 27   #endif
28   28  
29   #if BOOST_COROSIO_HAS_IO_URING 29   #if BOOST_COROSIO_HAS_IO_URING
30   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp> 30   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp>
31   #endif 31   #endif
32   32  
33   #if BOOST_COROSIO_HAS_IOCP 33   #if BOOST_COROSIO_HAS_IOCP
34   #include <boost/corosio/native/detail/iocp/win_udp_service.hpp> 34   #include <boost/corosio/native/detail/iocp/win_udp_service.hpp>
35   #endif 35   #endif
36   #endif // !BOOST_COROSIO_MRDOCS 36   #endif // !BOOST_COROSIO_MRDOCS
37   37  
38   namespace boost::corosio { 38   namespace boost::corosio {
39   39  
40   /** An asynchronous UDP socket with devirtualized I/O operations. 40   /** An asynchronous UDP socket with devirtualized I/O operations.
41   41  
42   This class template inherits from @ref udp_socket and shadows 42   This class template inherits from @ref udp_socket and shadows
43   the async operations (`send_to`, `recv_from`, `connect`, `send`, 43   the async operations (`send_to`, `recv_from`, `connect`, `send`,
44   `recv`) with versions that call the backend implementation 44   `recv`) with versions that call the backend implementation
45   directly, allowing the compiler to inline through the entire 45   directly, allowing the compiler to inline through the entire
46   call chain. 46   call chain.
47   47  
48   Non-async operations (`open`, `close`, `cancel`, `bind`, 48   Non-async operations (`open`, `close`, `cancel`, `bind`,
49   socket options) remain unchanged and dispatch through the 49   socket options) remain unchanged and dispatch through the
50   compiled library. 50   compiled library.
51   51  
52   A `native_udp_socket` IS-A `udp_socket` and can be passed to 52   A `native_udp_socket` IS-A `udp_socket` and can be passed to
53   any function expecting `udp_socket&`, in which case virtual 53   any function expecting `udp_socket&`, in which case virtual
54   dispatch is used transparently. 54   dispatch is used transparently.
55   55  
56   @tparam Backend A backend tag value (e.g., `epoll`) 56   @tparam Backend A backend tag value (e.g., `epoll`)
57   whose type provides the concrete implementation types. 57   whose type provides the concrete implementation types.
58   58  
59   @par Thread Safety 59   @par Thread Safety
60   Same as @ref udp_socket. 60   Same as @ref udp_socket.
61   61  
62   @par Example 62   @par Example
63   @code 63   @code
64   #include <boost/corosio/native/native_udp_socket.hpp> 64   #include <boost/corosio/native/native_udp_socket.hpp>
65   65  
66   native_io_context<epoll> ctx; 66   native_io_context<epoll> ctx;
67   native_udp_socket<epoll> s(ctx); 67   native_udp_socket<epoll> s(ctx);
68 - s.open(); 68 + if (auto ec = s.open())
69 - s.bind(endpoint(ipv4_address::any(), 9000)); 69 + co_return;
  70 + if (auto ec = s.bind(endpoint(ipv4_address::any(), 9000)))
  71 + co_return;
70   char buf[1024]; 72   char buf[1024];
71   endpoint sender; 73   endpoint sender;
72   auto [ec, n] = co_await s.recv_from( 74   auto [ec, n] = co_await s.recv_from(
73   capy::mutable_buffer(buf, sizeof(buf)), sender); 75   capy::mutable_buffer(buf, sizeof(buf)), sender);
74   @endcode 76   @endcode
75   77  
76   @see udp_socket, epoll_t 78   @see udp_socket, epoll_t
77   */ 79   */
78   template<auto Backend> 80   template<auto Backend>
79   class native_udp_socket : public udp_socket 81   class native_udp_socket : public udp_socket
80   { 82   {
81   using backend_type = decltype(Backend); 83   using backend_type = decltype(Backend);
82   using impl_type = typename backend_type::udp_socket_type; 84   using impl_type = typename backend_type::udp_socket_type;
83   using service_type = typename backend_type::udp_service_type; 85   using service_type = typename backend_type::udp_service_type;
84   86  
HITCBC 85   26 impl_type& get_impl() noexcept 87   26 impl_type& get_impl() noexcept
86   { 88   {
HITCBC 87   26 return *static_cast<impl_type*>(h_.get()); 89   26 return *static_cast<impl_type*>(h_.get());
88   } 90   }
89   91  
90   template<class ConstBufferSequence> 92   template<class ConstBufferSequence>
91   struct native_send_to_awaitable 93   struct native_send_to_awaitable
92   { 94   {
93   native_udp_socket& self_; 95   native_udp_socket& self_;
94   ConstBufferSequence buffers_; 96   ConstBufferSequence buffers_;
95   endpoint dest_; 97   endpoint dest_;
96   int flags_; 98   int flags_;
97   std::stop_token token_; 99   std::stop_token token_;
98   mutable std::error_code ec_; 100   mutable std::error_code ec_;
99   mutable std::size_t bytes_transferred_ = 0; 101   mutable std::size_t bytes_transferred_ = 0;
100   102  
HITCBC 101   4 native_send_to_awaitable( 103   6 native_send_to_awaitable(
102   native_udp_socket& self, 104   native_udp_socket& self,
103   ConstBufferSequence buffers, 105   ConstBufferSequence buffers,
104   endpoint dest, 106   endpoint dest,
105   int flags) noexcept 107   int flags) noexcept
HITCBC 106   4 : self_(self) 108   6 : self_(self)
HITCBC 107   4 , buffers_(std::move(buffers)) 109   6 , buffers_(std::move(buffers))
HITCBC 108   4 , dest_(dest) 110   6 , dest_(dest)
HITCBC 109   4 , flags_(flags) 111   6 , flags_(flags)
110   { 112   {
HITCBC 111   4 } 113   6 }
112   114  
HITCBC 113   4 bool await_ready() const noexcept 115   6 bool await_ready() const noexcept
114   { 116   {
ECB 115 - 4 return token_.stop_requested(); 117 + // A pre-set ec_ means the initiator failed before
  118 + // dispatch (e.g. a closed object).
HITGNC   119 + 6 return static_cast<bool>(ec_) || token_.stop_requested();
116   } 120   }
117   121  
HITCBC 118   4 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 122   6 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
119   { 123   {
HITCBC 120   4 if (token_.stop_requested()) 124   6 if (token_.stop_requested())
MISUBC 121   return {make_error_code(std::errc::operation_canceled), 0}; 125   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 122   4 return {ec_, bytes_transferred_}; 126   6 return {ec_, bytes_transferred_};
123   } 127   }
124   128  
HITCBC 125   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 129   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
126   -> std::coroutine_handle<> 130   -> std::coroutine_handle<>
127   { 131   {
HITCBC 128   4 token_ = env->stop_token; 132   4 token_ = env->stop_token;
HITCBC 129   12 return self_.get_impl().send_to( 133   12 return self_.get_impl().send_to(
HITCBC 130   4 h, env->executor, buffers_, dest_, flags_, 134   4 h, env->executor, buffers_, dest_, flags_,
HITCBC 131   12 token_, &ec_, &bytes_transferred_); 135   12 token_, &ec_, &bytes_transferred_);
132   } 136   }
133   }; 137   };
134   138  
135   template<class MutableBufferSequence> 139   template<class MutableBufferSequence>
136   struct native_recv_from_awaitable 140   struct native_recv_from_awaitable
137   { 141   {
138   native_udp_socket& self_; 142   native_udp_socket& self_;
139   MutableBufferSequence buffers_; 143   MutableBufferSequence buffers_;
140   endpoint& source_; 144   endpoint& source_;
141   int flags_; 145   int flags_;
142   std::stop_token token_; 146   std::stop_token token_;
143   mutable std::error_code ec_; 147   mutable std::error_code ec_;
144   mutable std::size_t bytes_transferred_ = 0; 148   mutable std::size_t bytes_transferred_ = 0;
145   149  
HITCBC 146   8 native_recv_from_awaitable( 150   10 native_recv_from_awaitable(
147   native_udp_socket& self, 151   native_udp_socket& self,
148   MutableBufferSequence buffers, 152   MutableBufferSequence buffers,
149   endpoint& source, 153   endpoint& source,
150   int flags) noexcept 154   int flags) noexcept
HITCBC 151   8 : self_(self) 155   10 : self_(self)
HITCBC 152   8 , buffers_(std::move(buffers)) 156   10 , buffers_(std::move(buffers))
HITCBC 153   8 , source_(source) 157   10 , source_(source)
HITCBC 154   8 , flags_(flags) 158   10 , flags_(flags)
155   { 159   {
HITCBC 156   8 } 160   10 }
157   161  
HITCBC 158   8 bool await_ready() const noexcept 162   10 bool await_ready() const noexcept
159   { 163   {
ECB 160 - 8 return token_.stop_requested(); 164 + // A pre-set ec_ means the initiator failed before
  165 + // dispatch (e.g. a closed object).
HITGNC   166 + 10 return static_cast<bool>(ec_) || token_.stop_requested();
161   } 167   }
162   168  
HITCBC 163   8 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 169   10 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
164   { 170   {
HITCBC 165   8 if (token_.stop_requested()) 171   10 if (token_.stop_requested())
MISUBC 166   return {make_error_code(std::errc::operation_canceled), 0}; 172   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 167   8 return {ec_, bytes_transferred_}; 173   10 return {ec_, bytes_transferred_};
168   } 174   }
169   175  
HITCBC 170   8 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 176   8 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
171   -> std::coroutine_handle<> 177   -> std::coroutine_handle<>
172   { 178   {
HITCBC 173   8 token_ = env->stop_token; 179   8 token_ = env->stop_token;
HITCBC 174   24 return self_.get_impl().recv_from( 180   24 return self_.get_impl().recv_from(
HITCBC 175   8 h, env->executor, buffers_, &source_, flags_, 181   8 h, env->executor, buffers_, &source_, flags_,
HITCBC 176   24 token_, &ec_, &bytes_transferred_); 182   24 token_, &ec_, &bytes_transferred_);
177   } 183   }
178   }; 184   };
179   185  
180   struct native_wait_awaitable 186   struct native_wait_awaitable
181   { 187   {
182   native_udp_socket& self_; 188   native_udp_socket& self_;
183   wait_type w_; 189   wait_type w_;
184   std::stop_token token_; 190   std::stop_token token_;
185   mutable std::error_code ec_; 191   mutable std::error_code ec_;
186   192  
HITCBC 187   2 native_wait_awaitable(native_udp_socket& self, wait_type w) noexcept 193   2 native_wait_awaitable(native_udp_socket& self, wait_type w) noexcept
HITCBC 188   2 : self_(self) 194   2 : self_(self)
HITCBC 189   2 , w_(w) 195   2 , w_(w)
190   { 196   {
HITCBC 191   2 } 197   2 }
192   198  
HITCBC 193   2 bool await_ready() const noexcept 199   2 bool await_ready() const noexcept
194   { 200   {
ECB 195 - 2 return token_.stop_requested(); 201 + // A pre-set ec_ means the initiator failed before
  202 + // dispatch (e.g. auto-open).
HITGNC   203 + 2 return static_cast<bool>(ec_) || token_.stop_requested();
196   } 204   }
197   205  
HITCBC 198   2 [[nodiscard]] capy::io_result<> await_resume() const noexcept 206   2 [[nodiscard]] capy::io_result<> await_resume() const noexcept
199   { 207   {
HITCBC 200   2 if (token_.stop_requested()) 208   2 if (token_.stop_requested())
MISUBC 201   return {make_error_code(std::errc::operation_canceled)}; 209   return {make_error_code(std::errc::operation_canceled)};
HITCBC 202   2 return {ec_}; 210   2 return {ec_};
203   } 211   }
204   212  
HITCBC 205   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 213   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
206   -> std::coroutine_handle<> 214   -> std::coroutine_handle<>
207   { 215   {
HITCBC 208   2 token_ = env->stop_token; 216   2 token_ = env->stop_token;
HITCBC 209   6 return self_.get_impl().wait( 217   6 return self_.get_impl().wait(
HITCBC 210   6 h, env->executor, w_, token_, &ec_); 218   6 h, env->executor, w_, token_, &ec_);
211   } 219   }
212   }; 220   };
213   221  
214   struct native_connect_awaitable 222   struct native_connect_awaitable
215   { 223   {
216   native_udp_socket& self_; 224   native_udp_socket& self_;
217   endpoint endpoint_; 225   endpoint endpoint_;
218   std::stop_token token_; 226   std::stop_token token_;
219   mutable std::error_code ec_; 227   mutable std::error_code ec_;
220   228  
HITCBC 221   6 native_connect_awaitable(native_udp_socket& self, endpoint ep) noexcept 229   6 native_connect_awaitable(native_udp_socket& self, endpoint ep) noexcept
HITCBC 222   6 : self_(self) 230   6 : self_(self)
HITCBC 223   6 , endpoint_(ep) 231   6 , endpoint_(ep)
224   { 232   {
HITCBC 225   6 } 233   6 }
226   234  
HITCBC 227   6 bool await_ready() const noexcept 235   6 bool await_ready() const noexcept
228   { 236   {
ECB 229 - 6 return token_.stop_requested(); 237 + // A pre-set ec_ means the initiator failed before
  238 + // dispatch (e.g. a closed object).
HITGNC   239 + 6 return static_cast<bool>(ec_) || token_.stop_requested();
230   } 240   }
231   241  
HITCBC 232   6 [[nodiscard]] capy::io_result<> await_resume() const noexcept 242   6 [[nodiscard]] capy::io_result<> await_resume() const noexcept
233   { 243   {
HITCBC 234   6 if (token_.stop_requested()) 244   6 if (token_.stop_requested())
MISUBC 235   return {make_error_code(std::errc::operation_canceled)}; 245   return {make_error_code(std::errc::operation_canceled)};
HITCBC 236   6 return {ec_}; 246   6 return {ec_};
237   } 247   }
238   248  
HITCBC 239   6 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 249   6 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
240   -> std::coroutine_handle<> 250   -> std::coroutine_handle<>
241   { 251   {
HITCBC 242   6 token_ = env->stop_token; 252   6 token_ = env->stop_token;
HITCBC 243   18 return self_.get_impl().connect( 253   18 return self_.get_impl().connect(
HITCBC 244   18 h, env->executor, endpoint_, token_, &ec_); 254   18 h, env->executor, endpoint_, token_, &ec_);
245   } 255   }
246   }; 256   };
247   257  
248   template<class ConstBufferSequence> 258   template<class ConstBufferSequence>
249   struct native_send_awaitable 259   struct native_send_awaitable
250   { 260   {
251   native_udp_socket& self_; 261   native_udp_socket& self_;
252   ConstBufferSequence buffers_; 262   ConstBufferSequence buffers_;
253   int flags_; 263   int flags_;
254   std::stop_token token_; 264   std::stop_token token_;
255   mutable std::error_code ec_; 265   mutable std::error_code ec_;
256   mutable std::size_t bytes_transferred_ = 0; 266   mutable std::size_t bytes_transferred_ = 0;
257   267  
HITCBC 258   4 native_send_awaitable( 268   6 native_send_awaitable(
259   native_udp_socket& self, 269   native_udp_socket& self,
260   ConstBufferSequence buffers, 270   ConstBufferSequence buffers,
261   int flags) noexcept 271   int flags) noexcept
HITCBC 262   4 : self_(self) 272   6 : self_(self)
HITCBC 263   4 , buffers_(std::move(buffers)) 273   6 , buffers_(std::move(buffers))
HITCBC 264   4 , flags_(flags) 274   6 , flags_(flags)
265   { 275   {
HITCBC 266   4 } 276   6 }
267   277  
HITCBC 268   4 bool await_ready() const noexcept 278   6 bool await_ready() const noexcept
269   { 279   {
ECB 270 - 4 return token_.stop_requested(); 280 + // A pre-set ec_ means the initiator failed before
  281 + // dispatch (e.g. a closed object).
HITGNC   282 + 6 return static_cast<bool>(ec_) || token_.stop_requested();
271   } 283   }
272   284  
HITCBC 273   4 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 285   6 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
274   { 286   {
HITCBC 275   4 if (token_.stop_requested()) 287   6 if (token_.stop_requested())
MISUBC 276   return {make_error_code(std::errc::operation_canceled), 0}; 288   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 277   4 return {ec_, bytes_transferred_}; 289   6 return {ec_, bytes_transferred_};
278   } 290   }
279   291  
HITCBC 280   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 292   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
281   -> std::coroutine_handle<> 293   -> std::coroutine_handle<>
282   { 294   {
HITCBC 283   4 token_ = env->stop_token; 295   4 token_ = env->stop_token;
HITCBC 284   12 return self_.get_impl().send( 296   12 return self_.get_impl().send(
HITCBC 285   4 h, env->executor, buffers_, flags_, 297   4 h, env->executor, buffers_, flags_,
HITCBC 286   12 token_, &ec_, &bytes_transferred_); 298   12 token_, &ec_, &bytes_transferred_);
287   } 299   }
288   }; 300   };
289   301  
290   template<class MutableBufferSequence> 302   template<class MutableBufferSequence>
291   struct native_recv_awaitable 303   struct native_recv_awaitable
292   { 304   {
293   native_udp_socket& self_; 305   native_udp_socket& self_;
294   MutableBufferSequence buffers_; 306   MutableBufferSequence buffers_;
295   int flags_; 307   int flags_;
296   std::stop_token token_; 308   std::stop_token token_;
297   mutable std::error_code ec_; 309   mutable std::error_code ec_;
298   mutable std::size_t bytes_transferred_ = 0; 310   mutable std::size_t bytes_transferred_ = 0;
299   311  
HITCBC 300   2 native_recv_awaitable( 312   4 native_recv_awaitable(
301   native_udp_socket& self, 313   native_udp_socket& self,
302   MutableBufferSequence buffers, 314   MutableBufferSequence buffers,
303   int flags) noexcept 315   int flags) noexcept
HITCBC 304   2 : self_(self) 316   4 : self_(self)
HITCBC 305   2 , buffers_(std::move(buffers)) 317   4 , buffers_(std::move(buffers))
HITCBC 306   2 , flags_(flags) 318   4 , flags_(flags)
307   { 319   {
HITCBC 308   2 } 320   4 }
309   321  
HITCBC 310   2 bool await_ready() const noexcept 322   4 bool await_ready() const noexcept
311   { 323   {
ECB 312 - 2 return token_.stop_requested(); 324 + // A pre-set ec_ means the initiator failed before
  325 + // dispatch (e.g. a closed object).
HITGNC   326 + 4 return static_cast<bool>(ec_) || token_.stop_requested();
313   } 327   }
314   328  
HITCBC 315   2 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 329   4 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
316   { 330   {
HITCBC 317   2 if (token_.stop_requested()) 331   4 if (token_.stop_requested())
MISUBC 318   return {make_error_code(std::errc::operation_canceled), 0}; 332   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 319   2 return {ec_, bytes_transferred_}; 333   4 return {ec_, bytes_transferred_};
320   } 334   }
321   335  
HITCBC 322   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 336   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
323   -> std::coroutine_handle<> 337   -> std::coroutine_handle<>
324   { 338   {
HITCBC 325   2 token_ = env->stop_token; 339   2 token_ = env->stop_token;
HITCBC 326   6 return self_.get_impl().recv( 340   6 return self_.get_impl().recv(
HITCBC 327   2 h, env->executor, buffers_, flags_, 341   2 h, env->executor, buffers_, flags_,
HITCBC 328   6 token_, &ec_, &bytes_transferred_); 342   6 token_, &ec_, &bytes_transferred_);
329   } 343   }
330   }; 344   };
331   345  
332   public: 346   public:
333   /** Construct a native UDP socket from an execution context. 347   /** Construct a native UDP socket from an execution context.
334   348  
335   @param ctx The execution context that will own this socket. 349   @param ctx The execution context that will own this socket.
336   */ 350   */
HITCBC 337   36 explicit native_udp_socket(capy::execution_context& ctx) 351   38 explicit native_udp_socket(capy::execution_context& ctx)
HITCBC 338   36 : udp_socket(create_handle<service_type>(ctx)) 352   38 : udp_socket(create_handle<service_type>(ctx))
339   { 353   {
HITCBC 340   36 } 354   38 }
341   355  
342   /** Construct a native UDP socket from an executor. 356   /** Construct a native UDP socket from an executor.
343   357  
344   @param ex The executor whose context will own the socket. 358   @param ex The executor whose context will own the socket.
345   */ 359   */
346   template<class Ex> 360   template<class Ex>
347   requires(!std::same_as<std::remove_cvref_t<Ex>, native_udp_socket>) && 361   requires(!std::same_as<std::remove_cvref_t<Ex>, native_udp_socket>) &&
348   capy::Executor<Ex> 362   capy::Executor<Ex>
349   explicit native_udp_socket(Ex const& ex) : native_udp_socket(ex.context()) 363   explicit native_udp_socket(Ex const& ex) : native_udp_socket(ex.context())
350   { 364   {
351   } 365   }
352   366  
353   /// Move construct. 367   /// Move construct.
HITCBC 354   2 native_udp_socket(native_udp_socket&&) noexcept = default; 368   2 native_udp_socket(native_udp_socket&&) noexcept = default;
355   369  
356   /// Move assign. 370   /// Move assign.
357   native_udp_socket& operator=(native_udp_socket&&) noexcept = default; 371   native_udp_socket& operator=(native_udp_socket&&) noexcept = default;
358   372  
359   native_udp_socket(native_udp_socket const&) = delete; 373   native_udp_socket(native_udp_socket const&) = delete;
360   native_udp_socket& operator=(native_udp_socket const&) = delete; 374   native_udp_socket& operator=(native_udp_socket const&) = delete;
361   375  
362   /** Send a datagram to the specified destination. 376   /** Send a datagram to the specified destination.
363   377  
364   Calls the backend implementation directly, bypassing virtual 378   Calls the backend implementation directly, bypassing virtual
365   dispatch. Otherwise identical to @ref udp_socket::send_to. 379   dispatch. Otherwise identical to @ref udp_socket::send_to.
366   380  
367   @param buffers The buffer sequence containing data to send. 381   @param buffers The buffer sequence containing data to send.
368   @param dest The destination endpoint. 382   @param dest The destination endpoint.
369   @param flags Message flags. 383   @param flags Message flags.
370   384  
371   @return An awaitable yielding `(error_code, std::size_t)`. 385   @return An awaitable yielding `(error_code, std::size_t)`.
  386 +
  387 + A closed socket reports `errc::bad_file_descriptor`.
372   */ 388   */
373   template<capy::ConstBufferSequence CB> 389   template<capy::ConstBufferSequence CB>
HITCBC 374 - 4 auto send_to( 390 + 6 [[nodiscard]] auto send_to(
375   CB const& buffers, 391   CB const& buffers,
376   endpoint dest, 392   endpoint dest,
377   corosio::message_flags flags) 393   corosio::message_flags flags)
378   { 394   {
HITGNC   395 + 6 native_send_to_awaitable<CB> aw(*this, buffers, dest, static_cast<int>(flags));
HITCBC 379   4 if (!is_open()) 396   6 if (!is_open())
HITGBC 380 - detail::throw_logic_error("send_to: socket not open"); 397 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITGIC 381 - return native_send_to_awaitable<CB>( 398 + 6 return aw;
DCB 382 - 4 *this, buffers, dest, static_cast<int>(flags));  
383   } 399   }
384   400  
385   /// @overload 401   /// @overload
386   template<capy::ConstBufferSequence CB> 402   template<capy::ConstBufferSequence CB>
HITCBC 387 - 4 auto send_to(CB const& buffers, endpoint dest) 403 + 6 [[nodiscard]] auto send_to(CB const& buffers, endpoint dest)
388   { 404   {
HITCBC 389   4 return send_to(buffers, dest, corosio::message_flags::none); 405   6 return send_to(buffers, dest, corosio::message_flags::none);
390   } 406   }
391   407  
392   /** Receive a datagram and capture the sender's endpoint. 408   /** Receive a datagram and capture the sender's endpoint.
393   409  
394   Calls the backend implementation directly, bypassing virtual 410   Calls the backend implementation directly, bypassing virtual
395   dispatch. Otherwise identical to @ref udp_socket::recv_from. 411   dispatch. Otherwise identical to @ref udp_socket::recv_from.
396   412  
397   @param buffers The buffer sequence to receive data into. 413   @param buffers The buffer sequence to receive data into.
398   @param source Reference to an endpoint that will be set to 414   @param source Reference to an endpoint that will be set to
399   the sender's address on successful completion. 415   the sender's address on successful completion.
400   @param flags Message flags (e.g. message_flags::peek). 416   @param flags Message flags (e.g. message_flags::peek).
401   417  
402   @return An awaitable yielding `(error_code, std::size_t)`. 418   @return An awaitable yielding `(error_code, std::size_t)`.
  419 +
  420 + A closed socket reports `errc::bad_file_descriptor`.
403   */ 421   */
404   template<capy::MutableBufferSequence MB> 422   template<capy::MutableBufferSequence MB>
HITCBC 405 - 8 auto recv_from( 423 + 10 [[nodiscard]] auto recv_from(
406   MB const& buffers, 424   MB const& buffers,
407   endpoint& source, 425   endpoint& source,
408   corosio::message_flags flags) 426   corosio::message_flags flags)
409   { 427   {
HITGNC   428 + 10 native_recv_from_awaitable<MB> aw(*this, buffers, source, static_cast<int>(flags));
HITCBC 410   8 if (!is_open()) 429   10 if (!is_open())
HITGBC 411 - detail::throw_logic_error("recv_from: socket not open"); 430 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITGIC 412 - return native_recv_from_awaitable<MB>( 431 + 10 return aw;
DCB 413 - 8 *this, buffers, source, static_cast<int>(flags));  
414   } 432   }
415   433  
416   /// @overload 434   /// @overload
417   template<capy::MutableBufferSequence MB> 435   template<capy::MutableBufferSequence MB>
HITCBC 418 - 8 auto recv_from(MB const& buffers, endpoint& source) 436 + 10 [[nodiscard]] auto recv_from(MB const& buffers, endpoint& source)
419   { 437   {
HITCBC 420   8 return recv_from(buffers, source, corosio::message_flags::none); 438   10 return recv_from(buffers, source, corosio::message_flags::none);
421   } 439   }
422   440  
423   /** Asynchronously connect to set the default peer. 441   /** Asynchronously connect to set the default peer.
424   442  
425   Calls the backend implementation directly, bypassing virtual 443   Calls the backend implementation directly, bypassing virtual
426   dispatch. Otherwise identical to @ref udp_socket::connect. 444   dispatch. Otherwise identical to @ref udp_socket::connect.
427   445  
428   If the socket is not already open, it is opened automatically 446   If the socket is not already open, it is opened automatically
429   using the address family of @p ep. 447   using the address family of @p ep.
430   448  
431   @param ep The remote endpoint to connect to. 449   @param ep The remote endpoint to connect to.
432   450  
433   @return An awaitable yielding `io_result<>`. 451   @return An awaitable yielding `io_result<>`.
434   452  
435 - @throws std::system_error if the socket needs to be opened 453 + If the socket needs to be opened and the open fails, the
436 - and the open fails. 454 + awaitable completes immediately with that error.
437   */ 455   */
HITCBC 438 - 6 auto connect(endpoint ep) 456 + 6 [[nodiscard]] auto connect(endpoint ep)
439   { 457   {
HITGNC   458 + 6 native_connect_awaitable aw(*this, ep);
HITCBC 440   6 if (!is_open()) 459   6 if (!is_open())
HITCBC 441 - 4 open(ep.is_v6() ? udp::v6() : udp::v4()); 460 + 4 aw.ec_ = open(ep.is_v6() ? udp::v6() : udp::v4());
HITCBC 442 - 6 return native_connect_awaitable(*this, ep); 461 + 6 return aw;
443   } 462   }
444   463  
445   /** Send a datagram to the connected peer. 464   /** Send a datagram to the connected peer.
446   465  
447   Calls the backend implementation directly, bypassing virtual 466   Calls the backend implementation directly, bypassing virtual
448   dispatch. Otherwise identical to @ref udp_socket::send. 467   dispatch. Otherwise identical to @ref udp_socket::send.
449   468  
450   @param buffers The buffer sequence containing data to send. 469   @param buffers The buffer sequence containing data to send.
451   @param flags Message flags. 470   @param flags Message flags.
452   471  
453   @return An awaitable yielding `(error_code, std::size_t)`. 472   @return An awaitable yielding `(error_code, std::size_t)`.
454   473  
455 - @throws std::logic_error if the socket is not open. 474 + A closed socket reports `errc::bad_file_descriptor`.
456   */ 475   */
457   template<capy::ConstBufferSequence CB> 476   template<capy::ConstBufferSequence CB>
HITCBC 458 - 4 auto send(CB const& buffers, corosio::message_flags flags) 477 + 6 [[nodiscard]] auto send(CB const& buffers, corosio::message_flags flags)
459   { 478   {
HITGNC   479 + 6 native_send_awaitable<CB> aw(*this, buffers, static_cast<int>(flags));
HITCBC 460   4 if (!is_open()) 480   6 if (!is_open())
HITGBC 461 - detail::throw_logic_error("send: socket not open"); 481 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITGIC 462 - return native_send_awaitable<CB>( 482 + 6 return aw;
DCB 463 - 4 *this, buffers, static_cast<int>(flags));  
464   } 483   }
465   484  
466   /// @overload 485   /// @overload
467   template<capy::ConstBufferSequence CB> 486   template<capy::ConstBufferSequence CB>
HITCBC 468 - 4 auto send(CB const& buffers) 487 + 6 [[nodiscard]] auto send(CB const& buffers)
469   { 488   {
HITCBC 470   4 return send(buffers, corosio::message_flags::none); 489   6 return send(buffers, corosio::message_flags::none);
471   } 490   }
472   491  
473   /** Receive a datagram from the connected peer. 492   /** Receive a datagram from the connected peer.
474   493  
475   Calls the backend implementation directly, bypassing virtual 494   Calls the backend implementation directly, bypassing virtual
476   dispatch. Otherwise identical to @ref udp_socket::recv. 495   dispatch. Otherwise identical to @ref udp_socket::recv.
477   496  
478   @param buffers The buffer sequence to receive data into. 497   @param buffers The buffer sequence to receive data into.
479   @param flags Message flags (e.g. message_flags::peek). 498   @param flags Message flags (e.g. message_flags::peek).
480   499  
481   @return An awaitable yielding `(error_code, std::size_t)`. 500   @return An awaitable yielding `(error_code, std::size_t)`.
482   501  
483 - @throws std::logic_error if the socket is not open. 502 + A closed socket reports `errc::bad_file_descriptor`.
484   */ 503   */
485   template<capy::MutableBufferSequence MB> 504   template<capy::MutableBufferSequence MB>
HITCBC 486 - 2 auto recv(MB const& buffers, corosio::message_flags flags) 505 + 4 [[nodiscard]] auto recv(MB const& buffers, corosio::message_flags flags)
487   { 506   {
HITGNC   507 + 4 native_recv_awaitable<MB> aw(*this, buffers, static_cast<int>(flags));
HITCBC 488   2 if (!is_open()) 508   4 if (!is_open())
HITGBC 489 - detail::throw_logic_error("recv: socket not open"); 509 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITGIC 490 - return native_recv_awaitable<MB>( 510 + 4 return aw;
DCB 491 - 2 *this, buffers, static_cast<int>(flags));  
492   } 511   }
493   512  
494   /// @overload 513   /// @overload
495   template<capy::MutableBufferSequence MB> 514   template<capy::MutableBufferSequence MB>
HITCBC 496 - 2 auto recv(MB const& buffers) 515 + 4 [[nodiscard]] auto recv(MB const& buffers)
497   { 516   {
HITCBC 498   2 return recv(buffers, corosio::message_flags::none); 517   4 return recv(buffers, corosio::message_flags::none);
499   } 518   }
500   519  
501   /** Asynchronously wait for the socket to be ready. 520   /** Asynchronously wait for the socket to be ready.
502   521  
503   Calls the backend implementation directly, bypassing virtual 522   Calls the backend implementation directly, bypassing virtual
504   dispatch. Otherwise identical to @ref udp_socket::wait. 523   dispatch. Otherwise identical to @ref udp_socket::wait.
505   524  
506   @param w The wait direction (read, write, or error). 525   @param w The wait direction (read, write, or error).
507   526  
508   @return An awaitable yielding `io_result<>`. 527   @return An awaitable yielding `io_result<>`.
509   */ 528   */
HITCBC 510   2 [[nodiscard]] auto wait(wait_type w) 529   2 [[nodiscard]] auto wait(wait_type w)
511   { 530   {
HITCBC 512   2 return native_wait_awaitable(*this, w); 531   2 return native_wait_awaitable(*this, w);
513   } 532   }
514   }; 533   };
515   534  
516   } // namespace boost::corosio 535   } // namespace boost::corosio
517   536  
518   #endif // BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP 537   #endif // BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP