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_TEST_MOCKET_HPP
11  
#ifndef BOOST_COROSIO_TEST_MOCKET_HPP
12  
#define BOOST_COROSIO_TEST_MOCKET_HPP
12  
#define BOOST_COROSIO_TEST_MOCKET_HPP
13  

13  

14  
#include <boost/corosio/detail/except.hpp>
14  
#include <boost/corosio/detail/except.hpp>
15  
#include <boost/corosio/io_context.hpp>
15  
#include <boost/corosio/io_context.hpp>
 
16 +
#include <boost/corosio/socket_option.hpp>
16  
#include <boost/corosio/tcp_acceptor.hpp>
17  
#include <boost/corosio/tcp_acceptor.hpp>
17  
#include <boost/corosio/tcp_socket.hpp>
18  
#include <boost/corosio/tcp_socket.hpp>
18  
#include <boost/capy/buffers/buffer_copy.hpp>
19  
#include <boost/capy/buffers/buffer_copy.hpp>
19  
#include <boost/capy/buffers/make_buffer.hpp>
20  
#include <boost/capy/buffers/make_buffer.hpp>
20  
#include <boost/capy/error.hpp>
21  
#include <boost/capy/error.hpp>
21  
#include <boost/capy/ex/run_async.hpp>
22  
#include <boost/capy/ex/run_async.hpp>
22  
#include <boost/capy/io_result.hpp>
23  
#include <boost/capy/io_result.hpp>
23  
#include <boost/capy/task.hpp>
24  
#include <boost/capy/task.hpp>
24  
#include <boost/capy/test/fuse.hpp>
25  
#include <boost/capy/test/fuse.hpp>
25  

26  

26  
#include <cstddef>
27  
#include <cstddef>
27  
#include <cstdio>
28  
#include <cstdio>
28  
#include <cstring>
29  
#include <cstring>
29  
#include <stdexcept>
30  
#include <stdexcept>
30  
#include <string>
31  
#include <string>
31  
#include <system_error>
32  
#include <system_error>
32  
#include <utility>
33  
#include <utility>
33  

34  

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

36  

36  
/** A mock socket for testing I/O operations.
37  
/** A mock socket for testing I/O operations.
37  

38  

38  
    This class provides a testable socket-like interface where data
39  
    This class provides a testable socket-like interface where data
39  
    can be staged for reading and expected data can be validated on
40  
    can be staged for reading and expected data can be validated on
40  
    writes. A mocket is paired with a regular socket using
41  
    writes. A mocket is paired with a regular socket using
41  
    @ref make_mocket_pair, allowing bidirectional communication testing.
42  
    @ref make_mocket_pair, allowing bidirectional communication testing.
42  

43  

43  
    When reading, data comes from the `provide()` buffer first.
44  
    When reading, data comes from the `provide()` buffer first.
44  
    When writing, data is validated against the `expect()` buffer.
45  
    When writing, data is validated against the `expect()` buffer.
45  
    Once buffers are exhausted, I/O passes through to the underlying
46  
    Once buffers are exhausted, I/O passes through to the underlying
46  
    socket connection.
47  
    socket connection.
47  

48  

48  
    Satisfies the `capy::Stream` concept.
49  
    Satisfies the `capy::Stream` concept.
49  

50  

50  
    @tparam Socket The underlying socket type (default `tcp_socket`).
51  
    @tparam Socket The underlying socket type (default `tcp_socket`).
51  

52  

52  
    @par Thread Safety
53  
    @par Thread Safety
53  
    Not thread-safe. All operations must occur on a single thread.
54  
    Not thread-safe. All operations must occur on a single thread.
54  
    All coroutines using the mocket must be suspended when calling
55  
    All coroutines using the mocket must be suspended when calling
55  
    `expect()` or `provide()`.
56  
    `expect()` or `provide()`.
56  

57  

57  
    @see make_mocket_pair
58  
    @see make_mocket_pair
58  
*/
59  
*/
59  
template<class Socket = tcp_socket>
60  
template<class Socket = tcp_socket>
60  
class basic_mocket
61  
class basic_mocket
61  
{
62  
{
62  
    Socket sock_;
63  
    Socket sock_;
63  
    std::string provide_;
64  
    std::string provide_;
64  
    std::string expect_;
65  
    std::string expect_;
65  
    capy::test::fuse fuse_;
66  
    capy::test::fuse fuse_;
66  
    std::size_t max_read_size_;
67  
    std::size_t max_read_size_;
67  
    std::size_t max_write_size_;
68  
    std::size_t max_write_size_;
68  

69  

69  
    template<class MutableBufferSequence>
70  
    template<class MutableBufferSequence>
70  
    std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept;
71  
    std::size_t consume_provide(MutableBufferSequence const& buffers) noexcept;
71  

72  

72  
    template<class ConstBufferSequence>
73  
    template<class ConstBufferSequence>
73  
    bool validate_expect(
74  
    bool validate_expect(
74  
        ConstBufferSequence const& buffers, std::size_t& bytes_written);
75  
        ConstBufferSequence const& buffers, std::size_t& bytes_written);
75  

76  

76  
public:
77  
public:
77  
    template<class MutableBufferSequence>
78  
    template<class MutableBufferSequence>
78  
    class read_some_awaitable;
79  
    class read_some_awaitable;
79  

80  

80  
    template<class ConstBufferSequence>
81  
    template<class ConstBufferSequence>
81  
    class write_some_awaitable;
82  
    class write_some_awaitable;
82  

83  

83  
    /** Destructor.
84  
    /** Destructor.
84  
    */
85  
    */
85  
    ~basic_mocket() = default;
86  
    ~basic_mocket() = default;
86  

87  

87  
    /** Construct a mocket.
88  
    /** Construct a mocket.
88  

89  

89  
        @param ctx The execution context for the socket.
90  
        @param ctx The execution context for the socket.
90  
        @param f The fuse for error injection testing.
91  
        @param f The fuse for error injection testing.
91  
        @param max_read_size Maximum bytes per read operation.
92  
        @param max_read_size Maximum bytes per read operation.
92  
        @param max_write_size Maximum bytes per write operation.
93  
        @param max_write_size Maximum bytes per write operation.
93  
    */
94  
    */
94  
    basic_mocket(
95  
    basic_mocket(
95  
        capy::execution_context& ctx,
96  
        capy::execution_context& ctx,
96  
        capy::test::fuse f         = {},
97  
        capy::test::fuse f         = {},
97  
        std::size_t max_read_size  = std::size_t(-1),
98  
        std::size_t max_read_size  = std::size_t(-1),
98  
        std::size_t max_write_size = std::size_t(-1))
99  
        std::size_t max_write_size = std::size_t(-1))
99  
        : sock_(ctx)
100  
        : sock_(ctx)
100  
        , fuse_(std::move(f))
101  
        , fuse_(std::move(f))
101  
        , max_read_size_(max_read_size)
102  
        , max_read_size_(max_read_size)
102  
        , max_write_size_(max_write_size)
103  
        , max_write_size_(max_write_size)
103  
    {
104  
    {
104  
        if (max_read_size == 0)
105  
        if (max_read_size == 0)
105  
            detail::throw_logic_error("mocket: max_read_size cannot be 0");
106  
            detail::throw_logic_error("mocket: max_read_size cannot be 0");
106  
        if (max_write_size == 0)
107  
        if (max_write_size == 0)
107  
            detail::throw_logic_error("mocket: max_write_size cannot be 0");
108  
            detail::throw_logic_error("mocket: max_write_size cannot be 0");
108  
    }
109  
    }
109  

110  

110  
    /** Move constructor.
111  
    /** Move constructor.
111  
    */
112  
    */
112  
    basic_mocket(basic_mocket&& other) noexcept
113  
    basic_mocket(basic_mocket&& other) noexcept
113  
        : sock_(std::move(other.sock_))
114  
        : sock_(std::move(other.sock_))
114  
        , provide_(std::move(other.provide_))
115  
        , provide_(std::move(other.provide_))
115  
        , expect_(std::move(other.expect_))
116  
        , expect_(std::move(other.expect_))
116  
        , fuse_(std::move(other.fuse_))
117  
        , fuse_(std::move(other.fuse_))
117  
        , max_read_size_(other.max_read_size_)
118  
        , max_read_size_(other.max_read_size_)
118  
        , max_write_size_(other.max_write_size_)
119  
        , max_write_size_(other.max_write_size_)
119  
    {
120  
    {
120  
    }
121  
    }
121  

122  

122  
    /** Move assignment.
123  
    /** Move assignment.
123  
    */
124  
    */
124  
    basic_mocket& operator=(basic_mocket&& other) noexcept
125  
    basic_mocket& operator=(basic_mocket&& other) noexcept
125  
    {
126  
    {
126  
        if (this != &other)
127  
        if (this != &other)
127  
        {
128  
        {
128  
            sock_           = std::move(other.sock_);
129  
            sock_           = std::move(other.sock_);
129  
            provide_        = std::move(other.provide_);
130  
            provide_        = std::move(other.provide_);
130  
            expect_         = std::move(other.expect_);
131  
            expect_         = std::move(other.expect_);
131  
            fuse_           = other.fuse_;
132  
            fuse_           = other.fuse_;
132  
            max_read_size_  = other.max_read_size_;
133  
            max_read_size_  = other.max_read_size_;
133  
            max_write_size_ = other.max_write_size_;
134  
            max_write_size_ = other.max_write_size_;
134  
        }
135  
        }
135  
        return *this;
136  
        return *this;
136  
    }
137  
    }
137  

138  

138  
    basic_mocket(basic_mocket const&)            = delete;
139  
    basic_mocket(basic_mocket const&)            = delete;
139  
    basic_mocket& operator=(basic_mocket const&) = delete;
140  
    basic_mocket& operator=(basic_mocket const&) = delete;
140  

141  

141  
    /** Return the execution context.
142  
    /** Return the execution context.
142  

143  

143  
        @return Reference to the execution context that owns this mocket.
144  
        @return Reference to the execution context that owns this mocket.
144  
    */
145  
    */
145  
    capy::execution_context& context() const noexcept
146  
    capy::execution_context& context() const noexcept
146  
    {
147  
    {
147  
        return sock_.context();
148  
        return sock_.context();
148  
    }
149  
    }
149  

150  

150  
    /** Return the underlying socket.
151  
    /** Return the underlying socket.
151  

152  

152  
        @return Reference to the underlying socket.
153  
        @return Reference to the underlying socket.
153  
    */
154  
    */
154  
    Socket& socket() noexcept
155  
    Socket& socket() noexcept
155  
    {
156  
    {
156  
        return sock_;
157  
        return sock_;
157  
    }
158  
    }
158  

159  

159  
    /** Stage data for reads.
160  
    /** Stage data for reads.
160  

161  

161  
        Appends the given string to this mocket's provide buffer.
162  
        Appends the given string to this mocket's provide buffer.
162  
        When `read_some` is called, it will receive this data first
163  
        When `read_some` is called, it will receive this data first
163  
        before reading from the underlying socket.
164  
        before reading from the underlying socket.
164  

165  

165  
        @param s The data to provide.
166  
        @param s The data to provide.
166  

167  

167  
        @pre All coroutines using this mocket must be suspended.
168  
        @pre All coroutines using this mocket must be suspended.
168  
    */
169  
    */
169  
    void provide(std::string const& s)
170  
    void provide(std::string const& s)
170  
    {
171  
    {
171  
        provide_.append(s);
172  
        provide_.append(s);
172  
    }
173  
    }
173  

174  

174  
    /** Set expected data for writes.
175  
    /** Set expected data for writes.
175  

176  

176  
        Appends the given string to this mocket's expect buffer.
177  
        Appends the given string to this mocket's expect buffer.
177  
        When the caller writes to this mocket, the written data
178  
        When the caller writes to this mocket, the written data
178  
        must match the expected data. On mismatch, `fuse::fail()`
179  
        must match the expected data. On mismatch, `fuse::fail()`
179  
        is called.
180  
        is called.
180  

181  

181  
        @param s The expected data.
182  
        @param s The expected data.
182  

183  

183  
        @pre All coroutines using this mocket must be suspended.
184  
        @pre All coroutines using this mocket must be suspended.
184  
    */
185  
    */
185  
    void expect(std::string const& s)
186  
    void expect(std::string const& s)
186  
    {
187  
    {
187  
        expect_.append(s);
188  
        expect_.append(s);
188  
    }
189  
    }
189  

190  

190  
    /** Close the mocket and verify test expectations.
191  
    /** Close the mocket and verify test expectations.
191  

192  

192  
        Closes the underlying socket and verifies that both the
193  
        Closes the underlying socket and verifies that both the
193  
        `expect()` and `provide()` buffers are empty. If either
194  
        `expect()` and `provide()` buffers are empty. If either
194  
        buffer contains unconsumed data, returns `test_failure`
195  
        buffer contains unconsumed data, returns `test_failure`
195  
        and calls `fuse::fail()`.
196  
        and calls `fuse::fail()`.
196  

197  

197  
        @return An error code indicating success or failure.
198  
        @return An error code indicating success or failure.
198  
            Returns `error::test_failure` if buffers are not empty.
199  
            Returns `error::test_failure` if buffers are not empty.
199  
    */
200  
    */
200  
    std::error_code close()
201  
    std::error_code close()
201  
    {
202  
    {
202  
        if (!sock_.is_open())
203  
        if (!sock_.is_open())
203  
            return {};
204  
            return {};
204  

205  

205  
        if (!expect_.empty())
206  
        if (!expect_.empty())
206  
        {
207  
        {
207  
            fuse_.fail();
208  
            fuse_.fail();
208  
            sock_.close();
209  
            sock_.close();
209  
            return capy::error::test_failure;
210  
            return capy::error::test_failure;
210  
        }
211  
        }
211  
        if (!provide_.empty())
212  
        if (!provide_.empty())
212  
        {
213  
        {
213  
            fuse_.fail();
214  
            fuse_.fail();
214  
            sock_.close();
215  
            sock_.close();
215  
            return capy::error::test_failure;
216  
            return capy::error::test_failure;
216  
        }
217  
        }
217  

218  

218  
        sock_.close();
219  
        sock_.close();
219  
        return {};
220  
        return {};
220  
    }
221  
    }
221  

222  

222  
    /** Cancel pending I/O operations.
223  
    /** Cancel pending I/O operations.
223  

224  

224  
        Cancels any pending asynchronous operations on the underlying
225  
        Cancels any pending asynchronous operations on the underlying
225  
        socket. Outstanding operations complete with `cond::canceled`.
226  
        socket. Outstanding operations complete with `cond::canceled`.
226  
    */
227  
    */
227  
    void cancel()
228  
    void cancel()
228  
    {
229  
    {
229  
        sock_.cancel();
230  
        sock_.cancel();
230  
    }
231  
    }
231  

232  

232  
    /** Check if the mocket is open.
233  
    /** Check if the mocket is open.
233  

234  

234  
        @return `true` if the mocket is open.
235  
        @return `true` if the mocket is open.
235  
    */
236  
    */
236  
    bool is_open() const noexcept
237  
    bool is_open() const noexcept
237  
    {
238  
    {
238  
        return sock_.is_open();
239  
        return sock_.is_open();
239  
    }
240  
    }
240  

241  

241  
    /** Initiate an asynchronous read operation.
242  
    /** Initiate an asynchronous read operation.
242  

243  

243  
        Reads available data into the provided buffer sequence. If the
244  
        Reads available data into the provided buffer sequence. If the
244  
        provide buffer has data, it is consumed first. Otherwise, the
245  
        provide buffer has data, it is consumed first. Otherwise, the
245  
        operation delegates to the underlying socket.
246  
        operation delegates to the underlying socket.
246  

247  

247  
        @param buffers The buffer sequence to read data into.
248  
        @param buffers The buffer sequence to read data into.
248  

249  

249  
        @return An awaitable yielding `(error_code, std::size_t)`.
250  
        @return An awaitable yielding `(error_code, std::size_t)`.
250  
    */
251  
    */
251  
    template<class MutableBufferSequence>
252  
    template<class MutableBufferSequence>
252  
    auto read_some(MutableBufferSequence const& buffers)
253  
    auto read_some(MutableBufferSequence const& buffers)
253  
    {
254  
    {
254  
        return read_some_awaitable<MutableBufferSequence>(*this, buffers);
255  
        return read_some_awaitable<MutableBufferSequence>(*this, buffers);
255  
    }
256  
    }
256  

257  

257  
    /** Initiate an asynchronous write operation.
258  
    /** Initiate an asynchronous write operation.
258  

259  

259  
        Writes data from the provided buffer sequence. If the expect
260  
        Writes data from the provided buffer sequence. If the expect
260  
        buffer has data, it is validated. Otherwise, the operation
261  
        buffer has data, it is validated. Otherwise, the operation
261  
        delegates to the underlying socket.
262  
        delegates to the underlying socket.
262  

263  

263  
        @param buffers The buffer sequence containing data to write.
264  
        @param buffers The buffer sequence containing data to write.
264  

265  

265  
        @return An awaitable yielding `(error_code, std::size_t)`.
266  
        @return An awaitable yielding `(error_code, std::size_t)`.
266  
    */
267  
    */
267  
    template<class ConstBufferSequence>
268  
    template<class ConstBufferSequence>
268  
    auto write_some(ConstBufferSequence const& buffers)
269  
    auto write_some(ConstBufferSequence const& buffers)
269  
    {
270  
    {
270  
        return write_some_awaitable<ConstBufferSequence>(*this, buffers);
271  
        return write_some_awaitable<ConstBufferSequence>(*this, buffers);
271  
    }
272  
    }
272  
};
273  
};
273  

274  

274  
/// Default mocket type using `tcp_socket`.
275  
/// Default mocket type using `tcp_socket`.
275  
using mocket = basic_mocket<>;
276  
using mocket = basic_mocket<>;
276  

277  

277  
template<class Socket>
278  
template<class Socket>
278  
template<class MutableBufferSequence>
279  
template<class MutableBufferSequence>
279  
std::size_t
280  
std::size_t
280  
basic_mocket<Socket>::consume_provide(
281  
basic_mocket<Socket>::consume_provide(
281  
    MutableBufferSequence const& buffers) noexcept
282  
    MutableBufferSequence const& buffers) noexcept
282  
{
283  
{
283  
    auto n =
284  
    auto n =
284  
        capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
285  
        capy::buffer_copy(buffers, capy::make_buffer(provide_), max_read_size_);
285  
    provide_.erase(0, n);
286  
    provide_.erase(0, n);
286  
    return n;
287  
    return n;
287  
}
288  
}
288  

289  

289  
template<class Socket>
290  
template<class Socket>
290  
template<class ConstBufferSequence>
291  
template<class ConstBufferSequence>
291  
bool
292  
bool
292  
basic_mocket<Socket>::validate_expect(
293  
basic_mocket<Socket>::validate_expect(
293  
    ConstBufferSequence const& buffers, std::size_t& bytes_written)
294  
    ConstBufferSequence const& buffers, std::size_t& bytes_written)
294  
{
295  
{
295  
    if (expect_.empty())
296  
    if (expect_.empty())
296  
        return true;
297  
        return true;
297  

298  

298  
    // Build the write data up to max_write_size_
299  
    // Build the write data up to max_write_size_
299  
    std::string written;
300  
    std::string written;
300  
    auto total = capy::buffer_size(buffers);
301  
    auto total = capy::buffer_size(buffers);
301  
    if (total > max_write_size_)
302  
    if (total > max_write_size_)
302  
        total = max_write_size_;
303  
        total = max_write_size_;
303  
    written.resize(total);
304  
    written.resize(total);
304  
    capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_);
305  
    capy::buffer_copy(capy::make_buffer(written), buffers, max_write_size_);
305  

306  

306  
    // Check if written data matches expect prefix
307  
    // Check if written data matches expect prefix
307  
    auto const match_size = (std::min)(written.size(), expect_.size());
308  
    auto const match_size = (std::min)(written.size(), expect_.size());
308  
    if (std::memcmp(written.data(), expect_.data(), match_size) != 0)
309  
    if (std::memcmp(written.data(), expect_.data(), match_size) != 0)
309  
    {
310  
    {
310  
        fuse_.fail();
311  
        fuse_.fail();
311  
        bytes_written = 0;
312  
        bytes_written = 0;
312  
        return false;
313  
        return false;
313  
    }
314  
    }
314  

315  

315  
    // Consume matched portion
316  
    // Consume matched portion
316  
    expect_.erase(0, match_size);
317  
    expect_.erase(0, match_size);
317  
    bytes_written = written.size();
318  
    bytes_written = written.size();
318  
    return true;
319  
    return true;
319  
}
320  
}
320  

321  

321  
template<class Socket>
322  
template<class Socket>
322  
template<class MutableBufferSequence>
323  
template<class MutableBufferSequence>
323  
class basic_mocket<Socket>::read_some_awaitable
324  
class basic_mocket<Socket>::read_some_awaitable
324  
{
325  
{
325  
    using sock_awaitable = decltype(std::declval<Socket&>().read_some(
326  
    using sock_awaitable = decltype(std::declval<Socket&>().read_some(
326  
        std::declval<MutableBufferSequence>()));
327  
        std::declval<MutableBufferSequence>()));
327  

328  

328  
    basic_mocket* m_;
329  
    basic_mocket* m_;
329  
    MutableBufferSequence buffers_;
330  
    MutableBufferSequence buffers_;
330  
    std::size_t n_ = 0;
331  
    std::size_t n_ = 0;
331  
    union
332  
    union
332  
    {
333  
    {
333  
        char dummy_;
334  
        char dummy_;
334  
        sock_awaitable underlying_;
335  
        sock_awaitable underlying_;
335  
    };
336  
    };
336  
    bool sync_ = true;
337  
    bool sync_ = true;
337  

338  

338  
public:
339  
public:
339  
    read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
340  
    read_some_awaitable(basic_mocket& m, MutableBufferSequence buffers) noexcept
340  
        : m_(&m)
341  
        : m_(&m)
341  
        , buffers_(std::move(buffers))
342  
        , buffers_(std::move(buffers))
342  
    {
343  
    {
343  
    }
344  
    }
344  

345  

345  
    ~read_some_awaitable()
346  
    ~read_some_awaitable()
346  
    {
347  
    {
347  
        if (!sync_)
348  
        if (!sync_)
348  
            underlying_.~sock_awaitable();
349  
            underlying_.~sock_awaitable();
349  
    }
350  
    }
350  

351  

351  
    read_some_awaitable(read_some_awaitable&& other) noexcept
352  
    read_some_awaitable(read_some_awaitable&& other) noexcept
352  
        : m_(other.m_)
353  
        : m_(other.m_)
353  
        , buffers_(std::move(other.buffers_))
354  
        , buffers_(std::move(other.buffers_))
354  
        , n_(other.n_)
355  
        , n_(other.n_)
355  
        , sync_(other.sync_)
356  
        , sync_(other.sync_)
356  
    {
357  
    {
357  
        if (!sync_)
358  
        if (!sync_)
358  
        {
359  
        {
359  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
360  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
360  
            other.underlying_.~sock_awaitable();
361  
            other.underlying_.~sock_awaitable();
361  
            other.sync_ = true;
362  
            other.sync_ = true;
362  
        }
363  
        }
363  
    }
364  
    }
364  

365  

365  
    read_some_awaitable(read_some_awaitable const&)            = delete;
366  
    read_some_awaitable(read_some_awaitable const&)            = delete;
366  
    read_some_awaitable& operator=(read_some_awaitable const&) = delete;
367  
    read_some_awaitable& operator=(read_some_awaitable const&) = delete;
367  
    read_some_awaitable& operator=(read_some_awaitable&&)      = delete;
368  
    read_some_awaitable& operator=(read_some_awaitable&&)      = delete;
368  

369  

369  
    bool await_ready()
370  
    bool await_ready()
370  
    {
371  
    {
371  
        if (!m_->provide_.empty())
372  
        if (!m_->provide_.empty())
372  
        {
373  
        {
373  
            n_ = m_->consume_provide(buffers_);
374  
            n_ = m_->consume_provide(buffers_);
374  
            return true;
375  
            return true;
375  
        }
376  
        }
376  
        new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
377  
        new (&underlying_) sock_awaitable(m_->sock_.read_some(buffers_));
377  
        sync_ = false;
378  
        sync_ = false;
378  
        return underlying_.await_ready();
379  
        return underlying_.await_ready();
379  
    }
380  
    }
380  

381  

381  
    template<class... Args>
382  
    template<class... Args>
382  
    auto await_suspend(Args&&... args)
383  
    auto await_suspend(Args&&... args)
383  
    {
384  
    {
384  
        return underlying_.await_suspend(std::forward<Args>(args)...);
385  
        return underlying_.await_suspend(std::forward<Args>(args)...);
385  
    }
386  
    }
386  

387  

387  
    capy::io_result<std::size_t> await_resume()
388  
    capy::io_result<std::size_t> await_resume()
388  
    {
389  
    {
389  
        if (sync_)
390  
        if (sync_)
390  
            return {{}, n_};
391  
            return {{}, n_};
391  
        return underlying_.await_resume();
392  
        return underlying_.await_resume();
392  
    }
393  
    }
393  
};
394  
};
394  

395  

395  
template<class Socket>
396  
template<class Socket>
396  
template<class ConstBufferSequence>
397  
template<class ConstBufferSequence>
397  
class basic_mocket<Socket>::write_some_awaitable
398  
class basic_mocket<Socket>::write_some_awaitable
398  
{
399  
{
399  
    using sock_awaitable = decltype(std::declval<Socket&>().write_some(
400  
    using sock_awaitable = decltype(std::declval<Socket&>().write_some(
400  
        std::declval<ConstBufferSequence>()));
401  
        std::declval<ConstBufferSequence>()));
401  

402  

402  
    basic_mocket* m_;
403  
    basic_mocket* m_;
403  
    ConstBufferSequence buffers_;
404  
    ConstBufferSequence buffers_;
404  
    std::size_t n_ = 0;
405  
    std::size_t n_ = 0;
405  
    std::error_code ec_;
406  
    std::error_code ec_;
406  
    union
407  
    union
407  
    {
408  
    {
408  
        char dummy_;
409  
        char dummy_;
409  
        sock_awaitable underlying_;
410  
        sock_awaitable underlying_;
410  
    };
411  
    };
411  
    bool sync_ = true;
412  
    bool sync_ = true;
412  

413  

413  
public:
414  
public:
414  
    write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
415  
    write_some_awaitable(basic_mocket& m, ConstBufferSequence buffers) noexcept
415  
        : m_(&m)
416  
        : m_(&m)
416  
        , buffers_(std::move(buffers))
417  
        , buffers_(std::move(buffers))
417  
    {
418  
    {
418  
    }
419  
    }
419  

420  

420  
    ~write_some_awaitable()
421  
    ~write_some_awaitable()
421  
    {
422  
    {
422  
        if (!sync_)
423  
        if (!sync_)
423  
            underlying_.~sock_awaitable();
424  
            underlying_.~sock_awaitable();
424  
    }
425  
    }
425  

426  

426  
    write_some_awaitable(write_some_awaitable&& other) noexcept
427  
    write_some_awaitable(write_some_awaitable&& other) noexcept
427  
        : m_(other.m_)
428  
        : m_(other.m_)
428  
        , buffers_(std::move(other.buffers_))
429  
        , buffers_(std::move(other.buffers_))
429  
        , n_(other.n_)
430  
        , n_(other.n_)
430  
        , ec_(other.ec_)
431  
        , ec_(other.ec_)
431  
        , sync_(other.sync_)
432  
        , sync_(other.sync_)
432  
    {
433  
    {
433  
        if (!sync_)
434  
        if (!sync_)
434  
        {
435  
        {
435  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
436  
            new (&underlying_) sock_awaitable(std::move(other.underlying_));
436  
            other.underlying_.~sock_awaitable();
437  
            other.underlying_.~sock_awaitable();
437  
            other.sync_ = true;
438  
            other.sync_ = true;
438  
        }
439  
        }
439  
    }
440  
    }
440  

441  

441  
    write_some_awaitable(write_some_awaitable const&)            = delete;
442  
    write_some_awaitable(write_some_awaitable const&)            = delete;
442  
    write_some_awaitable& operator=(write_some_awaitable const&) = delete;
443  
    write_some_awaitable& operator=(write_some_awaitable const&) = delete;
443  
    write_some_awaitable& operator=(write_some_awaitable&&)      = delete;
444  
    write_some_awaitable& operator=(write_some_awaitable&&)      = delete;
444  

445  

445  
    bool await_ready()
446  
    bool await_ready()
446  
    {
447  
    {
447  
        if (!m_->expect_.empty())
448  
        if (!m_->expect_.empty())
448  
        {
449  
        {
449  
            if (!m_->validate_expect(buffers_, n_))
450  
            if (!m_->validate_expect(buffers_, n_))
450  
            {
451  
            {
451  
                ec_ = capy::error::test_failure;
452  
                ec_ = capy::error::test_failure;
452  
                n_  = 0;
453  
                n_  = 0;
453  
            }
454  
            }
454  
            return true;
455  
            return true;
455  
        }
456  
        }
456  
        new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
457  
        new (&underlying_) sock_awaitable(m_->sock_.write_some(buffers_));
457  
        sync_ = false;
458  
        sync_ = false;
458  
        return underlying_.await_ready();
459  
        return underlying_.await_ready();
459  
    }
460  
    }
460  

461  

461  
    template<class... Args>
462  
    template<class... Args>
462  
    auto await_suspend(Args&&... args)
463  
    auto await_suspend(Args&&... args)
463  
    {
464  
    {
464  
        return underlying_.await_suspend(std::forward<Args>(args)...);
465  
        return underlying_.await_suspend(std::forward<Args>(args)...);
465  
    }
466  
    }
466  

467  

467  
    capy::io_result<std::size_t> await_resume()
468  
    capy::io_result<std::size_t> await_resume()
468  
    {
469  
    {
469  
        if (sync_)
470  
        if (sync_)
470  
            return {ec_, n_};
471  
            return {ec_, n_};
471  
        return underlying_.await_resume();
472  
        return underlying_.await_resume();
472  
    }
473  
    }
473  
};
474  
};
474  

475  

475  
/** Create a mocket paired with a socket.
476  
/** Create a mocket paired with a socket.
476  

477  

477  
    Creates a mocket and a socket connected via loopback.
478  
    Creates a mocket and a socket connected via loopback.
478  
    Data written to one can be read from the other.
479  
    Data written to one can be read from the other.
479  

480  

480  
    The mocket has fuse checks enabled via `maybe_fail()` and
481  
    The mocket has fuse checks enabled via `maybe_fail()` and
481  
    supports provide/expect buffers for test instrumentation.
482  
    supports provide/expect buffers for test instrumentation.
482  
    The socket is the "peer" end with no test instrumentation.
483  
    The socket is the "peer" end with no test instrumentation.
483  

484  

484  
    Optional max_read_size and max_write_size parameters limit the
485  
    Optional max_read_size and max_write_size parameters limit the
485  
    number of bytes transferred per I/O operation on the mocket,
486  
    number of bytes transferred per I/O operation on the mocket,
486  
    simulating chunked network delivery for testing purposes.
487  
    simulating chunked network delivery for testing purposes.
487  

488  

488  
    @tparam Socket The socket type (default `tcp_socket`).
489  
    @tparam Socket The socket type (default `tcp_socket`).
489  
    @tparam Acceptor The acceptor type (default `tcp_acceptor`).
490  
    @tparam Acceptor The acceptor type (default `tcp_acceptor`).
490  

491  

491  
    @param ctx The I/O context for the sockets.
492  
    @param ctx The I/O context for the sockets.
492  
    @param f The fuse for error injection testing.
493  
    @param f The fuse for error injection testing.
493  
    @param max_read_size Maximum bytes per read operation (default unlimited).
494  
    @param max_read_size Maximum bytes per read operation (default unlimited).
494  
    @param max_write_size Maximum bytes per write operation (default unlimited).
495  
    @param max_write_size Maximum bytes per write operation (default unlimited).
495  

496  

496  
    @return A pair of (mocket, socket).
497  
    @return A pair of (mocket, socket).
497  

498  

498  
    @note Mockets are not thread-safe and must be used in a
499  
    @note Mockets are not thread-safe and must be used in a
499  
        single-threaded, deterministic context.
500  
        single-threaded, deterministic context.
500  
*/
501  
*/
501  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
502  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
502  
std::pair<basic_mocket<Socket>, Socket>
503  
std::pair<basic_mocket<Socket>, Socket>
503  
make_mocket_pair(
504  
make_mocket_pair(
504  
    io_context& ctx,
505  
    io_context& ctx,
505  
    capy::test::fuse f         = {},
506  
    capy::test::fuse f         = {},
506  
    std::size_t max_read_size  = std::size_t(-1),
507  
    std::size_t max_read_size  = std::size_t(-1),
507  
    std::size_t max_write_size = std::size_t(-1))
508  
    std::size_t max_write_size = std::size_t(-1))
508  
{
509  
{
509  
    auto ex = ctx.get_executor();
510  
    auto ex = ctx.get_executor();
510  

511  

511  
    basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size);
512  
    basic_mocket<Socket> m(ctx, std::move(f), max_read_size, max_write_size);
512  

513  

513  
    Socket peer(ctx);
514  
    Socket peer(ctx);
514  

515  

515  
    std::error_code accept_ec;
516  
    std::error_code accept_ec;
516  
    std::error_code connect_ec;
517  
    std::error_code connect_ec;
517  
    bool accept_done  = false;
518  
    bool accept_done  = false;
518  
    bool connect_done = false;
519  
    bool connect_done = false;
519  

520  

520  
    Acceptor acc(ctx);
521  
    Acceptor acc(ctx);
521 -
    auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
522 +
    acc.open();
522 -
    if (listen_ec)
523 +
    acc.set_option(socket_option::reuse_address(true));
 
524 +
    if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
 
525 +
        throw std::runtime_error(
 
526 +
            "mocket bind failed: " + bind_ec.message());
 
527 +
    if (auto listen_ec = acc.listen())
523  
        throw std::runtime_error(
528  
        throw std::runtime_error(
524  
            "mocket listen failed: " + listen_ec.message());
529  
            "mocket listen failed: " + listen_ec.message());
525  
    auto port = acc.local_endpoint().port();
530  
    auto port = acc.local_endpoint().port();
526  

531  

527  
    peer.open();
532  
    peer.open();
528  

533  

529  
    Socket accepted_socket(ctx);
534  
    Socket accepted_socket(ctx);
530  

535  

531  
    capy::run_async(ex)(
536  
    capy::run_async(ex)(
532  
        [](Acceptor& a, Socket& s, std::error_code& ec_out,
537  
        [](Acceptor& a, Socket& s, std::error_code& ec_out,
533  
           bool& done_out) -> capy::task<> {
538  
           bool& done_out) -> capy::task<> {
534  
            auto [ec] = co_await a.accept(s);
539  
            auto [ec] = co_await a.accept(s);
535  
            ec_out    = ec;
540  
            ec_out    = ec;
536  
            done_out  = true;
541  
            done_out  = true;
537  
        }(acc, accepted_socket, accept_ec, accept_done));
542  
        }(acc, accepted_socket, accept_ec, accept_done));
538  

543  

539  
    capy::run_async(ex)(
544  
    capy::run_async(ex)(
540  
        [](Socket& s, endpoint ep, std::error_code& ec_out,
545  
        [](Socket& s, endpoint ep, std::error_code& ec_out,
541  
           bool& done_out) -> capy::task<> {
546  
           bool& done_out) -> capy::task<> {
542  
            auto [ec] = co_await s.connect(ep);
547  
            auto [ec] = co_await s.connect(ep);
543  
            ec_out    = ec;
548  
            ec_out    = ec;
544  
            done_out  = true;
549  
            done_out  = true;
545  
        }(peer, endpoint(ipv4_address::loopback(), port), connect_ec,
550  
        }(peer, endpoint(ipv4_address::loopback(), port), connect_ec,
546  
                           connect_done));
551  
                           connect_done));
547  

552  

548  
    ctx.run();
553  
    ctx.run();
549  
    ctx.restart();
554  
    ctx.restart();
550  

555  

551  
    if (!accept_done || accept_ec)
556  
    if (!accept_done || accept_ec)
552  
    {
557  
    {
553  
        std::fprintf(
558  
        std::fprintf(
554  
            stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
559  
            stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
555  
            accept_done, accept_ec.message().c_str());
560  
            accept_done, accept_ec.message().c_str());
556  
        acc.close();
561  
        acc.close();
557  
        throw std::runtime_error("mocket accept failed");
562  
        throw std::runtime_error("mocket accept failed");
558  
    }
563  
    }
559  

564  

560  
    if (!connect_done || connect_ec)
565  
    if (!connect_done || connect_ec)
561  
    {
566  
    {
562  
        std::fprintf(
567  
        std::fprintf(
563  
            stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
568  
            stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
564  
            connect_done, connect_ec.message().c_str());
569  
            connect_done, connect_ec.message().c_str());
565  
        acc.close();
570  
        acc.close();
566  
        accepted_socket.close();
571  
        accepted_socket.close();
567  
        throw std::runtime_error("mocket connect failed");
572  
        throw std::runtime_error("mocket connect failed");
568  
    }
573  
    }
569  

574  

570  
    m.socket() = std::move(accepted_socket);
575  
    m.socket() = std::move(accepted_socket);
571  

576  

572  
    acc.close();
577  
    acc.close();
573  

578  

574  
    return {std::move(m), std::move(peer)};
579  
    return {std::move(m), std::move(peer)};
575  
}
580  
}
576  

581  

577  
} // namespace boost::corosio::test
582  
} // namespace boost::corosio::test
578  

583  

579  
#endif
584  
#endif