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/capy/ex/run_async.hpp>
17  
#include <boost/capy/ex/run_async.hpp>
18  
#include <boost/capy/task.hpp>
18  
#include <boost/capy/task.hpp>
19  

19  

20  
#include <cstdio>
20  
#include <cstdio>
21  
#include <stdexcept>
21  
#include <stdexcept>
22  
#include <system_error>
22  
#include <system_error>
23  
#include <utility>
23  
#include <utility>
24  

24  

25  
namespace boost::corosio::test {
25  
namespace boost::corosio::test {
26  

26  

27  
/** Create a connected pair of sockets.
27  
/** Create a connected pair of sockets.
28  

28  

29  
    Creates two sockets connected via loopback TCP sockets.
29  
    Creates two sockets connected via loopback TCP sockets.
30  
    Data written to one socket can be read from the other.
30  
    Data written to one socket can be read from the other.
31  

31  

32  
    @tparam Socket The socket type (default `tcp_socket`).
32  
    @tparam Socket The socket type (default `tcp_socket`).
33  
    @tparam Acceptor The acceptor type (default `tcp_acceptor`).
33  
    @tparam Acceptor The acceptor type (default `tcp_acceptor`).
34  

34  

35  
    @param ctx The I/O context for the sockets.
35  
    @param ctx The I/O context for the sockets.
36  

36  

37  
    @return A pair of connected sockets.
37  
    @return A pair of connected sockets.
38  
*/
38  
*/
39  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
39  
template<class Socket = tcp_socket, class Acceptor = tcp_acceptor>
40  
std::pair<Socket, Socket>
40  
std::pair<Socket, Socket>
41  
make_socket_pair(io_context& ctx)
41  
make_socket_pair(io_context& ctx)
42  
{
42  
{
43  
    auto ex = ctx.get_executor();
43  
    auto ex = ctx.get_executor();
44  

44  

45  
    std::error_code accept_ec;
45  
    std::error_code accept_ec;
46  
    std::error_code connect_ec;
46  
    std::error_code connect_ec;
47  
    bool accept_done  = false;
47  
    bool accept_done  = false;
48  
    bool connect_done = false;
48  
    bool connect_done = false;
49  

49  

50  
    Acceptor acc(ctx);
50  
    Acceptor acc(ctx);
51  
    if (auto ec = acc.listen(endpoint(ipv4_address::loopback(), 0)))
51  
    if (auto ec = acc.listen(endpoint(ipv4_address::loopback(), 0)))
52  
        throw std::runtime_error("socket_pair listen failed: " + ec.message());
52  
        throw std::runtime_error("socket_pair listen failed: " + ec.message());
53  
    auto port = acc.local_endpoint().port();
53  
    auto port = acc.local_endpoint().port();
54  

54  

55  
    Socket s1(ctx);
55  
    Socket s1(ctx);
56  
    Socket s2(ctx);
56  
    Socket s2(ctx);
57  
    s2.open();
57  
    s2.open();
58  

58  

59  
    capy::run_async(ex)(
59  
    capy::run_async(ex)(
60  
        [](Acceptor& a, Socket& s, std::error_code& ec_out,
60  
        [](Acceptor& a, Socket& s, std::error_code& ec_out,
61  
           bool& done_out) -> capy::task<> {
61  
           bool& done_out) -> capy::task<> {
62  
            auto [ec] = co_await a.accept(s);
62  
            auto [ec] = co_await a.accept(s);
63  
            ec_out    = ec;
63  
            ec_out    = ec;
64  
            done_out  = true;
64  
            done_out  = true;
65  
        }(acc, s1, accept_ec, accept_done));
65  
        }(acc, s1, accept_ec, accept_done));
66  

66  

67  
    capy::run_async(ex)(
67  
    capy::run_async(ex)(
68  
        [](Socket& s, endpoint ep, std::error_code& ec_out,
68  
        [](Socket& s, endpoint ep, std::error_code& ec_out,
69  
           bool& done_out) -> capy::task<> {
69  
           bool& done_out) -> capy::task<> {
70  
            auto [ec] = co_await s.connect(ep);
70  
            auto [ec] = co_await s.connect(ep);
71  
            ec_out    = ec;
71  
            ec_out    = ec;
72  
            done_out  = true;
72  
            done_out  = true;
73  
        }(s2, endpoint(ipv4_address::loopback(), port), connect_ec,
73  
        }(s2, endpoint(ipv4_address::loopback(), port), connect_ec,
74  
                           connect_done));
74  
                           connect_done));
75  

75  

76  
    ctx.run();
76  
    ctx.run();
77  
    ctx.restart();
77  
    ctx.restart();
78  

78  

79  
    if (!accept_done || accept_ec)
79  
    if (!accept_done || accept_ec)
80  
    {
80  
    {
81  
        std::fprintf(
81  
        std::fprintf(
82  
            stderr, "socket_pair: accept failed (done=%d, ec=%s)\n",
82  
            stderr, "socket_pair: accept failed (done=%d, ec=%s)\n",
83  
            accept_done, accept_ec.message().c_str());
83  
            accept_done, accept_ec.message().c_str());
84  
        acc.close();
84  
        acc.close();
85  
        throw std::runtime_error("socket_pair accept failed");
85  
        throw std::runtime_error("socket_pair accept failed");
86  
    }
86  
    }
87  

87  

88  
    if (!connect_done || connect_ec)
88  
    if (!connect_done || connect_ec)
89  
    {
89  
    {
90  
        std::fprintf(
90  
        std::fprintf(
91  
            stderr, "socket_pair: connect failed (done=%d, ec=%s)\n",
91  
            stderr, "socket_pair: connect failed (done=%d, ec=%s)\n",
92  
            connect_done, connect_ec.message().c_str());
92  
            connect_done, connect_ec.message().c_str());
93  
        acc.close();
93  
        acc.close();
94  
        s1.close();
94  
        s1.close();
95  
        throw std::runtime_error("socket_pair connect failed");
95  
        throw std::runtime_error("socket_pair connect failed");
96  
    }
96  
    }
97  

97  

98  
    acc.close();
98  
    acc.close();
99  

99  

100  
    s1.set_linger(true, 0);
100  
    s1.set_linger(true, 0);
101  
    s2.set_linger(true, 0);
101  
    s2.set_linger(true, 0);
102  

102  

103  
    return {std::move(s1), std::move(s2)};
103  
    return {std::move(s1), std::move(s2)};
104  
}
104  
}
105  

105  

106  
} // namespace boost::corosio::test
106  
} // namespace boost::corosio::test
107  

107  

108  
#endif
108  
#endif