1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
10  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
11  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
11  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  

12  

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

23  

24  
#include <system_error>
24  
#include <system_error>
25  

25  

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

32  

33  
namespace boost::corosio {
33  
namespace boost::corosio {
34  

34  

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

36  

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

40  

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

43  

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

48  

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

52  

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

59  

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

77  

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

83  

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

88  

89  
        capy::io_result<> await_resume() const noexcept
89  
        capy::io_result<> await_resume() const noexcept
90  
        {
90  
        {
91  
            if (token_.stop_requested())
91  
            if (token_.stop_requested())
92  
                return {make_error_code(std::errc::operation_canceled)};
92  
                return {make_error_code(std::errc::operation_canceled)};
93  
            
93  
            
94  
            // Transfer the accepted impl to the peer socket
94  
            // Transfer the accepted impl to the peer socket
95  
            // (acceptor is a friend of socket, so we can access impl_)
95  
            // (acceptor is a friend of socket, so we can access impl_)
96  
            if (!ec_ && peer_impl_)
96  
            if (!ec_ && peer_impl_)
97  
            {
97  
            {
98  
                peer_.close();
98  
                peer_.close();
99  
                peer_.impl_ = peer_impl_;
99  
                peer_.impl_ = peer_impl_;
100  
            }
100  
            }
101  
            return {ec_};
101  
            return {ec_};
102  
        }
102  
        }
103  

103  

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

112  

113  
public:
113  
public:
114  
    /** Destructor.
114  
    /** Destructor.
115  

115  

116  
        Closes the acceptor if open, cancelling any pending operations.
116  
        Closes the acceptor if open, cancelling any pending operations.
117  
    */
117  
    */
118  
    ~tcp_acceptor();
118  
    ~tcp_acceptor();
119  

119  

120  
    /** Construct an acceptor from an execution context.
120  
    /** Construct an acceptor from an execution context.
121  

121  

122  
        @param ctx The execution context that will own this acceptor.
122  
        @param ctx The execution context that will own this acceptor.
123  
    */
123  
    */
124  
    explicit tcp_acceptor(capy::execution_context& ctx);
124  
    explicit tcp_acceptor(capy::execution_context& ctx);
125  

125  

126  
    /** Construct an acceptor from an executor.
126  
    /** Construct an acceptor from an executor.
127  

127  

128  
        The acceptor is associated with the executor's context.
128  
        The acceptor is associated with the executor's context.
129  

129  

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

139  

140  
    /** Move constructor.
140  
    /** Move constructor.
141  

141  

142  
        Transfers ownership of the acceptor resources.
142  
        Transfers ownership of the acceptor resources.
143  

143  

144  
        @param other The acceptor to move from.
144  
        @param other The acceptor to move from.
145  
    */
145  
    */
146  
    tcp_acceptor(tcp_acceptor&& other) noexcept
146  
    tcp_acceptor(tcp_acceptor&& other) noexcept
147  
        : io_object(other.context())
147  
        : io_object(other.context())
148  
    {
148  
    {
149  
        impl_ = other.impl_;
149  
        impl_ = other.impl_;
150  
        other.impl_ = nullptr;
150  
        other.impl_ = nullptr;
151  
    }
151  
    }
152  

152  

153  
    /** Move assignment operator.
153  
    /** Move assignment operator.
154  

154  

155  
        Closes any existing acceptor and transfers ownership.
155  
        Closes any existing acceptor and transfers ownership.
156  
        The source and destination must share the same execution context.
156  
        The source and destination must share the same execution context.
157  

157  

158  
        @param other The acceptor to move from.
158  
        @param other The acceptor to move from.
159  

159  

160  
        @return Reference to this acceptor.
160  
        @return Reference to this acceptor.
161  

161  

162  
        @throws std::logic_error if the acceptors have different execution contexts.
162  
        @throws std::logic_error if the acceptors have different execution contexts.
163  
    */
163  
    */
164  
    tcp_acceptor& operator=(tcp_acceptor&& other)
164  
    tcp_acceptor& operator=(tcp_acceptor&& other)
165  
    {
165  
    {
166  
        if (this != &other)
166  
        if (this != &other)
167  
        {
167  
        {
168  
            if (ctx_ != other.ctx_)
168  
            if (ctx_ != other.ctx_)
169  
                detail::throw_logic_error(
169  
                detail::throw_logic_error(
170  
                    "cannot move tcp_acceptor across execution contexts");
170  
                    "cannot move tcp_acceptor across execution contexts");
171  
            close();
171  
            close();
172  
            impl_ = other.impl_;
172  
            impl_ = other.impl_;
173  
            other.impl_ = nullptr;
173  
            other.impl_ = nullptr;
174  
        }
174  
        }
175  
        return *this;
175  
        return *this;
176  
    }
176  
    }
177  

177  

178  
    tcp_acceptor(tcp_acceptor const&) = delete;
178  
    tcp_acceptor(tcp_acceptor const&) = delete;
179  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
179  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
180  

180  

181  
    /** Open, bind, and listen on an endpoint.
181  
    /** Open, bind, and listen on an endpoint.
182  

182  

183  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
183  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
184  
        and begins listening for incoming connections. This must be
184  
        and begins listening for incoming connections. This must be
185  
        called before initiating accept operations.
185  
        called before initiating accept operations.
186  

186  

187  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
187  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
188  
            bind to all interfaces on a specific port.
188  
            bind to all interfaces on a specific port.
189  

189  

190  
        @param backlog The maximum length of the queue of pending
190  
        @param backlog The maximum length of the queue of pending
191  
            connections. Defaults to 128.
191  
            connections. Defaults to 128.
192  

192  

193  
        @return An error code indicating success or the reason for failure.
193  
        @return An error code indicating success or the reason for failure.
194  
            A default-constructed error code indicates success.
194  
            A default-constructed error code indicates success.
195  

195  

196  
        @par Error Conditions
196  
        @par Error Conditions
197  
        @li `errc::address_in_use`: The endpoint is already in use.
197  
        @li `errc::address_in_use`: The endpoint is already in use.
198  
        @li `errc::address_not_available`: The address is not available
198  
        @li `errc::address_not_available`: The address is not available
199  
            on any local interface.
199  
            on any local interface.
200  
        @li `errc::permission_denied`: Insufficient privileges to bind
200  
        @li `errc::permission_denied`: Insufficient privileges to bind
201  
            to the endpoint (e.g., privileged port).
201  
            to the endpoint (e.g., privileged port).
202  
        @li `errc::operation_not_supported`: The acceptor service is
202  
        @li `errc::operation_not_supported`: The acceptor service is
203  
            unavailable in the context (POSIX only).
203  
            unavailable in the context (POSIX only).
204  

204  

205  
        @throws Nothing.
205  
        @throws Nothing.
206  
    */
206  
    */
207  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
207  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
208  

208  

209  
    /** Close the acceptor.
209  
    /** Close the acceptor.
210  

210  

211  
        Releases acceptor resources. Any pending operations complete
211  
        Releases acceptor resources. Any pending operations complete
212  
        with `errc::operation_canceled`.
212  
        with `errc::operation_canceled`.
213  
    */
213  
    */
214  
    void close();
214  
    void close();
215  

215  

216  
    /** Check if the acceptor is listening.
216  
    /** Check if the acceptor is listening.
217  

217  

218  
        @return `true` if the acceptor is open and listening.
218  
        @return `true` if the acceptor is open and listening.
219  
    */
219  
    */
220  
    bool is_open() const noexcept
220  
    bool is_open() const noexcept
221  
    {
221  
    {
222  
        return impl_ != nullptr;
222  
        return impl_ != nullptr;
223  
    }
223  
    }
224  

224  

225  
    /** Initiate an asynchronous accept operation.
225  
    /** Initiate an asynchronous accept operation.
226  

226  

227  
        Accepts an incoming connection and initializes the provided
227  
        Accepts an incoming connection and initializes the provided
228  
        socket with the new connection. The acceptor must be listening
228  
        socket with the new connection. The acceptor must be listening
229  
        before calling this function.
229  
        before calling this function.
230  

230  

231  
        The operation supports cancellation via `std::stop_token` through
231  
        The operation supports cancellation via `std::stop_token` through
232  
        the affine awaitable protocol. If the associated stop token is
232  
        the affine awaitable protocol. If the associated stop token is
233  
        triggered, the operation completes immediately with
233  
        triggered, the operation completes immediately with
234  
        `errc::operation_canceled`.
234  
        `errc::operation_canceled`.
235  

235  

236  
        @param peer The socket to receive the accepted connection. Any
236  
        @param peer The socket to receive the accepted connection. Any
237  
            existing connection on this socket will be closed.
237  
            existing connection on this socket will be closed.
238  

238  

239  
        @return An awaitable that completes with `io_result<>`.
239  
        @return An awaitable that completes with `io_result<>`.
240  
            Returns success on successful accept, or an error code on
240  
            Returns success on successful accept, or an error code on
241  
            failure including:
241  
            failure including:
242  
            - operation_canceled: Cancelled via stop_token or cancel().
242  
            - operation_canceled: Cancelled via stop_token or cancel().
243  
                Check `ec == cond::canceled` for portable comparison.
243  
                Check `ec == cond::canceled` for portable comparison.
244  

244  

245  
        @par Preconditions
245  
        @par Preconditions
246  
        The acceptor must be listening (`is_open() == true`).
246  
        The acceptor must be listening (`is_open() == true`).
247  
        The peer socket must be associated with the same execution context.
247  
        The peer socket must be associated with the same execution context.
248  

248  

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

264  

265  
    /** Cancel any pending asynchronous operations.
265  
    /** Cancel any pending asynchronous operations.
266  

266  

267  
        All outstanding operations complete with `errc::operation_canceled`.
267  
        All outstanding operations complete with `errc::operation_canceled`.
268  
        Check `ec == cond::canceled` for portable comparison.
268  
        Check `ec == cond::canceled` for portable comparison.
269  
    */
269  
    */
270  
    void cancel();
270  
    void cancel();
271  

271  

272  
    /** Get the local endpoint of the acceptor.
272  
    /** Get the local endpoint of the acceptor.
273  

273  

274  
        Returns the local address and port to which the acceptor is bound.
274  
        Returns the local address and port to which the acceptor is bound.
275  
        This is useful when binding to port 0 (ephemeral port) to discover
275  
        This is useful when binding to port 0 (ephemeral port) to discover
276  
        the OS-assigned port number. The endpoint is cached when listen()
276  
        the OS-assigned port number. The endpoint is cached when listen()
277  
        is called.
277  
        is called.
278  

278  

279  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
279  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
280  
            the acceptor is not listening.
280  
            the acceptor is not listening.
281  

281  

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

289  

290  
    struct acceptor_impl : io_object_impl
290  
    struct acceptor_impl : io_object_impl
291  
    {
291  
    {
292  
        virtual std::coroutine_handle<> accept(
292  
        virtual std::coroutine_handle<> accept(
293  
            std::coroutine_handle<>,
293  
            std::coroutine_handle<>,
294  
            capy::executor_ref,
294  
            capy::executor_ref,
295  
            std::stop_token,
295  
            std::stop_token,
296  
            std::error_code*,
296  
            std::error_code*,
297  
            io_object_impl**) = 0;
297  
            io_object_impl**) = 0;
298  

298  

299  
        /// Returns the cached local endpoint.
299  
        /// Returns the cached local endpoint.
300  
        virtual endpoint local_endpoint() const noexcept = 0;
300  
        virtual endpoint local_endpoint() const noexcept = 0;
301  

301  

302  
        /** Cancel any pending asynchronous operations.
302  
        /** Cancel any pending asynchronous operations.
303  

303  

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

308  

309  
private:
309  
private:
310  
    inline acceptor_impl& get() const noexcept
310  
    inline acceptor_impl& get() const noexcept
311  
    {
311  
    {
312  
        return *static_cast<acceptor_impl*>(impl_);
312  
        return *static_cast<acceptor_impl*>(impl_);
313  
    }
313  
    }
314  
};
314  
};
315  

315  

316  
} // namespace boost::corosio
316  
} // namespace boost::corosio
317  

317  

318  
#endif
318  
#endif