TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Michael Vandeberg
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_LOCAL_DATAGRAM_SOCKET_HPP
11 : #define BOOST_COROSIO_LOCAL_DATAGRAM_SOCKET_HPP
12 :
13 : #include <boost/corosio/family.hpp>
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/detail/platform.hpp>
16 :
17 : #if BOOST_COROSIO_POSIX
18 :
19 : #include <boost/corosio/detail/except.hpp>
20 : #include <boost/corosio/detail/native_handle.hpp>
21 : #include <boost/corosio/detail/op_base.hpp>
22 : #include <boost/corosio/io/io_object.hpp>
23 : #include <boost/capy/io_result.hpp>
24 : #include <boost/corosio/detail/buffer_param.hpp>
25 : #include <boost/corosio/local_endpoint.hpp>
26 : #include <boost/corosio/message_flags.hpp>
27 : #include <boost/corosio/shutdown_type.hpp>
28 : #include <boost/corosio/wait_type.hpp>
29 : #include <boost/capy/ex/executor_ref.hpp>
30 : #include <boost/capy/ex/execution_context.hpp>
31 : #include <boost/capy/ex/io_env.hpp>
32 : #include <boost/capy/concept/executor.hpp>
33 :
34 : #include <system_error>
35 :
36 : #include <concepts>
37 : #include <coroutine>
38 : #include <cstddef>
39 : #include <stop_token>
40 : #include <type_traits>
41 :
42 : namespace boost::corosio {
43 :
44 : /** An asynchronous Unix datagram socket for coroutine I/O.
45 :
46 : This class provides asynchronous Unix domain datagram socket
47 : operations that return awaitable types. Each operation
48 : participates in the affine awaitable protocol, ensuring
49 : coroutines resume on the correct executor.
50 :
51 : Supports two modes of operation:
52 :
53 : @li **Connectionless:** each send_to() specifies a destination
54 : endpoint, and each recv_from() captures the source. The
55 : socket must be opened (and optionally bound) before I/O.
56 :
57 : @li **Connected:** call connect() to set a default peer,
58 : then use send()/recv() without endpoint arguments. The
59 : kernel filters incoming datagrams to those from the
60 : connected peer.
61 :
62 : @note Not available on Windows. Windows does not support
63 : AF_UNIX datagram sockets (SOCK_DGRAM). Attempting to
64 : open this socket on Windows will fail.
65 :
66 : @par Cancellation
67 : All asynchronous operations support cancellation through
68 : `std::stop_token` via the affine protocol, or explicitly
69 : through cancel(). Cancelled operations complete with
70 : `capy::cond::canceled`. Datagram sends and receives are
71 : atomic — there is no partial progress on cancellation.
72 :
73 : @par Thread Safety
74 : Distinct objects: Safe.@n
75 : Shared objects: Unsafe. A socket must not have concurrent
76 : operations of the same type (e.g., two simultaneous
77 : recv_from). One send and one recv may be in flight
78 : simultaneously. Note that recv and recv_from share the
79 : same internal read slot, so they must not overlap; likewise
80 : send and send_to share the write slot.
81 :
82 : @par Example
83 : @par !example connectionless_and_connected
84 : */
85 : class BOOST_COROSIO_DECL local_datagram_socket : public io_object
86 : {
87 : public:
88 : /// The shutdown direction type used by shutdown().
89 : using shutdown_type = corosio::shutdown_type;
90 : using enum corosio::shutdown_type;
91 :
92 : /** Define backend hooks for local datagram socket operations.
93 :
94 : Platform backends (epoll, kqueue, select) derive from this
95 : to implement datagram I/O, connection, and option management.
96 : */
97 : struct implementation : io_object::implementation
98 : {
99 : /** Initiate an asynchronous send_to operation.
100 :
101 : @param h Coroutine handle to resume on completion.
102 : @param ex Executor for dispatching the completion.
103 : @param buf The buffer data to send.
104 : @param dest The destination endpoint.
105 : @param token Stop token for cancellation.
106 : @param ec Output error code.
107 : @param bytes_out Output bytes transferred.
108 :
109 : @return Coroutine handle to resume immediately.
110 : */
111 : virtual std::coroutine_handle<> send_to(
112 : std::coroutine_handle<> h,
113 : capy::executor_ref ex,
114 : buffer_param buf,
115 : corosio::local_endpoint dest,
116 : int flags,
117 : std::stop_token token,
118 : std::error_code* ec,
119 : std::size_t* bytes_out) = 0;
120 :
121 : /** Initiate an asynchronous recv_from operation.
122 :
123 : @param h Coroutine handle to resume on completion.
124 : @param ex Executor for dispatching the completion.
125 : @param buf The buffer to receive into.
126 : @param source Output endpoint for the sender's address.
127 : @param token Stop token for cancellation.
128 : @param ec Output error code.
129 : @param bytes_out Output bytes transferred.
130 :
131 : @return Coroutine handle to resume immediately.
132 : */
133 : virtual std::coroutine_handle<> recv_from(
134 : std::coroutine_handle<> h,
135 : capy::executor_ref ex,
136 : buffer_param buf,
137 : corosio::local_endpoint* source,
138 : int flags,
139 : std::stop_token token,
140 : std::error_code* ec,
141 : std::size_t* bytes_out) = 0;
142 :
143 : /** Initiate an asynchronous connect to set the default peer.
144 :
145 : @param h Coroutine handle to resume on completion.
146 : @param ex Executor for dispatching the completion.
147 : @param ep The remote endpoint to connect to.
148 : @param token Stop token for cancellation.
149 : @param ec Output error code.
150 :
151 : @return Coroutine handle to resume immediately.
152 : */
153 : virtual std::coroutine_handle<> connect(
154 : std::coroutine_handle<> h,
155 : capy::executor_ref ex,
156 : corosio::local_endpoint ep,
157 : std::stop_token token,
158 : std::error_code* ec) = 0;
159 :
160 : /** Initiate an asynchronous connected send operation.
161 :
162 : @param h Coroutine handle to resume on completion.
163 : @param ex Executor for dispatching the completion.
164 : @param buf The buffer data to send.
165 : @param token Stop token for cancellation.
166 : @param ec Output error code.
167 : @param bytes_out Output bytes transferred.
168 :
169 : @return Coroutine handle to resume immediately.
170 : */
171 : virtual std::coroutine_handle<> send(
172 : std::coroutine_handle<> h,
173 : capy::executor_ref ex,
174 : buffer_param buf,
175 : int flags,
176 : std::stop_token token,
177 : std::error_code* ec,
178 : std::size_t* bytes_out) = 0;
179 :
180 : /** Initiate an asynchronous connected recv operation.
181 :
182 : @param h Coroutine handle to resume on completion.
183 : @param ex Executor for dispatching the completion.
184 : @param buf The buffer to receive into.
185 : @param flags Message flags (e.g. MSG_PEEK).
186 : @param token Stop token for cancellation.
187 : @param ec Output error code.
188 : @param bytes_out Output bytes transferred.
189 :
190 : @return Coroutine handle to resume immediately.
191 : */
192 : virtual std::coroutine_handle<> recv(
193 : std::coroutine_handle<> h,
194 : capy::executor_ref ex,
195 : buffer_param buf,
196 : int flags,
197 : std::stop_token token,
198 : std::error_code* ec,
199 : std::size_t* bytes_out) = 0;
200 :
201 : /** Initiate an asynchronous wait for socket readiness.
202 :
203 : Completes when the socket becomes ready for the
204 : specified direction, or an error condition is
205 : reported. No bytes are transferred.
206 :
207 : @param h Coroutine handle to resume on completion.
208 : @param ex Executor for dispatching the completion.
209 : @param w The direction to wait on.
210 : @param token Stop token for cancellation.
211 : @param ec Output error code.
212 :
213 : @return Coroutine handle to resume immediately.
214 : */
215 : virtual std::coroutine_handle<> wait(
216 : std::coroutine_handle<> h,
217 : capy::executor_ref ex,
218 : wait_type w,
219 : std::stop_token token,
220 : std::error_code* ec) = 0;
221 :
222 : /// Shut down part or all of the socket.
223 : virtual std::error_code shutdown(shutdown_type what) noexcept = 0;
224 :
225 : /// Return the platform socket descriptor.
226 : virtual native_handle_type native_handle() const noexcept = 0;
227 :
228 : /** Return the socket's address family.
229 :
230 : Local sockets have no IP family; implementations return
231 : `v4`, which the family-neutral options applicable to them
232 : ignore.
233 :
234 : @return The address family for option rendering.
235 : */
236 : virtual corosio::family family() const noexcept = 0;
237 :
238 : /** Release ownership of the socket descriptor.
239 :
240 : The implementation deregisters from the reactor and cancels
241 : pending operations. The caller takes ownership of the
242 : returned descriptor.
243 :
244 : @return The native handle, or an invalid sentinel if
245 : not open.
246 : */
247 : virtual native_handle_type release_socket() noexcept = 0;
248 :
249 : /** Request cancellation of pending asynchronous operations.
250 :
251 : Operations still in flight complete with `operation_canceled`;
252 : an operation whose result is already decided reports that
253 : result. Check `ec == cond::canceled` for portable comparison.
254 : */
255 : virtual void cancel() noexcept = 0;
256 :
257 : /** Set a socket option.
258 :
259 : @param level The protocol level (e.g. SOL_SOCKET).
260 : @param optname The option name.
261 : @param data Pointer to the option value.
262 : @param size Size of the option value in bytes.
263 : @return Error code on failure, empty on success.
264 : */
265 : virtual std::error_code set_option(
266 : int level,
267 : int optname,
268 : void const* data,
269 : std::size_t size) noexcept = 0;
270 :
271 : /** Get a socket option.
272 :
273 : @param level The protocol level (e.g. SOL_SOCKET).
274 : @param optname The option name.
275 : @param data Pointer to receive the option value.
276 : @param size On entry, the size of the buffer. On exit,
277 : the size of the option value.
278 : @return Error code on failure, empty on success.
279 : */
280 : virtual std::error_code
281 : get_option(int level, int optname, void* data, std::size_t* size)
282 : const noexcept = 0;
283 :
284 : /// Return the cached local endpoint.
285 : virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
286 :
287 : /// Return the cached remote endpoint (connected mode).
288 : virtual corosio::local_endpoint remote_endpoint() const noexcept = 0;
289 :
290 : /** Bind the socket to a local endpoint.
291 :
292 : @param ep The local endpoint to bind to.
293 : @return Error code on failure, empty on success.
294 : */
295 : virtual std::error_code bind(corosio::local_endpoint ep) noexcept = 0;
296 : };
297 :
298 : /** Represent the awaitable returned by @ref send_to.
299 :
300 : Captures the destination endpoint and buffer, then dispatches
301 : to the backend implementation on suspension.
302 : */
303 : struct send_to_awaitable : detail::bytes_op_base<send_to_awaitable>
304 : {
305 : local_datagram_socket& s_;
306 : buffer_param buf_;
307 : corosio::local_endpoint dest_;
308 : int flags_;
309 :
310 HIT 88 : send_to_awaitable(
311 : local_datagram_socket& s,
312 : buffer_param buf,
313 : corosio::local_endpoint dest,
314 : int flags = 0) noexcept
315 176 : : s_(s)
316 88 : , buf_(buf)
317 88 : , dest_(dest)
318 88 : , flags_(flags)
319 : {
320 88 : }
321 :
322 : std::coroutine_handle<>
323 84 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
324 : {
325 168 : return s_.get().send_to(
326 168 : h, ex, buf_, dest_, flags_, token_, &ec_, &bytes_);
327 : }
328 : };
329 :
330 : /** Represent the awaitable returned by @ref recv_from.
331 :
332 : Captures the source endpoint reference and buffer, then
333 : dispatches to the backend implementation on suspension.
334 : */
335 : struct recv_from_awaitable : detail::bytes_op_base<recv_from_awaitable>
336 : {
337 : local_datagram_socket& s_;
338 : buffer_param buf_;
339 : corosio::local_endpoint& source_;
340 : int flags_;
341 :
342 88 : recv_from_awaitable(
343 : local_datagram_socket& s,
344 : buffer_param buf,
345 : corosio::local_endpoint& source,
346 : int flags = 0) noexcept
347 176 : : s_(s)
348 88 : , buf_(buf)
349 88 : , source_(source)
350 88 : , flags_(flags)
351 : {
352 88 : }
353 :
354 : std::coroutine_handle<>
355 84 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
356 : {
357 168 : return s_.get().recv_from(
358 168 : h, ex, buf_, &source_, flags_, token_, &ec_, &bytes_);
359 : }
360 : };
361 :
362 : /** Represent the awaitable returned by @ref connect.
363 :
364 : Captures the target endpoint, then dispatches to the
365 : backend implementation on suspension.
366 : */
367 : struct connect_awaitable : detail::void_op_base<connect_awaitable>
368 : {
369 : local_datagram_socket& s_;
370 : corosio::local_endpoint endpoint_;
371 :
372 2 : connect_awaitable(
373 : local_datagram_socket& s, corosio::local_endpoint ep) noexcept
374 4 : : s_(s)
375 2 : , endpoint_(ep)
376 : {
377 2 : }
378 :
379 : std::coroutine_handle<>
380 2 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
381 : {
382 2 : return s_.get().connect(h, ex, endpoint_, token_, &ec_);
383 : }
384 : };
385 :
386 : /// Represent the awaitable returned by @ref wait.
387 : struct wait_awaitable : detail::void_op_base<wait_awaitable>
388 : {
389 : local_datagram_socket& s_;
390 : wait_type w_;
391 :
392 14 : wait_awaitable(local_datagram_socket& s, wait_type w) noexcept
393 28 : : s_(s)
394 14 : , w_(w)
395 : {
396 14 : }
397 :
398 : std::coroutine_handle<>
399 12 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
400 : {
401 12 : return s_.get().wait(h, ex, w_, token_, &ec_);
402 : }
403 : };
404 :
405 : /** Represent the awaitable returned by @ref send.
406 :
407 : Captures the buffer, then dispatches to the backend
408 : implementation on suspension. Requires a prior connect().
409 : */
410 : struct send_awaitable : detail::bytes_op_base<send_awaitable>
411 : {
412 : local_datagram_socket& s_;
413 : buffer_param buf_;
414 : int flags_;
415 :
416 93 : send_awaitable(
417 : local_datagram_socket& s, buffer_param buf, int flags = 0) noexcept
418 186 : : s_(s)
419 93 : , buf_(buf)
420 93 : , flags_(flags)
421 : {
422 93 : }
423 :
424 : std::coroutine_handle<>
425 89 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
426 : {
427 89 : return s_.get().send(h, ex, buf_, flags_, token_, &ec_, &bytes_);
428 : }
429 : };
430 :
431 : /** Represent the awaitable returned by @ref recv.
432 :
433 : Captures the buffer, then dispatches to the backend
434 : implementation on suspension. Requires a prior connect().
435 : */
436 : struct recv_awaitable : detail::bytes_op_base<recv_awaitable>
437 : {
438 : local_datagram_socket& s_;
439 : buffer_param buf_;
440 : int flags_;
441 :
442 97 : recv_awaitable(
443 : local_datagram_socket& s, buffer_param buf, int flags = 0) noexcept
444 194 : : s_(s)
445 97 : , buf_(buf)
446 97 : , flags_(flags)
447 : {
448 97 : }
449 :
450 : std::coroutine_handle<>
451 93 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
452 : {
453 93 : return s_.get().recv(h, ex, buf_, flags_, token_, &ec_, &bytes_);
454 : }
455 : };
456 :
457 : public:
458 : /** Destructor.
459 :
460 : Closes the socket if open, cancelling any pending operations.
461 : */
462 : ~local_datagram_socket() override;
463 :
464 : /** Construct a socket from an execution context.
465 :
466 : @param ctx The execution context that will own this socket.
467 : */
468 : explicit local_datagram_socket(capy::execution_context& ctx);
469 :
470 : /** Construct a socket from an executor.
471 :
472 : The socket is associated with the executor's context.
473 :
474 : @param ex The executor whose context will own the socket.
475 : */
476 : template<class Ex>
477 : requires(!std::
478 : same_as<std::remove_cvref_t<Ex>, local_datagram_socket>) &&
479 : capy::Executor<Ex>
480 : explicit local_datagram_socket(Ex const& ex)
481 : : local_datagram_socket(ex.context())
482 : {
483 : }
484 :
485 : /** Move constructor.
486 :
487 : Transfers ownership of the socket resources.
488 :
489 : @param other The socket to move from.
490 : */
491 2 : local_datagram_socket(local_datagram_socket&& other) noexcept
492 2 : : io_object(std::move(other))
493 : {
494 2 : }
495 :
496 : /** Move assignment operator.
497 :
498 : Closes any existing socket and transfers ownership.
499 :
500 : @param other The socket to move from.
501 : @return Reference to this socket.
502 : */
503 2 : local_datagram_socket& operator=(local_datagram_socket&& other) noexcept
504 : {
505 2 : if (this != &other)
506 : {
507 2 : close();
508 2 : io_object::operator=(std::move(other));
509 : }
510 2 : return *this;
511 : }
512 :
513 : local_datagram_socket(local_datagram_socket const&) = delete;
514 : local_datagram_socket& operator=(local_datagram_socket const&) = delete;
515 :
516 : /** Open the socket.
517 :
518 : Creates a Unix datagram socket and associates it with
519 : the platform reactor.
520 :
521 : Failures such as descriptor exhaustion are normal runtime
522 : conditions and are reported through the returned error code.
523 : Opening an already-open socket is a no-op that reports
524 : success.
525 :
526 :
527 : @return The error code, empty on success.
528 : */
529 : [[nodiscard]] std::error_code open() noexcept;
530 :
531 : /** Close the socket.
532 :
533 : Cancels any pending asynchronous operations and releases
534 : the underlying file descriptor. Has no effect if the
535 : socket is not open.
536 :
537 : @post is_open() == false
538 : */
539 : void close() noexcept;
540 :
541 : /** Check if the socket is open.
542 :
543 : @return `true` if the socket holds a valid file descriptor,
544 : `false` otherwise.
545 : */
546 1169 : bool is_open() const noexcept
547 : {
548 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
549 : return h_ && get().native_handle() != ~native_handle_type(0);
550 : #else
551 1169 : return h_ && get().native_handle() >= 0;
552 : #endif
553 : }
554 :
555 : /** Bind the socket to a local endpoint.
556 :
557 : Associates the socket with a local address (filesystem path).
558 : Required before calling recv_from in connectionless mode.
559 :
560 : @param ep The local endpoint to bind to.
561 :
562 : @return Error code on failure, empty on success.
563 :
564 : A closed socket reports `errc::bad_file_descriptor`.
565 : */
566 : [[nodiscard]] std::error_code bind(corosio::local_endpoint ep) noexcept;
567 :
568 : /** Initiate an asynchronous connect to set the default peer.
569 :
570 : If the socket is not already open, it is opened automatically.
571 : After successful completion, send()/recv() may be used
572 : without specifying an endpoint.
573 :
574 : @param ep The remote endpoint to connect to.
575 :
576 : @par Cancellation
577 : Supports cancellation via the awaitable's stop_token or by
578 : calling cancel(). On cancellation, yields
579 : `capy::cond::canceled`.
580 :
581 : @return An awaitable that completes with io_result<>.
582 :
583 : If the socket needs to be opened and the open fails, the
584 : awaitable completes immediately with that error.
585 : */
586 2 : [[nodiscard]] auto connect(corosio::local_endpoint ep)
587 : {
588 2 : connect_awaitable aw(*this, ep);
589 2 : if (!is_open())
590 2 : aw.ec_ = open();
591 2 : return aw;
592 : }
593 :
594 : /** Wait for the socket to become ready in a given direction.
595 :
596 : Suspends until the socket is ready for the requested
597 : direction, or an error condition is reported. No bytes
598 : are transferred.
599 :
600 : @param w The wait direction (read, write, or error).
601 :
602 : @return An awaitable that completes with `io_result<>`.
603 :
604 : A closed socket completes with `errc::bad_file_descriptor`.
605 :
606 : @par Preconditions
607 : This socket must outlive the returned awaitable.
608 : */
609 14 : [[nodiscard]] auto wait(wait_type w)
610 : {
611 14 : return wait_awaitable(*this, w);
612 : }
613 :
614 : /** Send a datagram to the specified destination.
615 :
616 : Completes when the entire datagram has been accepted
617 : by the kernel. The bytes_transferred value equals the
618 : datagram size on success.
619 :
620 : @param buf The buffer containing data to send.
621 : @param dest The destination endpoint.
622 :
623 : @par Cancellation
624 : Supports cancellation via stop_token or cancel().
625 :
626 : @return An awaitable that completes with
627 : io_result<std::size_t>.
628 :
629 : A closed socket reports `errc::bad_file_descriptor`.
630 : */
631 : template<capy::ConstBufferSequence Buffers>
632 88 : [[nodiscard]] auto send_to(
633 : Buffers const& buf,
634 : corosio::local_endpoint dest,
635 : corosio::message_flags flags)
636 : {
637 88 : send_to_awaitable aw(*this, buf, dest, static_cast<int>(flags));
638 88 : if (!is_open())
639 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
640 88 : return aw;
641 : }
642 :
643 : /// @overload
644 : template<capy::ConstBufferSequence Buffers>
645 88 : [[nodiscard]] auto send_to(Buffers const& buf, corosio::local_endpoint dest)
646 : {
647 88 : return send_to(buf, dest, corosio::message_flags::none);
648 : }
649 :
650 : /** Receive a datagram and capture the sender's endpoint.
651 :
652 : Completes when one datagram has been received. The
653 : bytes_transferred value is the number of bytes copied
654 : into the buffer. If the buffer is smaller than the
655 : datagram, excess bytes are discarded (datagram
656 : semantics).
657 :
658 : @param buf The buffer to receive data into.
659 : @param source Reference to an endpoint that will be set to
660 : the sender's address on successful completion.
661 : @param flags Message flags (e.g. message_flags::peek).
662 :
663 : @par Cancellation
664 : Supports cancellation via stop_token or cancel().
665 :
666 : @return An awaitable that completes with
667 : io_result<std::size_t>.
668 :
669 : A closed socket reports `errc::bad_file_descriptor`.
670 : */
671 : template<capy::MutableBufferSequence Buffers>
672 88 : [[nodiscard]] auto recv_from(
673 : Buffers const& buf,
674 : corosio::local_endpoint& source,
675 : corosio::message_flags flags)
676 : {
677 88 : recv_from_awaitable aw(*this, buf, source, static_cast<int>(flags));
678 88 : if (!is_open())
679 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
680 88 : return aw;
681 : }
682 :
683 : /// @overload
684 : template<capy::MutableBufferSequence Buffers>
685 : [[nodiscard]] auto
686 86 : recv_from(Buffers const& buf, corosio::local_endpoint& source)
687 : {
688 86 : return recv_from(buf, source, corosio::message_flags::none);
689 : }
690 :
691 : /** Send a datagram to the connected peer.
692 :
693 : @pre connect() has been called successfully.
694 :
695 : @param buf The buffer containing data to send.
696 : @param flags Message flags.
697 :
698 : @par Cancellation
699 : Supports cancellation via stop_token or cancel().
700 :
701 : @return An awaitable that completes with
702 : io_result<std::size_t>.
703 :
704 : A closed socket reports `errc::bad_file_descriptor`.
705 : */
706 : template<capy::ConstBufferSequence Buffers>
707 93 : [[nodiscard]] auto send(Buffers const& buf, corosio::message_flags flags)
708 : {
709 93 : send_awaitable aw(*this, buf, static_cast<int>(flags));
710 93 : if (!is_open())
711 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
712 93 : return aw;
713 : }
714 :
715 : /// @overload
716 : template<capy::ConstBufferSequence Buffers>
717 93 : [[nodiscard]] auto send(Buffers const& buf)
718 : {
719 93 : return send(buf, corosio::message_flags::none);
720 : }
721 :
722 : /** Receive a datagram from the connected peer.
723 :
724 : @pre connect() has been called successfully.
725 :
726 : @param buf The buffer to receive data into.
727 : @param flags Message flags (e.g. message_flags::peek).
728 :
729 : @par Cancellation
730 : Supports cancellation via stop_token or cancel().
731 :
732 : @return An awaitable that completes with
733 : io_result<std::size_t>.
734 :
735 : A closed socket reports `errc::bad_file_descriptor`.
736 : */
737 : template<capy::MutableBufferSequence Buffers>
738 97 : [[nodiscard]] auto recv(Buffers const& buf, corosio::message_flags flags)
739 : {
740 97 : recv_awaitable aw(*this, buf, static_cast<int>(flags));
741 97 : if (!is_open())
742 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
743 97 : return aw;
744 : }
745 :
746 : /// @overload
747 : template<capy::MutableBufferSequence Buffers>
748 95 : [[nodiscard]] auto recv(Buffers const& buf)
749 : {
750 95 : return recv(buf, corosio::message_flags::none);
751 : }
752 :
753 : /** Cancel any pending asynchronous operations.
754 :
755 : Operations still in flight complete with
756 : `errc::operation_canceled`; an operation whose result is
757 : already decided reports that result. Check
758 : `ec == cond::canceled` for portable comparison.
759 : */
760 : void cancel() noexcept;
761 :
762 : /** Get the native socket handle.
763 :
764 : @return The native socket handle, or -1 if not open.
765 : */
766 : native_handle_type native_handle() const noexcept;
767 :
768 : /** Release ownership of the native socket handle.
769 :
770 : Deregisters the socket from the reactor and cancels pending
771 : operations without closing the fd. The caller takes ownership
772 : of the returned descriptor.
773 :
774 : @return The native handle.
775 :
776 : @throws std::system_error `errc::bad_file_descriptor` if the
777 : socket is not open.
778 : */
779 : native_handle_type release();
780 :
781 : /** Query the number of bytes available for reading.
782 :
783 : @return The number of bytes that can be read without blocking.
784 :
785 : @throws std::system_error `errc::bad_file_descriptor` if the
786 : socket is not open; otherwise thrown on ioctl failure.
787 : */
788 : std::size_t available() const;
789 :
790 : /** Shut down part or all of the socket.
791 :
792 : Failures such as an unconnected socket are normal runtime
793 : conditions and are reported through the returned error
794 : code. A closed socket reports `errc::bad_file_descriptor`.
795 :
796 : @param what Which direction to shut down.
797 :
798 : @return The error code, empty on success.
799 : */
800 : [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept;
801 :
802 : /** Set a socket option.
803 :
804 : @tparam Option A socket option type that provides static
805 : `level()` and `name()` members, and `data()` / `size()`
806 : accessors for the option value.
807 :
808 : @param opt The option to set.
809 :
810 : @throws std::system_error `errc::bad_file_descriptor` if the
811 : socket is not open; otherwise thrown on failure.
812 : */
813 : template<class Option>
814 24 : void set_option(Option const& opt)
815 : {
816 24 : if (!is_open())
817 2 : detail::throw_system_error(
818 4 : make_error_code(std::errc::bad_file_descriptor),
819 : "local_datagram_socket::set_option");
820 22 : auto const fam = get().family();
821 22 : std::error_code ec = get().set_option(
822 : opt.level(fam), opt.name(fam), opt.data(fam), opt.size(fam));
823 22 : if (ec)
824 2 : detail::throw_system_error(ec, "local_datagram_socket::set_option");
825 20 : }
826 :
827 : /** Get a socket option.
828 :
829 : @tparam Option A socket option type that provides static
830 : `level()` and `name()` members, `data()` / `size()`
831 : accessors, and a `resize()` member.
832 :
833 : @return The current option value.
834 :
835 : @throws std::system_error `errc::bad_file_descriptor` if the
836 : socket is not open; otherwise thrown on failure.
837 : */
838 : template<class Option>
839 8 : Option get_option() const
840 : {
841 8 : if (!is_open())
842 2 : detail::throw_system_error(
843 4 : make_error_code(std::errc::bad_file_descriptor),
844 : "local_datagram_socket::get_option");
845 6 : Option opt{};
846 6 : auto const fam = get().family();
847 6 : std::size_t sz = opt.size(fam);
848 : std::error_code ec =
849 6 : get().get_option(opt.level(fam), opt.name(fam), opt.data(fam), &sz);
850 6 : if (ec)
851 2 : detail::throw_system_error(ec, "local_datagram_socket::get_option");
852 4 : opt.resize(fam, sz);
853 4 : return opt;
854 : }
855 :
856 : /** Assign an existing native socket to this object.
857 :
858 : Adopts a Unix domain datagram socket created outside the
859 : library — from `socketpair()`, received over `SCM_RIGHTS`,
860 : or made natively — and registers it with the backend. The
861 : socket must be a datagram socket in the `AF_UNIX` family.
862 : Adoption never alters the descriptor's flags or options; the
863 : fd must already be non-blocking.
864 :
865 : If this object is already open, pending operations complete
866 : with `errc::operation_canceled` and the held socket is
867 : closed before the new one is adopted.
868 :
869 : @par Exception Safety
870 : Strong guarantee on validation failure: the object is
871 : unchanged. If backend registration fails, the object either
872 : retains its previous socket or is left closed, depending on
873 : the backend. In all failure cases the caller retains
874 : ownership of `fd`.
875 :
876 : @param fd The native socket to adopt. On success the object
877 : owns it and will close it.
878 :
879 : @return The error code, empty on success. Validation and
880 : registration failures are normal runtime conditions when
881 : adopting foreign descriptors.
882 : */
883 : [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
884 :
885 : /** Get the local endpoint of the socket.
886 :
887 : @return The local endpoint, or a default endpoint if not bound.
888 : */
889 : corosio::local_endpoint local_endpoint() const noexcept;
890 :
891 : /** Get the remote endpoint of the socket.
892 :
893 : Returns the address of the connected peer.
894 :
895 : @return The remote endpoint, or a default endpoint if
896 : not connected.
897 : */
898 : corosio::local_endpoint remote_endpoint() const noexcept;
899 :
900 : protected:
901 : /// Default-construct (for derived types).
902 : local_datagram_socket() noexcept = default;
903 :
904 : /// Construct from a pre-built handle.
905 30 : explicit local_datagram_socket(handle h) noexcept : io_object(std::move(h))
906 : {
907 30 : }
908 :
909 : private:
910 : [[nodiscard]] std::error_code
911 : open_for_family(int family, int type, int protocol) noexcept;
912 :
913 1618 : inline implementation& get() const noexcept
914 : {
915 1618 : return *static_cast<implementation*>(h_.get());
916 : }
917 : };
918 :
919 : } // namespace boost::corosio
920 :
921 : #endif // BOOST_COROSIO_POSIX
922 :
923 : #endif // BOOST_COROSIO_LOCAL_DATAGRAM_SOCKET_HPP
|