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 : s.open();
69 : s.bind(endpoint(ipv4_address::any(), 9000));
70 : char buf[1024];
71 : endpoint sender;
72 : auto [ec, n] = co_await s.recv_from(
73 : capy::mutable_buffer(buf, sizeof(buf)), sender);
74 : @endcode
75 :
76 : @see udp_socket, epoll_t
77 : */
78 : template<auto Backend>
79 : class native_udp_socket : public udp_socket
80 : {
81 : using backend_type = decltype(Backend);
82 : using impl_type = typename backend_type::udp_socket_type;
83 : using service_type = typename backend_type::udp_service_type;
84 :
85 HIT 26 : impl_type& get_impl() noexcept
86 : {
87 26 : return *static_cast<impl_type*>(h_.get());
88 : }
89 :
90 : template<class ConstBufferSequence>
91 : struct native_send_to_awaitable
92 : {
93 : native_udp_socket& self_;
94 : ConstBufferSequence buffers_;
95 : endpoint dest_;
96 : int flags_;
97 : std::stop_token token_;
98 : mutable std::error_code ec_;
99 : mutable std::size_t bytes_transferred_ = 0;
100 :
101 4 : native_send_to_awaitable(
102 : native_udp_socket& self,
103 : ConstBufferSequence buffers,
104 : endpoint dest,
105 : int flags) noexcept
106 4 : : self_(self)
107 4 : , buffers_(std::move(buffers))
108 4 : , dest_(dest)
109 4 : , flags_(flags)
110 : {
111 4 : }
112 :
113 4 : bool await_ready() const noexcept
114 : {
115 4 : return token_.stop_requested();
116 : }
117 :
118 4 : capy::io_result<std::size_t> await_resume() const noexcept
119 : {
120 4 : if (token_.stop_requested())
121 MIS 0 : return {make_error_code(std::errc::operation_canceled), 0};
122 HIT 4 : return {ec_, bytes_transferred_};
123 : }
124 :
125 4 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
126 : -> std::coroutine_handle<>
127 : {
128 4 : token_ = env->stop_token;
129 12 : return self_.get_impl().send_to(
130 4 : h, env->executor, buffers_, dest_, flags_,
131 12 : token_, &ec_, &bytes_transferred_);
132 : }
133 : };
134 :
135 : template<class MutableBufferSequence>
136 : struct native_recv_from_awaitable
137 : {
138 : native_udp_socket& self_;
139 : MutableBufferSequence buffers_;
140 : endpoint& source_;
141 : int flags_;
142 : std::stop_token token_;
143 : mutable std::error_code ec_;
144 : mutable std::size_t bytes_transferred_ = 0;
145 :
146 8 : native_recv_from_awaitable(
147 : native_udp_socket& self,
148 : MutableBufferSequence buffers,
149 : endpoint& source,
150 : int flags) noexcept
151 8 : : self_(self)
152 8 : , buffers_(std::move(buffers))
153 8 : , source_(source)
154 8 : , flags_(flags)
155 : {
156 8 : }
157 :
158 8 : bool await_ready() const noexcept
159 : {
160 8 : return token_.stop_requested();
161 : }
162 :
163 8 : capy::io_result<std::size_t> await_resume() const noexcept
164 : {
165 8 : if (token_.stop_requested())
166 MIS 0 : return {make_error_code(std::errc::operation_canceled), 0};
167 HIT 8 : return {ec_, bytes_transferred_};
168 : }
169 :
170 8 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
171 : -> std::coroutine_handle<>
172 : {
173 8 : token_ = env->stop_token;
174 24 : return self_.get_impl().recv_from(
175 8 : h, env->executor, buffers_, &source_, flags_,
176 24 : token_, &ec_, &bytes_transferred_);
177 : }
178 : };
179 :
180 : struct native_wait_awaitable
181 : {
182 : native_udp_socket& self_;
183 : wait_type w_;
184 : std::stop_token token_;
185 : mutable std::error_code ec_;
186 :
187 2 : native_wait_awaitable(native_udp_socket& self, wait_type w) noexcept
188 2 : : self_(self)
189 2 : , w_(w)
190 : {
191 2 : }
192 :
193 2 : bool await_ready() const noexcept
194 : {
195 2 : return token_.stop_requested();
196 : }
197 :
198 2 : capy::io_result<> await_resume() const noexcept
199 : {
200 2 : if (token_.stop_requested())
201 MIS 0 : return {make_error_code(std::errc::operation_canceled)};
202 HIT 2 : return {ec_};
203 : }
204 :
205 2 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
206 : -> std::coroutine_handle<>
207 : {
208 2 : token_ = env->stop_token;
209 6 : return self_.get_impl().wait(
210 6 : h, env->executor, w_, token_, &ec_);
211 : }
212 : };
213 :
214 : struct native_connect_awaitable
215 : {
216 : native_udp_socket& self_;
217 : endpoint endpoint_;
218 : std::stop_token token_;
219 : mutable std::error_code ec_;
220 :
221 6 : native_connect_awaitable(native_udp_socket& self, endpoint ep) noexcept
222 6 : : self_(self)
223 6 : , endpoint_(ep)
224 : {
225 6 : }
226 :
227 6 : bool await_ready() const noexcept
228 : {
229 6 : return token_.stop_requested();
230 : }
231 :
232 6 : capy::io_result<> await_resume() const noexcept
233 : {
234 6 : if (token_.stop_requested())
235 MIS 0 : return {make_error_code(std::errc::operation_canceled)};
236 HIT 6 : return {ec_};
237 : }
238 :
239 6 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
240 : -> std::coroutine_handle<>
241 : {
242 6 : token_ = env->stop_token;
243 18 : return self_.get_impl().connect(
244 18 : h, env->executor, endpoint_, token_, &ec_);
245 : }
246 : };
247 :
248 : template<class ConstBufferSequence>
249 : struct native_send_awaitable
250 : {
251 : native_udp_socket& self_;
252 : ConstBufferSequence buffers_;
253 : int flags_;
254 : std::stop_token token_;
255 : mutable std::error_code ec_;
256 : mutable std::size_t bytes_transferred_ = 0;
257 :
258 4 : native_send_awaitable(
259 : native_udp_socket& self,
260 : ConstBufferSequence buffers,
261 : int flags) noexcept
262 4 : : self_(self)
263 4 : , buffers_(std::move(buffers))
264 4 : , flags_(flags)
265 : {
266 4 : }
267 :
268 4 : bool await_ready() const noexcept
269 : {
270 4 : return token_.stop_requested();
271 : }
272 :
273 4 : capy::io_result<std::size_t> await_resume() const noexcept
274 : {
275 4 : if (token_.stop_requested())
276 MIS 0 : return {make_error_code(std::errc::operation_canceled), 0};
277 HIT 4 : return {ec_, bytes_transferred_};
278 : }
279 :
280 4 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
281 : -> std::coroutine_handle<>
282 : {
283 4 : token_ = env->stop_token;
284 12 : return self_.get_impl().send(
285 4 : h, env->executor, buffers_, flags_,
286 12 : 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 2 : native_recv_awaitable(
301 : native_udp_socket& self,
302 : MutableBufferSequence buffers,
303 : int flags) noexcept
304 2 : : self_(self)
305 2 : , buffers_(std::move(buffers))
306 2 : , flags_(flags)
307 : {
308 2 : }
309 :
310 2 : bool await_ready() const noexcept
311 : {
312 2 : return token_.stop_requested();
313 : }
314 :
315 2 : capy::io_result<std::size_t> await_resume() const noexcept
316 : {
317 2 : if (token_.stop_requested())
318 MIS 0 : return {make_error_code(std::errc::operation_canceled), 0};
319 HIT 2 : return {ec_, bytes_transferred_};
320 : }
321 :
322 2 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
323 : -> std::coroutine_handle<>
324 : {
325 2 : token_ = env->stop_token;
326 6 : return self_.get_impl().recv(
327 2 : h, env->executor, buffers_, flags_,
328 6 : token_, &ec_, &bytes_transferred_);
329 : }
330 : };
331 :
332 : public:
333 : /** Construct a native UDP socket from an execution context.
334 :
335 : @param ctx The execution context that will own this socket.
336 : */
337 36 : explicit native_udp_socket(capy::execution_context& ctx)
338 36 : : udp_socket(create_handle<service_type>(ctx))
339 : {
340 36 : }
341 :
342 : /** Construct a native UDP socket from an executor.
343 :
344 : @param ex The executor whose context will own the socket.
345 : */
346 : template<class Ex>
347 : requires(!std::same_as<std::remove_cvref_t<Ex>, native_udp_socket>) &&
348 : capy::Executor<Ex>
349 : explicit native_udp_socket(Ex const& ex) : native_udp_socket(ex.context())
350 : {
351 : }
352 :
353 : /// Move construct.
354 2 : native_udp_socket(native_udp_socket&&) noexcept = default;
355 :
356 : /// Move assign.
357 : native_udp_socket& operator=(native_udp_socket&&) noexcept = default;
358 :
359 : native_udp_socket(native_udp_socket const&) = delete;
360 : native_udp_socket& operator=(native_udp_socket const&) = delete;
361 :
362 : /** Send a datagram to the specified destination.
363 :
364 : Calls the backend implementation directly, bypassing virtual
365 : dispatch. Otherwise identical to @ref udp_socket::send_to.
366 :
367 : @param buffers The buffer sequence containing data to send.
368 : @param dest The destination endpoint.
369 : @param flags Message flags.
370 :
371 : @return An awaitable yielding `(error_code, std::size_t)`.
372 : */
373 : template<capy::ConstBufferSequence CB>
374 4 : auto send_to(
375 : CB const& buffers,
376 : endpoint dest,
377 : corosio::message_flags flags)
378 : {
379 4 : if (!is_open())
380 MIS 0 : detail::throw_logic_error("send_to: socket not open");
381 : return native_send_to_awaitable<CB>(
382 HIT 4 : *this, buffers, dest, static_cast<int>(flags));
383 : }
384 :
385 : /// @overload
386 : template<capy::ConstBufferSequence CB>
387 4 : auto send_to(CB const& buffers, endpoint dest)
388 : {
389 4 : return send_to(buffers, dest, corosio::message_flags::none);
390 : }
391 :
392 : /** Receive a datagram and capture the sender's endpoint.
393 :
394 : Calls the backend implementation directly, bypassing virtual
395 : dispatch. Otherwise identical to @ref udp_socket::recv_from.
396 :
397 : @param buffers The buffer sequence to receive data into.
398 : @param source Reference to an endpoint that will be set to
399 : the sender's address on successful completion.
400 : @param flags Message flags (e.g. message_flags::peek).
401 :
402 : @return An awaitable yielding `(error_code, std::size_t)`.
403 : */
404 : template<capy::MutableBufferSequence MB>
405 8 : auto recv_from(
406 : MB const& buffers,
407 : endpoint& source,
408 : corosio::message_flags flags)
409 : {
410 8 : if (!is_open())
411 MIS 0 : detail::throw_logic_error("recv_from: socket not open");
412 : return native_recv_from_awaitable<MB>(
413 HIT 8 : *this, buffers, source, static_cast<int>(flags));
414 : }
415 :
416 : /// @overload
417 : template<capy::MutableBufferSequence MB>
418 8 : auto recv_from(MB const& buffers, endpoint& source)
419 : {
420 8 : return recv_from(buffers, source, corosio::message_flags::none);
421 : }
422 :
423 : /** Asynchronously connect to set the default peer.
424 :
425 : Calls the backend implementation directly, bypassing virtual
426 : dispatch. Otherwise identical to @ref udp_socket::connect.
427 :
428 : If the socket is not already open, it is opened automatically
429 : using the address family of @p ep.
430 :
431 : @param ep The remote endpoint to connect to.
432 :
433 : @return An awaitable yielding `io_result<>`.
434 :
435 : @throws std::system_error if the socket needs to be opened
436 : and the open fails.
437 : */
438 6 : auto connect(endpoint ep)
439 : {
440 6 : if (!is_open())
441 4 : open(ep.is_v6() ? udp::v6() : udp::v4());
442 6 : return native_connect_awaitable(*this, ep);
443 : }
444 :
445 : /** Send a datagram to the connected peer.
446 :
447 : Calls the backend implementation directly, bypassing virtual
448 : dispatch. Otherwise identical to @ref udp_socket::send.
449 :
450 : @param buffers The buffer sequence containing data to send.
451 : @param flags Message flags.
452 :
453 : @return An awaitable yielding `(error_code, std::size_t)`.
454 :
455 : @throws std::logic_error if the socket is not open.
456 : */
457 : template<capy::ConstBufferSequence CB>
458 4 : auto send(CB const& buffers, corosio::message_flags flags)
459 : {
460 4 : if (!is_open())
461 MIS 0 : detail::throw_logic_error("send: socket not open");
462 : return native_send_awaitable<CB>(
463 HIT 4 : *this, buffers, static_cast<int>(flags));
464 : }
465 :
466 : /// @overload
467 : template<capy::ConstBufferSequence CB>
468 4 : auto send(CB const& buffers)
469 : {
470 4 : return send(buffers, corosio::message_flags::none);
471 : }
472 :
473 : /** Receive a datagram from the connected peer.
474 :
475 : Calls the backend implementation directly, bypassing virtual
476 : dispatch. Otherwise identical to @ref udp_socket::recv.
477 :
478 : @param buffers The buffer sequence to receive data into.
479 : @param flags Message flags (e.g. message_flags::peek).
480 :
481 : @return An awaitable yielding `(error_code, std::size_t)`.
482 :
483 : @throws std::logic_error if the socket is not open.
484 : */
485 : template<capy::MutableBufferSequence MB>
486 2 : auto recv(MB const& buffers, corosio::message_flags flags)
487 : {
488 2 : if (!is_open())
489 MIS 0 : detail::throw_logic_error("recv: socket not open");
490 : return native_recv_awaitable<MB>(
491 HIT 2 : *this, buffers, static_cast<int>(flags));
492 : }
493 :
494 : /// @overload
495 : template<capy::MutableBufferSequence MB>
496 2 : auto recv(MB const& buffers)
497 : {
498 2 : return recv(buffers, corosio::message_flags::none);
499 : }
500 :
501 : /** Asynchronously wait for the socket to be ready.
502 :
503 : Calls the backend implementation directly, bypassing virtual
504 : dispatch. Otherwise identical to @ref udp_socket::wait.
505 :
506 : @param w The wait direction (read, write, or error).
507 :
508 : @return An awaitable yielding `io_result<>`.
509 : */
510 2 : [[nodiscard]] auto wait(wait_type w)
511 : {
512 2 : return native_wait_awaitable(*this, w);
513 : }
514 : };
515 :
516 : } // namespace boost::corosio
517 :
518 : #endif // BOOST_COROSIO_NATIVE_NATIVE_UDP_SOCKET_HPP
|