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

jbaldwin / libcoro / 18356238626

08 Oct 2025 07:47PM UTC coverage: 88.853%. First build
18356238626

Pull #400

github

web-flow
Merge e88827681 into 749e5b474
Pull Request #400: Executor types remove circular refs

48 of 56 new or added lines in 15 files covered. (85.71%)

1658 of 1866 relevant lines covered (88.85%)

5389464.64 hits per line

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

78.43
/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(std::unique_ptr<coro::io_scheduler>& scheduler, std::shared_ptr<context> tls_ctx, options opts)
101✔
11
    : m_io_scheduler(scheduler.get()),
101✔
12
      m_tls_ctx(std::move(tls_ctx)),
97✔
13
      m_options(std::move(opts)),
95✔
14
      m_socket(net::make_socket(
95✔
15
          net::socket::options{m_options.address.domain(), net::socket::type_t::tcp, net::socket::blocking_t::no}))
96✔
16
{
17
    if (m_io_scheduler == nullptr)
93✔
18
    {
19
        throw std::runtime_error{"tls::client cannot have nullptr io_scheduler"};
×
20
    }
21

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

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

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

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

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

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

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

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

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

99
    sockaddr_in server{};
100
    server.sin_family = static_cast<int>(m_options.address.domain());
101
    server.sin_port   = htons(m_options.port);
102
    server.sin_addr   = *reinterpret_cast<const in_addr*>(m_options.address.data().data());
103

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

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

138
    co_return return_value(connection_status::error);
139
}
192✔
140

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

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

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

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

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

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

203
    co_return connection_status::connected;
204
}
360✔
205

206
auto client::tls_shutdown_and_free(
202✔
207
    std::chrono::milliseconds     timeout) -> coro::task<void>
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
                    co_return;
243
                default:
244
                    // continue shutdown.
245
                    break;
246
            }
247
        }
248
        else // r < 0 error
249
        {
250
            co_return;
251
        }
252
    }
253
}
404✔
254

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

257
#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