include/boost/corosio/udp_socket.hpp

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