TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : //
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)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_TEST_SOCKET_PAIR_HPP
12 : #define BOOST_COROSIO_TEST_SOCKET_PAIR_HPP
13 :
14 : #include <boost/corosio/io_context.hpp>
15 : #include <boost/corosio/tcp_acceptor.hpp>
16 : #include <boost/corosio/tcp_socket.hpp>
17 : #include <boost/corosio/socket_option.hpp>
18 : #include <boost/capy/ex/run_async.hpp>
19 : #include <boost/capy/results.hpp>
20 : #include <boost/capy/task.hpp>
21 :
22 : #include <cstdio>
23 : #include <system_error>
24 : #include <utility>
25 :
26 : namespace boost::corosio::test {
27 :
28 : /** Create a connected pair of sockets.
29 :
30 : Creates two sockets connected via loopback TCP sockets.
31 : Data written to one socket can be read from the other.
32 :
33 : Runs `ctx` until the connection is made, restarting it first if
34 : it was stopped, and leaves it restarted. A failure before the
35 : connection attempt returns without running `ctx`, so its state is
36 : unchanged.
37 :
38 : On failure the returned sockets are closed and the error code
39 : is that of the step that failed.
40 :
41 : @tparam Socket The socket type (default `tcp_socket`).
42 : @tparam Acceptor The acceptor type (default `tcp_acceptor`).
43 : @tparam Linger Whether to enable `SO_LINGER` with a zero timeout
44 : on both sockets (default `true`).
45 :
46 : @param ctx The I/O context for the sockets.
47 :
48 : @return The error code and the connected sockets.
49 : */
50 : template<
51 : class Socket = tcp_socket,
52 : class Acceptor = tcp_acceptor,
53 : bool Linger = true>
54 : [[nodiscard]] capy::results<Socket, Socket>
55 HIT 942 : make_socket_pair(io_context& ctx)
56 : {
57 942 : auto ex = ctx.get_executor();
58 :
59 942 : Socket s1(ctx);
60 942 : Socket s2(ctx);
61 :
62 967 : auto fail = [&](std::error_code ec) -> capy::results<Socket, Socket> {
63 25 : s1.close();
64 25 : s2.close();
65 25 : return {ec, std::move(s1), std::move(s2)};
66 : };
67 :
68 942 : std::error_code accept_ec;
69 942 : std::error_code connect_ec;
70 942 : bool accept_done = false;
71 942 : bool connect_done = false;
72 :
73 942 : Acceptor acc(ctx);
74 942 : if (auto ec = acc.open())
75 5 : return fail(ec);
76 937 : if (auto ec = acc.set_option(socket_option::reuse_address(true)))
77 5 : return fail(ec);
78 932 : if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
79 MIS 0 : return fail(ec);
80 HIT 932 : if (auto ec = acc.listen())
81 MIS 0 : return fail(ec);
82 HIT 932 : auto port = acc.local_endpoint().port();
83 :
84 932 : if (auto ec = s2.open())
85 MIS 0 : return fail(ec);
86 :
87 HIT 932 : capy::run_async(ex)(
88 1864 : [](Acceptor& a, Socket& s, std::error_code& ec_out,
89 : bool& done_out) -> capy::task<> {
90 : auto [ec] = co_await a.accept(s);
91 : ec_out = ec;
92 : done_out = true;
93 : }(acc, s1, accept_ec, accept_done));
94 :
95 : // A failed connect leaves nothing for the accept to receive.
96 1864 : capy::run_async(ex)(
97 932 : [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out,
98 : bool& done_out) -> capy::task<> {
99 : auto [ec] = co_await s.connect(ep);
100 : ec_out = ec;
101 : done_out = true;
102 : if (ec)
103 : a.cancel();
104 1864 : }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec,
105 : connect_done));
106 :
107 : // Both coroutines write to locals here, so they must finish before
108 : // this returns, even on a context something stopped.
109 1865 : while (!accept_done || !connect_done)
110 : {
111 933 : ctx.run();
112 933 : ctx.restart();
113 : }
114 :
115 932 : if (connect_ec)
116 : {
117 5 : std::fprintf(
118 : stderr, "socket_pair: connect failed: %s\n",
119 : connect_ec.message().c_str());
120 5 : acc.close();
121 5 : return fail(connect_ec);
122 : }
123 :
124 927 : if (accept_ec)
125 : {
126 MIS 0 : std::fprintf(
127 : stderr, "socket_pair: accept failed: %s\n",
128 : accept_ec.message().c_str());
129 0 : acc.close();
130 0 : return fail(accept_ec);
131 : }
132 :
133 HIT 927 : acc.close();
134 :
135 : if constexpr (Linger)
136 : {
137 137 : if (auto ec = s1.set_option(socket_option::linger(true, 0)))
138 5 : return fail(ec);
139 132 : if (auto ec = s2.set_option(socket_option::linger(true, 0)))
140 5 : return fail(ec);
141 : }
142 :
143 917 : return {std::error_code{}, std::move(s1), std::move(s2)};
144 942 : }
145 :
146 : } // namespace boost::corosio::test
147 :
148 : #endif
|