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