1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
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_UDP_SOCKET_HPP
10  
#ifndef BOOST_COROSIO_UDP_SOCKET_HPP
11  
#define BOOST_COROSIO_UDP_SOCKET_HPP
11  
#define BOOST_COROSIO_UDP_SOCKET_HPP
12  

12  

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

26  

27  
#include <system_error>
27  
#include <system_error>
28  

28  

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

34  

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

36  

37  
/** An asynchronous UDP socket for coroutine I/O.
37  
/** An asynchronous UDP socket for coroutine I/O.
38  

38  

39  
    This class provides asynchronous UDP datagram operations that
39  
    This class provides asynchronous UDP datagram operations that
40  
    return awaitable types. Each operation participates in the affine
40  
    return awaitable types. Each operation participates in the affine
41  
    awaitable protocol, ensuring coroutines resume on the correct
41  
    awaitable protocol, ensuring coroutines resume on the correct
42  
    executor.
42  
    executor.
43  

43  

44  
    Supports two modes of operation:
44  
    Supports two modes of operation:
45  

45  

46  
    **Connectionless mode**: each `send_to` specifies a destination
46  
    **Connectionless mode**: each `send_to` specifies a destination
47  
    endpoint, and each `recv_from` captures the source endpoint.
47  
    endpoint, and each `recv_from` captures the source endpoint.
48  
    The socket must be opened (and optionally bound) before I/O.
48  
    The socket must be opened (and optionally bound) before I/O.
49  

49  

50  
    **Connected mode**: call `connect()` to set a default peer,
50  
    **Connected mode**: call `connect()` to set a default peer,
51  
    then use `send()`/`recv()` without endpoint arguments.
51  
    then use `send()`/`recv()` without endpoint arguments.
52  
    The kernel filters incoming datagrams to those from the
52  
    The kernel filters incoming datagrams to those from the
53  
    connected peer.
53  
    connected peer.
54  

54  

55  
    @par Thread Safety
55  
    @par Thread Safety
56  
    Distinct objects: Safe.@n
56  
    Distinct objects: Safe.@n
57  
    Shared objects: Unsafe. A socket must not have concurrent
57  
    Shared objects: Unsafe. A socket must not have concurrent
58  
    operations of the same type (e.g., two simultaneous recv_from).
58  
    operations of the same type (e.g., two simultaneous recv_from).
59  
    One send_to and one recv_from may be in flight simultaneously.
59  
    One send_to and one recv_from may be in flight simultaneously.
60  

60  

61  
    @par Example
61  
    @par Example
62  
    @code
62  
    @code
63  
    // Connectionless mode
63  
    // Connectionless mode
64  
    io_context ioc;
64  
    io_context ioc;
65  
    udp_socket sock( ioc );
65  
    udp_socket sock( ioc );
66  
    sock.open( udp::v4() );
66  
    sock.open( udp::v4() );
67  
    sock.bind( endpoint( ipv4_address::any(), 9000 ) );
67  
    sock.bind( endpoint( ipv4_address::any(), 9000 ) );
68  

68  

69  
    char buf[1024];
69  
    char buf[1024];
70  
    endpoint sender;
70  
    endpoint sender;
71  
    auto [ec, n] = co_await sock.recv_from(
71  
    auto [ec, n] = co_await sock.recv_from(
72  
        capy::mutable_buffer( buf, sizeof( buf ) ), sender );
72  
        capy::mutable_buffer( buf, sizeof( buf ) ), sender );
73  
    if ( !ec )
73  
    if ( !ec )
74  
        co_await sock.send_to(
74  
        co_await sock.send_to(
75  
            capy::const_buffer( buf, n ), sender );
75  
            capy::const_buffer( buf, n ), sender );
76  

76  

77  
    // Connected mode
77  
    // Connected mode
78  
    udp_socket csock( ioc );
78  
    udp_socket csock( ioc );
79  
    auto [cec] = co_await csock.connect(
79  
    auto [cec] = co_await csock.connect(
80  
        endpoint( ipv4_address::loopback(), 9000 ) );
80  
        endpoint( ipv4_address::loopback(), 9000 ) );
81  
    if ( !cec )
81  
    if ( !cec )
82  
        co_await csock.send(
82  
        co_await csock.send(
83  
            capy::const_buffer( buf, n ) );
83  
            capy::const_buffer( buf, n ) );
84  
    @endcode
84  
    @endcode
85  
*/
85  
*/
86  
class BOOST_COROSIO_DECL udp_socket : public io_object
86  
class BOOST_COROSIO_DECL udp_socket : public io_object
87  
{
87  
{
88  
public:
88  
public:
89  
    /** Define backend hooks for UDP socket operations.
89  
    /** Define backend hooks for UDP socket operations.
90  

90  

91  
        Platform backends (epoll, kqueue, select) derive from
91  
        Platform backends (epoll, kqueue, select) derive from
92  
        this to implement datagram I/O and option management.
92  
        this to implement datagram I/O and option management.
93  
    */
93  
    */
94  
    struct implementation : io_object::implementation
94  
    struct implementation : io_object::implementation
95  
    {
95  
    {
96  
        /** Initiate an asynchronous send_to operation.
96  
        /** Initiate an asynchronous send_to operation.
97  

97  

98  
            @param h Coroutine handle to resume on completion.
98  
            @param h Coroutine handle to resume on completion.
99  
            @param ex Executor for dispatching the completion.
99  
            @param ex Executor for dispatching the completion.
100  
            @param buf The buffer data to send.
100  
            @param buf The buffer data to send.
101  
            @param dest The destination endpoint.
101  
            @param dest The destination endpoint.
102  
            @param token Stop token for cancellation.
102  
            @param token Stop token for cancellation.
103  
            @param ec Output error code.
103  
            @param ec Output error code.
104  
            @param bytes_out Output bytes transferred.
104  
            @param bytes_out Output bytes transferred.
105  

105  

106  
            @return Coroutine handle to resume immediately.
106  
            @return Coroutine handle to resume immediately.
107  
        */
107  
        */
108  
        virtual std::coroutine_handle<> send_to(
108  
        virtual std::coroutine_handle<> send_to(
109  
            std::coroutine_handle<> h,
109  
            std::coroutine_handle<> h,
110  
            capy::executor_ref ex,
110  
            capy::executor_ref ex,
111  
            buffer_param buf,
111  
            buffer_param buf,
112  
            endpoint dest,
112  
            endpoint dest,
113  
            std::stop_token token,
113  
            std::stop_token token,
114  
            std::error_code* ec,
114  
            std::error_code* ec,
115  
            std::size_t* bytes_out) = 0;
115  
            std::size_t* bytes_out) = 0;
116  

116  

117  
        /** Initiate an asynchronous recv_from operation.
117  
        /** Initiate an asynchronous recv_from operation.
118  

118  

119  
            @param h Coroutine handle to resume on completion.
119  
            @param h Coroutine handle to resume on completion.
120  
            @param ex Executor for dispatching the completion.
120  
            @param ex Executor for dispatching the completion.
121  
            @param buf The buffer to receive into.
121  
            @param buf The buffer to receive into.
122  
            @param source Output endpoint for the sender's address.
122  
            @param source Output endpoint for the sender's address.
123  
            @param token Stop token for cancellation.
123  
            @param token Stop token for cancellation.
124  
            @param ec Output error code.
124  
            @param ec Output error code.
125  
            @param bytes_out Output bytes transferred.
125  
            @param bytes_out Output bytes transferred.
126  

126  

127  
            @return Coroutine handle to resume immediately.
127  
            @return Coroutine handle to resume immediately.
128  
        */
128  
        */
129  
        virtual std::coroutine_handle<> recv_from(
129  
        virtual std::coroutine_handle<> recv_from(
130  
            std::coroutine_handle<> h,
130  
            std::coroutine_handle<> h,
131  
            capy::executor_ref ex,
131  
            capy::executor_ref ex,
132  
            buffer_param buf,
132  
            buffer_param buf,
133  
            endpoint* source,
133  
            endpoint* source,
134  
            std::stop_token token,
134  
            std::stop_token token,
135  
            std::error_code* ec,
135  
            std::error_code* ec,
136  
            std::size_t* bytes_out) = 0;
136  
            std::size_t* bytes_out) = 0;
137  

137  

138  
        /// Return the platform socket descriptor.
138  
        /// Return the platform socket descriptor.
139  
        virtual native_handle_type native_handle() const noexcept = 0;
139  
        virtual native_handle_type native_handle() const noexcept = 0;
140  

140  

141  
        /** Request cancellation of pending asynchronous operations.
141  
        /** Request cancellation of pending asynchronous operations.
142  

142  

143  
            All outstanding operations complete with operation_canceled
143  
            All outstanding operations complete with operation_canceled
144  
            error. Check `ec == cond::canceled` for portable comparison.
144  
            error. Check `ec == cond::canceled` for portable comparison.
145  
        */
145  
        */
146  
        virtual void cancel() noexcept = 0;
146  
        virtual void cancel() noexcept = 0;
147  

147  

148  
        /** Set a socket option.
148  
        /** Set a socket option.
149  

149  

150  
            @param level The protocol level (e.g. `SOL_SOCKET`).
150  
            @param level The protocol level (e.g. `SOL_SOCKET`).
151  
            @param optname The option name.
151  
            @param optname The option name.
152  
            @param data Pointer to the option value.
152  
            @param data Pointer to the option value.
153  
            @param size Size of the option value in bytes.
153  
            @param size Size of the option value in bytes.
154  
            @return Error code on failure, empty on success.
154  
            @return Error code on failure, empty on success.
155  
        */
155  
        */
156  
        virtual std::error_code set_option(
156  
        virtual std::error_code set_option(
157  
            int level,
157  
            int level,
158  
            int optname,
158  
            int optname,
159  
            void const* data,
159  
            void const* data,
160  
            std::size_t size) noexcept = 0;
160  
            std::size_t size) noexcept = 0;
161  

161  

162  
        /** Get a socket option.
162  
        /** Get a socket option.
163  

163  

164  
            @param level The protocol level (e.g. `SOL_SOCKET`).
164  
            @param level The protocol level (e.g. `SOL_SOCKET`).
165  
            @param optname The option name.
165  
            @param optname The option name.
166  
            @param data Pointer to receive the option value.
166  
            @param data Pointer to receive the option value.
167  
            @param size On entry, the size of the buffer. On exit,
167  
            @param size On entry, the size of the buffer. On exit,
168  
                the size of the option value.
168  
                the size of the option value.
169  
            @return Error code on failure, empty on success.
169  
            @return Error code on failure, empty on success.
170  
        */
170  
        */
171  
        virtual std::error_code
171  
        virtual std::error_code
172  
        get_option(int level, int optname, void* data, std::size_t* size)
172  
        get_option(int level, int optname, void* data, std::size_t* size)
173  
            const noexcept = 0;
173  
            const noexcept = 0;
174  

174  

175  
        /// Return the cached local endpoint.
175  
        /// Return the cached local endpoint.
176  
        virtual endpoint local_endpoint() const noexcept = 0;
176  
        virtual endpoint local_endpoint() const noexcept = 0;
177  

177  

178  
        /// Return the cached remote endpoint (connected mode).
178  
        /// Return the cached remote endpoint (connected mode).
179  
        virtual endpoint remote_endpoint() const noexcept = 0;
179  
        virtual endpoint remote_endpoint() const noexcept = 0;
180  

180  

181  
        /** Initiate an asynchronous connect to set the default peer.
181  
        /** Initiate an asynchronous connect to set the default peer.
182  

182  

183  
            @param h Coroutine handle to resume on completion.
183  
            @param h Coroutine handle to resume on completion.
184  
            @param ex Executor for dispatching the completion.
184  
            @param ex Executor for dispatching the completion.
185  
            @param ep The remote endpoint to connect to.
185  
            @param ep The remote endpoint to connect to.
186  
            @param token Stop token for cancellation.
186  
            @param token Stop token for cancellation.
187  
            @param ec Output error code.
187  
            @param ec Output error code.
188  

188  

189  
            @return Coroutine handle to resume immediately.
189  
            @return Coroutine handle to resume immediately.
190  
        */
190  
        */
191  
        virtual std::coroutine_handle<> connect(
191  
        virtual std::coroutine_handle<> connect(
192  
            std::coroutine_handle<> h,
192  
            std::coroutine_handle<> h,
193  
            capy::executor_ref ex,
193  
            capy::executor_ref ex,
194  
            endpoint ep,
194  
            endpoint ep,
195  
            std::stop_token token,
195  
            std::stop_token token,
196  
            std::error_code* ec) = 0;
196  
            std::error_code* ec) = 0;
197  

197  

198  
        /** Initiate an asynchronous connected send operation.
198  
        /** Initiate an asynchronous connected send operation.
199  

199  

200  
            @param h Coroutine handle to resume on completion.
200  
            @param h Coroutine handle to resume on completion.
201  
            @param ex Executor for dispatching the completion.
201  
            @param ex Executor for dispatching the completion.
202  
            @param buf The buffer data to send.
202  
            @param buf The buffer data to send.
203  
            @param token Stop token for cancellation.
203  
            @param token Stop token for cancellation.
204  
            @param ec Output error code.
204  
            @param ec Output error code.
205  
            @param bytes_out Output bytes transferred.
205  
            @param bytes_out Output bytes transferred.
206  

206  

207  
            @return Coroutine handle to resume immediately.
207  
            @return Coroutine handle to resume immediately.
208  
        */
208  
        */
209  
        virtual std::coroutine_handle<> send(
209  
        virtual std::coroutine_handle<> send(
210  
            std::coroutine_handle<> h,
210  
            std::coroutine_handle<> h,
211  
            capy::executor_ref ex,
211  
            capy::executor_ref ex,
212  
            buffer_param buf,
212  
            buffer_param buf,
213  
            std::stop_token token,
213  
            std::stop_token token,
214  
            std::error_code* ec,
214  
            std::error_code* ec,
215  
            std::size_t* bytes_out) = 0;
215  
            std::size_t* bytes_out) = 0;
216  

216  

217  
        /** Initiate an asynchronous connected recv operation.
217  
        /** Initiate an asynchronous connected recv operation.
218  

218  

219  
            @param h Coroutine handle to resume on completion.
219  
            @param h Coroutine handle to resume on completion.
220  
            @param ex Executor for dispatching the completion.
220  
            @param ex Executor for dispatching the completion.
221  
            @param buf The buffer to receive into.
221  
            @param buf The buffer to receive into.
222  
            @param token Stop token for cancellation.
222  
            @param token Stop token for cancellation.
223  
            @param ec Output error code.
223  
            @param ec Output error code.
224  
            @param bytes_out Output bytes transferred.
224  
            @param bytes_out Output bytes transferred.
225  

225  

226  
            @return Coroutine handle to resume immediately.
226  
            @return Coroutine handle to resume immediately.
227  
        */
227  
        */
228  
        virtual std::coroutine_handle<> recv(
228  
        virtual std::coroutine_handle<> recv(
229  
            std::coroutine_handle<> h,
229  
            std::coroutine_handle<> h,
230  
            capy::executor_ref ex,
230  
            capy::executor_ref ex,
231  
            buffer_param buf,
231  
            buffer_param buf,
232  
            std::stop_token token,
232  
            std::stop_token token,
233  
            std::error_code* ec,
233  
            std::error_code* ec,
234  
            std::size_t* bytes_out) = 0;
234  
            std::size_t* bytes_out) = 0;
235  
    };
235  
    };
236  

236  

237  
    /** Represent the awaitable returned by @ref send_to.
237  
    /** Represent the awaitable returned by @ref send_to.
238  

238  

239  
        Captures the destination endpoint and buffer, then dispatches
239  
        Captures the destination endpoint and buffer, then dispatches
240  
        to the backend implementation on suspension.
240  
        to the backend implementation on suspension.
241  
    */
241  
    */
242  
    struct send_to_awaitable
242  
    struct send_to_awaitable
243  
    {
243  
    {
244  
        udp_socket& s_;
244  
        udp_socket& s_;
245  
        buffer_param buf_;
245  
        buffer_param buf_;
246  
        endpoint dest_;
246  
        endpoint dest_;
247  
        std::stop_token token_;
247  
        std::stop_token token_;
248  
        mutable std::error_code ec_;
248  
        mutable std::error_code ec_;
249  
        mutable std::size_t bytes_ = 0;
249  
        mutable std::size_t bytes_ = 0;
250  

250  

251  
        send_to_awaitable(
251  
        send_to_awaitable(
252  
            udp_socket& s, buffer_param buf, endpoint dest) noexcept
252  
            udp_socket& s, buffer_param buf, endpoint dest) noexcept
253  
            : s_(s)
253  
            : s_(s)
254  
            , buf_(buf)
254  
            , buf_(buf)
255  
            , dest_(dest)
255  
            , dest_(dest)
256  
        {
256  
        {
257  
        }
257  
        }
258  

258  

259  
        bool await_ready() const noexcept
259  
        bool await_ready() const noexcept
260  
        {
260  
        {
261  
            return token_.stop_requested();
261  
            return token_.stop_requested();
262  
        }
262  
        }
263  

263  

264  
        capy::io_result<std::size_t> await_resume() const noexcept
264  
        capy::io_result<std::size_t> await_resume() const noexcept
265  
        {
265  
        {
266  
            if (token_.stop_requested())
266  
            if (token_.stop_requested())
267  
                return {make_error_code(std::errc::operation_canceled), 0};
267  
                return {make_error_code(std::errc::operation_canceled), 0};
268  
            return {ec_, bytes_};
268  
            return {ec_, bytes_};
269  
        }
269  
        }
270  

270  

271  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
271  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
272  
            -> std::coroutine_handle<>
272  
            -> std::coroutine_handle<>
273  
        {
273  
        {
274  
            token_ = env->stop_token;
274  
            token_ = env->stop_token;
275  
            return s_.get().send_to(
275  
            return s_.get().send_to(
276  
                h, env->executor, buf_, dest_, token_, &ec_, &bytes_);
276  
                h, env->executor, buf_, dest_, token_, &ec_, &bytes_);
277  
        }
277  
        }
278  
    };
278  
    };
279  

279  

280  
    /** Represent the awaitable returned by @ref recv_from.
280  
    /** Represent the awaitable returned by @ref recv_from.
281  

281  

282  
        Captures the receive buffer and source endpoint reference,
282  
        Captures the receive buffer and source endpoint reference,
283  
        then dispatches to the backend implementation on suspension.
283  
        then dispatches to the backend implementation on suspension.
284  
    */
284  
    */
285  
    struct recv_from_awaitable
285  
    struct recv_from_awaitable
286  
    {
286  
    {
287  
        udp_socket& s_;
287  
        udp_socket& s_;
288  
        buffer_param buf_;
288  
        buffer_param buf_;
289  
        endpoint& source_;
289  
        endpoint& source_;
290  
        std::stop_token token_;
290  
        std::stop_token token_;
291  
        mutable std::error_code ec_;
291  
        mutable std::error_code ec_;
292  
        mutable std::size_t bytes_ = 0;
292  
        mutable std::size_t bytes_ = 0;
293  

293  

294  
        recv_from_awaitable(
294  
        recv_from_awaitable(
295  
            udp_socket& s, buffer_param buf, endpoint& source) noexcept
295  
            udp_socket& s, buffer_param buf, endpoint& source) noexcept
296  
            : s_(s)
296  
            : s_(s)
297  
            , buf_(buf)
297  
            , buf_(buf)
298  
            , source_(source)
298  
            , source_(source)
299  
        {
299  
        {
300  
        }
300  
        }
301  

301  

302  
        bool await_ready() const noexcept
302  
        bool await_ready() const noexcept
303  
        {
303  
        {
304  
            return token_.stop_requested();
304  
            return token_.stop_requested();
305  
        }
305  
        }
306  

306  

307  
        capy::io_result<std::size_t> await_resume() const noexcept
307  
        capy::io_result<std::size_t> await_resume() const noexcept
308  
        {
308  
        {
309  
            if (token_.stop_requested())
309  
            if (token_.stop_requested())
310  
                return {make_error_code(std::errc::operation_canceled), 0};
310  
                return {make_error_code(std::errc::operation_canceled), 0};
311  
            return {ec_, bytes_};
311  
            return {ec_, bytes_};
312  
        }
312  
        }
313  

313  

314  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
314  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
315  
            -> std::coroutine_handle<>
315  
            -> std::coroutine_handle<>
316  
        {
316  
        {
317  
            token_ = env->stop_token;
317  
            token_ = env->stop_token;
318  
            return s_.get().recv_from(
318  
            return s_.get().recv_from(
319  
                h, env->executor, buf_, &source_, token_, &ec_, &bytes_);
319  
                h, env->executor, buf_, &source_, token_, &ec_, &bytes_);
320  
        }
320  
        }
321  
    };
321  
    };
322  

322  

323  
    /** Represent the awaitable returned by @ref connect.
323  
    /** Represent the awaitable returned by @ref connect.
324  

324  

325  
        Captures the target endpoint, then dispatches to the backend
325  
        Captures the target endpoint, then dispatches to the backend
326  
        implementation on suspension.
326  
        implementation on suspension.
327  
    */
327  
    */
328  
    struct connect_awaitable
328  
    struct connect_awaitable
329  
    {
329  
    {
330  
        udp_socket& s_;
330  
        udp_socket& s_;
331  
        endpoint endpoint_;
331  
        endpoint endpoint_;
332  
        std::stop_token token_;
332  
        std::stop_token token_;
333  
        mutable std::error_code ec_;
333  
        mutable std::error_code ec_;
334  

334  

335  
        connect_awaitable(udp_socket& s, endpoint ep) noexcept
335  
        connect_awaitable(udp_socket& s, endpoint ep) noexcept
336  
            : s_(s)
336  
            : s_(s)
337  
            , endpoint_(ep)
337  
            , endpoint_(ep)
338  
        {
338  
        {
339  
        }
339  
        }
340  

340  

341  
        bool await_ready() const noexcept
341  
        bool await_ready() const noexcept
342  
        {
342  
        {
343  
            return token_.stop_requested();
343  
            return token_.stop_requested();
344  
        }
344  
        }
345  

345  

346  
        capy::io_result<> await_resume() const noexcept
346  
        capy::io_result<> await_resume() const noexcept
347  
        {
347  
        {
348  
            if (token_.stop_requested())
348  
            if (token_.stop_requested())
349  
                return {make_error_code(std::errc::operation_canceled)};
349  
                return {make_error_code(std::errc::operation_canceled)};
350  
            return {ec_};
350  
            return {ec_};
351  
        }
351  
        }
352  

352  

353  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
353  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
354  
            -> std::coroutine_handle<>
354  
            -> std::coroutine_handle<>
355  
        {
355  
        {
356  
            token_ = env->stop_token;
356  
            token_ = env->stop_token;
357  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
357  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
358  
        }
358  
        }
359  
    };
359  
    };
360  

360  

361  
    /** Represent the awaitable returned by @ref send.
361  
    /** Represent the awaitable returned by @ref send.
362  

362  

363  
        Captures the buffer, then dispatches to the backend
363  
        Captures the buffer, then dispatches to the backend
364  
        implementation on suspension. No endpoint argument
364  
        implementation on suspension. No endpoint argument
365  
        (uses the connected peer).
365  
        (uses the connected peer).
366  
    */
366  
    */
367  
    struct send_awaitable
367  
    struct send_awaitable
368  
    {
368  
    {
369  
        udp_socket& s_;
369  
        udp_socket& s_;
370  
        buffer_param buf_;
370  
        buffer_param buf_;
371  
        std::stop_token token_;
371  
        std::stop_token token_;
372  
        mutable std::error_code ec_;
372  
        mutable std::error_code ec_;
373  
        mutable std::size_t bytes_ = 0;
373  
        mutable std::size_t bytes_ = 0;
374  

374  

375  
        send_awaitable(udp_socket& s, buffer_param buf) noexcept
375  
        send_awaitable(udp_socket& s, buffer_param buf) noexcept
376  
            : s_(s)
376  
            : s_(s)
377  
            , buf_(buf)
377  
            , buf_(buf)
378  
        {
378  
        {
379  
        }
379  
        }
380  

380  

381  
        bool await_ready() const noexcept
381  
        bool await_ready() const noexcept
382  
        {
382  
        {
383  
            return token_.stop_requested();
383  
            return token_.stop_requested();
384  
        }
384  
        }
385  

385  

386  
        capy::io_result<std::size_t> await_resume() const noexcept
386  
        capy::io_result<std::size_t> await_resume() const noexcept
387  
        {
387  
        {
388  
            if (token_.stop_requested())
388  
            if (token_.stop_requested())
389  
                return {make_error_code(std::errc::operation_canceled), 0};
389  
                return {make_error_code(std::errc::operation_canceled), 0};
390  
            return {ec_, bytes_};
390  
            return {ec_, bytes_};
391  
        }
391  
        }
392  

392  

393  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
393  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
394  
            -> std::coroutine_handle<>
394  
            -> std::coroutine_handle<>
395  
        {
395  
        {
396  
            token_ = env->stop_token;
396  
            token_ = env->stop_token;
397  
            return s_.get().send(h, env->executor, buf_, token_, &ec_, &bytes_);
397  
            return s_.get().send(h, env->executor, buf_, token_, &ec_, &bytes_);
398  
        }
398  
        }
399  
    };
399  
    };
400  

400  

401  
    /** Represent the awaitable returned by @ref recv.
401  
    /** Represent the awaitable returned by @ref recv.
402  

402  

403  
        Captures the receive buffer, then dispatches to the backend
403  
        Captures the receive buffer, then dispatches to the backend
404  
        implementation on suspension. No source endpoint (connected
404  
        implementation on suspension. No source endpoint (connected
405  
        mode filters at the kernel level).
405  
        mode filters at the kernel level).
406  
    */
406  
    */
407  
    struct recv_awaitable
407  
    struct recv_awaitable
408  
    {
408  
    {
409  
        udp_socket& s_;
409  
        udp_socket& s_;
410  
        buffer_param buf_;
410  
        buffer_param buf_;
411  
        std::stop_token token_;
411  
        std::stop_token token_;
412  
        mutable std::error_code ec_;
412  
        mutable std::error_code ec_;
413  
        mutable std::size_t bytes_ = 0;
413  
        mutable std::size_t bytes_ = 0;
414  

414  

415  
        recv_awaitable(udp_socket& s, buffer_param buf) noexcept
415  
        recv_awaitable(udp_socket& s, buffer_param buf) noexcept
416  
            : s_(s)
416  
            : s_(s)
417  
            , buf_(buf)
417  
            , buf_(buf)
418  
        {
418  
        {
419  
        }
419  
        }
420  

420  

421  
        bool await_ready() const noexcept
421  
        bool await_ready() const noexcept
422  
        {
422  
        {
423  
            return token_.stop_requested();
423  
            return token_.stop_requested();
424  
        }
424  
        }
425  

425  

426  
        capy::io_result<std::size_t> await_resume() const noexcept
426  
        capy::io_result<std::size_t> await_resume() const noexcept
427  
        {
427  
        {
428  
            if (token_.stop_requested())
428  
            if (token_.stop_requested())
429  
                return {make_error_code(std::errc::operation_canceled), 0};
429  
                return {make_error_code(std::errc::operation_canceled), 0};
430  
            return {ec_, bytes_};
430  
            return {ec_, bytes_};
431  
        }
431  
        }
432  

432  

433  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
433  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
434  
            -> std::coroutine_handle<>
434  
            -> std::coroutine_handle<>
435  
        {
435  
        {
436  
            token_ = env->stop_token;
436  
            token_ = env->stop_token;
437  
            return s_.get().recv(h, env->executor, buf_, token_, &ec_, &bytes_);
437  
            return s_.get().recv(h, env->executor, buf_, token_, &ec_, &bytes_);
438  
        }
438  
        }
439  
    };
439  
    };
440  

440  

441  
public:
441  
public:
442  
    /** Destructor.
442  
    /** Destructor.
443  

443  

444  
        Closes the socket if open, cancelling any pending operations.
444  
        Closes the socket if open, cancelling any pending operations.
445  
    */
445  
    */
446  
    ~udp_socket() override;
446  
    ~udp_socket() override;
447  

447  

448  
    /** Construct a socket from an execution context.
448  
    /** Construct a socket from an execution context.
449  

449  

450  
        @param ctx The execution context that will own this socket.
450  
        @param ctx The execution context that will own this socket.
451  
    */
451  
    */
452  
    explicit udp_socket(capy::execution_context& ctx);
452  
    explicit udp_socket(capy::execution_context& ctx);
453  

453  

454  
    /** Construct a socket from an executor.
454  
    /** Construct a socket from an executor.
455  

455  

456  
        The socket is associated with the executor's context.
456  
        The socket is associated with the executor's context.
457  

457  

458  
        @param ex The executor whose context will own the socket.
458  
        @param ex The executor whose context will own the socket.
459  
    */
459  
    */
460  
    template<class Ex>
460  
    template<class Ex>
461  
        requires(!std::same_as<std::remove_cvref_t<Ex>, udp_socket>) &&
461  
        requires(!std::same_as<std::remove_cvref_t<Ex>, udp_socket>) &&
462  
        capy::Executor<Ex>
462  
        capy::Executor<Ex>
463  
    explicit udp_socket(Ex const& ex) : udp_socket(ex.context())
463  
    explicit udp_socket(Ex const& ex) : udp_socket(ex.context())
464  
    {
464  
    {
465  
    }
465  
    }
466  

466  

467  
    /** Move constructor.
467  
    /** Move constructor.
468  

468  

469  
        Transfers ownership of the socket resources.
469  
        Transfers ownership of the socket resources.
470  

470  

471  
        @param other The socket to move from.
471  
        @param other The socket to move from.
472  
    */
472  
    */
473  
    udp_socket(udp_socket&& other) noexcept : io_object(std::move(other)) {}
473  
    udp_socket(udp_socket&& other) noexcept : io_object(std::move(other)) {}
474  

474  

475  
    /** Move assignment operator.
475  
    /** Move assignment operator.
476  

476  

477  
        Closes any existing socket and transfers ownership.
477  
        Closes any existing socket and transfers ownership.
478  

478  

479  
        @param other The socket to move from.
479  
        @param other The socket to move from.
480  
        @return Reference to this socket.
480  
        @return Reference to this socket.
481  
    */
481  
    */
482  
    udp_socket& operator=(udp_socket&& other) noexcept
482  
    udp_socket& operator=(udp_socket&& other) noexcept
483  
    {
483  
    {
484  
        if (this != &other)
484  
        if (this != &other)
485  
        {
485  
        {
486  
            close();
486  
            close();
487  
            h_ = std::move(other.h_);
487  
            h_ = std::move(other.h_);
488  
        }
488  
        }
489  
        return *this;
489  
        return *this;
490  
    }
490  
    }
491  

491  

492  
    udp_socket(udp_socket const&)            = delete;
492  
    udp_socket(udp_socket const&)            = delete;
493  
    udp_socket& operator=(udp_socket const&) = delete;
493  
    udp_socket& operator=(udp_socket const&) = delete;
494  

494  

495  
    /** Open the socket.
495  
    /** Open the socket.
496  

496  

497  
        Creates a UDP socket and associates it with the platform
497  
        Creates a UDP socket and associates it with the platform
498  
        reactor.
498  
        reactor.
499  

499  

500  
        @param proto The protocol (IPv4 or IPv6). Defaults to
500  
        @param proto The protocol (IPv4 or IPv6). Defaults to
501  
            `udp::v4()`.
501  
            `udp::v4()`.
502  

502  

503  
        @throws std::system_error on failure.
503  
        @throws std::system_error on failure.
504  
    */
504  
    */
505  
    void open(udp proto = udp::v4());
505  
    void open(udp proto = udp::v4());
506  

506  

507  
    /** Close the socket.
507  
    /** Close the socket.
508  

508  

509  
        Releases socket resources. Any pending operations complete
509  
        Releases socket resources. Any pending operations complete
510  
        with `errc::operation_canceled`.
510  
        with `errc::operation_canceled`.
511  
    */
511  
    */
512  
    void close();
512  
    void close();
513  

513  

514  
    /** Check if the socket is open.
514  
    /** Check if the socket is open.
515  

515  

516  
        @return `true` if the socket is open and ready for operations.
516  
        @return `true` if the socket is open and ready for operations.
517  
    */
517  
    */
518  
    bool is_open() const noexcept
518  
    bool is_open() const noexcept
519  
    {
519  
    {
520  
#if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
520  
#if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
521  
        return h_ && get().native_handle() != ~native_handle_type(0);
521  
        return h_ && get().native_handle() != ~native_handle_type(0);
522  
#else
522  
#else
523  
        return h_ && get().native_handle() >= 0;
523  
        return h_ && get().native_handle() >= 0;
524  
#endif
524  
#endif
525  
    }
525  
    }
526  

526  

527  
    /** Bind the socket to a local endpoint.
527  
    /** Bind the socket to a local endpoint.
528  

528  

529  
        Associates the socket with a local address and port.
529  
        Associates the socket with a local address and port.
530  
        Required before calling `recv_from`.
530  
        Required before calling `recv_from`.
531  

531  

532  
        @param ep The local endpoint to bind to.
532  
        @param ep The local endpoint to bind to.
533  

533  

534  
        @return Error code on failure, empty on success.
534  
        @return Error code on failure, empty on success.
535  

535  

536  
        @throws std::logic_error if the socket is not open.
536  
        @throws std::logic_error if the socket is not open.
537  
    */
537  
    */
538  
    std::error_code bind(endpoint ep);
538  
    std::error_code bind(endpoint ep);
539  

539  

540  
    /** Cancel any pending asynchronous operations.
540  
    /** Cancel any pending asynchronous operations.
541  

541  

542  
        All outstanding operations complete with
542  
        All outstanding operations complete with
543  
        `errc::operation_canceled`. Check `ec == cond::canceled`
543  
        `errc::operation_canceled`. Check `ec == cond::canceled`
544  
        for portable comparison.
544  
        for portable comparison.
545  
    */
545  
    */
546  
    void cancel();
546  
    void cancel();
547  

547  

548  
    /** Get the native socket handle.
548  
    /** Get the native socket handle.
549  

549  

550  
        @return The native socket handle, or -1 if not open.
550  
        @return The native socket handle, or -1 if not open.
551  
    */
551  
    */
552  
    native_handle_type native_handle() const noexcept;
552  
    native_handle_type native_handle() const noexcept;
553  

553  

554  
    /** Set a socket option.
554  
    /** Set a socket option.
555  

555  

556  
        @param opt The option to set.
556  
        @param opt The option to set.
557  

557  

558  
        @throws std::logic_error if the socket is not open.
558  
        @throws std::logic_error if the socket is not open.
559  
        @throws std::system_error on failure.
559  
        @throws std::system_error on failure.
560  
    */
560  
    */
561  
    template<class Option>
561  
    template<class Option>
562  
    void set_option(Option const& opt)
562  
    void set_option(Option const& opt)
563  
    {
563  
    {
564  
        if (!is_open())
564  
        if (!is_open())
565  
            detail::throw_logic_error("set_option: socket not open");
565  
            detail::throw_logic_error("set_option: socket not open");
566  
        std::error_code ec = get().set_option(
566  
        std::error_code ec = get().set_option(
567  
            Option::level(), Option::name(), opt.data(), opt.size());
567  
            Option::level(), Option::name(), opt.data(), opt.size());
568  
        if (ec)
568  
        if (ec)
569  
            detail::throw_system_error(ec, "udp_socket::set_option");
569  
            detail::throw_system_error(ec, "udp_socket::set_option");
570  
    }
570  
    }
571  

571  

572  
    /** Get a socket option.
572  
    /** Get a socket option.
573  

573  

574  
        @return The current option value.
574  
        @return The current option value.
575  

575  

576  
        @throws std::logic_error if the socket is not open.
576  
        @throws std::logic_error if the socket is not open.
577  
        @throws std::system_error on failure.
577  
        @throws std::system_error on failure.
578  
    */
578  
    */
579  
    template<class Option>
579  
    template<class Option>
580  
    Option get_option() const
580  
    Option get_option() const
581  
    {
581  
    {
582  
        if (!is_open())
582  
        if (!is_open())
583  
            detail::throw_logic_error("get_option: socket not open");
583  
            detail::throw_logic_error("get_option: socket not open");
584  
        Option opt{};
584  
        Option opt{};
585  
        std::size_t sz = opt.size();
585  
        std::size_t sz = opt.size();
586  
        std::error_code ec =
586  
        std::error_code ec =
587  
            get().get_option(Option::level(), Option::name(), opt.data(), &sz);
587  
            get().get_option(Option::level(), Option::name(), opt.data(), &sz);
588  
        if (ec)
588  
        if (ec)
589  
            detail::throw_system_error(ec, "udp_socket::get_option");
589  
            detail::throw_system_error(ec, "udp_socket::get_option");
590  
        opt.resize(sz);
590  
        opt.resize(sz);
591  
        return opt;
591  
        return opt;
592  
    }
592  
    }
593  

593  

594  
    /** Get the local endpoint of the socket.
594  
    /** Get the local endpoint of the socket.
595  

595  

596  
        @return The local endpoint, or a default endpoint if not bound.
596  
        @return The local endpoint, or a default endpoint if not bound.
597  
    */
597  
    */
598  
    endpoint local_endpoint() const noexcept;
598  
    endpoint local_endpoint() const noexcept;
599  

599  

600  
    /** Send a datagram to the specified destination.
600  
    /** Send a datagram to the specified destination.
601  

601  

602  
        @param buf The buffer containing data to send.
602  
        @param buf The buffer containing data to send.
603  
        @param dest The destination endpoint.
603  
        @param dest The destination endpoint.
604  

604  

605  
        @return An awaitable that completes with
605  
        @return An awaitable that completes with
606  
            `io_result<std::size_t>`.
606  
            `io_result<std::size_t>`.
607  

607  

608  
        @throws std::logic_error if the socket is not open.
608  
        @throws std::logic_error if the socket is not open.
609  
    */
609  
    */
610  
    template<capy::ConstBufferSequence Buffers>
610  
    template<capy::ConstBufferSequence Buffers>
611  
    auto send_to(Buffers const& buf, endpoint dest)
611  
    auto send_to(Buffers const& buf, endpoint dest)
612  
    {
612  
    {
613  
        if (!is_open())
613  
        if (!is_open())
614  
            detail::throw_logic_error("send_to: socket not open");
614  
            detail::throw_logic_error("send_to: socket not open");
615  
        return send_to_awaitable(*this, buf, dest);
615  
        return send_to_awaitable(*this, buf, dest);
616  
    }
616  
    }
617  

617  

618  
    /** Receive a datagram and capture the sender's endpoint.
618  
    /** Receive a datagram and capture the sender's endpoint.
619  

619  

620  
        @param buf The buffer to receive data into.
620  
        @param buf The buffer to receive data into.
621  
        @param source Reference to an endpoint that will be set to
621  
        @param source Reference to an endpoint that will be set to
622  
            the sender's address on successful completion.
622  
            the sender's address on successful completion.
623  

623  

624  
        @return An awaitable that completes with
624  
        @return An awaitable that completes with
625  
            `io_result<std::size_t>`.
625  
            `io_result<std::size_t>`.
626  

626  

627  
        @throws std::logic_error if the socket is not open.
627  
        @throws std::logic_error if the socket is not open.
628  
    */
628  
    */
629  
    template<capy::MutableBufferSequence Buffers>
629  
    template<capy::MutableBufferSequence Buffers>
630  
    auto recv_from(Buffers const& buf, endpoint& source)
630  
    auto recv_from(Buffers const& buf, endpoint& source)
631  
    {
631  
    {
632  
        if (!is_open())
632  
        if (!is_open())
633  
            detail::throw_logic_error("recv_from: socket not open");
633  
            detail::throw_logic_error("recv_from: socket not open");
634  
        return recv_from_awaitable(*this, buf, source);
634  
        return recv_from_awaitable(*this, buf, source);
635  
    }
635  
    }
636  

636  

637  
    /** Initiate an asynchronous connect to set the default peer.
637  
    /** Initiate an asynchronous connect to set the default peer.
638  

638  

639  
        If the socket is not already open, it is opened automatically
639  
        If the socket is not already open, it is opened automatically
640  
        using the address family of @p ep.
640  
        using the address family of @p ep.
641  

641  

642  
        @param ep The remote endpoint to connect to.
642  
        @param ep The remote endpoint to connect to.
643  

643  

644  
        @return An awaitable that completes with `io_result<>`.
644  
        @return An awaitable that completes with `io_result<>`.
645  

645  

646  
        @throws std::system_error if the socket needs to be opened
646  
        @throws std::system_error if the socket needs to be opened
647  
            and the open fails.
647  
            and the open fails.
648  
    */
648  
    */
649  
    auto connect(endpoint ep)
649  
    auto connect(endpoint ep)
650  
    {
650  
    {
651  
        if (!is_open())
651  
        if (!is_open())
652  
            open(ep.is_v6() ? udp::v6() : udp::v4());
652  
            open(ep.is_v6() ? udp::v6() : udp::v4());
653  
        return connect_awaitable(*this, ep);
653  
        return connect_awaitable(*this, ep);
654  
    }
654  
    }
655  

655  

656  
    /** Send a datagram to the connected peer.
656  
    /** Send a datagram to the connected peer.
657  

657  

658  
        @param buf The buffer containing data to send.
658  
        @param buf The buffer containing data to send.
659  

659  

660  
        @return An awaitable that completes with
660  
        @return An awaitable that completes with
661  
            `io_result<std::size_t>`.
661  
            `io_result<std::size_t>`.
662  

662  

663  
        @throws std::logic_error if the socket is not open.
663  
        @throws std::logic_error if the socket is not open.
664  
    */
664  
    */
665  
    template<capy::ConstBufferSequence Buffers>
665  
    template<capy::ConstBufferSequence Buffers>
666  
    auto send(Buffers const& buf)
666  
    auto send(Buffers const& buf)
667  
    {
667  
    {
668  
        if (!is_open())
668  
        if (!is_open())
669  
            detail::throw_logic_error("send: socket not open");
669  
            detail::throw_logic_error("send: socket not open");
670  
        return send_awaitable(*this, buf);
670  
        return send_awaitable(*this, buf);
671  
    }
671  
    }
672  

672  

673  
    /** Receive a datagram from the connected peer.
673  
    /** Receive a datagram from the connected peer.
674  

674  

675  
        @param buf The buffer to receive data into.
675  
        @param buf The buffer to receive data into.
676  

676  

677  
        @return An awaitable that completes with
677  
        @return An awaitable that completes with
678  
            `io_result<std::size_t>`.
678  
            `io_result<std::size_t>`.
679  

679  

680  
        @throws std::logic_error if the socket is not open.
680  
        @throws std::logic_error if the socket is not open.
681  
    */
681  
    */
682  
    template<capy::MutableBufferSequence Buffers>
682  
    template<capy::MutableBufferSequence Buffers>
683  
    auto recv(Buffers const& buf)
683  
    auto recv(Buffers const& buf)
684  
    {
684  
    {
685  
        if (!is_open())
685  
        if (!is_open())
686  
            detail::throw_logic_error("recv: socket not open");
686  
            detail::throw_logic_error("recv: socket not open");
687  
        return recv_awaitable(*this, buf);
687  
        return recv_awaitable(*this, buf);
688  
    }
688  
    }
689  

689  

690  
    /** Get the remote endpoint of the socket.
690  
    /** Get the remote endpoint of the socket.
691  

691  

692  
        Returns the address and port of the connected peer.
692  
        Returns the address and port of the connected peer.
693  

693  

694  
        @return The remote endpoint, or a default endpoint if
694  
        @return The remote endpoint, or a default endpoint if
695  
            not connected.
695  
            not connected.
696  
    */
696  
    */
697  
    endpoint remote_endpoint() const noexcept;
697  
    endpoint remote_endpoint() const noexcept;
698  

698  

699  
protected:
699  
protected:
700  
    /// Construct from a pre-built handle (for native_udp_socket).
700  
    /// Construct from a pre-built handle (for native_udp_socket).
701  
    explicit udp_socket(io_object::handle h) noexcept : io_object(std::move(h))
701  
    explicit udp_socket(io_object::handle h) noexcept : io_object(std::move(h))
702  
    {
702  
    {
703  
    }
703  
    }
704  

704  

705  
private:
705  
private:
706  
    /// Open the socket for the given protocol triple.
706  
    /// Open the socket for the given protocol triple.
707  
    void open_for_family(int family, int type, int protocol);
707  
    void open_for_family(int family, int type, int protocol);
708  

708  

709  
    inline implementation& get() const noexcept
709  
    inline implementation& get() const noexcept
710  
    {
710  
    {
711  
        return *static_cast<implementation*>(h_.get());
711  
        return *static_cast<implementation*>(h_.get());
712  
    }
712  
    }
713  
};
713  
};
714  

714  

715  
} // namespace boost::corosio
715  
} // namespace boost::corosio
716  

716  

717  
#endif // BOOST_COROSIO_UDP_SOCKET_HPP
717  
#endif // BOOST_COROSIO_UDP_SOCKET_HPP