include/boost/corosio/udp_socket.hpp

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