Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/corosio
8 : //
9 :
10 : #include <boost/corosio/test/mocket.hpp>
11 : #include <boost/corosio/tcp_acceptor.hpp>
12 : #include <boost/corosio/detail/config.hpp>
13 : #include <boost/corosio/detail/except.hpp>
14 : #include <boost/corosio/io_context.hpp>
15 : #include <boost/capy/buffers/slice.hpp>
16 : #include <boost/capy/ex/run_async.hpp>
17 : #include <boost/capy/task.hpp>
18 : #include <boost/capy/test/fuse.hpp>
19 :
20 : #include <algorithm>
21 : #include <cstdio>
22 : #include <cstring>
23 :
24 : namespace boost::corosio::test {
25 :
26 : //------------------------------------------------------------------------------
27 :
28 8 : mocket::
29 : ~mocket() = default;
30 :
31 4 : mocket::
32 : mocket(
33 : capy::execution_context& ctx,
34 : capy::test::fuse& f,
35 : std::size_t max_read_size,
36 4 : std::size_t max_write_size)
37 4 : : sock_(ctx)
38 4 : , fuse_(&f)
39 4 : , max_read_size_(max_read_size)
40 4 : , max_write_size_(max_write_size)
41 : {
42 4 : if (max_read_size == 0)
43 0 : detail::throw_logic_error("mocket: max_read_size cannot be 0");
44 4 : if (max_write_size == 0)
45 0 : detail::throw_logic_error("mocket: max_write_size cannot be 0");
46 4 : }
47 :
48 4 : mocket::
49 4 : mocket(mocket&& other) noexcept
50 4 : : sock_(std::move(other.sock_))
51 4 : , provide_(std::move(other.provide_))
52 4 : , expect_(std::move(other.expect_))
53 4 : , fuse_(other.fuse_)
54 4 : , max_read_size_(other.max_read_size_)
55 4 : , max_write_size_(other.max_write_size_)
56 : {
57 4 : other.fuse_ = nullptr;
58 4 : }
59 :
60 : mocket&
61 0 : mocket::
62 : operator=(mocket&& other) noexcept
63 : {
64 0 : if (this != &other)
65 : {
66 0 : sock_ = std::move(other.sock_);
67 0 : provide_ = std::move(other.provide_);
68 0 : expect_ = std::move(other.expect_);
69 0 : fuse_ = other.fuse_;
70 0 : max_read_size_ = other.max_read_size_;
71 0 : max_write_size_ = other.max_write_size_;
72 0 : other.fuse_ = nullptr;
73 : }
74 0 : return *this;
75 : }
76 :
77 : void
78 2 : mocket::
79 : provide(std::string s)
80 : {
81 2 : provide_.append(std::move(s));
82 2 : }
83 :
84 : void
85 2 : mocket::
86 : expect(std::string s)
87 : {
88 2 : expect_.append(std::move(s));
89 2 : }
90 :
91 : std::error_code
92 4 : mocket::
93 : close()
94 : {
95 4 : if (!sock_.is_open())
96 0 : return {};
97 :
98 : // Verify test expectations
99 4 : if (!expect_.empty())
100 : {
101 1 : fuse_->fail();
102 1 : sock_.close();
103 1 : return capy::error::test_failure;
104 : }
105 3 : if (!provide_.empty())
106 : {
107 1 : fuse_->fail();
108 1 : sock_.close();
109 1 : return capy::error::test_failure;
110 : }
111 :
112 2 : sock_.close();
113 2 : return {};
114 : }
115 :
116 : void
117 0 : mocket::
118 : cancel()
119 : {
120 0 : sock_.cancel();
121 0 : }
122 :
123 : bool
124 1 : mocket::
125 : is_open() const noexcept
126 : {
127 1 : return sock_.is_open();
128 : }
129 :
130 : //------------------------------------------------------------------------------
131 :
132 : std::pair<mocket, tcp_socket>
133 4 : make_mocket_pair(
134 : capy::execution_context& ctx,
135 : capy::test::fuse& f,
136 : std::size_t max_read_size,
137 : std::size_t max_write_size)
138 : {
139 4 : auto& ioc = static_cast<io_context&>(ctx);
140 4 : auto ex = ioc.get_executor();
141 :
142 : // Create the mocket
143 4 : mocket m(ctx, f, max_read_size, max_write_size);
144 :
145 : // Create the peer socket
146 4 : tcp_socket peer(ctx);
147 :
148 4 : std::error_code accept_ec;
149 4 : std::error_code connect_ec;
150 4 : bool accept_done = false;
151 4 : bool connect_done = false;
152 :
153 : // Use ephemeral port (0) - OS assigns an available port
154 4 : tcp_acceptor acc(ctx);
155 4 : auto listen_ec = acc.listen(endpoint(ipv4_address::loopback(), 0));
156 4 : if (listen_ec)
157 0 : throw std::runtime_error("mocket listen failed: " + listen_ec.message());
158 4 : auto port = acc.local_endpoint().port();
159 :
160 : // Open peer socket for connect
161 4 : peer.open();
162 :
163 : // Create a tcp_socket to receive the accepted connection
164 4 : tcp_socket accepted_socket(ctx);
165 :
166 : // Launch accept operation
167 8 : capy::run_async(ex)(
168 4 : [](tcp_acceptor& a, tcp_socket& s,
169 : std::error_code& ec_out, bool& done_out) -> capy::task<>
170 : {
171 : auto [ec] = co_await a.accept(s);
172 : ec_out = ec;
173 : done_out = true;
174 16 : }(acc, accepted_socket, accept_ec, accept_done));
175 :
176 : // Launch connect operation
177 8 : capy::run_async(ex)(
178 4 : [](tcp_socket& s, endpoint ep,
179 : std::error_code& ec_out, bool& done_out) -> capy::task<>
180 : {
181 : auto [ec] = co_await s.connect(ep);
182 : ec_out = ec;
183 : done_out = true;
184 16 : }(peer, endpoint(ipv4_address::loopback(), port),
185 : connect_ec, connect_done));
186 :
187 : // Run until both complete
188 4 : ioc.run();
189 4 : ioc.restart();
190 :
191 : // Check for errors
192 4 : if (!accept_done || accept_ec)
193 : {
194 0 : std::fprintf(stderr, "make_mocket_pair: accept failed (done=%d, ec=%s)\n",
195 0 : accept_done, accept_ec.message().c_str());
196 0 : acc.close();
197 0 : throw std::runtime_error("mocket accept failed");
198 : }
199 :
200 4 : if (!connect_done || connect_ec)
201 : {
202 0 : std::fprintf(stderr, "make_mocket_pair: connect failed (done=%d, ec=%s)\n",
203 0 : connect_done, connect_ec.message().c_str());
204 0 : acc.close();
205 0 : accepted_socket.close();
206 0 : throw std::runtime_error("mocket connect failed");
207 : }
208 :
209 : // Transfer the accepted socket to mocket
210 4 : m.socket() = std::move(accepted_socket);
211 :
212 4 : acc.close();
213 :
214 8 : return {std::move(m), std::move(peer)};
215 4 : }
216 :
217 : } // namespace boost::corosio::test
|