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/task.hpp>
20 :
21 : #include <cstdio>
22 : #include <stdexcept>
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.
35 :
36 : @tparam Socket The socket type (default `tcp_socket`).
37 : @tparam Acceptor The acceptor type (default `tcp_acceptor`).
38 : @tparam Linger Whether to enable `SO_LINGER` with a zero timeout
39 : on both sockets (default `true`).
40 :
41 : @param ctx The I/O context for the sockets.
42 :
43 : @return A pair of connected sockets.
44 :
45 : @throws std::runtime_error if opening, binding, listening,
46 : accepting, or connecting fails.
47 : */
48 : template<
49 : class Socket = tcp_socket,
50 : class Acceptor = tcp_acceptor,
51 : bool Linger = true>
52 : std::pair<Socket, Socket>
53 HIT 913 : make_socket_pair(io_context& ctx)
54 : {
55 913 : auto ex = ctx.get_executor();
56 :
57 913 : std::error_code accept_ec;
58 913 : std::error_code connect_ec;
59 913 : bool accept_done = false;
60 913 : bool connect_done = false;
61 :
62 913 : Acceptor acc(ctx);
63 913 : if (auto open_ec = acc.open())
64 MIS 0 : throw std::runtime_error(
65 : "socket_pair open failed: " + open_ec.message());
66 HIT 913 : acc.set_option(socket_option::reuse_address(true));
67 913 : if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)))
68 MIS 0 : throw std::runtime_error("socket_pair bind failed: " + ec.message());
69 HIT 913 : if (auto ec = acc.listen())
70 MIS 0 : throw std::runtime_error("socket_pair listen failed: " + ec.message());
71 HIT 913 : auto port = acc.local_endpoint().port();
72 :
73 913 : Socket s1(ctx);
74 913 : Socket s2(ctx);
75 913 : if (auto open_ec = s2.open())
76 MIS 0 : throw std::runtime_error(
77 : "socket_pair open failed: " + open_ec.message());
78 :
79 HIT 913 : capy::run_async(ex)(
80 1826 : [](Acceptor& a, Socket& s, std::error_code& ec_out,
81 : bool& done_out) -> capy::task<> {
82 : auto [ec] = co_await a.accept(s);
83 : ec_out = ec;
84 : done_out = true;
85 : }(acc, s1, accept_ec, accept_done));
86 :
87 : // A failed connect leaves nothing for the accept to receive.
88 1826 : capy::run_async(ex)(
89 913 : [](Socket& s, Acceptor& a, endpoint ep, std::error_code& ec_out,
90 : bool& done_out) -> capy::task<> {
91 : auto [ec] = co_await s.connect(ep);
92 : ec_out = ec;
93 : done_out = true;
94 : if (ec)
95 : a.cancel();
96 1826 : }(s2, acc, endpoint(ipv4_address::loopback(), port), connect_ec,
97 : connect_done));
98 :
99 : // Both coroutines write to locals here, so they must finish before
100 : // this returns or throws, even on a context something stopped.
101 1827 : while (!accept_done || !connect_done)
102 : {
103 914 : ctx.run();
104 914 : ctx.restart();
105 : }
106 :
107 913 : if (connect_ec)
108 : {
109 5 : std::fprintf(
110 : stderr, "socket_pair: connect failed: %s\n",
111 : connect_ec.message().c_str());
112 5 : acc.close();
113 5 : s1.close();
114 5 : throw std::runtime_error("socket_pair connect failed");
115 : }
116 :
117 908 : if (accept_ec)
118 : {
119 MIS 0 : std::fprintf(
120 : stderr, "socket_pair: accept failed: %s\n",
121 : accept_ec.message().c_str());
122 0 : acc.close();
123 0 : throw std::runtime_error("socket_pair accept failed");
124 : }
125 :
126 HIT 908 : acc.close();
127 :
128 : if constexpr (Linger)
129 : {
130 118 : s1.set_option(socket_option::linger(true, 0));
131 118 : s2.set_option(socket_option::linger(true, 0));
132 : }
133 :
134 1816 : return {std::move(s1), std::move(s2)};
135 923 : }
136 :
137 : } // namespace boost::corosio::test
138 :
139 : #endif
|