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

25  

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

33  

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

35  

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

37  

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

42  

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

47  

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

49  

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

51  

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

56  

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

68  

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

71  

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

75  

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

79  

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

82  

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

86  

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

88  

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

109  

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

121  

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

137  

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

140  

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

142  

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

149  

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

151  

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

158  

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

160  

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

164  

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

166  

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

173  

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

175  

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

180  

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

182  

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

189  

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

191  

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

196  

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

204  

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

217  

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

221  

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

223  

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

231  

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

233  

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

240  

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

242  

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

246  

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

248  

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

256  

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

258  

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

262  

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

264  

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

273  

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

276  

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

288  

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

297  

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

305  

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

314  

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

320  

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

327  

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

337  

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

344  

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

350  

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

364  

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

368  

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

380  

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

386  

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

394  

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

401  

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

412  

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

419  

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

425  

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

440  

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

444  

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

460  

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

466  

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

474  

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

476  

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

479  

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

483  

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

487  

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

490  

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

495  

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

497  

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

510  

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

512  

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

514  

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

519  

520  
    Acceptor acc(ctx);
520  
    Acceptor acc(ctx);
521  
    auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
521  
    auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
522  
    if (listen_ec)
522  
    if (listen_ec)
523  
        throw std::runtime_error(
523  
        throw std::runtime_error(
524  
            "mocket listen failed: " + listen_ec.message());
524  
            "mocket listen failed: " + listen_ec.message());
525  
    auto port = acc.local_endpoint().port();
525  
    auto port = acc.local_endpoint().port();
526  

526  

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

528  

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

530  

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

538  

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

547  

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

550  

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

559  

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

569  

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

571  

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

573  

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

576  

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

578  

579  
#endif
579  
#endif