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