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_STREAM_SOCKET_HPP
11 : #define BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP
12 :
13 : #include <boost/corosio/family.hpp>
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/detail/platform.hpp>
16 : #include <boost/corosio/detail/except.hpp>
17 : #include <boost/corosio/detail/native_handle.hpp>
18 : #include <boost/corosio/detail/op_base.hpp>
19 : #include <boost/corosio/io/io_stream.hpp>
20 : #include <boost/capy/io_result.hpp>
21 : #include <boost/corosio/detail/buffer_param.hpp>
22 : #include <boost/corosio/error.hpp>
23 : #include <boost/corosio/local_endpoint.hpp>
24 : #include <boost/corosio/shutdown_type.hpp>
25 : #include <boost/corosio/wait_type.hpp>
26 : #include <boost/capy/ex/executor_ref.hpp>
27 : #include <boost/capy/ex/execution_context.hpp>
28 : #include <boost/capy/ex/io_env.hpp>
29 : #include <boost/capy/concept/executor.hpp>
30 :
31 : #include <system_error>
32 :
33 : #include <concepts>
34 : #include <coroutine>
35 : #include <cstddef>
36 : #include <stop_token>
37 : #include <type_traits>
38 :
39 : namespace boost::corosio {
40 :
41 : /** Reads and writes a Unix domain stream, from a coroutine.
42 :
43 : This class provides asynchronous Unix domain stream socket
44 : operations that return awaitable types. Each operation
45 : participates in the affine awaitable protocol, ensuring
46 : coroutines resume on the correct executor.
47 :
48 : The socket must be opened before performing I/O operations.
49 : Operations support cancellation through `std::stop_token` via
50 : the affine protocol, or explicitly through the `cancel()`
51 : member function.
52 :
53 : @par Thread Safety
54 : Distinct objects: Safe.@n
55 : Shared objects: Unsafe. A socket must not have concurrent
56 : operations of the same type (e.g., two simultaneous reads).
57 : One read and one write may be in flight simultaneously.
58 :
59 : @par Semantics
60 : Wraps the platform Unix domain socket stack. Operations
61 : dispatch to OS socket APIs via the `io_context` backend
62 : (epoll, kqueue, select, or IOCP). Satisfies @ref capy::Stream.
63 :
64 : @par Example
65 : @par !example connect_and_read
66 : */
67 : class BOOST_COROSIO_DECL local_stream_socket : public io_stream
68 : {
69 : public:
70 : /// The endpoint type used by this socket.
71 : using endpoint_type = corosio::local_endpoint;
72 :
73 : /// The shutdown direction type used by this socket.
74 : using shutdown_type = corosio::shutdown_type;
75 : using enum corosio::shutdown_type;
76 :
77 : /** Define backend hooks for local stream socket operations.
78 :
79 : Platform backends (epoll, kqueue, select) derive from this
80 : to implement socket I/O, connection, and option management.
81 : */
82 : struct implementation : io_stream::implementation
83 : {
84 : /** Initiate an asynchronous connect to the given endpoint.
85 :
86 : @param h Coroutine handle to resume on completion.
87 : @param ex Executor for dispatching the completion.
88 : @param ep The local endpoint (path) to connect to.
89 : @param token Stop token for cancellation.
90 : @param ec Output error code.
91 :
92 : @return Coroutine handle to resume immediately.
93 : */
94 : virtual std::coroutine_handle<> connect(
95 : std::coroutine_handle<> h,
96 : capy::executor_ref ex,
97 : corosio::local_endpoint ep,
98 : std::stop_token token,
99 : std::error_code* ec) = 0;
100 :
101 : /** Initiate an asynchronous wait for socket readiness.
102 :
103 : Completes when the socket becomes ready for the
104 : specified direction, or an error condition is
105 : reported. No bytes are transferred.
106 :
107 : @param h Coroutine handle to resume on completion.
108 : @param ex Executor for dispatching the completion.
109 : @param w The direction to wait on.
110 : @param token Stop token for cancellation.
111 : @param ec Output error code.
112 :
113 : @return Coroutine handle to resume immediately.
114 : */
115 : virtual std::coroutine_handle<> wait(
116 : std::coroutine_handle<> h,
117 : capy::executor_ref ex,
118 : wait_type w,
119 : std::stop_token token,
120 : std::error_code* ec) = 0;
121 :
122 : /** Shut down the socket for the given direction(s).
123 :
124 : @param what The shutdown direction.
125 :
126 : @return Error code on failure, empty on success.
127 : */
128 : virtual std::error_code shutdown(shutdown_type what) noexcept = 0;
129 :
130 : /// Return the platform socket descriptor.
131 : virtual native_handle_type native_handle() const noexcept = 0;
132 :
133 : /** Return the socket's address family.
134 :
135 : Local sockets have no IP family; implementations return
136 : `v4`, which the family-neutral options applicable to them
137 : ignore.
138 :
139 : @return The address family for option rendering.
140 : */
141 : virtual corosio::family family() const noexcept = 0;
142 :
143 : /** Release ownership of the native socket handle.
144 :
145 : Deregisters the socket from the reactor without closing
146 : the descriptor. The caller takes ownership.
147 :
148 : @return The native handle.
149 : */
150 : virtual native_handle_type release_socket() noexcept = 0;
151 :
152 : /** Request cancellation of pending asynchronous operations.
153 :
154 : Operations still in flight complete with `operation_canceled`; an
155 : operation whose result is already decided reports that result.
156 : Check `ec == cond::canceled` for portable comparison.
157 : */
158 : virtual void cancel() noexcept = 0;
159 :
160 : /** Set a socket option.
161 :
162 : @param level The protocol level (e.g. `SOL_SOCKET`).
163 : @param optname The option name (e.g. `SO_KEEPALIVE`).
164 : @param data Pointer to the option value.
165 : @param size Size of the option value in bytes.
166 : @return Error code on failure, empty on success.
167 : */
168 : virtual std::error_code set_option(
169 : int level,
170 : int optname,
171 : void const* data,
172 : std::size_t size) noexcept = 0;
173 :
174 : /** Get a socket option.
175 :
176 : @param level The protocol level (e.g. `SOL_SOCKET`).
177 : @param optname The option name (e.g. `SO_KEEPALIVE`).
178 : @param data Pointer to receive the option value.
179 : @param size On entry, the size of the buffer. On exit,
180 : the size of the option value.
181 : @return Error code on failure, empty on success.
182 : */
183 : virtual std::error_code
184 : get_option(int level, int optname, void* data, std::size_t* size)
185 : const noexcept = 0;
186 :
187 : /// Return the cached local endpoint.
188 : virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
189 :
190 : /// Return the cached remote endpoint.
191 : virtual corosio::local_endpoint remote_endpoint() const noexcept = 0;
192 : };
193 :
194 : /// Represent the awaitable returned by @ref connect.
195 : struct connect_awaitable : detail::void_op_base<connect_awaitable>
196 : {
197 : private:
198 : friend local_stream_socket;
199 :
200 HIT 25 : connect_awaitable(
201 : local_stream_socket& s, corosio::local_endpoint ep) noexcept
202 50 : : s_(s)
203 25 : , endpoint_(ep)
204 : {
205 25 : }
206 :
207 : friend detail::void_op_base<connect_awaitable>;
208 :
209 : local_stream_socket& s_;
210 : corosio::local_endpoint endpoint_;
211 :
212 : std::coroutine_handle<>
213 23 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
214 : {
215 23 : return s_.get().connect(h, ex, endpoint_, token_, &ec_);
216 : }
217 : };
218 :
219 : /// Represent the awaitable returned by @ref wait.
220 : struct wait_awaitable : detail::void_op_base<wait_awaitable>
221 : {
222 : private:
223 : friend local_stream_socket;
224 :
225 16 : wait_awaitable(local_stream_socket& s, wait_type w) noexcept
226 32 : : s_(s)
227 16 : , w_(w)
228 : {
229 16 : }
230 :
231 : friend detail::void_op_base<wait_awaitable>;
232 :
233 : local_stream_socket& s_;
234 : wait_type w_;
235 :
236 : std::coroutine_handle<>
237 14 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
238 : {
239 14 : return s_.get().wait(h, ex, w_, token_, &ec_);
240 : }
241 : };
242 :
243 : public:
244 : /** Destructor.
245 :
246 : Closes the socket if open, cancelling any pending operations.
247 : */
248 : ~local_stream_socket() override;
249 :
250 : /** Construct a socket from an execution context.
251 :
252 : @param ctx The execution context that owns this socket.
253 : */
254 : explicit local_stream_socket(capy::execution_context& ctx);
255 :
256 : /** Construct a socket from an executor.
257 :
258 : The socket is associated with the executor's context.
259 :
260 : @tparam Ex A type satisfying capy::Executor.
261 :
262 : @param ex The executor whose context owns the socket.
263 : */
264 : template<class Ex>
265 : requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_socket>) &&
266 : capy::Executor<Ex>
267 : explicit local_stream_socket(Ex const& ex)
268 : : local_stream_socket(ex.context())
269 : {
270 : }
271 :
272 : /** Move constructor.
273 :
274 : Transfers ownership of the socket resources.
275 :
276 : @param other The socket to move from.
277 :
278 : @pre No awaitables returned by @p other's methods exist.
279 : @pre The execution context associated with @p other must
280 : outlive this socket.
281 : */
282 14 : local_stream_socket(local_stream_socket&& other) noexcept
283 14 : : io_object(std::move(other))
284 : {
285 14 : }
286 :
287 : /** Move assignment operator.
288 :
289 : Closes any existing socket and transfers ownership.
290 :
291 : @param other The socket to move from.
292 :
293 : @pre No awaitables returned by either `*this` or @p other's
294 : methods exist.
295 : @pre The execution context associated with @p other must
296 : outlive this socket.
297 :
298 : @return Reference to this socket.
299 : */
300 4 : local_stream_socket& operator=(local_stream_socket&& other) noexcept
301 : {
302 4 : if (this != &other)
303 : {
304 2 : close();
305 2 : io_object::operator=(std::move(other));
306 : }
307 4 : return *this;
308 : }
309 :
310 : /// Copy construction is disabled; the handle is uniquely owned.
311 : local_stream_socket(local_stream_socket const&) = delete;
312 : /// Copy assignment is disabled; the handle is uniquely owned.
313 : local_stream_socket& operator=(local_stream_socket const&) = delete;
314 :
315 : /** Open the socket.
316 :
317 : Creates a Unix stream socket and associates it with
318 : the platform reactor.
319 :
320 : Failures such as descriptor exhaustion are normal runtime
321 : conditions and are reported through the returned error code.
322 : Opening an already-open socket is a no-op that reports
323 : success.
324 :
325 :
326 : @return The error code, empty on success.
327 : */
328 : [[nodiscard]] std::error_code open() noexcept;
329 :
330 : /** Close the socket.
331 :
332 : Releases socket resources. Any pending operations complete
333 : with `errc::operation_canceled`.
334 : */
335 : void close() noexcept;
336 :
337 : /** Check if the socket is open.
338 :
339 : @return `true` if the socket is open and ready for operations.
340 : */
341 1062 : bool is_open() const noexcept
342 : {
343 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
344 : return h_ && get().native_handle() != ~native_handle_type(0);
345 : #else
346 1062 : return h_ && get().native_handle() >= 0;
347 : #endif
348 : }
349 :
350 : /** Initiate an asynchronous connect operation.
351 :
352 : If the socket is not already open, it is opened automatically.
353 :
354 : @param ep The local endpoint (path) to connect to.
355 :
356 : @return An awaitable that completes with io_result<>.
357 :
358 : If the socket needs to be opened and the open fails, the
359 : awaitable completes immediately with that error.
360 : */
361 25 : [[nodiscard]] auto connect(corosio::local_endpoint ep)
362 : {
363 25 : connect_awaitable aw(*this, ep);
364 25 : if (!is_open())
365 17 : aw.ec_ = open();
366 25 : return aw;
367 : }
368 :
369 : /** Wait for the socket to become ready in a given direction.
370 :
371 : Suspends until the socket is ready for the requested
372 : direction, or an error condition is reported. No bytes
373 : are transferred.
374 :
375 : @param w The wait direction (read, write, or error).
376 :
377 : @return An awaitable that completes with `io_result<>`.
378 :
379 : A closed socket completes with `errc::bad_file_descriptor`.
380 :
381 : @pre This socket must outlive the returned awaitable.
382 : */
383 16 : [[nodiscard]] auto wait(wait_type w)
384 : {
385 16 : return wait_awaitable(*this, w);
386 : }
387 :
388 : /** Cancel any pending asynchronous operations.
389 :
390 : Operations still in flight complete with `errc::operation_canceled`;
391 : an operation whose result is already decided reports that result.
392 : Check `ec == cond::canceled` for portable comparison.
393 : */
394 : void cancel() noexcept;
395 :
396 : /** Get the native socket handle.
397 :
398 : Returns the underlying platform-specific socket descriptor.
399 : On POSIX systems this is an `int` file descriptor.
400 :
401 : @return The native socket handle, or an invalid sentinel
402 : if not open.
403 : */
404 : native_handle_type native_handle() const noexcept;
405 :
406 : /** Query the number of bytes available for reading.
407 :
408 : @return The number of bytes that can be read without blocking.
409 :
410 : @throws std::system_error `errc::bad_file_descriptor` if the
411 : socket is not open; otherwise thrown on ioctl failure.
412 : */
413 : std::size_t available() const;
414 :
415 : /** Release ownership of the native socket handle.
416 :
417 : Deregisters the socket from the backend and cancels pending
418 : operations without closing the descriptor. The caller takes
419 : ownership of the returned handle.
420 :
421 : @return The native handle.
422 :
423 : @throws std::system_error `errc::bad_file_descriptor` if the
424 : socket is not open.
425 :
426 : @post is_open() == false
427 : */
428 : native_handle_type release();
429 :
430 : /** Disable sends or receives on the socket.
431 :
432 : Unix stream connections are full-duplex: each direction
433 : (send and receive) operates independently. This function
434 : allows you to close one or both directions without
435 : destroying the socket.
436 :
437 : Failures such as a peer that already disconnected are
438 : normal runtime conditions and are reported through the
439 : returned error code. A closed socket reports
440 : `errc::bad_file_descriptor`.
441 :
442 : @param what Determines which operations are no longer
443 : allowed.
444 :
445 : @return The error code, empty on success.
446 : */
447 : [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept;
448 :
449 : /** Set a socket option.
450 :
451 : Applies a type-safe socket option to the underlying socket.
452 : The option type encodes the protocol level and option name.
453 :
454 : @param opt The option to set.
455 :
456 : @throws std::system_error `errc::bad_file_descriptor` if the
457 : socket is not open; otherwise thrown on failure.
458 : */
459 : template<class Option>
460 14 : void set_option(Option const& opt)
461 : {
462 14 : if (!is_open())
463 2 : detail::throw_system_error(
464 4 : make_error_code(std::errc::bad_file_descriptor),
465 : "local_stream_socket::set_option");
466 12 : auto const fam = get().family();
467 12 : std::error_code ec = get().set_option(
468 : opt.level(fam), opt.name(fam), opt.data(fam), opt.size(fam));
469 12 : if (ec)
470 2 : detail::throw_system_error(ec, "local_stream_socket::set_option");
471 10 : }
472 :
473 : /** Get a socket option.
474 :
475 : Retrieves the current value of a type-safe socket option.
476 :
477 : @return The current option value.
478 :
479 : @throws std::system_error `errc::bad_file_descriptor` if the
480 : socket is not open; otherwise thrown on failure.
481 : */
482 : template<class Option>
483 10 : Option get_option() const
484 : {
485 10 : if (!is_open())
486 2 : detail::throw_system_error(
487 4 : make_error_code(std::errc::bad_file_descriptor),
488 : "local_stream_socket::get_option");
489 8 : Option opt{};
490 8 : auto const fam = get().family();
491 8 : std::size_t sz = opt.size(fam);
492 : std::error_code ec =
493 8 : get().get_option(opt.level(fam), opt.name(fam), opt.data(fam), &sz);
494 8 : if (ec)
495 2 : detail::throw_system_error(ec, "local_stream_socket::get_option");
496 6 : opt.resize(fam, sz);
497 6 : return opt;
498 : }
499 :
500 : /** Assign an existing native socket to this object.
501 :
502 : Adopts a Unix domain stream socket created outside the
503 : library — from `socketpair()`, received over `SCM_RIGHTS`,
504 : or made natively — and registers it with the backend. The
505 : socket must be a stream socket in the `AF_UNIX` family.
506 : Adoption never alters the descriptor's flags or options: on
507 : POSIX the fd must already be non-blocking, and on Windows
508 : the socket must be overlapped-capable.
509 :
510 : The object must be closed. To replace a held socket, `close()`
511 : or `release()` it first.
512 :
513 : @par Exception Safety
514 : Throws nothing. On failure the object is unchanged and the
515 : caller retains ownership of `fd`.
516 :
517 : @param fd The native socket to adopt. On success the object
518 : owns it and closes it.
519 :
520 : @return `error::already_open` if this object is open.
521 : Otherwise the error code, empty on success. Validation and
522 : registration failures are normal runtime conditions when
523 : adopting foreign descriptors.
524 : */
525 : [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
526 :
527 : /** Get the local endpoint of the socket.
528 :
529 : Returns the local address (path) to which the socket is bound.
530 : The endpoint is cached when the connection is established.
531 :
532 : @return The local endpoint, or a default endpoint if the socket
533 : is not connected.
534 : */
535 : corosio::local_endpoint local_endpoint() const noexcept;
536 :
537 : /** Get the remote endpoint of the socket.
538 :
539 : Returns the remote address (path) to which the socket is connected.
540 : The endpoint is cached when the connection is established.
541 :
542 : @return The remote endpoint, or a default endpoint if the socket
543 : is not connected.
544 : */
545 : corosio::local_endpoint remote_endpoint() const noexcept;
546 :
547 : protected:
548 : /// Default construct a closed socket for a derived class to open.
549 44 : local_stream_socket() noexcept = default;
550 :
551 : /** Adopt an existing handle.
552 :
553 : @param h The handle the socket takes ownership of.
554 : */
555 : explicit local_stream_socket(handle h) noexcept : io_object(std::move(h)) {}
556 :
557 : private:
558 : friend class local_stream_acceptor;
559 :
560 : [[nodiscard]] std::error_code
561 : open_for_family(int family, int type, int protocol) noexcept;
562 :
563 1166 : inline implementation& get() const noexcept
564 : {
565 1166 : return *static_cast<implementation*>(h_.get());
566 : }
567 : };
568 :
569 : } // namespace boost::corosio
570 :
571 : #endif // BOOST_COROSIO_LOCAL_STREAM_SOCKET_HPP
|