• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jbaldwin / libcoro / 21525305491

30 Jan 2026 05:51PM UTC coverage: 87.069%. First build
21525305491

Pull #439

github

web-flow
Merge 296243c5e into 4c8566d3a
Pull Request #439: "One `endpoint` to rule them all" & UB fix

86 of 95 new or added lines in 10 files covered. (90.53%)

1791 of 2057 relevant lines covered (87.07%)

4855564.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.85
/src/net/tls/client.cpp
1
#ifdef LIBCORO_FEATURE_TLS
2

3
    #include "coro/net/tls/client.hpp"
4
    #include "coro/sync_wait.hpp"
5

6
namespace coro::net::tls
7
{
8
using namespace std::chrono_literals;
9

10
client::client(
95✔
11
    std::unique_ptr<coro::io_scheduler>& scheduler, std::shared_ptr<context> tls_ctx, const net::endpoint& endpoint)
95✔
12
    : m_io_scheduler(scheduler.get()),
95✔
13
      m_tls_ctx(std::move(tls_ctx)),
87✔
14
      m_endpoint(endpoint),
90✔
15
      m_socket(
90✔
16
          net::make_socket(
17
              net::socket::options{net::socket::type_t::tcp, net::socket::blocking_t::no}, endpoint.domain()))
92✔
18
{
19
    if (m_io_scheduler == nullptr)
94✔
20
    {
21
        throw std::runtime_error{"tls::client cannot have nullptr io_scheduler"};
×
22
    }
23

24
    if (m_tls_ctx == nullptr)
94✔
25
    {
26
        throw std::runtime_error{"tls::client cannot have nullptr tls_ctx"};
×
27
    }
28
}
93✔
29

30
client::client(
101✔
31
    coro::io_scheduler* scheduler, std::shared_ptr<context> tls_ctx, net::socket socket, const net::endpoint& endpoint)
101✔
32
    : m_io_scheduler(scheduler),
101✔
33
      m_tls_ctx(std::move(tls_ctx)),
101✔
34
      m_endpoint(endpoint),
101✔
35
      m_socket(std::move(socket)),
101✔
36
      m_connect_status(connection_status::connected),
101✔
37
      m_tls_info(tls_connection_type::accept)
101✔
38
{
39
    // io_scheduler is assumed good since it comes from a tls::server.
40
    // tls_ctx is assumed good since it comes from a tls::server.
41

42
    // Force the socket to be non-blocking.
43
    m_socket.blocking(coro::net::socket::blocking_t::no);
101✔
44
}
101✔
45

46
client::client(client&& other) noexcept
402✔
47
    : m_io_scheduler(std::exchange(other.m_io_scheduler, nullptr)),
402✔
48
      m_tls_ctx(std::move(other.m_tls_ctx)),
402✔
49
      m_endpoint(std::move(other.m_endpoint)),
402✔
50
      m_socket(std::move(other.m_socket)),
402✔
51
      m_connect_status(std::exchange(other.m_connect_status, std::nullopt)),
402✔
52
      m_tls_info(std::move(other.m_tls_info))
402✔
53
{
54
}
402✔
55

56
client::~client()
604✔
57
{
58
    // If the user didn't shutdown the client block on shutting down to clean up resources.
59
    if (!m_shutdown.load(std::memory_order::acquire))
604✔
60
    {
61
        coro::sync_wait(shutdown(std::chrono::seconds{30}));
402✔
62
    }
63
}
604✔
64

65
auto client::operator=(client&& other) noexcept -> client&
×
66
{
67
    if (std::addressof(other) != this)
×
68
    {
69
        m_io_scheduler   = std::exchange(other.m_io_scheduler, nullptr);
×
70
        m_tls_ctx        = std::move(other.m_tls_ctx);
×
NEW
71
        m_endpoint       = std::move(other.m_endpoint);
×
72
        m_socket         = std::move(other.m_socket);
×
73
        m_connect_status = std::exchange(other.m_connect_status, std::nullopt);
×
74
        m_tls_info       = std::move(other.m_tls_info);
×
75
    }
76
    return *this;
×
77
}
78

79
auto client::connect(std::chrono::milliseconds timeout) -> coro::task<connection_status>
94✔
80
{
81
    // Only allow the user to connect per tcp client once, if they need to re-connect they should
82
    // make a new tls::client.
83
    if (m_connect_status.has_value())
84
    {
85
        co_return m_connect_status.value();
86
    }
87

88
    // tls context isn't setup and is required.
89
    if (m_tls_ctx == nullptr)
90
    {
91
        co_return connection_status::context_required;
92
    }
93

94
    // This enforces the connection status is aways set on the client object upon returning.
95
    auto return_value = [this](connection_status s) -> connection_status
202✔
96
    {
97
        m_connect_status = s;
101✔
98
        return s;
101✔
99
    };
100

101
    auto [addr, len] = m_endpoint.data();
102

103
    auto cret = ::connect(m_socket.native_handle(), addr, len);
104
    if (cret == 0)
105
    {
106
        co_return return_value(co_await handshake(timeout));
107
    }
108
    else if (cret == -1)
109
    {
110
        // If the connect is happening in the background poll for write on the socket to trigger
111
        // when the connection is established.
112
        if (errno == EAGAIN || errno == EINPROGRESS)
113
        {
114
            auto pstatus = co_await m_io_scheduler->poll(m_socket, poll_op::write, timeout);
115
            if (pstatus == poll_status::write)
116
            {
117
                int       result{0};
118
                socklen_t result_length{sizeof(result)};
119
                if (getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_ERROR, &result, &result_length) < 0)
120
                {
121
                    std::cerr << "connect failed to getsockopt after write poll event\n";
122
                }
123

124
                if (result == 0)
125
                {
126
                    // TODO: delta the already used time and remove from the handshake timeout.
127
                    co_return return_value(co_await handshake(timeout));
128
                }
129
            }
130
            else if (pstatus == poll_status::timeout)
131
            {
132
                co_return return_value(connection_status::timeout);
133
            }
134
        }
135
    }
136

137
    co_return return_value(connection_status::error);
138
}
182✔
139

140
auto client::handshake(std::chrono::milliseconds timeout) -> coro::task<connection_status>
191✔
141
{
142
    m_tls_info.m_tls_ptr = tls_unique_ptr{SSL_new(m_tls_ctx->native_handle())};
143
    if (m_tls_info.m_tls_ptr == nullptr)
144
    {
145
        co_return connection_status::resource_allocation_failed;
146
    }
147

148
    auto* tls = m_tls_info.m_tls_ptr.get();
149

150
    if (auto r = SSL_set_fd(tls, m_socket.native_handle()); r == 0)
151
    {
152
        co_return connection_status::set_fd_failure;
153
    }
154

155
    if (m_tls_info.m_tls_connection_type == tls_connection_type::connect)
156
    {
157
        SSL_set_connect_state(tls);
158
    }
159
    else // ssl_connection_type::accept
160
    {
161
        SSL_set_accept_state(tls);
162
    }
163

164
    int r{0};
165
    ERR_clear_error();
166
    while ((r = SSL_connect(tls)) != 1)
167
    {
168
        poll_op op{poll_op::read_write};
169
        int     err = SSL_get_error(tls, r);
170
        if (err == SSL_ERROR_WANT_WRITE)
171
        {
172
            op = poll_op::write;
173
        }
174
        else if (err == SSL_ERROR_WANT_READ)
175
        {
176
            op = poll_op::read;
177
        }
178
        else
179
        {
180
            // char error_buffer[256];
181
            // ERR_error_string(err, error_buffer);
182
            // std::cerr << "ssl_handleshake error=[" << error_buffer << "]\n";
183
            co_return connection_status::handshake_failed;
184
        }
185

186
        // TODO: adjust timeout based on elapsed time so far.
187
        auto pstatus = co_await m_io_scheduler->poll(m_socket, op, timeout);
188
        switch (pstatus)
189
        {
190
            case poll_status::timeout:
191
                co_return connection_status::timeout;
192
            case poll_status::error:
193
                co_return connection_status::poll_error;
194
            case poll_status::closed:
195
                co_return connection_status::unexpected_close;
196
            case poll_status::cancelled:
197
                co_return connection_status::unexpected_close;
198
            default:
199
                // Event triggered, continue handshake.
200
                break;
201
        }
202
    }
203

204
    co_return connection_status::connected;
205
}
372✔
206

207
auto client::tls_shutdown_and_free(std::chrono::milliseconds timeout) -> coro::task<void>
202✔
208
{
209
    auto* tls_ptr = m_tls_info.m_tls_ptr.get();
210

211
    while (true)
212
    {
213
        ERR_clear_error();
214
        auto r = SSL_shutdown(tls_ptr);
215
        if (r == 1) // shutdown complete
216
        {
217
            co_return;
218
        }
219
        else if (r == 0) // shutdown in progress
220
        {
221
            coro::poll_op op{coro::poll_op::read_write};
222
            auto          err = SSL_get_error(tls_ptr, r);
223
            if (err == SSL_ERROR_WANT_WRITE)
224
            {
225
                op = coro::poll_op::write;
226
            }
227
            else if (err == SSL_ERROR_WANT_READ)
228
            {
229
                op = coro::poll_op::read;
230
            }
231
            else
232
            {
233
                co_return;
234
            }
235

236
            auto pstatus = co_await m_io_scheduler->poll(m_socket, op, timeout);
237
            switch (pstatus)
238
            {
239
                case poll_status::timeout:
240
                case poll_status::error:
241
                case poll_status::closed:
242
                case poll_status::cancelled:
243
                    co_return;
244
                default:
245
                    // continue shutdown.
246
                    break;
247
            }
248
        }
249
        else // r < 0 error
250
        {
251
            co_return;
252
        }
253
    }
254
}
404✔
255

256
} // namespace coro::net::tls
257

258
#endif // #ifdef LIBCORO_FEATURE_TLS
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc