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/socket_option.hpp>
 
16  
#include <boost/corosio/io_context.hpp>
15  
#include <boost/corosio/io_context.hpp>
17  
#include <boost/corosio/tcp_acceptor.hpp>
16  
#include <boost/corosio/tcp_acceptor.hpp>
18  
#include <boost/corosio/tcp_socket.hpp>
17  
#include <boost/corosio/tcp_socket.hpp>
19  
#include <boost/capy/buffers/buffer_copy.hpp>
18  
#include <boost/capy/buffers/buffer_copy.hpp>
20  
#include <boost/capy/buffers/make_buffer.hpp>
19  
#include <boost/capy/buffers/make_buffer.hpp>
21  
#include <boost/capy/error.hpp>
20  
#include <boost/capy/error.hpp>
22  
#include <boost/capy/ex/run_async.hpp>
21  
#include <boost/capy/ex/run_async.hpp>
23  
#include <boost/capy/io_result.hpp>
22  
#include <boost/capy/io_result.hpp>
24  
#include <boost/capy/task.hpp>
23  
#include <boost/capy/task.hpp>
25  
#include <boost/capy/test/fuse.hpp>
24  
#include <boost/capy/test/fuse.hpp>
26  

25  

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

33  

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

35  

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

37  

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

42  

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

47  

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

49  

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

51  

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

56  

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

68  

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

71  

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

75  

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

79  

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

82  

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

86  

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

88  

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

109  

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

121  

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

137  

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

140  

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

142  

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

149  

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

151  

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

158  

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

160  

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

164  

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

166  

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

173  

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

175  

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

180  

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

182  

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

189  

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

191  

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

196  

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

204  

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

217  

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

221  

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

223  

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

231  

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

233  

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

240  

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

242  

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

246  

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

248  

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

256  

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

258  

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

262  

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

264  

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

273  

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

276  

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

288  

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

297  

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

305  

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

314  

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

320  

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

327  

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

337  

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

344  

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

350  

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

364  

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

368  

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

380  

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

386  

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

394  

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

401  

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

412  

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

419  

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

425  

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

440  

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

444  

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

460  

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

466  

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

474  

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

476  

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

479  

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

483  

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

487  

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

490  

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

495  

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

497  

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

510  

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

512  

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

514  

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

519  

521  
    Acceptor acc(ctx);
520  
    Acceptor acc(ctx);
522 -
    acc.open();
521 +
    auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
523 -
    acc.set_option(socket_option::reuse_address(true));
522 +
    if (listen_ec)
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())
 
528  
        throw std::runtime_error(
523  
        throw std::runtime_error(
529  
            "mocket listen failed: " + listen_ec.message());
524  
            "mocket listen failed: " + listen_ec.message());
530  
    auto port = acc.local_endpoint().port();
525  
    auto port = acc.local_endpoint().port();
531  

526  

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

528  

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

530  

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

538  

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

547  

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

550  

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

559  

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

569  

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

571  

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

573  

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

576  

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

578  

584  
#endif
579  
#endif