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