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/io/io_stream.hpp>
17  
#include <boost/corosio/io/io_stream.hpp>
18  
#include <boost/capy/io_result.hpp>
18  
#include <boost/capy/io_result.hpp>
19  
#include <boost/corosio/io_buffer_param.hpp>
19  
#include <boost/corosio/io_buffer_param.hpp>
20  
#include <boost/corosio/endpoint.hpp>
20  
#include <boost/corosio/endpoint.hpp>
 
21 +
#include <boost/corosio/tcp.hpp>
21  
#include <boost/capy/ex/executor_ref.hpp>
22  
#include <boost/capy/ex/executor_ref.hpp>
22  
#include <boost/capy/ex/execution_context.hpp>
23  
#include <boost/capy/ex/execution_context.hpp>
23  
#include <boost/capy/ex/io_env.hpp>
24  
#include <boost/capy/ex/io_env.hpp>
24  
#include <boost/capy/concept/executor.hpp>
25  
#include <boost/capy/concept/executor.hpp>
25  

26  

26  
#include <system_error>
27  
#include <system_error>
27  

28  

28  
#include <concepts>
29  
#include <concepts>
29  
#include <coroutine>
30  
#include <coroutine>
30  
#include <cstddef>
31  
#include <cstddef>
31  
#include <memory>
32  
#include <memory>
32  
#include <stop_token>
33  
#include <stop_token>
33  
#include <type_traits>
34  
#include <type_traits>
34  

35  

35  
namespace boost::corosio {
36  
namespace boost::corosio {
36  

37  

37  
#if BOOST_COROSIO_HAS_IOCP
38  
#if BOOST_COROSIO_HAS_IOCP
38  
using native_handle_type = std::uintptr_t; // SOCKET
39  
using native_handle_type = std::uintptr_t; // SOCKET
39  
#else
40  
#else
40  
using native_handle_type = int;
41  
using native_handle_type = int;
41  
#endif
42  
#endif
42  

43  

43  
/** An asynchronous TCP socket for coroutine I/O.
44  
/** An asynchronous TCP socket for coroutine I/O.
44  

45  

45  
    This class provides asynchronous TCP socket operations that return
46  
    This class provides asynchronous TCP socket operations that return
46  
    awaitable types. Each operation participates in the affine awaitable
47  
    awaitable types. Each operation participates in the affine awaitable
47  
    protocol, ensuring coroutines resume on the correct executor.
48  
    protocol, ensuring coroutines resume on the correct executor.
48  

49  

49  
    The socket must be opened before performing I/O operations. Operations
50  
    The socket must be opened before performing I/O operations. Operations
50  
    support cancellation through `std::stop_token` via the affine protocol,
51  
    support cancellation through `std::stop_token` via the affine protocol,
51  
    or explicitly through the `cancel()` member function.
52  
    or explicitly through the `cancel()` member function.
52  

53  

53  
    @par Thread Safety
54  
    @par Thread Safety
54  
    Distinct objects: Safe.@n
55  
    Distinct objects: Safe.@n
55  
    Shared objects: Unsafe. A socket must not have concurrent operations
56  
    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  
    of the same type (e.g., two simultaneous reads). One read and one
57  
    write may be in flight simultaneously.
58  
    write may be in flight simultaneously.
58  

59  

59  
    @par Semantics
60  
    @par Semantics
60  
    Wraps the platform TCP/IP stack. Operations dispatch to
61  
    Wraps the platform TCP/IP stack. Operations dispatch to
61  
    OS socket APIs via the io_context reactor (epoll, IOCP,
62  
    OS socket APIs via the io_context reactor (epoll, IOCP,
62  
    kqueue). Satisfies @ref capy::Stream.
63  
    kqueue). Satisfies @ref capy::Stream.
63  

64  

64  
    @par Example
65  
    @par Example
65  
    @code
66  
    @code
66  
    io_context ioc;
67  
    io_context ioc;
67  
    tcp_socket s(ioc);
68  
    tcp_socket s(ioc);
68  
    s.open();
69  
    s.open();
69  

70  

70  
    // Using structured bindings
71  
    // Using structured bindings
71  
    auto [ec] = co_await s.connect(
72  
    auto [ec] = co_await s.connect(
72  
        endpoint(ipv4_address::loopback(), 8080));
73  
        endpoint(ipv4_address::loopback(), 8080));
73  
    if (ec)
74  
    if (ec)
74  
        co_return;
75  
        co_return;
75  

76  

76  
    char buf[1024];
77  
    char buf[1024];
77  
    auto [read_ec, n] = co_await s.read_some(
78  
    auto [read_ec, n] = co_await s.read_some(
78  
        capy::mutable_buffer(buf, sizeof(buf)));
79  
        capy::mutable_buffer(buf, sizeof(buf)));
79  
    @endcode
80  
    @endcode
80  
*/
81  
*/
81  
class BOOST_COROSIO_DECL tcp_socket : public io_stream
82  
class BOOST_COROSIO_DECL tcp_socket : public io_stream
82  
{
83  
{
83  
public:
84  
public:
84  
    /** Different ways a socket may be shutdown. */
85  
    /** Different ways a socket may be shutdown. */
85  
    enum shutdown_type
86  
    enum shutdown_type
86  
    {
87  
    {
87  
        shutdown_receive,
88  
        shutdown_receive,
88  
        shutdown_send,
89  
        shutdown_send,
89  
        shutdown_both
90  
        shutdown_both
90  
    };
91  
    };
91 -
    /** Options for SO_LINGER socket option. */
 
92 -
    struct linger_options
 
93 -
    {
 
94 -
        bool enabled = false;
 
95 -
        int timeout  = 0; // seconds
 
96 -
    };
 
97 -

 
98  

92  

99  
    struct implementation : io_stream::implementation
93  
    struct implementation : io_stream::implementation
100  
    {
94  
    {
101  
        virtual std::coroutine_handle<> connect(
95  
        virtual std::coroutine_handle<> connect(
102  
            std::coroutine_handle<>,
96  
            std::coroutine_handle<>,
103  
            capy::executor_ref,
97  
            capy::executor_ref,
104  
            endpoint,
98  
            endpoint,
105  
            std::stop_token,
99  
            std::stop_token,
106  
            std::error_code*) = 0;
100  
            std::error_code*) = 0;
107  

101  

108  
        virtual std::error_code shutdown(shutdown_type) noexcept = 0;
102  
        virtual std::error_code shutdown(shutdown_type) noexcept = 0;
109  

103  

110  
        virtual native_handle_type native_handle() const noexcept = 0;
104  
        virtual native_handle_type native_handle() const noexcept = 0;
111  

105  

112  
        /** Request cancellation of pending asynchronous operations.
106  
        /** Request cancellation of pending asynchronous operations.
113  

107  

114  
            All outstanding operations complete with operation_canceled error.
108  
            All outstanding operations complete with operation_canceled error.
115  
            Check `ec == cond::canceled` for portable comparison.
109  
            Check `ec == cond::canceled` for portable comparison.
116  
        */
110  
        */
117  
        virtual void cancel() noexcept = 0;
111  
        virtual void cancel() noexcept = 0;
118  

112  

119 -
        // Socket options
113 +
        /** Set a socket option.
120 -
        virtual std::error_code set_no_delay(bool value) noexcept = 0;
 
121 -
        virtual bool no_delay(std::error_code& ec) const noexcept = 0;
 
122 -

 
123 -
        virtual std::error_code set_keep_alive(bool value) noexcept = 0;
 
124 -
        virtual bool keep_alive(std::error_code& ec) const noexcept = 0;
 
125  

114  

126 -
        virtual std::error_code set_receive_buffer_size(int size) noexcept  = 0;
115 +
            @param level The protocol level (e.g. `SOL_SOCKET`).
127 -
        virtual int receive_buffer_size(std::error_code& ec) const noexcept = 0;
116 +
            @param optname The option name (e.g. `SO_KEEPALIVE`).
 
117 +
            @param data Pointer to the option value.
 
118 +
            @param size Size of the option value in bytes.
 
119 +
            @return Error code on failure, empty on success.
 
120 +
        */
 
121 +
        virtual std::error_code set_option(
 
122 +
            int level, int optname,
 
123 +
            void const* data, std::size_t size) noexcept = 0;
128  

124  

129 -
        virtual std::error_code set_send_buffer_size(int size) noexcept  = 0;
125 +
        /** Get a socket option.
130 -
        virtual int send_buffer_size(std::error_code& ec) const noexcept = 0;
 
131  

126  

132 -
        virtual std::error_code
127 +
            @param level The protocol level (e.g. `SOL_SOCKET`).
133 -
        set_linger(bool enabled, int timeout) noexcept                    = 0;
128 +
            @param optname The option name (e.g. `SO_KEEPALIVE`).
134 -
        virtual linger_options linger(std::error_code& ec) const noexcept = 0;
129 +
            @param data Pointer to receive the option value.
 
130 +
            @param size On entry, the size of the buffer. On exit,
 
131 +
                the size of the option value.
 
132 +
            @return Error code on failure, empty on success.
 
133 +
        */
 
134 +
        virtual std::error_code get_option(
 
135 +
            int level, int optname,
 
136 +
            void* data, std::size_t* size) const noexcept = 0;
135  

137  

136  
        /// Returns the cached local endpoint.
138  
        /// Returns the cached local endpoint.
137  
        virtual endpoint local_endpoint() const noexcept = 0;
139  
        virtual endpoint local_endpoint() const noexcept = 0;
138  

140  

139  
        /// Returns the cached remote endpoint.
141  
        /// Returns the cached remote endpoint.
140  
        virtual endpoint remote_endpoint() const noexcept = 0;
142  
        virtual endpoint remote_endpoint() const noexcept = 0;
141  
    };
143  
    };
142  

144  

143  
    struct connect_awaitable
145  
    struct connect_awaitable
144  
    {
146  
    {
145  
        tcp_socket& s_;
147  
        tcp_socket& s_;
146  
        endpoint endpoint_;
148  
        endpoint endpoint_;
147  
        std::stop_token token_;
149  
        std::stop_token token_;
148  
        mutable std::error_code ec_;
150  
        mutable std::error_code ec_;
149  

151  

150  
        connect_awaitable(tcp_socket& s, endpoint ep) noexcept
152  
        connect_awaitable(tcp_socket& s, endpoint ep) noexcept
151  
            : s_(s)
153  
            : s_(s)
152  
            , endpoint_(ep)
154  
            , endpoint_(ep)
153  
        {
155  
        {
154  
        }
156  
        }
155  

157  

156  
        bool await_ready() const noexcept
158  
        bool await_ready() const noexcept
157  
        {
159  
        {
158  
            return token_.stop_requested();
160  
            return token_.stop_requested();
159  
        }
161  
        }
160  

162  

161  
        capy::io_result<> await_resume() const noexcept
163  
        capy::io_result<> await_resume() const noexcept
162  
        {
164  
        {
163  
            if (token_.stop_requested())
165  
            if (token_.stop_requested())
164  
                return {make_error_code(std::errc::operation_canceled)};
166  
                return {make_error_code(std::errc::operation_canceled)};
165  
            return {ec_};
167  
            return {ec_};
166  
        }
168  
        }
167  

169  

168  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
170  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
169  
            -> std::coroutine_handle<>
171  
            -> std::coroutine_handle<>
170  
        {
172  
        {
171  
            token_ = env->stop_token;
173  
            token_ = env->stop_token;
172  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
174  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
173  
        }
175  
        }
174  
    };
176  
    };
175  

177  

176  
public:
178  
public:
177  
    /** Destructor.
179  
    /** Destructor.
178  

180  

179  
        Closes the socket if open, cancelling any pending operations.
181  
        Closes the socket if open, cancelling any pending operations.
180  
    */
182  
    */
181  
    ~tcp_socket() override;
183  
    ~tcp_socket() override;
182  

184  

183  
    /** Construct a socket from an execution context.
185  
    /** Construct a socket from an execution context.
184  

186  

185  
        @param ctx The execution context that will own this socket.
187  
        @param ctx The execution context that will own this socket.
186  
    */
188  
    */
187  
    explicit tcp_socket(capy::execution_context& ctx);
189  
    explicit tcp_socket(capy::execution_context& ctx);
188  

190  

189  
    /** Construct a socket from an executor.
191  
    /** Construct a socket from an executor.
190  

192  

191  
        The socket is associated with the executor's context.
193  
        The socket is associated with the executor's context.
192  

194  

193  
        @param ex The executor whose context will own the socket.
195  
        @param ex The executor whose context will own the socket.
194  
    */
196  
    */
195  
    template<class Ex>
197  
    template<class Ex>
196  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
198  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
197  
        capy::Executor<Ex>
199  
        capy::Executor<Ex>
198  
    explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
200  
    explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
199  
    {
201  
    {
200  
    }
202  
    }
201  

203  

202  
    /** Move constructor.
204  
    /** Move constructor.
203  

205  

204  
        Transfers ownership of the socket resources.
206  
        Transfers ownership of the socket resources.
205  

207  

206  
        @param other The socket to move from.
208  
        @param other The socket to move from.
207  

209  

208  
        @pre No awaitables returned by @p other's methods exist.
210  
        @pre No awaitables returned by @p other's methods exist.
209  
        @pre @p other is not referenced as a peer in any outstanding
211  
        @pre @p other is not referenced as a peer in any outstanding
210  
            accept awaitable.
212  
            accept awaitable.
211  
        @pre The execution context associated with @p other must
213  
        @pre The execution context associated with @p other must
212  
            outlive this socket.
214  
            outlive this socket.
213  
    */
215  
    */
214  
    tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
216  
    tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
215  

217  

216  
    /** Move assignment operator.
218  
    /** Move assignment operator.
217  

219  

218  
        Closes any existing socket and transfers ownership.
220  
        Closes any existing socket and transfers ownership.
219  

221  

220  
        @param other The socket to move from.
222  
        @param other The socket to move from.
221  

223  

222  
        @pre No awaitables returned by either `*this` or @p other's
224  
        @pre No awaitables returned by either `*this` or @p other's
223  
            methods exist.
225  
            methods exist.
224  
        @pre Neither `*this` nor @p other is referenced as a peer in
226  
        @pre Neither `*this` nor @p other is referenced as a peer in
225  
            any outstanding accept awaitable.
227  
            any outstanding accept awaitable.
226  
        @pre The execution context associated with @p other must
228  
        @pre The execution context associated with @p other must
227  
            outlive this socket.
229  
            outlive this socket.
228  

230  

229  
        @return Reference to this socket.
231  
        @return Reference to this socket.
230  
    */
232  
    */
231  
    tcp_socket& operator=(tcp_socket&& other) noexcept
233  
    tcp_socket& operator=(tcp_socket&& other) noexcept
232  
    {
234  
    {
233  
        if (this != &other)
235  
        if (this != &other)
234  
        {
236  
        {
235  
            close();
237  
            close();
236  
            h_ = std::move(other.h_);
238  
            h_ = std::move(other.h_);
237  
        }
239  
        }
238  
        return *this;
240  
        return *this;
239  
    }
241  
    }
240  

242  

241  
    tcp_socket(tcp_socket const&)            = delete;
243  
    tcp_socket(tcp_socket const&)            = delete;
242  
    tcp_socket& operator=(tcp_socket const&) = delete;
244  
    tcp_socket& operator=(tcp_socket const&) = delete;
243  

245  

244  
    /** Open the socket.
246  
    /** Open the socket.
245  

247  

246 -
        Creates an IPv4 TCP socket and associates it with the platform
248 +
        Creates a TCP socket and associates it with the platform
247 -
        reactor (IOCP on Windows). This must be called before initiating
249 +
        reactor (IOCP on Windows). Calling @ref connect on a closed
248 -
        I/O operations.
250 +
        socket opens it automatically with the endpoint's address family,
 
251 +
        so explicit `open()` is only needed when socket options must be
 
252 +
        set before connecting.
 
253 +

 
254 +
        @param proto The protocol (IPv4 or IPv6). Defaults to
 
255 +
            `tcp::v4()`.
249  

256  

250  
        @throws std::system_error on failure.
257  
        @throws std::system_error on failure.
251  
    */
258  
    */
252 -
    void open();
259 +
    void open( tcp proto = tcp::v4() );
253  

260  

254  
    /** Close the socket.
261  
    /** Close the socket.
255  

262  

256  
        Releases socket resources. Any pending operations complete
263  
        Releases socket resources. Any pending operations complete
257  
        with `errc::operation_canceled`.
264  
        with `errc::operation_canceled`.
258  
    */
265  
    */
259  
    void close();
266  
    void close();
260  

267  

261  
    /** Check if the socket is open.
268  
    /** Check if the socket is open.
262  

269  

263  
        @return `true` if the socket is open and ready for operations.
270  
        @return `true` if the socket is open and ready for operations.
264  
    */
271  
    */
265  
    bool is_open() const noexcept
272  
    bool is_open() const noexcept
266  
    {
273  
    {
267  
#if BOOST_COROSIO_HAS_IOCP
274  
#if BOOST_COROSIO_HAS_IOCP
268  
        return h_ && get().native_handle() != ~native_handle_type(0);
275  
        return h_ && get().native_handle() != ~native_handle_type(0);
269  
#else
276  
#else
270  
        return h_ && get().native_handle() >= 0;
277  
        return h_ && get().native_handle() >= 0;
271  
#endif
278  
#endif
272  
    }
279  
    }
273  

280  

274  
    /** Initiate an asynchronous connect operation.
281  
    /** Initiate an asynchronous connect operation.
275  

282  

276 -
        Connects the socket to the specified remote endpoint. The socket
283 +
        If the socket is not already open, it is opened automatically
277 -
        must be open before calling this function.
284 +
        using the address family of @p ep (IPv4 or IPv6). If the socket
 
285 +
        is already open, the existing file descriptor is used as-is.
278  

286  

279  
        The operation supports cancellation via `std::stop_token` through
287  
        The operation supports cancellation via `std::stop_token` through
280  
        the affine awaitable protocol. If the associated stop token is
288  
        the affine awaitable protocol. If the associated stop token is
281  
        triggered, the operation completes immediately with
289  
        triggered, the operation completes immediately with
282  
        `errc::operation_canceled`.
290  
        `errc::operation_canceled`.
283  

291  

284  
        @param ep The remote endpoint to connect to.
292  
        @param ep The remote endpoint to connect to.
285  

293  

286  
        @return An awaitable that completes with `io_result<>`.
294  
        @return An awaitable that completes with `io_result<>`.
287  
            Returns success (default error_code) on successful connection,
295  
            Returns success (default error_code) on successful connection,
288  
            or an error code on failure including:
296  
            or an error code on failure including:
289  
            - connection_refused: No server listening at endpoint
297  
            - connection_refused: No server listening at endpoint
290  
            - timed_out: Connection attempt timed out
298  
            - timed_out: Connection attempt timed out
291  
            - network_unreachable: No route to host
299  
            - network_unreachable: No route to host
292  
            - operation_canceled: Cancelled via stop_token or cancel().
300  
            - operation_canceled: Cancelled via stop_token or cancel().
293  
                Check `ec == cond::canceled` for portable comparison.
301  
                Check `ec == cond::canceled` for portable comparison.
294  

302  

295 -
        @throws std::logic_error if the socket is not open.
303 +
        @throws std::system_error if the socket needs to be opened
 
304 +
            and the open fails.
296  

305  

297 -
        The socket must be open (`is_open() == true`).
 
298 -

 
299  
        @par Preconditions
306  
        @par Preconditions
300  
        This socket must outlive the returned awaitable.
307  
        This socket must outlive the returned awaitable.
301  

308  

302  
        @par Example
309  
        @par Example
303  
        @code
310  
        @code
 
311 +
        // Socket opened automatically with correct address family:
304  
        auto [ec] = co_await s.connect(endpoint);
312  
        auto [ec] = co_await s.connect(endpoint);
305  
        if (ec) { ... }
313  
        if (ec) { ... }
306  
        @endcode
314  
        @endcode
307  
    */
315  
    */
308  
    auto connect(endpoint ep)
316  
    auto connect(endpoint ep)
309  
    {
317  
    {
310  
        if (!is_open())
318  
        if (!is_open())
311 -
            detail::throw_logic_error("connect: socket not open");
319 +
            open(ep.is_v6() ? tcp::v6() : tcp::v4());
312  
        return connect_awaitable(*this, ep);
320  
        return connect_awaitable(*this, ep);
313  
    }
321  
    }
314  

322  

315  
    /** Cancel any pending asynchronous operations.
323  
    /** Cancel any pending asynchronous operations.
316  

324  

317  
        All outstanding operations complete with `errc::operation_canceled`.
325  
        All outstanding operations complete with `errc::operation_canceled`.
318  
        Check `ec == cond::canceled` for portable comparison.
326  
        Check `ec == cond::canceled` for portable comparison.
319  
    */
327  
    */
320  
    void cancel();
328  
    void cancel();
321  

329  

322  
    /** Get the native socket handle.
330  
    /** Get the native socket handle.
323  

331  

324  
        Returns the underlying platform-specific socket descriptor.
332  
        Returns the underlying platform-specific socket descriptor.
325  
        On POSIX systems this is an `int` file descriptor.
333  
        On POSIX systems this is an `int` file descriptor.
326  
        On Windows this is a `SOCKET` handle.
334  
        On Windows this is a `SOCKET` handle.
327  

335  

328  
        @return The native socket handle, or -1/INVALID_SOCKET if not open.
336  
        @return The native socket handle, or -1/INVALID_SOCKET if not open.
329  

337  

330  
        @par Preconditions
338  
        @par Preconditions
331  
        None. May be called on closed sockets.
339  
        None. May be called on closed sockets.
332  
    */
340  
    */
333  
    native_handle_type native_handle() const noexcept;
341  
    native_handle_type native_handle() const noexcept;
334  

342  

335  
    /** Disable sends or receives on the socket.
343  
    /** Disable sends or receives on the socket.
336  

344  

337  
        TCP connections are full-duplex: each direction (send and receive)
345  
        TCP connections are full-duplex: each direction (send and receive)
338  
        operates independently. This function allows you to close one or
346  
        operates independently. This function allows you to close one or
339  
        both directions without destroying the socket.
347  
        both directions without destroying the socket.
340  

348  

341  
        @li @ref shutdown_send sends a TCP FIN packet to the peer,
349  
        @li @ref shutdown_send sends a TCP FIN packet to the peer,
342  
            signaling that you have no more data to send. You can still
350  
            signaling that you have no more data to send. You can still
343  
            receive data until the peer also closes their send direction.
351  
            receive data until the peer also closes their send direction.
344  
            This is the most common use case, typically called before
352  
            This is the most common use case, typically called before
345  
            close() to ensure graceful connection termination.
353  
            close() to ensure graceful connection termination.
346  

354  

347  
        @li @ref shutdown_receive disables reading on the socket. This
355  
        @li @ref shutdown_receive disables reading on the socket. This
348  
            does NOT send anything to the peer - they are not informed
356  
            does NOT send anything to the peer - they are not informed
349  
            and may continue sending data. Subsequent reads will fail
357  
            and may continue sending data. Subsequent reads will fail
350  
            or return end-of-file. Incoming data may be discarded or
358  
            or return end-of-file. Incoming data may be discarded or
351  
            buffered depending on the operating system.
359  
            buffered depending on the operating system.
352  

360  

353  
        @li @ref shutdown_both combines both effects: sends a FIN and
361  
        @li @ref shutdown_both combines both effects: sends a FIN and
354  
            disables reading.
362  
            disables reading.
355  

363  

356  
        When the peer shuts down their send direction (sends a FIN),
364  
        When the peer shuts down their send direction (sends a FIN),
357  
        subsequent read operations will complete with `capy::cond::eof`.
365  
        subsequent read operations will complete with `capy::cond::eof`.
358  
        Use the portable condition test rather than comparing error
366  
        Use the portable condition test rather than comparing error
359  
        codes directly:
367  
        codes directly:
360  

368  

361  
        @code
369  
        @code
362  
        auto [ec, n] = co_await sock.read_some(buffer);
370  
        auto [ec, n] = co_await sock.read_some(buffer);
363  
        if (ec == capy::cond::eof)
371  
        if (ec == capy::cond::eof)
364  
        {
372  
        {
365  
            // Peer closed their send direction
373  
            // Peer closed their send direction
366  
        }
374  
        }
367  
        @endcode
375  
        @endcode
368  

376  

369  
        Any error from the underlying system call is silently discarded
377  
        Any error from the underlying system call is silently discarded
370  
        because it is unlikely to be helpful.
378  
        because it is unlikely to be helpful.
371  

379  

372  
        @param what Determines what operations will no longer be allowed.
380  
        @param what Determines what operations will no longer be allowed.
373  
    */
381  
    */
374  
    void shutdown(shutdown_type what);
382  
    void shutdown(shutdown_type what);
375  

383  

376 -
    //
384 +
    /** Set a socket option.
377 -
    // Socket Options
 
378 -
    //
 
379 -

 
380 -
    /** Enable or disable TCP_NODELAY (disable Nagle's algorithm).
 
381 -

 
382 -
        When enabled, segments are sent as soon as possible even if
 
383 -
        there is only a small amount of data. This reduces latency
 
384 -
        at the potential cost of increased network traffic.
 
385 -

 
386 -
        @param value `true` to disable Nagle's algorithm (enable no-delay).
 
387 -

 
388 -
        @throws std::logic_error if the socket is not open.
 
389 -
        @throws std::system_error on failure.
 
390 -
    */
 
391 -
    void set_no_delay(bool value);
 
392 -

 
393 -
    /** Get the current TCP_NODELAY setting.
 
394 -

 
395 -
        @return `true` if Nagle's algorithm is disabled.
 
396 -

 
397 -
        @throws std::logic_error if the socket is not open.
 
398 -
        @throws std::system_error on failure.
 
399 -
    */
 
400 -
    bool no_delay() const;
 
401 -

 
402 -
    /** Enable or disable SO_KEEPALIVE.
 
403 -

 
404 -
        When enabled, the socket will periodically send keepalive probes
 
405 -
        to detect if the peer is still reachable.
 
406 -

 
407 -
        @param value `true` to enable keepalive probes.
 
408 -

 
409 -
        @throws std::logic_error if the socket is not open.
 
410 -
        @throws std::system_error on failure.
 
411 -
    */
 
412 -
    void set_keep_alive(bool value);
 
413 -

 
414 -
    /** Get the current SO_KEEPALIVE setting.
 
415 -

 
416 -
        @return `true` if keepalive is enabled.
 
417 -

 
418 -
        @throws std::logic_error if the socket is not open.
 
419 -
        @throws std::system_error on failure.
 
420 -
    */
 
421 -
    bool keep_alive() const;
 
422 -

 
423 -
    /** Set the receive buffer size (SO_RCVBUF).
 
424 -

 
425 -
        @param size The desired receive buffer size in bytes.
 
426 -

 
427 -
        @throws std::logic_error if the socket is not open.
 
428 -
        @throws std::system_error on failure.
 
429 -

 
430 -
        @note The operating system may adjust the actual buffer size.
 
431 -
    */
 
432 -
    void set_receive_buffer_size(int size);
 
433 -

 
434 -
    /** Get the receive buffer size (SO_RCVBUF).
 
435 -

 
436 -
        @return The current receive buffer size in bytes.
 
437 -

 
438 -
        @throws std::logic_error if the socket is not open.
 
439 -
        @throws std::system_error on failure.
 
440 -
    */
 
441 -
    int receive_buffer_size() const;
 
442 -

 
443 -
    /** Set the send buffer size (SO_SNDBUF).
 
444 -

 
445 -
        @param size The desired send buffer size in bytes.
 
446 -

 
447 -
        @throws std::logic_error if the socket is not open.
 
448 -
        @throws std::system_error on failure.
 
449  

385  

450 -
        @note The operating system may adjust the actual buffer size.
386 +
        Applies a type-safe socket option to the underlying socket.
451 -
    */
387 +
        The option type encodes the protocol level and option name.
452 -
    void set_send_buffer_size(int size);
 
453  

388  

454 -
    /** Get the send buffer size (SO_SNDBUF).
389 +
        @par Example
 
390 +
        @code
 
391 +
        sock.set_option( socket_option::no_delay( true ) );
 
392 +
        sock.set_option( socket_option::receive_buffer_size( 65536 ) );
 
393 +
        @endcode
455  

394  

456 -
        @return The current send buffer size in bytes.
395 +
        @param opt The option to set.
457  

396  

458  
        @throws std::logic_error if the socket is not open.
397  
        @throws std::logic_error if the socket is not open.
459  
        @throws std::system_error on failure.
398  
        @throws std::system_error on failure.
460  
    */
399  
    */
461 -
    int send_buffer_size() const;
400 +
    template<class Option>
462 -

401 +
    void set_option( Option const& opt )
463 -
    /** Set the SO_LINGER option.
402 +
    {
464 -

403 +
        if (!is_open())
465 -
        Controls behavior when closing a socket with unsent data.
404 +
            detail::throw_logic_error( "set_option: socket not open" );
 
405 +
        std::error_code ec = get().set_option(
 
406 +
            Option::level(), Option::name(), opt.data(), opt.size() );
 
407 +
        if (ec)
 
408 +
            detail::throw_system_error( ec, "tcp_socket::set_option" );
 
409 +
    }
466  

410  

467 -
        @param enabled If `true`, close() will block until data is sent
411 +
    /** Get a socket option.
468 -
            or the timeout expires. If `false`, close() returns immediately.
 
469 -
        @param timeout The linger timeout in seconds (only used if enabled).
 
470  

412  

471 -
        @throws std::logic_error if the socket is not open.
413 +
        Retrieves the current value of a type-safe socket option.
472 -
        @throws std::system_error on failure.
 
473 -
    */
 
474 -
    void set_linger(bool enabled, int timeout);
 
475  

414  

476 -
    /** Get the current SO_LINGER setting.
415 +
        @par Example
 
416 +
        @code
 
417 +
        auto nd = sock.get_option<socket_option::no_delay>();
 
418 +
        if ( nd.value() )
 
419 +
            // Nagle's algorithm is disabled
 
420 +
        @endcode
477  

421  

478 -
        @return The current linger options.
422 +
        @return The current option value.
479  

423  

480  
        @throws std::logic_error if the socket is not open.
424  
        @throws std::logic_error if the socket is not open.
481  
        @throws std::system_error on failure.
425  
        @throws std::system_error on failure.
482  
    */
426  
    */
483 -
    linger_options linger() const;
427 +
    template<class Option>
 
428 +
    Option get_option() const
 
429 +
    {
 
430 +
        if (!is_open())
 
431 +
            detail::throw_logic_error( "get_option: socket not open" );
 
432 +
        Option opt{};
 
433 +
        std::size_t sz = opt.size();
 
434 +
        std::error_code ec = get().get_option(
 
435 +
            Option::level(), Option::name(), opt.data(), &sz );
 
436 +
        if (ec)
 
437 +
            detail::throw_system_error( ec, "tcp_socket::get_option" );
 
438 +
        opt.resize( sz );
 
439 +
        return opt;
 
440 +
    }
484  

441  

485  
    /** Get the local endpoint of the socket.
442  
    /** Get the local endpoint of the socket.
486  

443  

487  
        Returns the local address and port to which the socket is bound.
444  
        Returns the local address and port to which the socket is bound.
488  
        For a connected socket, this is the local side of the connection.
445  
        For a connected socket, this is the local side of the connection.
489  
        The endpoint is cached when the connection is established.
446  
        The endpoint is cached when the connection is established.
490  

447  

491  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
448  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
492  
            the socket is not connected.
449  
            the socket is not connected.
493  

450  

494  
        @par Thread Safety
451  
        @par Thread Safety
495  
        The cached endpoint value is set during connect/accept completion
452  
        The cached endpoint value is set during connect/accept completion
496  
        and cleared during close(). This function may be called concurrently
453  
        and cleared during close(). This function may be called concurrently
497  
        with I/O operations, but must not be called concurrently with
454  
        with I/O operations, but must not be called concurrently with
498  
        connect(), accept(), or close().
455  
        connect(), accept(), or close().
499  
    */
456  
    */
500  
    endpoint local_endpoint() const noexcept;
457  
    endpoint local_endpoint() const noexcept;
501  

458  

502  
    /** Get the remote endpoint of the socket.
459  
    /** Get the remote endpoint of the socket.
503  

460  

504  
        Returns the remote address and port to which the socket is connected.
461  
        Returns the remote address and port to which the socket is connected.
505  
        The endpoint is cached when the connection is established.
462  
        The endpoint is cached when the connection is established.
506  

463  

507  
        @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
464  
        @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
508  
            the socket is not connected.
465  
            the socket is not connected.
509  

466  

510  
        @par Thread Safety
467  
        @par Thread Safety
511  
        The cached endpoint value is set during connect/accept completion
468  
        The cached endpoint value is set during connect/accept completion
512  
        and cleared during close(). This function may be called concurrently
469  
        and cleared during close(). This function may be called concurrently
513  
        with I/O operations, but must not be called concurrently with
470  
        with I/O operations, but must not be called concurrently with
514  
        connect(), accept(), or close().
471  
        connect(), accept(), or close().
515  
    */
472  
    */
516  
    endpoint remote_endpoint() const noexcept;
473  
    endpoint remote_endpoint() const noexcept;
517  

474  

518  
protected:
475  
protected:
519  
    tcp_socket() noexcept = default;
476  
    tcp_socket() noexcept = default;
520  

477  

521  
    explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
478  
    explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
522  

479  

523  
private:
480  
private:
524  
    friend class tcp_acceptor;
481  
    friend class tcp_acceptor;
 
482 +

 
483 +
    /// Open the socket for the given protocol triple.
 
484 +
    void open_for_family(int family, int type, int protocol);
525  

485  

526  
    inline implementation& get() const noexcept
486  
    inline implementation& get() const noexcept
527  
    {
487  
    {
528  
        return *static_cast<implementation*>(h_.get());
488  
        return *static_cast<implementation*>(h_.get());
529  
    }
489  
    }
530  
};
490  
};
531  

491  

532  
} // namespace boost::corosio
492  
} // namespace boost::corosio
533  

493  

534  
#endif
494  
#endif