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  
        @pre No awaitables returned by @p other's methods exist.
141  
        @pre No awaitables returned by @p other's methods exist.
142  
        @pre The execution context associated with @p other must
142  
        @pre The execution context associated with @p other must
143  
            outlive this acceptor.
143  
            outlive this acceptor.
144  
    */
144  
    */
145  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
145  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
146  

146  

147  
    /** Move assignment operator.
147  
    /** Move assignment operator.
148  

148  

149  
        Closes any existing acceptor and transfers ownership.
149  
        Closes any existing acceptor and transfers ownership.
150  

150  

151  
        @param other The acceptor to move from.
151  
        @param other The acceptor to move from.
152  

152  

153  
        @pre No awaitables returned by either `*this` or @p other's
153  
        @pre No awaitables returned by either `*this` or @p other's
154  
            methods exist.
154  
            methods exist.
155  
        @pre The execution context associated with @p other must
155  
        @pre The execution context associated with @p other must
156  
            outlive this acceptor.
156  
            outlive this acceptor.
157  

157  

158  
        @return Reference to this acceptor.
158  
        @return Reference to this acceptor.
159  
    */
159  
    */
160  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
160  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
161  
    {
161  
    {
162  
        if (this != &other)
162  
        if (this != &other)
163  
        {
163  
        {
164  
            close();
164  
            close();
165  
            h_ = std::move(other.h_);
165  
            h_ = std::move(other.h_);
166  
        }
166  
        }
167  
        return *this;
167  
        return *this;
168  
    }
168  
    }
169  

169  

170  
    tcp_acceptor(tcp_acceptor const&)            = delete;
170  
    tcp_acceptor(tcp_acceptor const&)            = delete;
171  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
171  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
172  

172  

173  
    /** Open, bind, and listen on an endpoint.
173  
    /** Open, bind, and listen on an endpoint.
174  

174  

175  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
175  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
176  
        and begins listening for incoming connections. This must be
176  
        and begins listening for incoming connections. This must be
177  
        called before initiating accept operations.
177  
        called before initiating accept operations.
178  

178  

179  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
179  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
180  
            bind to all interfaces on a specific port.
180  
            bind to all interfaces on a specific port.
181  

181  

182  
        @param backlog The maximum length of the queue of pending
182  
        @param backlog The maximum length of the queue of pending
183  
            connections. Defaults to 128.
183  
            connections. Defaults to 128.
184  

184  

185  
        @return An error code indicating success or the reason for failure.
185  
        @return An error code indicating success or the reason for failure.
186  
            A default-constructed error code indicates success.
186  
            A default-constructed error code indicates success.
187  

187  

188  
        @par Error Conditions
188  
        @par Error Conditions
189  
        @li `errc::address_in_use`: The endpoint is already in use.
189  
        @li `errc::address_in_use`: The endpoint is already in use.
190  
        @li `errc::address_not_available`: The address is not available
190  
        @li `errc::address_not_available`: The address is not available
191  
            on any local interface.
191  
            on any local interface.
192  
        @li `errc::permission_denied`: Insufficient privileges to bind
192  
        @li `errc::permission_denied`: Insufficient privileges to bind
193  
            to the endpoint (e.g., privileged port).
193  
            to the endpoint (e.g., privileged port).
194  
        @li `errc::operation_not_supported`: The acceptor service is
194  
        @li `errc::operation_not_supported`: The acceptor service is
195  
            unavailable in the context (POSIX only).
195  
            unavailable in the context (POSIX only).
196  

196  

197  
        @throws Nothing.
197  
        @throws Nothing.
198  
    */
198  
    */
199  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
199  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
200  

200  

201  
    /** Close the acceptor.
201  
    /** Close the acceptor.
202  

202  

203  
        Releases acceptor resources. Any pending operations complete
203  
        Releases acceptor resources. Any pending operations complete
204  
        with `errc::operation_canceled`.
204  
        with `errc::operation_canceled`.
205  
    */
205  
    */
206  
    void close();
206  
    void close();
207  

207  

208  
    /** Check if the acceptor is listening.
208  
    /** Check if the acceptor is listening.
209  

209  

210  
        @return `true` if the acceptor is open and listening.
210  
        @return `true` if the acceptor is open and listening.
211  
    */
211  
    */
212  
    bool is_open() const noexcept
212  
    bool is_open() const noexcept
213  
    {
213  
    {
214  
        return h_ && get().is_open();
214  
        return h_ && get().is_open();
215  
    }
215  
    }
216  

216  

217  
    /** Initiate an asynchronous accept operation.
217  
    /** Initiate an asynchronous accept operation.
218  

218  

219  
        Accepts an incoming connection and initializes the provided
219  
        Accepts an incoming connection and initializes the provided
220  
        socket with the new connection. The acceptor must be listening
220  
        socket with the new connection. The acceptor must be listening
221  
        before calling this function.
221  
        before calling this function.
222  

222  

223  
        The operation supports cancellation via `std::stop_token` through
223  
        The operation supports cancellation via `std::stop_token` through
224  
        the affine awaitable protocol. If the associated stop token is
224  
        the affine awaitable protocol. If the associated stop token is
225  
        triggered, the operation completes immediately with
225  
        triggered, the operation completes immediately with
226  
        `errc::operation_canceled`.
226  
        `errc::operation_canceled`.
227  

227  

228  
        @param peer The socket to receive the accepted connection. Any
228  
        @param peer The socket to receive the accepted connection. Any
229  
            existing connection on this socket will be closed.
229  
            existing connection on this socket will be closed.
230  

230  

231  
        @return An awaitable that completes with `io_result<>`.
231  
        @return An awaitable that completes with `io_result<>`.
232  
            Returns success on successful accept, or an error code on
232  
            Returns success on successful accept, or an error code on
233  
            failure including:
233  
            failure including:
234  
            - operation_canceled: Cancelled via stop_token or cancel().
234  
            - operation_canceled: Cancelled via stop_token or cancel().
235  
                Check `ec == cond::canceled` for portable comparison.
235  
                Check `ec == cond::canceled` for portable comparison.
236  

236  

237  
        @par Preconditions
237  
        @par Preconditions
238  
        The acceptor must be listening (`is_open() == true`).
238  
        The acceptor must be listening (`is_open() == true`).
239  
        The peer socket must be associated with the same execution context.
239  
        The peer socket must be associated with the same execution context.
240  

240  

241  
        Both this acceptor and @p peer must outlive the returned
241  
        Both this acceptor and @p peer must outlive the returned
242  
        awaitable.
242  
        awaitable.
243  

243  

244  
        @par Example
244  
        @par Example
245  
        @code
245  
        @code
246  
        tcp_socket peer(ioc);
246  
        tcp_socket peer(ioc);
247  
        auto [ec] = co_await acc.accept(peer);
247  
        auto [ec] = co_await acc.accept(peer);
248  
        if (!ec) {
248  
        if (!ec) {
249  
            // Use peer socket
249  
            // Use peer socket
250  
        }
250  
        }
251  
        @endcode
251  
        @endcode
252  
    */
252  
    */
253  
    auto accept(tcp_socket& peer)
253  
    auto accept(tcp_socket& peer)
254  
    {
254  
    {
255  
        if (!is_open())
255  
        if (!is_open())
256  
            detail::throw_logic_error("accept: acceptor not listening");
256  
            detail::throw_logic_error("accept: acceptor not listening");
257  
        return accept_awaitable(*this, peer);
257  
        return accept_awaitable(*this, peer);
258  
    }
258  
    }
259  

259  

260  
    /** Cancel any pending asynchronous operations.
260  
    /** Cancel any pending asynchronous operations.
261  

261  

262  
        All outstanding operations complete with `errc::operation_canceled`.
262  
        All outstanding operations complete with `errc::operation_canceled`.
263  
        Check `ec == cond::canceled` for portable comparison.
263  
        Check `ec == cond::canceled` for portable comparison.
264  
    */
264  
    */
265  
    void cancel();
265  
    void cancel();
266  

266  

267  
    /** Get the local endpoint of the acceptor.
267  
    /** Get the local endpoint of the acceptor.
268  

268  

269  
        Returns the local address and port to which the acceptor is bound.
269  
        Returns the local address and port to which the acceptor is bound.
270  
        This is useful when binding to port 0 (ephemeral port) to discover
270  
        This is useful when binding to port 0 (ephemeral port) to discover
271  
        the OS-assigned port number. The endpoint is cached when listen()
271  
        the OS-assigned port number. The endpoint is cached when listen()
272  
        is called.
272  
        is called.
273  

273  

274  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
274  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
275  
            the acceptor is not listening.
275  
            the acceptor is not listening.
276  

276  

277  
        @par Thread Safety
277  
        @par Thread Safety
278  
        The cached endpoint value is set during listen() and cleared
278  
        The cached endpoint value is set during listen() and cleared
279  
        during close(). This function may be called concurrently with
279  
        during close(). This function may be called concurrently with
280  
        accept operations, but must not be called concurrently with
280  
        accept operations, but must not be called concurrently with
281  
        listen() or close().
281  
        listen() or close().
282  
    */
282  
    */
283  
    endpoint local_endpoint() const noexcept;
283  
    endpoint local_endpoint() const noexcept;
284  

284  

285  
    struct implementation : io_object::implementation
285  
    struct implementation : io_object::implementation
286  
    {
286  
    {
287  
        virtual std::coroutine_handle<> accept(
287  
        virtual std::coroutine_handle<> accept(
288  
            std::coroutine_handle<>,
288  
            std::coroutine_handle<>,
289  
            capy::executor_ref,
289  
            capy::executor_ref,
290  
            std::stop_token,
290  
            std::stop_token,
291  
            std::error_code*,
291  
            std::error_code*,
292  
            io_object::implementation**) = 0;
292  
            io_object::implementation**) = 0;
293  

293  

294  
        /// Returns the cached local endpoint.
294  
        /// Returns the cached local endpoint.
295  
        virtual endpoint local_endpoint() const noexcept = 0;
295  
        virtual endpoint local_endpoint() const noexcept = 0;
296  

296  

297  
        /// Return true if the acceptor has a kernel resource open.
297  
        /// Return true if the acceptor has a kernel resource open.
298  
        virtual bool is_open() const noexcept = 0;
298  
        virtual bool is_open() const noexcept = 0;
299  

299  

300  
        /** Cancel any pending asynchronous operations.
300  
        /** Cancel any pending asynchronous operations.
301  

301  

302  
            All outstanding operations complete with operation_canceled error.
302  
            All outstanding operations complete with operation_canceled error.
303  
        */
303  
        */
304  
        virtual void cancel() noexcept = 0;
304  
        virtual void cancel() noexcept = 0;
305  
    };
305  
    };
306  

306  

307  
protected:
307  
protected:
308  
    explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
308  
    explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
309  

309  

310  
    /// Transfer accepted peer impl to the peer socket.
310  
    /// Transfer accepted peer impl to the peer socket.
311  
    static void
311  
    static void
312  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
312  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
313  
    {
313  
    {
314  
        if (impl)
314  
        if (impl)
315  
            peer.h_.reset(impl);
315  
            peer.h_.reset(impl);
316  
    }
316  
    }
317  

317  

318  
private:
318  
private:
319  
    inline implementation& get() const noexcept
319  
    inline implementation& get() const noexcept
320  
    {
320  
    {
321  
        return *static_cast<implementation*>(h_.get());
321  
        return *static_cast<implementation*>(h_.get());
322  
    }
322  
    }
323  
};
323  
};
324  

324  

325  
} // namespace boost::corosio
325  
} // namespace boost::corosio
326  

326  

327  
#endif
327  
#endif