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