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