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_URING
31 : #include <boost/corosio/native/detail/uring/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_, token_, &ec_,
123 12 : &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_, token_, &ec_,
170 20 : &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 4 : return self_.get_impl().wait(h, env->executor, w_, token_, &ec_);
206 : }
207 : };
208 :
209 : struct native_connect_awaitable
210 : {
211 : native_udp_socket& self_;
212 : endpoint endpoint_;
213 : std::stop_token token_;
214 : mutable std::error_code ec_;
215 :
216 10 : native_connect_awaitable(native_udp_socket& self, endpoint ep) noexcept
217 10 : : self_(self)
218 10 : , endpoint_(ep)
219 : {
220 10 : }
221 :
222 10 : bool await_ready() const noexcept
223 : {
224 : // A pre-set ec_ means the initiator failed before
225 : // dispatch (e.g. a closed object).
226 10 : return static_cast<bool>(ec_) || token_.stop_requested();
227 : }
228 :
229 10 : [[nodiscard]] capy::io_result<> await_resume() const noexcept
230 : {
231 10 : if (token_.stop_requested())
232 2 : return {make_error_code(std::errc::operation_canceled)};
233 8 : return {ec_};
234 : }
235 :
236 10 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
237 : -> std::coroutine_handle<>
238 : {
239 10 : token_ = env->stop_token;
240 30 : return self_.get_impl().connect(
241 30 : h, env->executor, endpoint_, token_, &ec_);
242 : }
243 : };
244 :
245 : template<class ConstBufferSequence>
246 : struct native_send_awaitable
247 : {
248 : native_udp_socket& self_;
249 : ConstBufferSequence buffers_;
250 : int flags_;
251 : std::stop_token token_;
252 : mutable std::error_code ec_;
253 : mutable std::size_t bytes_transferred_ = 0;
254 :
255 8 : native_send_awaitable(
256 : native_udp_socket& self,
257 : ConstBufferSequence buffers,
258 : int flags) noexcept
259 8 : : self_(self)
260 8 : , buffers_(std::move(buffers))
261 8 : , flags_(flags)
262 : {
263 8 : }
264 :
265 8 : bool await_ready() const noexcept
266 : {
267 : // A pre-set ec_ means the initiator failed before
268 : // dispatch (e.g. a closed object).
269 8 : return static_cast<bool>(ec_) || token_.stop_requested();
270 : }
271 :
272 8 : [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
273 : {
274 8 : if (token_.stop_requested())
275 2 : return {make_error_code(std::errc::operation_canceled), 0};
276 6 : return {ec_, bytes_transferred_};
277 : }
278 :
279 6 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
280 : -> std::coroutine_handle<>
281 : {
282 6 : token_ = env->stop_token;
283 18 : return self_.get_impl().send(
284 6 : h, env->executor, buffers_, flags_, token_, &ec_,
285 12 : &bytes_transferred_);
286 : }
287 : };
288 :
289 : template<class MutableBufferSequence>
290 : struct native_recv_awaitable
291 : {
292 : native_udp_socket& self_;
293 : MutableBufferSequence buffers_;
294 : int flags_;
295 : std::stop_token token_;
296 : mutable std::error_code ec_;
297 : mutable std::size_t bytes_transferred_ = 0;
298 :
299 6 : native_recv_awaitable(
300 : native_udp_socket& self,
301 : MutableBufferSequence buffers,
302 : int flags) noexcept
303 6 : : self_(self)
304 6 : , buffers_(std::move(buffers))
305 6 : , flags_(flags)
306 : {
307 6 : }
308 :
309 6 : bool await_ready() const noexcept
310 : {
311 : // A pre-set ec_ means the initiator failed before
312 : // dispatch (e.g. a closed object).
313 6 : return static_cast<bool>(ec_) || token_.stop_requested();
314 : }
315 :
316 6 : [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
317 : {
318 6 : if (token_.stop_requested())
319 2 : return {make_error_code(std::errc::operation_canceled), 0};
320 4 : return {ec_, bytes_transferred_};
321 : }
322 :
323 4 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
324 : -> std::coroutine_handle<>
325 : {
326 4 : token_ = env->stop_token;
327 12 : return self_.get_impl().recv(
328 4 : h, env->executor, buffers_, flags_, token_, &ec_,
329 8 : &bytes_transferred_);
330 : }
331 : };
332 :
333 : public:
334 : /** Construct a native UDP socket from an execution context.
335 :
336 : @param ctx The execution context that will own this socket.
337 : */
338 42 : explicit native_udp_socket(capy::execution_context& ctx)
339 42 : : udp_socket(create_handle<service_type>(ctx))
340 : {
341 42 : }
342 :
343 : /** Construct a native UDP socket from an executor.
344 :
345 : @param ex The executor whose context will own the socket.
346 : */
347 : template<class Ex>
348 : requires(!std::same_as<std::remove_cvref_t<Ex>, native_udp_socket>) &&
349 : capy::Executor<Ex>
350 : explicit native_udp_socket(Ex const& ex) : native_udp_socket(ex.context())
351 : {
352 : }
353 :
354 : /// Move construct.
355 2 : native_udp_socket(native_udp_socket&&) noexcept = default;
356 :
357 : /// Move assign.
358 : native_udp_socket& operator=(native_udp_socket&&) noexcept = default;
359 :
360 : native_udp_socket(native_udp_socket const&) = delete;
361 : native_udp_socket& operator=(native_udp_socket const&) = delete;
362 :
363 : /** Send a datagram to the specified destination.
364 :
365 : Calls the backend implementation directly, bypassing virtual
366 : dispatch. Otherwise identical to @ref udp_socket::send_to.
367 :
368 : @param buffers The buffer sequence containing data to send.
369 : @param dest The destination endpoint.
370 : @param flags Message flags.
371 :
372 : @return An awaitable yielding `(error_code, std::size_t)`.
373 :
374 : A closed socket reports `errc::bad_file_descriptor`.
375 : */
376 : template<capy::ConstBufferSequence CB>
377 : [[nodiscard]] auto
378 8 : send_to(CB const& buffers, endpoint dest, corosio::message_flags flags)
379 : {
380 8 : native_send_to_awaitable<CB> aw(
381 : *this, buffers, dest, static_cast<int>(flags));
382 8 : if (!is_open())
383 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
384 8 : return aw;
385 : }
386 :
387 : /// @overload
388 : template<capy::ConstBufferSequence CB>
389 8 : [[nodiscard]] auto send_to(CB const& buffers, endpoint dest)
390 : {
391 8 : return send_to(buffers, dest, corosio::message_flags::none);
392 : }
393 :
394 : /** Receive a datagram and capture the sender's endpoint.
395 :
396 : Calls the backend implementation directly, bypassing virtual
397 : dispatch. Otherwise identical to @ref udp_socket::recv_from.
398 :
399 : @param buffers The buffer sequence to receive data into.
400 : @param source Reference to an endpoint that will be set to
401 : the sender's address on successful completion.
402 : @param flags Message flags (e.g. message_flags::peek).
403 :
404 : @return An awaitable yielding `(error_code, std::size_t)`.
405 :
406 : A closed socket reports `errc::bad_file_descriptor`.
407 : */
408 : template<capy::MutableBufferSequence MB>
409 : [[nodiscard]] auto
410 12 : recv_from(MB const& buffers, endpoint& source, corosio::message_flags flags)
411 : {
412 12 : native_recv_from_awaitable<MB> aw(
413 : *this, buffers, source, static_cast<int>(flags));
414 12 : if (!is_open())
415 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
416 12 : return aw;
417 : }
418 :
419 : /// @overload
420 : template<capy::MutableBufferSequence MB>
421 12 : [[nodiscard]] auto recv_from(MB const& buffers, endpoint& source)
422 : {
423 12 : return recv_from(buffers, source, corosio::message_flags::none);
424 : }
425 :
426 : /** Asynchronously connect to set the default peer.
427 :
428 : Calls the backend implementation directly, bypassing virtual
429 : dispatch. Otherwise identical to @ref udp_socket::connect.
430 :
431 : If the socket is not already open, it is opened automatically
432 : using the address family of @p ep.
433 :
434 : @param ep The remote endpoint to connect to.
435 :
436 : @return An awaitable yielding `io_result<>`.
437 :
438 : If the socket needs to be opened and the open fails, the
439 : awaitable completes immediately with that error.
440 : */
441 10 : [[nodiscard]] auto connect(endpoint ep)
442 : {
443 10 : native_connect_awaitable aw(*this, ep);
444 10 : if (!is_open())
445 4 : aw.ec_ = open(ep.is_v6() ? udp::v6() : udp::v4());
446 10 : return aw;
447 : }
448 :
449 : /** Send a datagram to the connected peer.
450 :
451 : Calls the backend implementation directly, bypassing virtual
452 : dispatch. Otherwise identical to @ref udp_socket::send.
453 :
454 : @param buffers The buffer sequence containing data to send.
455 : @param flags Message flags.
456 :
457 : @return An awaitable yielding `(error_code, std::size_t)`.
458 :
459 : A closed socket reports `errc::bad_file_descriptor`.
460 : */
461 : template<capy::ConstBufferSequence CB>
462 8 : [[nodiscard]] auto send(CB const& buffers, corosio::message_flags flags)
463 : {
464 8 : native_send_awaitable<CB> aw(*this, buffers, static_cast<int>(flags));
465 8 : if (!is_open())
466 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
467 8 : return aw;
468 : }
469 :
470 : /// @overload
471 : template<capy::ConstBufferSequence CB>
472 8 : [[nodiscard]] auto send(CB const& buffers)
473 : {
474 8 : return send(buffers, corosio::message_flags::none);
475 : }
476 :
477 : /** Receive a datagram from the connected peer.
478 :
479 : Calls the backend implementation directly, bypassing virtual
480 : dispatch. Otherwise identical to @ref udp_socket::recv.
481 :
482 : @param buffers The buffer sequence to receive data into.
483 : @param flags Message flags (e.g. message_flags::peek).
484 :
485 : @return An awaitable yielding `(error_code, std::size_t)`.
486 :
487 : A closed socket reports `errc::bad_file_descriptor`.
488 : */
489 : template<capy::MutableBufferSequence MB>
490 6 : [[nodiscard]] auto recv(MB const& buffers, corosio::message_flags flags)
491 : {
492 6 : native_recv_awaitable<MB> aw(*this, buffers, static_cast<int>(flags));
493 6 : if (!is_open())
494 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
495 6 : return aw;
496 : }
497 :
498 : /// @overload
499 : template<capy::MutableBufferSequence MB>
500 6 : [[nodiscard]] auto recv(MB const& buffers)
501 : {
502 6 : return recv(buffers, corosio::message_flags::none);
503 : }
504 :
505 : /** Asynchronously wait for the socket to be ready.
506 :
507 : Calls the backend implementation directly, bypassing virtual
508 : dispatch. Otherwise identical to @ref udp_socket::wait.
509 :
510 : @param w The wait direction (read, write, or error).
511 :
512 : @return An awaitable yielding `io_result<>`.
513 : */
514 4 : [[nodiscard]] auto wait(wait_type w)
515 : {
516 4 : return native_wait_awaitable(*this, w);
517 : }
518 : };
519 :
520 : } // namespace boost::corosio
521 :
522 : #endif // BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP
|