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

jbaldwin / libcoro / 21315795177

24 Jan 2026 01:23PM UTC coverage: 86.888%. First build
21315795177

Pull #435

github

web-flow
Merge b39d95fbe into 44d12235a
Pull Request #435: Rename io_scheduler to scheduler

51 of 71 new or added lines in 11 files covered. (71.83%)

1756 of 2021 relevant lines covered (86.89%)

4941352.34 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::scheduler>& scheduler, std::shared_ptr<context> tls_ctx, options opts)
101✔
11
    : m_scheduler(scheduler.get()),
101✔
12
      m_tls_ctx(std::move(tls_ctx)),
88✔
13
      m_options(std::move(opts)),
91✔
14
      m_socket(
88✔
15
          net::make_socket(
16
              net::socket::options{m_options.address.domain(), net::socket::type_t::tcp, net::socket::blocking_t::no}))
90✔
17
{
18
    if (m_scheduler == nullptr)
87✔
19
    {
NEW
20
        throw std::runtime_error{"tls::client cannot have nullptr scheduler"};
×
21
    }
22

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

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

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

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

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

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

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

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

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

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

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

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

139
    co_return return_value(connection_status::error);
140
}
178✔
141

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

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

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

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

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

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

206
    co_return connection_status::connected;
207
}
376✔
208

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

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

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

259
} // namespace coro::net::tls
260

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