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