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

26  

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

34  

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

36  

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

38  

39  
    This class provides a testable socket-like interface where data
39  
    This class provides a testable socket-like interface where data
40  
    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
41  
    writes. A mocket is paired with a regular socket using
41  
    writes. A mocket is paired with a regular socket using
42  
    @ref make_mocket_pair, allowing bidirectional communication testing.
42  
    @ref make_mocket_pair, allowing bidirectional communication testing.
43  

43  

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

48  

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

50  

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

52  

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

57  

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

69  

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

72  

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

76  

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

80  

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

83  

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

87  

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

89  

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

110  

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

122  

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

138  

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

141  

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

143  

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

150  

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

152  

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

159  

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

161  

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

165  

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

167  

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

174  

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

176  

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

181  

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

183  

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

190  

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

192  

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

197  

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

205  

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

218  

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

222  

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

224  

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

232  

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

234  

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

241  

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

243  

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

247  

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

249  

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

257  

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

259  

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

263  

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

265  

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

274  

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

277  

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

289  

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

298  

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

306  

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

315  

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

321  

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

328  

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

338  

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

345  

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

351  

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

365  

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

369  

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

381  

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

387  

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

395  

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

402  

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

413  

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

420  

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

426  

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

441  

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

445  

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

461  

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

467  

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

475  

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

477  

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

480  

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

484  

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

488  

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

491  

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

496  

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

498  

499  
    @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
500  
        single-threaded, deterministic context.
500  
        single-threaded, deterministic context.
501  
*/
501  
*/
502  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
502  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
503  
std::pair<basic_mocket<Socket>, Socket>
503  
std::pair<basic_mocket<Socket>, Socket>
504  
make_mocket_pair(
504  
make_mocket_pair(
505  
    io_context& ctx,
505  
    io_context& ctx,
506  
    capy::test::fuse f         = {},
506  
    capy::test::fuse f         = {},
507  
    std::size_t max_read_size  = std::size_t(-1),
507  
    std::size_t max_read_size  = std::size_t(-1),
508  
    std::size_t max_write_size = std::size_t(-1))
508  
    std::size_t max_write_size = std::size_t(-1))
509  
{
509  
{
510  
    auto ex = ctx.get_executor();
510  
    auto ex = ctx.get_executor();
511  

511  

512  
    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);
513  

513  

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

515  

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

520  

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

531  

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

533  

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

535  

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

543  

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

552  

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

555  

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

564  

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

574  

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

576  

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

578  

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

581  

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

583  

584  
#endif
584  
#endif