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