include/boost/corosio/udp_socket.hpp

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