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_ACCEPTOR_HPP
11  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
13  

13  

14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/detail/except.hpp>
15  
#include <boost/corosio/detail/except.hpp>
16  
#include <boost/corosio/io/io_object.hpp>
16  
#include <boost/corosio/io/io_object.hpp>
17  
#include <boost/capy/io_result.hpp>
17  
#include <boost/capy/io_result.hpp>
18  
#include <boost/corosio/endpoint.hpp>
18  
#include <boost/corosio/endpoint.hpp>
19  
#include <boost/corosio/tcp_socket.hpp>
19  
#include <boost/corosio/tcp_socket.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
21  
#include <boost/capy/ex/execution_context.hpp>
21  
#include <boost/capy/ex/execution_context.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
23  
#include <boost/capy/concept/executor.hpp>
23  
#include <boost/capy/concept/executor.hpp>
24  

24  

25  
#include <system_error>
25  
#include <system_error>
26  

26  

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

33  

34  
namespace boost::corosio {
34  
namespace boost::corosio {
35  

35  

36  
/** An asynchronous TCP acceptor for coroutine I/O.
36  
/** An asynchronous TCP acceptor for coroutine I/O.
37  

37  

38  
    This class provides asynchronous TCP accept operations that return
38  
    This class provides asynchronous TCP accept operations that return
39  
    awaitable types. The acceptor binds to a local endpoint and listens
39  
    awaitable types. The acceptor binds to a local endpoint and listens
40  
    for incoming connections.
40  
    for incoming connections.
41  

41  

42  
    Each accept operation participates in the affine awaitable protocol,
42  
    Each accept operation participates in the affine awaitable protocol,
43  
    ensuring coroutines resume on the correct executor.
43  
    ensuring coroutines resume on the correct executor.
44  

44  

45  
    @par Thread Safety
45  
    @par Thread Safety
46  
    Distinct objects: Safe.@n
46  
    Distinct objects: Safe.@n
47  
    Shared objects: Unsafe. An acceptor must not have concurrent accept
47  
    Shared objects: Unsafe. An acceptor must not have concurrent accept
48  
    operations.
48  
    operations.
49  

49  

50  
    @par Semantics
50  
    @par Semantics
51  
    Wraps the platform TCP listener. Operations dispatch to
51  
    Wraps the platform TCP listener. Operations dispatch to
52  
    OS accept APIs via the io_context reactor.
52  
    OS accept APIs via the io_context reactor.
53  

53  

54  
    @par Example
54  
    @par Example
55  
    @code
55  
    @code
56  
    io_context ioc;
56  
    io_context ioc;
57  
    tcp_acceptor acc(ioc);
57  
    tcp_acceptor acc(ioc);
58  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
58  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
59  
        return ec;
59  
        return ec;
60  

60  

61  
    tcp_socket peer(ioc);
61  
    tcp_socket peer(ioc);
62  
    auto [ec] = co_await acc.accept(peer);
62  
    auto [ec] = co_await acc.accept(peer);
63  
    if (!ec) {
63  
    if (!ec) {
64  
        // peer is now a connected socket
64  
        // peer is now a connected socket
65  
        auto [ec2, n] = co_await peer.read_some(buf);
65  
        auto [ec2, n] = co_await peer.read_some(buf);
66  
    }
66  
    }
67  
    @endcode
67  
    @endcode
68  
*/
68  
*/
69  
class BOOST_COROSIO_DECL tcp_acceptor : public io_object
69  
class BOOST_COROSIO_DECL tcp_acceptor : public io_object
70  
{
70  
{
71  
    struct accept_awaitable
71  
    struct accept_awaitable
72  
    {
72  
    {
73  
        tcp_acceptor& acc_;
73  
        tcp_acceptor& acc_;
74  
        tcp_socket& peer_;
74  
        tcp_socket& peer_;
75  
        std::stop_token token_;
75  
        std::stop_token token_;
76  
        mutable std::error_code ec_;
76  
        mutable std::error_code ec_;
77  
        mutable io_object::implementation* peer_impl_ = nullptr;
77  
        mutable io_object::implementation* peer_impl_ = nullptr;
78  

78  

79  
        accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
79  
        accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
80  
            : acc_(acc)
80  
            : acc_(acc)
81  
            , peer_(peer)
81  
            , peer_(peer)
82  
        {
82  
        {
83  
        }
83  
        }
84  

84  

85  
        bool await_ready() const noexcept
85  
        bool await_ready() const noexcept
86  
        {
86  
        {
87  
            return token_.stop_requested();
87  
            return token_.stop_requested();
88  
        }
88  
        }
89  

89  

90  
        capy::io_result<> await_resume() const noexcept
90  
        capy::io_result<> await_resume() const noexcept
91  
        {
91  
        {
92  
            if (token_.stop_requested())
92  
            if (token_.stop_requested())
93  
                return {make_error_code(std::errc::operation_canceled)};
93  
                return {make_error_code(std::errc::operation_canceled)};
94  

94  

95  
            if (!ec_ && peer_impl_)
95  
            if (!ec_ && peer_impl_)
96  
                peer_.h_.reset(peer_impl_);
96  
                peer_.h_.reset(peer_impl_);
97  
            return {ec_};
97  
            return {ec_};
98  
        }
98  
        }
99  

99  

100  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
100  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
101  
            -> std::coroutine_handle<>
101  
            -> std::coroutine_handle<>
102  
        {
102  
        {
103  
            token_ = env->stop_token;
103  
            token_ = env->stop_token;
104  
            return acc_.get().accept(
104  
            return acc_.get().accept(
105  
                h, env->executor, token_, &ec_, &peer_impl_);
105  
                h, env->executor, token_, &ec_, &peer_impl_);
106  
        }
106  
        }
107  
    };
107  
    };
108  

108  

109  
public:
109  
public:
110  
    /** Destructor.
110  
    /** Destructor.
111  

111  

112  
        Closes the acceptor if open, cancelling any pending operations.
112  
        Closes the acceptor if open, cancelling any pending operations.
113  
    */
113  
    */
114  
    ~tcp_acceptor() override;
114  
    ~tcp_acceptor() override;
115  

115  

116  
    /** Construct an acceptor from an execution context.
116  
    /** Construct an acceptor from an execution context.
117  

117  

118  
        @param ctx The execution context that will own this acceptor.
118  
        @param ctx The execution context that will own this acceptor.
119  
    */
119  
    */
120  
    explicit tcp_acceptor(capy::execution_context& ctx);
120  
    explicit tcp_acceptor(capy::execution_context& ctx);
121  

121  

122  
    /** Construct an acceptor from an executor.
122  
    /** Construct an acceptor from an executor.
123  

123  

124  
        The acceptor is associated with the executor's context.
124  
        The acceptor is associated with the executor's context.
125  

125  

126  
        @param ex The executor whose context will own the acceptor.
126  
        @param ex The executor whose context will own the acceptor.
127  
    */
127  
    */
128  
    template<class Ex>
128  
    template<class Ex>
129  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
129  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
130  
        capy::Executor<Ex>
130  
        capy::Executor<Ex>
131  
    explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
131  
    explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
132  
    {
132  
    {
133  
    }
133  
    }
134  

134  

135  
    /** Move constructor.
135  
    /** Move constructor.
136  

136  

137  
        Transfers ownership of the acceptor resources.
137  
        Transfers ownership of the acceptor resources.
138  

138  

139  
        @param other The acceptor to move from.
139  
        @param other The acceptor to move from.
140  
    */
140  
    */
141  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
141  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
142  

142  

143  
    /** Move assignment operator.
143  
    /** Move assignment operator.
144  

144  

145  
        Closes any existing acceptor and transfers ownership.
145  
        Closes any existing acceptor and transfers ownership.
146  
        @param other The acceptor to move from.
146  
        @param other The acceptor to move from.
147  

147  

148  
        @return Reference to this acceptor.
148  
        @return Reference to this acceptor.
149  
    */
149  
    */
150  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
150  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
151  
    {
151  
    {
152  
        if (this != &other)
152  
        if (this != &other)
153  
        {
153  
        {
154  
            close();
154  
            close();
155  
            h_ = std::move(other.h_);
155  
            h_ = std::move(other.h_);
156  
        }
156  
        }
157  
        return *this;
157  
        return *this;
158  
    }
158  
    }
159  

159  

160  
    tcp_acceptor(tcp_acceptor const&)            = delete;
160  
    tcp_acceptor(tcp_acceptor const&)            = delete;
161  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
161  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
162  

162  

163  
    /** Open, bind, and listen on an endpoint.
163  
    /** Open, bind, and listen on an endpoint.
164  

164  

165  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
165  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
166  
        and begins listening for incoming connections. This must be
166  
        and begins listening for incoming connections. This must be
167  
        called before initiating accept operations.
167  
        called before initiating accept operations.
168  

168  

169  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
169  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
170  
            bind to all interfaces on a specific port.
170  
            bind to all interfaces on a specific port.
171  

171  

172  
        @param backlog The maximum length of the queue of pending
172  
        @param backlog The maximum length of the queue of pending
173  
            connections. Defaults to 128.
173  
            connections. Defaults to 128.
174  

174  

175  
        @return An error code indicating success or the reason for failure.
175  
        @return An error code indicating success or the reason for failure.
176  
            A default-constructed error code indicates success.
176  
            A default-constructed error code indicates success.
177  

177  

178  
        @par Error Conditions
178  
        @par Error Conditions
179  
        @li `errc::address_in_use`: The endpoint is already in use.
179  
        @li `errc::address_in_use`: The endpoint is already in use.
180  
        @li `errc::address_not_available`: The address is not available
180  
        @li `errc::address_not_available`: The address is not available
181  
            on any local interface.
181  
            on any local interface.
182  
        @li `errc::permission_denied`: Insufficient privileges to bind
182  
        @li `errc::permission_denied`: Insufficient privileges to bind
183  
            to the endpoint (e.g., privileged port).
183  
            to the endpoint (e.g., privileged port).
184  
        @li `errc::operation_not_supported`: The acceptor service is
184  
        @li `errc::operation_not_supported`: The acceptor service is
185  
            unavailable in the context (POSIX only).
185  
            unavailable in the context (POSIX only).
186  

186  

187  
        @throws Nothing.
187  
        @throws Nothing.
188  
    */
188  
    */
189  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
189  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
190  

190  

191  
    /** Close the acceptor.
191  
    /** Close the acceptor.
192  

192  

193  
        Releases acceptor resources. Any pending operations complete
193  
        Releases acceptor resources. Any pending operations complete
194  
        with `errc::operation_canceled`.
194  
        with `errc::operation_canceled`.
195  
    */
195  
    */
196  
    void close();
196  
    void close();
197  

197  

198  
    /** Check if the acceptor is listening.
198  
    /** Check if the acceptor is listening.
199  

199  

200  
        @return `true` if the acceptor is open and listening.
200  
        @return `true` if the acceptor is open and listening.
201  
    */
201  
    */
202  
    bool is_open() const noexcept
202  
    bool is_open() const noexcept
203  
    {
203  
    {
204  
        return h_ && get().is_open();
204  
        return h_ && get().is_open();
205  
    }
205  
    }
206  

206  

207  
    /** Initiate an asynchronous accept operation.
207  
    /** Initiate an asynchronous accept operation.
208  

208  

209  
        Accepts an incoming connection and initializes the provided
209  
        Accepts an incoming connection and initializes the provided
210  
        socket with the new connection. The acceptor must be listening
210  
        socket with the new connection. The acceptor must be listening
211  
        before calling this function.
211  
        before calling this function.
212  

212  

213  
        The operation supports cancellation via `std::stop_token` through
213  
        The operation supports cancellation via `std::stop_token` through
214  
        the affine awaitable protocol. If the associated stop token is
214  
        the affine awaitable protocol. If the associated stop token is
215  
        triggered, the operation completes immediately with
215  
        triggered, the operation completes immediately with
216  
        `errc::operation_canceled`.
216  
        `errc::operation_canceled`.
217  

217  

218  
        @param peer The socket to receive the accepted connection. Any
218  
        @param peer The socket to receive the accepted connection. Any
219  
            existing connection on this socket will be closed.
219  
            existing connection on this socket will be closed.
220  

220  

221  
        @return An awaitable that completes with `io_result<>`.
221  
        @return An awaitable that completes with `io_result<>`.
222  
            Returns success on successful accept, or an error code on
222  
            Returns success on successful accept, or an error code on
223  
            failure including:
223  
            failure including:
224  
            - operation_canceled: Cancelled via stop_token or cancel().
224  
            - operation_canceled: Cancelled via stop_token or cancel().
225  
                Check `ec == cond::canceled` for portable comparison.
225  
                Check `ec == cond::canceled` for portable comparison.
226  

226  

227  
        @par Preconditions
227  
        @par Preconditions
228  
        The acceptor must be listening (`is_open() == true`).
228  
        The acceptor must be listening (`is_open() == true`).
229  
        The peer socket must be associated with the same execution context.
229  
        The peer socket must be associated with the same execution context.
230  

230  

231  
        @par Example
231  
        @par Example
232  
        @code
232  
        @code
233  
        tcp_socket peer(ioc);
233  
        tcp_socket peer(ioc);
234  
        auto [ec] = co_await acc.accept(peer);
234  
        auto [ec] = co_await acc.accept(peer);
235  
        if (!ec) {
235  
        if (!ec) {
236  
            // Use peer socket
236  
            // Use peer socket
237  
        }
237  
        }
238  
        @endcode
238  
        @endcode
239  
    */
239  
    */
240  
    auto accept(tcp_socket& peer)
240  
    auto accept(tcp_socket& peer)
241  
    {
241  
    {
242  
        if (!is_open())
242  
        if (!is_open())
243  
            detail::throw_logic_error("accept: acceptor not listening");
243  
            detail::throw_logic_error("accept: acceptor not listening");
244  
        return accept_awaitable(*this, peer);
244  
        return accept_awaitable(*this, peer);
245  
    }
245  
    }
246  

246  

247  
    /** Cancel any pending asynchronous operations.
247  
    /** Cancel any pending asynchronous operations.
248  

248  

249  
        All outstanding operations complete with `errc::operation_canceled`.
249  
        All outstanding operations complete with `errc::operation_canceled`.
250  
        Check `ec == cond::canceled` for portable comparison.
250  
        Check `ec == cond::canceled` for portable comparison.
251  
    */
251  
    */
252  
    void cancel();
252  
    void cancel();
253  

253  

254  
    /** Get the local endpoint of the acceptor.
254  
    /** Get the local endpoint of the acceptor.
255  

255  

256  
        Returns the local address and port to which the acceptor is bound.
256  
        Returns the local address and port to which the acceptor is bound.
257  
        This is useful when binding to port 0 (ephemeral port) to discover
257  
        This is useful when binding to port 0 (ephemeral port) to discover
258  
        the OS-assigned port number. The endpoint is cached when listen()
258  
        the OS-assigned port number. The endpoint is cached when listen()
259  
        is called.
259  
        is called.
260  

260  

261  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
261  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
262  
            the acceptor is not listening.
262  
            the acceptor is not listening.
263  

263  

264  
        @par Thread Safety
264  
        @par Thread Safety
265  
        The cached endpoint value is set during listen() and cleared
265  
        The cached endpoint value is set during listen() and cleared
266  
        during close(). This function may be called concurrently with
266  
        during close(). This function may be called concurrently with
267  
        accept operations, but must not be called concurrently with
267  
        accept operations, but must not be called concurrently with
268  
        listen() or close().
268  
        listen() or close().
269  
    */
269  
    */
270  
    endpoint local_endpoint() const noexcept;
270  
    endpoint local_endpoint() const noexcept;
271  

271  

272  
    struct implementation : io_object::implementation
272  
    struct implementation : io_object::implementation
273  
    {
273  
    {
274  
        virtual std::coroutine_handle<> accept(
274  
        virtual std::coroutine_handle<> accept(
275  
            std::coroutine_handle<>,
275  
            std::coroutine_handle<>,
276  
            capy::executor_ref,
276  
            capy::executor_ref,
277  
            std::stop_token,
277  
            std::stop_token,
278  
            std::error_code*,
278  
            std::error_code*,
279  
            io_object::implementation**) = 0;
279  
            io_object::implementation**) = 0;
280  

280  

281  
        /// Returns the cached local endpoint.
281  
        /// Returns the cached local endpoint.
282  
        virtual endpoint local_endpoint() const noexcept = 0;
282  
        virtual endpoint local_endpoint() const noexcept = 0;
283  

283  

284  
        /// Return true if the acceptor has a kernel resource open.
284  
        /// Return true if the acceptor has a kernel resource open.
285  
        virtual bool is_open() const noexcept = 0;
285  
        virtual bool is_open() const noexcept = 0;
286  

286  

287  
        /** Cancel any pending asynchronous operations.
287  
        /** Cancel any pending asynchronous operations.
288  

288  

289  
            All outstanding operations complete with operation_canceled error.
289  
            All outstanding operations complete with operation_canceled error.
290  
        */
290  
        */
291  
        virtual void cancel() noexcept = 0;
291  
        virtual void cancel() noexcept = 0;
292  
    };
292  
    };
293  

293  

294  
protected:
294  
protected:
295  
    explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
295  
    explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
296  

296  

297  
    /// Transfer accepted peer impl to the peer socket.
297  
    /// Transfer accepted peer impl to the peer socket.
298  
    static void
298  
    static void
299  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
299  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
300  
    {
300  
    {
301  
        if (impl)
301  
        if (impl)
302  
            peer.h_.reset(impl);
302  
            peer.h_.reset(impl);
303  
    }
303  
    }
304  

304  

305  
private:
305  
private:
306  
    inline implementation& get() const noexcept
306  
    inline implementation& get() const noexcept
307  
    {
307  
    {
308  
        return *static_cast<implementation*>(h_.get());
308  
        return *static_cast<implementation*>(h_.get());
309  
    }
309  
    }
310  
};
310  
};
311  

311  

312  
} // namespace boost::corosio
312  
} // namespace boost::corosio
313  

313  

314  
#endif
314  
#endif