TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : // Copyright (c) 2026 Michael Vandeberg
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_TCP_SOCKET_HPP
13 : #define BOOST_COROSIO_TCP_SOCKET_HPP
14 :
15 : #include <boost/corosio/family.hpp>
16 : #include <boost/corosio/detail/config.hpp>
17 : #include <boost/corosio/detail/platform.hpp>
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_stream.hpp>
22 : #include <boost/capy/io_result.hpp>
23 : #include <boost/corosio/detail/buffer_param.hpp>
24 : #include <boost/corosio/error.hpp>
25 : #include <boost/corosio/endpoint.hpp>
26 : #include <boost/corosio/shutdown_type.hpp>
27 : #include <boost/corosio/wait_type.hpp>
28 : #include <boost/capy/ex/executor_ref.hpp>
29 : #include <boost/capy/ex/execution_context.hpp>
30 : #include <boost/capy/ex/io_env.hpp>
31 : #include <boost/capy/concept/executor.hpp>
32 :
33 : #include <system_error>
34 :
35 : #include <concepts>
36 : #include <coroutine>
37 : #include <cstddef>
38 : #include <stop_token>
39 : #include <type_traits>
40 :
41 : namespace boost::corosio {
42 :
43 : /** Connects, reads, and writes over TCP, from a coroutine.
44 :
45 : This class provides asynchronous TCP socket operations that return
46 : awaitable types. Each operation participates in the affine awaitable
47 : protocol, ensuring coroutines resume on the correct executor.
48 :
49 : The socket must be opened before performing I/O operations. Operations
50 : support cancellation through `std::stop_token` via the affine protocol,
51 : or explicitly through the `cancel()` member function.
52 :
53 : @par Thread Safety
54 : Distinct objects: Safe.@n
55 : Shared objects: Unsafe. A socket must not have concurrent operations
56 : of the same type (e.g., two simultaneous reads). One read and one
57 : write may be in flight simultaneously.
58 :
59 : @par Semantics
60 : Wraps the platform TCP/IP stack. Operations dispatch to
61 : OS socket APIs via the `io_context` reactor (epoll, IOCP,
62 : kqueue). Satisfies @ref capy::Stream.
63 :
64 : @par Example
65 : @par !example connect_and_read
66 : */
67 : class BOOST_COROSIO_DECL tcp_socket : public io_stream
68 : {
69 : public:
70 : /// The endpoint type used by this socket.
71 : using endpoint_type = corosio::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 TCP socket operations.
78 :
79 : Platform backends (epoll, IOCP, kqueue, select) derive from
80 : this 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 remote endpoint 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 : 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 : Socket options render for this family.
136 :
137 : @return The socket's address family.
138 : */
139 : virtual corosio::family family() const noexcept = 0;
140 :
141 : /** Release ownership of the native socket handle.
142 :
143 : Deregisters the socket from the backend and cancels
144 : pending operations without closing the descriptor. The
145 : caller takes ownership.
146 :
147 : @return The native handle.
148 : */
149 : virtual native_handle_type release_socket() noexcept = 0;
150 :
151 : /** Request cancellation of pending asynchronous operations.
152 :
153 : Operations still in flight complete with `operation_canceled`; an
154 : operation whose result is already decided reports that result.
155 : Check `ec == cond::canceled` for portable comparison.
156 : */
157 : virtual void cancel() noexcept = 0;
158 :
159 : /** Set a socket option.
160 :
161 : @param level The protocol level (e.g. `SOL_SOCKET`).
162 : @param optname The option name (e.g. `SO_KEEPALIVE`).
163 : @param data Pointer to the option value.
164 : @param size Size of the option value in bytes.
165 : @return Error code on failure, empty on success.
166 : */
167 : virtual std::error_code set_option(
168 : int level,
169 : int optname,
170 : void const* data,
171 : std::size_t size) noexcept = 0;
172 :
173 : /** Get a socket option.
174 :
175 : @param level The protocol level (e.g. `SOL_SOCKET`).
176 : @param optname The option name (e.g. `SO_KEEPALIVE`).
177 : @param data Pointer to receive the option value.
178 : @param size On entry, the size of the buffer. On exit,
179 : the size of the option value.
180 : @return Error code on failure, empty on success.
181 : */
182 : virtual std::error_code
183 : get_option(int level, int optname, void* data, std::size_t* size)
184 : const noexcept = 0;
185 :
186 : /// Return the cached local endpoint.
187 : virtual endpoint local_endpoint() const noexcept = 0;
188 :
189 : /// Return the cached remote endpoint.
190 : virtual endpoint remote_endpoint() const noexcept = 0;
191 : };
192 :
193 : /// Represent the awaitable returned by @ref connect.
194 : struct connect_awaitable : detail::void_op_base<connect_awaitable>
195 : {
196 : private:
197 : friend tcp_socket;
198 :
199 HIT 4616 : connect_awaitable(tcp_socket& s, endpoint ep) noexcept
200 9232 : : s_(s)
201 4616 : , endpoint_(ep)
202 : {
203 4616 : }
204 :
205 : friend detail::void_op_base<connect_awaitable>;
206 :
207 : tcp_socket& s_;
208 : endpoint endpoint_;
209 :
210 : std::coroutine_handle<>
211 4613 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
212 : {
213 4613 : return s_.get().connect(h, ex, endpoint_, token_, &ec_);
214 : }
215 : };
216 :
217 : /// Represent the awaitable returned by @ref wait.
218 : struct wait_awaitable : detail::void_op_base<wait_awaitable>
219 : {
220 : private:
221 : friend tcp_socket;
222 :
223 74 : wait_awaitable(tcp_socket& s, wait_type w) noexcept : s_(s), w_(w) {}
224 :
225 : friend detail::void_op_base<wait_awaitable>;
226 :
227 : tcp_socket& s_;
228 : wait_type w_;
229 :
230 : std::coroutine_handle<>
231 70 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
232 : {
233 70 : return s_.get().wait(h, ex, w_, token_, &ec_);
234 : }
235 : };
236 :
237 : public:
238 : /** Closes the socket if open, cancelling any pending operations. */
239 : ~tcp_socket() override;
240 :
241 : /** Construct a socket from an execution context.
242 :
243 : @param ctx The execution context that owns this socket.
244 : */
245 : explicit tcp_socket(capy::execution_context& ctx);
246 :
247 : /** Construct a socket from an executor.
248 :
249 : The socket is associated with the executor's context.
250 :
251 : @tparam Ex A type satisfying capy::Executor.
252 :
253 : @param ex The executor whose context owns the socket.
254 : */
255 : template<class Ex>
256 : requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
257 : capy::Executor<Ex>
258 1 : explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
259 : {
260 1 : }
261 :
262 : /** Move constructor.
263 :
264 : Transfers ownership of the socket resources.
265 :
266 : @param other The socket to move from.
267 :
268 : @pre No awaitables returned by @p other's methods exist.
269 : @pre @p other is not referenced as a peer in any outstanding
270 : accept awaitable.
271 : @pre The execution context associated with @p other must
272 : outlive this socket.
273 : */
274 723 : tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
275 :
276 : /** Move assignment operator.
277 :
278 : Closes any existing socket and transfers ownership.
279 :
280 : @param other The socket to move from.
281 :
282 : @pre No awaitables returned by either `*this` or @p other's
283 : methods exist.
284 : @pre Neither `*this` nor @p other is referenced as a peer in
285 : any outstanding accept awaitable.
286 : @pre The execution context associated with @p other must
287 : outlive this socket.
288 :
289 : @return Reference to this socket.
290 : */
291 25 : tcp_socket& operator=(tcp_socket&& other) noexcept
292 : {
293 25 : if (this != &other)
294 : {
295 25 : close();
296 25 : h_ = std::move(other.h_);
297 : }
298 25 : return *this;
299 : }
300 :
301 : /// Copy construction is disabled; the handle is uniquely owned.
302 : tcp_socket(tcp_socket const&) = delete;
303 : /// Copy assignment is disabled; the handle is uniquely owned.
304 : tcp_socket& operator=(tcp_socket const&) = delete;
305 :
306 : /** Open the socket.
307 :
308 : Creates a TCP socket and associates it with the platform
309 : reactor (IOCP on Windows). Calling @ref connect on a closed
310 : socket opens it automatically with the endpoint's address family.
311 : An explicit `open()` is therefore needed only when socket options
312 : must be set before connecting.
313 :
314 : Failures such as descriptor exhaustion are normal runtime
315 : conditions and are reported through the returned error code.
316 : Opening an already-open socket is a no-op that reports
317 : success.
318 :
319 : @param f The address family (IPv4 or IPv6). Defaults to
320 : `family::v4`.
321 :
322 : @return The error code, empty on success.
323 : */
324 : [[nodiscard]] std::error_code open(family f = family::v4) noexcept;
325 :
326 : /** Bind the socket to a local endpoint.
327 :
328 : Associates the socket with a local address and port before
329 : connecting. Useful for multi-homed hosts or source-port
330 : pinning.
331 :
332 : @param ep The local endpoint to bind to.
333 :
334 : @return An error code indicating success or the reason for
335 : failure.
336 :
337 : @par Error Conditions
338 : @li `errc::address_in_use`: The endpoint is already in use.
339 : @li `errc::address_not_available`: The address is not
340 : available on any local interface.
341 : @li `errc::permission_denied`: Insufficient privileges to
342 : bind to the endpoint (e.g., privileged port).
343 : @li `errc::bad_file_descriptor`: The socket is closed.
344 : */
345 : [[nodiscard]] std::error_code bind(endpoint ep) noexcept;
346 :
347 : /** Close the socket.
348 :
349 : Releases socket resources. Any pending operations complete
350 : with `errc::operation_canceled`.
351 : */
352 : void close() noexcept;
353 :
354 : /** Check if the socket is open.
355 :
356 : @return `true` if the socket is open and ready for operations.
357 : */
358 29520 : bool is_open() const noexcept
359 : {
360 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
361 : return h_ && get().native_handle() != ~native_handle_type(0);
362 : #else
363 29520 : return h_ && get().native_handle() >= 0;
364 : #endif
365 : }
366 :
367 : /** Initiate an asynchronous connect operation.
368 :
369 : If the socket is not already open, it is opened automatically
370 : using the address family of @p ep (IPv4 or IPv6). If the socket
371 : is already open, the existing file descriptor is used as-is.
372 :
373 : The operation supports cancellation via `std::stop_token` through
374 : the affine awaitable protocol. If the associated stop token is
375 : triggered, the operation completes immediately with
376 : `errc::operation_canceled`.
377 :
378 : @param ep The remote endpoint to connect to.
379 :
380 : @return An awaitable that completes with `io_result<>`.
381 : Returns success (default `error_code`) on successful connection,
382 : or an error code on failure including:
383 : - `connection_refused`: No server listening at endpoint
384 : - `timed_out`: Connection attempt timed out
385 : - `network_unreachable`: No route to host
386 : - `operation_canceled`: Cancelled via stop_token or cancel().
387 : Check `ec == cond::canceled` for portable comparison.
388 :
389 : If the socket needs to be opened and the open fails, the
390 : awaitable completes immediately with that error.
391 :
392 : @pre This socket must outlive the returned awaitable.
393 :
394 : @par Example
395 : @par !example connect
396 : */
397 4616 : [[nodiscard]] auto connect(endpoint ep)
398 : {
399 4616 : connect_awaitable aw(*this, ep);
400 4616 : if (!is_open())
401 87 : aw.ec_ = open(ep.address().family());
402 4616 : return aw;
403 : }
404 :
405 : /** Wait for the socket to become ready in a given direction.
406 :
407 : Suspends until the socket is ready for the requested
408 : direction, or an error condition is reported. No bytes are
409 : transferred. This suits C libraries that own the I/O on a
410 : nonblocking fd and need only readiness notification, such as
411 : libpq async and libssh.
412 :
413 : The operation supports cancellation via `std::stop_token`
414 : through the affine awaitable protocol. If the associated
415 : stop token is triggered, the operation completes
416 : immediately with `errc::operation_canceled`.
417 :
418 : @param w The wait direction (read, write, or error).
419 :
420 : @return An awaitable that completes with `io_result<>`.
421 : On success, the wait consumes no bytes from the
422 : stream; a subsequent `read_some` (for read waits)
423 : returns the available data.
424 :
425 : A closed socket completes with `errc::bad_file_descriptor`.
426 :
427 : @pre This socket must outlive the returned awaitable.
428 : */
429 74 : [[nodiscard]] auto wait(wait_type w)
430 : {
431 74 : return wait_awaitable(*this, w);
432 : }
433 :
434 : /** Cancel any pending asynchronous operations.
435 :
436 : Operations still in flight complete with `errc::operation_canceled`;
437 : an operation whose result is already decided reports that result.
438 : Check `ec == cond::canceled` for portable comparison.
439 : */
440 : void cancel() noexcept;
441 :
442 : /** Get the native socket handle.
443 :
444 : Returns the underlying platform-specific socket descriptor.
445 : On POSIX systems this is an `int` file descriptor.
446 : On Windows this is a `SOCKET` handle.
447 :
448 : @return The native socket handle, or -1/INVALID_SOCKET if not open.
449 :
450 : @pre None. May be called on closed sockets.
451 : */
452 : native_handle_type native_handle() const noexcept;
453 :
454 : /** Assign an existing native socket to this object.
455 :
456 : Adopts a TCP socket created outside the library — received
457 : from another process, inherited, or made natively — and
458 : registers it with the backend. The socket must be a stream
459 : socket in the `AF_INET` or `AF_INET6` family. Adoption never
460 : alters the descriptor's flags or options: on POSIX the fd
461 : must already be non-blocking, and on Windows the socket must
462 : be overlapped-capable.
463 :
464 : The object must be closed. To replace a held socket, `close()`
465 : or `release()` it first.
466 :
467 : @par Exception Safety
468 : Throws nothing. On failure the object is unchanged and the
469 : caller retains ownership of `fd`.
470 :
471 : @param fd The native socket to adopt. On success the object
472 : owns it and closes it.
473 :
474 : @return `error::already_open` if this object is open.
475 : Otherwise the error code, empty on success. Validation and
476 : registration failures are normal runtime conditions when
477 : adopting foreign descriptors.
478 : */
479 : [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
480 :
481 : /** Release ownership of the native socket handle.
482 :
483 : Deregisters the socket from the backend and cancels pending
484 : operations without closing the descriptor. The caller takes
485 : ownership of the returned handle.
486 :
487 : @return The native handle.
488 :
489 : @throws std::system_error `errc::bad_file_descriptor` if the
490 : socket is not open.
491 :
492 : @post is_open() == false
493 : */
494 : native_handle_type release();
495 :
496 : /** Disable sends or receives on the socket.
497 :
498 : TCP connections are full-duplex: each direction (send and receive)
499 : operates independently. This function allows you to close one or
500 : both directions without destroying the socket.
501 :
502 : @li @ref shutdown_send sends a TCP FIN packet to the peer,
503 : signaling that you have no more data to send. You can still
504 : receive data until the peer also closes their send direction.
505 : This is the most common use case, typically called before
506 : close() to ensure graceful connection termination.
507 :
508 : @li @ref shutdown_receive disables reading on the socket. This
509 : does not send anything to the peer. The peer is not informed
510 : and may continue sending data. Subsequent reads fail
511 : or return end-of-file. Incoming data may be discarded or
512 : buffered depending on the operating system.
513 :
514 : @li @ref shutdown_both combines both effects: sends a FIN and
515 : disables reading.
516 :
517 : When the peer shuts down their send direction (sends a FIN),
518 : subsequent read operations complete with `capy::cond::eof`.
519 : Use the portable condition test rather than comparing error
520 : codes directly:
521 :
522 : @par !example shutdown
523 :
524 : @par Error Conditions
525 : Failures such as a peer that already disconnected are
526 : normal runtime conditions and are reported through the
527 : returned error code. A closed socket reports
528 : `errc::bad_file_descriptor`.
529 :
530 : @param what Determines which operations are no longer allowed.
531 :
532 : @return The error code, empty on success.
533 : */
534 : [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept;
535 :
536 : /** Set a socket option.
537 :
538 : Applies a type-safe socket option to the underlying socket.
539 : The option type encodes the protocol level and option name.
540 :
541 : @par Example
542 : @par !example set_option
543 :
544 : @param opt The option to set.
545 :
546 : @throws std::system_error `errc::bad_file_descriptor` if the
547 : socket is not open; otherwise thrown on failure.
548 : */
549 : template<class Option>
550 302 : void set_option(Option const& opt)
551 : {
552 302 : if (!is_open())
553 2 : detail::throw_system_error(
554 4 : make_error_code(std::errc::bad_file_descriptor),
555 : "tcp_socket::set_option");
556 300 : auto const fam = get().family();
557 300 : std::error_code ec = get().set_option(
558 : opt.level(fam), opt.name(fam), opt.data(fam), opt.size(fam));
559 300 : if (ec)
560 7 : detail::throw_system_error(ec, "tcp_socket::set_option");
561 293 : }
562 :
563 : /** Get a socket option.
564 :
565 : Retrieves the current value of a type-safe socket option.
566 :
567 : @par Example
568 : @par !example get_option
569 :
570 : @return The current option value.
571 :
572 : @throws std::system_error `errc::bad_file_descriptor` if the
573 : socket is not open; otherwise thrown on failure.
574 : */
575 : template<class Option>
576 97 : Option get_option() const
577 : {
578 97 : if (!is_open())
579 2 : detail::throw_system_error(
580 4 : make_error_code(std::errc::bad_file_descriptor),
581 : "tcp_socket::get_option");
582 95 : Option opt{};
583 95 : auto const fam = get().family();
584 95 : std::size_t sz = opt.size(fam);
585 : std::error_code ec =
586 95 : get().get_option(opt.level(fam), opt.name(fam), opt.data(fam), &sz);
587 95 : if (ec)
588 7 : detail::throw_system_error(ec, "tcp_socket::get_option");
589 88 : opt.resize(fam, sz);
590 88 : return opt;
591 : }
592 :
593 : /** Get the local endpoint of the socket.
594 :
595 : Returns the local address and port to which the socket is bound.
596 : For a connected socket, this is the local side of the connection.
597 : The endpoint is cached when the connection is established.
598 :
599 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
600 : the socket is not connected.
601 :
602 : @par Thread Safety
603 : The cached endpoint value is set during connect/accept completion
604 : and cleared during close(). This function may be called concurrently
605 : with I/O operations, but must not be called concurrently with
606 : connect(), accept(), or close().
607 : */
608 : endpoint local_endpoint() const noexcept;
609 :
610 : /** Get the remote endpoint of the socket.
611 :
612 : Returns the remote address and port to which the socket is connected.
613 : The endpoint is cached when the connection is established.
614 :
615 : @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
616 : the socket is not connected.
617 :
618 : @par Thread Safety
619 : The cached endpoint value is set during connect/accept completion
620 : and cleared during close(). This function may be called concurrently
621 : with I/O operations, but must not be called concurrently with
622 : connect(), accept(), or close().
623 : */
624 : endpoint remote_endpoint() const noexcept;
625 :
626 : protected:
627 : /// Default construct a closed socket for a derived class to open.
628 55 : tcp_socket() noexcept = default;
629 :
630 : /** Adopt an existing handle.
631 :
632 : @param h The handle the socket takes ownership of.
633 : */
634 : explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
635 :
636 : private:
637 : friend class tcp_acceptor;
638 :
639 : /// Open the socket for the given protocol triple.
640 : [[nodiscard]] std::error_code
641 : open_for_family(int family, int type, int protocol) noexcept;
642 :
643 34651 : inline implementation& get() const noexcept
644 : {
645 34651 : return *static_cast<implementation*>(h_.get());
646 : }
647 : };
648 :
649 : } // namespace boost::corosio
650 :
651 : #endif
|