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

randombit / botan / 16265592430

14 Jul 2025 11:26AM UTC coverage: 90.624% (-0.001%) from 90.625%
16265592430

push

github

web-flow
Merge pull request #4989 from randombit/jack/fix-more-named-parameters

Fix more readability-named-parameter warnings from clang-tidy

99627 of 109934 relevant lines covered (90.62%)

12300021.1 hits per line

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

94.77
/src/tests/test_tls_stream_integration.cpp
1
/*
2
* TLS ASIO Stream Client-Server Interaction Test
3
* (C) 2018-2020 Jack Lloyd
4
*     2018-2020 Hannes Rantzsch, René Meusel
5
*     2022      René Meusel, Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#include "tests.h"
11

12
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_ASIO_STREAM) && defined(BOTAN_TARGET_OS_HAS_THREADS)
13

14
   #include <botan/asio_compat.h>
15
   #if defined(BOTAN_FOUND_COMPATIBLE_BOOST_ASIO_VERSION)
16

17
      #include <functional>
18
      #include <memory>
19
      #include <optional>
20
      #include <thread>
21
      #include <utility>
22

23
      #include <botan/asio_stream.h>
24
      #include <botan/auto_rng.h>
25
      #include <botan/tls_messages.h>
26
      #include <botan/tls_session_manager_noop.h>
27

28
      #include <boost/asio.hpp>
29
      #include <boost/asio/yield.hpp>
30
      #include <boost/exception/exception.hpp>
31

32
      #include "../cli/tls_helpers.h"  // for Basic_Credentials_Manager
33
      #define BOTAN_TEST_TLS_STREAM_INTEGRATION
34
   #endif
35
#endif
36

37
namespace Botan_Tests {
38

39
#if defined(BOTAN_TEST_TLS_STREAM_INTEGRATION)
40

41
// NOLINTBEGIN(*-avoid-bind)
42

43
namespace {
44

45
namespace net = boost::asio;
46

47
using tcp = net::ip::tcp;
48
using error_code = boost::system::error_code;
49
using ssl_stream = Botan::TLS::Stream<net::ip::tcp::socket>;
50
using namespace std::placeholders;
51

52
// NOLINTBEGIN(cert-err58-cpp)
53
const auto k_timeout = std::chrono::seconds(30);
54
const auto k_endpoints = std::vector<tcp::endpoint>{tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}};
55
// NOLINTEND(cert-err58-cpp)
56

57
constexpr size_t MAX_MSG_LENGTH = 512;
58

59
std::string server_cert() {
44✔
60
   return Test::data_file("x509/certstor/cert1.crt");
88✔
61
}
62

63
std::string server_key() {
44✔
64
   return Test::data_file("x509/certstor/key01.pem");
88✔
65
}
66

67
class Timeout_Exception : public std::runtime_error {
68
      using std::runtime_error::runtime_error;
×
69
};
70

71
class PeerCallbacks : public Botan::TLS::StreamCallbacks {
88✔
72
   public:
73
      void fail_on_handshake_message(const Botan::TLS::Handshake_Type msg_type, Botan::TLS::AlertType alert) {
8✔
74
         m_handshake_alerts[msg_type] = alert;
8✔
75
      }
76

77
      void tls_inspect_handshake_msg(const Botan::TLS::Handshake_Message& msg) final {
637✔
78
         if(m_handshake_alerts.contains(msg.type())) {
1,274✔
79
            throw Botan::TLS::TLS_Exception(m_handshake_alerts[msg.type()], "Test was configured to throw");
8✔
80
         }
81
      }
629✔
82

83
   private:
84
      std::map<Botan::TLS::Handshake_Type, Botan::TLS::AlertType> m_handshake_alerts;
85
};
86

87
class Peer {
88
   private:
89
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy,
88✔
90
           net::io_context& ioc,
91
           std::shared_ptr<Basic_Credentials_Manager> credentials_manager) :
88✔
92
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
93
            m_credentials_manager(std::move(credentials_manager)),
88✔
94
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
95
            m_callbacks(std::make_shared<PeerCallbacks>()),
96
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
88✔
97
            m_timeout_timer(ioc) {}
264✔
98

99
   public:
100
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) :
44✔
101
            Peer(policy, ioc, std::make_shared<Basic_Credentials_Manager>(true, "")) {}
88✔
102

103
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy,
44✔
104
           net::io_context& ioc,
105
           const std::string& server_cert,
106
           const std::string& server_key) :
44✔
107
            Peer(policy, ioc, std::make_shared<Basic_Credentials_Manager>(server_cert, server_key)) {}
88✔
108

109
      Peer(const Peer& other) = delete;
110
      Peer(Peer&& other) = delete;
111
      Peer& operator=(const Peer& other) = delete;
112
      Peer& operator=(Peer&& other) = delete;
113

114
      // NOLINTNEXTLINE(*-exception-escape)
115
      virtual ~Peer() { cancel_timeout(); }
616✔
116

117
      net::mutable_buffer buffer() { return net::buffer(m_data, MAX_MSG_LENGTH); }
36✔
118

119
      net::mutable_buffer buffer(size_t size) { return net::buffer(m_data, size); }
16✔
120

121
      std::string message() const { return std::string(m_data); }
12✔
122

123
      // This is a CompletionCondition for net::async_read().
124
      // Our toy protocol always expects a single \0-terminated string.
125
      std::size_t received_zero_byte(const boost::system::error_code& error, std::size_t bytes_transferred) {
152✔
126
         if(error) {
152✔
127
            return 0;
128
         }
129

130
         if(bytes_transferred > 0 && m_data[bytes_transferred - 1] == '\0') {
116✔
131
            return 0;
132
         }
133

134
         return MAX_MSG_LENGTH - bytes_transferred;
76✔
135
      }
136

137
      void on_timeout(std::function<void(const std::string&)> cb) { m_on_timeout = std::move(cb); }
88✔
138

139
      void reset_timeout(const std::string& message) {
328✔
140
         m_timeout_timer.expires_after(k_timeout);
328✔
141
         m_timeout_timer.async_wait([&](const error_code& ec) {
328✔
142
            if(ec != net::error::operation_aborted)  // timer cancelled
328✔
143
            {
144
               if(m_on_timeout) {
×
145
                  m_on_timeout(message);
×
146
               }
147

148
               // hard-close the underlying transport to maximize the
149
               // probability for all participating peers to error out
150
               // of their pending I/O operations
151
               if(m_stream) {
×
152
                  m_stream->lowest_layer().close();
×
153
               }
154

155
               throw Timeout_Exception("timeout occured: " + message);
×
156
            }
157
         });
328✔
158
      }
328✔
159

160
      void cancel_timeout() { m_timeout_timer.cancel(); }
132✔
161

162
      ssl_stream& stream() { return *m_stream; }
204✔
163

164
      void create_stream(std::unique_ptr<ssl_stream> stream) {
88✔
165
         BOTAN_ASSERT(!m_stream, "Stream is only assigned once");
88✔
166
         m_stream = std::move(stream);
88✔
167
      }
88✔
168

169
      std::shared_ptr<Botan::TLS::Context> ctx() { return m_ctx; }
4✔
170

171
   protected:
172
      std::shared_ptr<PeerCallbacks> callbacks() { return m_callbacks; }
192✔
173

174
   private:
175
      std::shared_ptr<Botan::AutoSeeded_RNG> m_rng;
176
      std::shared_ptr<Basic_Credentials_Manager> m_credentials_manager;
177
      std::shared_ptr<Botan::TLS::Session_Manager_Noop> m_session_mgr;
178
      std::shared_ptr<PeerCallbacks> m_callbacks;
179
      std::shared_ptr<Botan::TLS::Context> m_ctx;
180
      std::unique_ptr<ssl_stream> m_stream;
181
      net::system_timer m_timeout_timer;
182
      std::function<void(const std::string&)> m_on_timeout;
183

184
      char m_data[MAX_MSG_LENGTH]{};
185
};
186

187
class Result_Wrapper {
44✔
188
   public:
189
      explicit Result_Wrapper(std::string name) : m_result(std::move(name)) {}
88✔
190

191
      Test::Result& result() { return m_result; }
88✔
192

193
      void expect_success(const std::string& msg, const error_code& ec) {
376✔
194
         error_code success;
376✔
195
         expect_ec(msg, success, ec);
376✔
196
      }
197

198
      void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec) {
448✔
199
         if(ec != expected) {
448✔
200
            m_result.test_failure(msg, "Unexpected error code: " + ec.message() + " expected: " + expected.message());
×
201
         } else {
202
            m_result.test_success(msg);
448✔
203
         }
204
      }
448✔
205

206
      void confirm(const std::string& msg, bool condition) { m_result.confirm(msg, condition); }
48✔
207

208
      void test_failure(const std::string& msg) { m_result.test_failure(msg); }
×
209

210
   private:
211
      Test::Result m_result;
212
};
213

214
// NOLINTBEGIN(cert-err58-cpp)
215

216
// Control messages
217
// The messages below can be used by the test clients in order to configure the server's behavior during a test
218
// case.
219
//
220
// Tell the server that the next read should result in a StreamTruncated error
221
const std::string EXPECT_SHORT_READ_MESSAGE = "SHORT_READ";
222
// Prepare the server for the test case "Shutdown No Response"
223
const std::string PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE = "SHUTDOWN_NOW";
224

225
// NOLINTEND(cert-err58-cpp)
226

227
class Server : public Peer,
228
               public std::enable_shared_from_this<Server> {
229
   public:
230
      Server(const std::shared_ptr<const Botan::TLS::Policy>& policy,
44✔
231
             net::io_context& ioc,
232
             const std::string& test_name) :
44✔
233
            Peer(policy, ioc, server_cert(), server_key()),
88✔
234
            m_acceptor(ioc),
44✔
235
            m_result("Server (" + test_name + ")"),
88✔
236
            m_short_read_expected(false),
44✔
237
            m_move_before_accept(false) {
176✔
238
         reset_timeout("startup");
44✔
239
      }
44✔
240

241
      void listen() {
44✔
242
         error_code ec;
44✔
243
         const auto& endpoint = k_endpoints.back();
44✔
244

245
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
246
         m_acceptor.open(endpoint.protocol(), ec);
44✔
247

248
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
249
         m_acceptor.set_option(net::socket_base::reuse_address(true), ec);
44✔
250

251
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
252
         m_acceptor.bind(endpoint, ec);
44✔
253

254
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
255
         m_acceptor.listen(net::socket_base::max_listen_connections, ec);
44✔
256

257
         m_result.expect_success("listen", ec);
88✔
258

259
         reset_timeout("accept");
44✔
260
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
88✔
261
      }
44✔
262

263
      void expect_short_read() { m_short_read_expected = true; }
4✔
264

265
      void move_before_accept() { m_move_before_accept = true; }
4✔
266

267
      void fail_on_handshake_message(const Botan::TLS::Handshake_Type msg_type, Botan::TLS::AlertType alert) {
8✔
268
         callbacks()->fail_on_handshake_message(msg_type, alert);
8✔
269
         m_expected_handshake_failure = alert;
8✔
270
      }
8✔
271

272
      Result_Wrapper result() { return m_result; }
44✔
273

274
   private:
275
      void start_session(const error_code& ec, tcp::socket socket) {
44✔
276
         // Note: If this fails with 'Operation canceled', it likely means the timer expired and the port is taken.
277
         m_result.expect_success("accept", ec);
88✔
278

279
         // Note: If this was a real server, we should create a new session (with its own stream) for each accepted
280
         // connection. In this test we only have one connection.
281

282
         if(m_move_before_accept) {
44✔
283
            // regression test for #2635
284
            ssl_stream s(std::move(socket), ctx(), callbacks());
8✔
285
            create_stream(std::make_unique<ssl_stream>(std::move(s)));
8✔
286
         } else {
4✔
287
            create_stream(std::make_unique<ssl_stream>(std::move(socket), ctx(), callbacks()));
80✔
288
         }
289

290
         reset_timeout("handshake");
44✔
291
         stream().async_handshake(Botan::TLS::Connection_Side::Server,
88✔
292
                                  std::bind(&Server::handle_handshake, shared_from_this(), _1));
88✔
293
      }
44✔
294

295
      void shutdown() {
8✔
296
         error_code shutdown_ec;
8✔
297
         stream().shutdown(shutdown_ec);
8✔
298
         m_result.expect_success("shutdown", shutdown_ec);
16✔
299
         handle_write(error_code{});
8✔
300
      }
8✔
301

302
      void handle_handshake(const error_code& ec) {
44✔
303
         if(!m_expected_handshake_failure) {
44✔
304
            m_result.expect_success("handshake", ec);
72✔
305
            handle_write(error_code{});
36✔
306
         } else {
307
            m_result.expect_ec("handshake", m_expected_handshake_failure.value(), ec);
8✔
308
            cancel_timeout();
8✔
309
         }
310
      }
44✔
311

312
      void handle_write(const error_code& ec) {
60✔
313
         m_result.expect_success("send_response", ec);
120✔
314
         reset_timeout("read_message");
60✔
315
         net::async_read(stream(),
60✔
316
                         buffer(),
60✔
317
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
318
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
319
      }
60✔
320

321
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
322
         if(m_short_read_expected) {
60✔
323
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
324
            cancel_timeout();
16✔
325
            return;
16✔
326
         }
327

328
         if(ec) {
44✔
329
            if(stream().shutdown_received()) {
20✔
330
               m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
20✔
331
               reset_timeout("shutdown");
20✔
332
               stream().async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
20✔
333
            } else {
334
               m_result.test_failure("Unexpected error code: " + ec.message());
×
335
               cancel_timeout();
×
336
            }
337
            return;
20✔
338
         }
339

340
         m_result.expect_success("read_message", ec);
48✔
341

342
         if(message() == PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE) {
24✔
343
            m_short_read_expected = true;
8✔
344
            shutdown();
8✔
345
            return;
8✔
346
         }
347

348
         if(message() == EXPECT_SHORT_READ_MESSAGE) {
16✔
349
            m_short_read_expected = true;
4✔
350
         }
351

352
         reset_timeout("send_response");
16✔
353
         net::async_write(
32✔
354
            stream(), buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
355
      }
356

357
      void handle_shutdown(const error_code& ec) {
20✔
358
         m_result.expect_success("shutdown", ec);
40✔
359
         cancel_timeout();
20✔
360
      }
20✔
361

362
   private:
363
      tcp::acceptor m_acceptor;
364
      Result_Wrapper m_result;
365
      std::optional<Botan::TLS::AlertType> m_expected_handshake_failure;
366
      bool m_short_read_expected;
367

368
      // regression test for #2635
369
      bool m_move_before_accept;
370
};
371

372
class Client : public Peer {
44✔
373
   private:
374
      static void accept_all(const std::vector<Botan::X509_Certificate>& /*cert*/,
36✔
375
                             const std::vector<std::optional<Botan::OCSP::Response>>& /*ocsp*/,
376
                             const std::vector<Botan::Certificate_Store*>& /*trusted*/,
377
                             Botan::Usage_Type /*usage*/,
378
                             std::string_view /*hostname*/,
379
                             const Botan::TLS::Policy& /*policy*/) {}
36✔
380

381
   public:
382
      Client(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) : Peer(policy, ioc) {
44✔
383
         ctx()->set_verify_callback(accept_all);
44✔
384
         create_stream(std::make_unique<ssl_stream>(ioc, ctx(), callbacks()));
88✔
385
      }
44✔
386

387
      void close_socket() {
32✔
388
         // Shutdown on TCP level before closing the socket for portable behavior. Otherwise the peer will see a
389
         // connection_reset error rather than EOF on Windows.
390
         // See the remark in
391
         // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html
392
         stream().lowest_layer().shutdown(tcp::socket::shutdown_both);
28✔
393
         stream().lowest_layer().close();
32✔
394
      }
16✔
395
};
396

397
class TestBase {
398
   public:
399
      TestBase(net::io_context& ioc,
44✔
400
               const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
401
               const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
402
               const std::string& name,
403
               const std::string& config_name) :
44✔
404
            m_name(name + " (" + config_name + ")"),
88✔
405
            m_client(std::make_shared<Client>(client_policy, ioc)),
44✔
406
            m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
44✔
407
            m_result(m_name) {
132✔
408
         m_client->on_timeout(
44✔
409
            [&](const std::string& msg) { m_result.test_failure("timeout in client during: " + msg); });
44✔
410
         m_server->on_timeout(
44✔
411
            [&](const std::string& msg) { m_result.test_failure("timeout in server during: " + msg); });
44✔
412

413
         m_server->listen();
44✔
414
      }
44✔
415

416
      virtual ~TestBase() = default;
132✔
417

418
      TestBase(const TestBase& other) = delete;
419
      TestBase(TestBase&& other) = delete;
420
      TestBase& operator=(const TestBase& other) = delete;
421
      TestBase& operator=(TestBase&& other) = delete;
422

423
      virtual void finishAsynchronousWork() {}
24✔
424

425
      void fail(const std::string& msg) { m_result.test_failure(msg); }
×
426

427
      void extend_results(std::vector<Test::Result>& results) {
44✔
428
         results.push_back(m_result.result());
44✔
429
         results.push_back(m_server->result().result());
44✔
430
      }
44✔
431

432
   protected:
433
      //! retire client and server instances after a job well done
434
      void teardown() { m_client->cancel_timeout(); }
28✔
435

436
      std::shared_ptr<Client>& client() { return m_client; }
48✔
437

438
      std::shared_ptr<Server>& server() { return m_server; }
16✔
439

440
      Result_Wrapper& result() { return m_result; }
168✔
441

442
   private:
443
      std::string m_name;
444

445
      std::shared_ptr<Client> m_client;
446
      std::shared_ptr<Server> m_server;
447

448
      Result_Wrapper m_result;
449
};
450

451
class Synchronous_Test : public TestBase {
20✔
452
   public:
453
      using TestBase::TestBase;
20✔
454

455
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
456

457
      void run(const error_code& /*err*/) {
20✔
458
         m_client_thread = std::thread([this] {
20✔
459
            try {
20✔
460
               this->run_synchronous_client();
20✔
461
            } catch(const std::exception& ex) {
×
462
               result().test_failure(std::string("sync client failed with: ") + ex.what());
×
463
            } catch(const boost::exception&) {
×
464
               result().test_failure("sync client failed with boost exception");
×
465
            } catch(...) {
×
466
               result().test_failure("sync client failed with unknown error");
×
467
            }
×
468
         });
40✔
469
      }
20✔
470

471
      virtual void run_synchronous_client() = 0;
472

473
   private:
474
      std::thread m_client_thread;
475
};
476

477
/* In this test case both parties perform the handshake, exchange a message, and do a full shutdown.
478
 *
479
 * The client expects the server to echo the same message it sent. The client then initiates the shutdown. The server is
480
 * expected to receive a close_notify and complete its shutdown with an error_code Success, the client is expected to
481
 * receive a close_notify and complete its shutdown with an error_code EOF.
482
 */
483
class Test_Conversation : public TestBase,
484
                          public net::coroutine,
485
                          public std::enable_shared_from_this<Test_Conversation> {
486
   public:
487
      Test_Conversation(net::io_context& ioc,
8✔
488
                        const std::string& config_name,
489
                        const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
490
                        const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
491
                        const std::string& test_name = "Test Conversation") :
8✔
492
            TestBase(ioc, client_policy, server_policy, test_name, config_name) {}
8✔
493

494
      void run(const error_code& ec) {
56✔
495
         static auto test_case = &Test_Conversation::run;
56✔
496
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
497

498
         reenter(*this) {
56✔
499
            client()->reset_timeout("connect");
8✔
500
            yield net::async_connect(
16✔
501
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
502
            result().expect_success("connect", ec);
16✔
503

504
            client()->reset_timeout("handshake");
8✔
505
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
506
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
507
            result().expect_success("handshake", ec);
16✔
508

509
            client()->reset_timeout("send_message");
8✔
510
            yield net::async_write(client()->stream(),
24✔
511
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
512
                                   std::bind(test_case, shared_from_this(), _1));
16✔
513
            result().expect_success("send_message", ec);
16✔
514

515
            client()->reset_timeout("receive_response");
8✔
516
            yield net::async_read(client()->stream(),
24✔
517
                                  client()->buffer(),
8✔
518
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
8✔
519
                                  std::bind(test_case, shared_from_this(), _1));
16✔
520
            result().expect_success("receive_response", ec);
16✔
521
            result().confirm("correct message", client()->message() == message);
16✔
522

523
            client()->reset_timeout("shutdown");
8✔
524
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
525
            result().expect_success("shutdown", ec);
16✔
526

527
            client()->reset_timeout("await close_notify");
8✔
528
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
529
            result().confirm("received close_notify", client()->stream().shutdown_received());
24✔
530
            result().expect_ec("closed with EOF", net::error::eof, ec);
8✔
531

532
            teardown();
64✔
533
         }
56✔
534
      }
56✔
535
};
536

537
class Test_Conversation_Sync : public Synchronous_Test {
4✔
538
   public:
539
      Test_Conversation_Sync(net::io_context& ioc,
4✔
540
                             const std::string& config_name,
541
                             const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
542
                             const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
543
            Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
8✔
544

545
      void run_synchronous_client() override {
4✔
546
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
547
         error_code ec;
4✔
548

549
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
550
         result().expect_success("connect", ec);
8✔
551

552
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
553
         result().expect_success("handshake", ec);
8✔
554

555
         net::write(client()->stream(),
4✔
556
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
557
                    ec);
558
         result().expect_success("send_message", ec);
8✔
559

560
         net::read(
8✔
561
            client()->stream(), client()->buffer(), std::bind(&Client::received_zero_byte, client().get(), _1, _2), ec);
4✔
562
         result().expect_success("receive_response", ec);
8✔
563
         result().confirm("correct message", client()->message() == message);
8✔
564

565
         client()->stream().shutdown(ec);
4✔
566
         result().expect_success("shutdown", ec);
8✔
567

568
         net::read(client()->stream(), client()->buffer(), ec);
4✔
569
         result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
570
         result().expect_ec("closed with EOF", net::error::eof, ec);
4✔
571

572
         teardown();
8✔
573
      }
4✔
574
};
575

576
/* In this test case the client shuts down the SSL connection, but does not wait for the server's response before
577
 * closing the socket. Accordingly, it will not receive the server's close_notify alert. Instead, the async_read
578
 * operation will be aborted. The server should be able to successfully shutdown nonetheless.
579
 */
580
class Test_Eager_Close : public TestBase,
581
                         public net::coroutine,
582
                         public std::enable_shared_from_this<Test_Eager_Close> {
583
   public:
584
      Test_Eager_Close(net::io_context& ioc,
4✔
585
                       const std::string& config_name,
586
                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
587
                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
588
            TestBase(ioc, client_policy, server_policy, "Test Eager Close", config_name) {}
8✔
589

590
      void run(const error_code& ec) {
16✔
591
         static auto test_case = &Test_Eager_Close::run;
16✔
592
         reenter(*this) {
16✔
593
            client()->reset_timeout("connect");
4✔
594
            yield net::async_connect(
8✔
595
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
596
            result().expect_success("connect", ec);
8✔
597

598
            client()->reset_timeout("handshake");
4✔
599
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
600
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
601
            result().expect_success("handshake", ec);
8✔
602

603
            client()->reset_timeout("shutdown");
4✔
604
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
605
            result().expect_success("shutdown", ec);
8✔
606

607
            client()->close_socket();
4✔
608
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
609

610
            teardown();
20✔
611
         }
16✔
612
      }
16✔
613
};
614

615
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
616
   public:
617
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
618
                            const std::string& config_name,
619
                            const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
620
                            const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
621
            Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
8✔
622

623
      void run_synchronous_client() override {
4✔
624
         error_code ec;
4✔
625

626
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
627
         result().expect_success("connect", ec);
8✔
628

629
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
630
         result().expect_success("handshake", ec);
8✔
631

632
         client()->stream().shutdown(ec);
4✔
633
         result().expect_success("shutdown", ec);
8✔
634

635
         client()->close_socket();
4✔
636
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
637

638
         teardown();
4✔
639
      }
4✔
640
};
641

642
/* In this test case the client closes the socket without properly shutting down the connection.
643
 * The server should see a StreamTruncated error.
644
 */
645
class Test_Close_Without_Shutdown : public TestBase,
646
                                    public net::coroutine,
647
                                    public std::enable_shared_from_this<Test_Close_Without_Shutdown> {
648
   public:
649
      Test_Close_Without_Shutdown(net::io_context& ioc,
4✔
650
                                  const std::string& config_name,
651
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
652
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
653
            TestBase(ioc, client_policy, server_policy, "Test Close Without Shutdown", config_name) {}
8✔
654

655
      void run(const error_code& ec) {
20✔
656
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
657
         reenter(*this) {
20✔
658
            client()->reset_timeout("connect");
4✔
659
            yield net::async_connect(
8✔
660
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
661
            result().expect_success("connect", ec);
8✔
662

663
            client()->reset_timeout("handshake");
4✔
664
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
665
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
666
            result().expect_success("handshake", ec);
8✔
667

668
            // send the control message to configure the server to expect a short-read
669
            client()->reset_timeout("send expect_short_read message");
4✔
670
            yield net::async_write(client()->stream(),
12✔
671
                                   net::buffer(EXPECT_SHORT_READ_MESSAGE.c_str(),
4✔
672
                                               EXPECT_SHORT_READ_MESSAGE.size() + 1),  // including \0 termination
4✔
673
                                   std::bind(test_case, shared_from_this(), _1));
8✔
674
            result().expect_success("send expect_short_read message", ec);
8✔
675

676
            // read the confirmation of the control message above
677
            client()->reset_timeout("receive_response");
4✔
678
            yield net::async_read(client()->stream(),
12✔
679
                                  client()->buffer(),
4✔
680
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
4✔
681
                                  std::bind(test_case, shared_from_this(), _1));
8✔
682
            result().expect_success("receive_response", ec);
8✔
683

684
            client()->close_socket();
4✔
685
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
686

687
            teardown();
24✔
688
         }
20✔
689
      }
20✔
690
};
691

692
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
693
   public:
694
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
695
                                       const std::string& config_name,
696
                                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
697
                                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
698
            Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
8✔
699

700
      void run_synchronous_client() override {
4✔
701
         error_code ec;
4✔
702
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
703
         result().expect_success("connect", ec);
8✔
704

705
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
706
         result().expect_success("handshake", ec);
8✔
707

708
         server()->expect_short_read();
4✔
709

710
         client()->close_socket();
4✔
711
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
712

713
         teardown();
4✔
714
      }
4✔
715
};
716

717
/* In this test case the server shuts down the connection but the client doesn't send the corresponding close_notify
718
 * response. Instead, it closes the socket immediately.
719
 * The server should see a short-read error.
720
 */
721
class Test_No_Shutdown_Response : public TestBase,
722
                                  public net::coroutine,
723
                                  public std::enable_shared_from_this<Test_No_Shutdown_Response> {
724
   public:
725
      Test_No_Shutdown_Response(net::io_context& ioc,
4✔
726
                                const std::string& config_name,
727
                                const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
728
                                const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
729
            TestBase(ioc, client_policy, server_policy, "Test No Shutdown Response", config_name) {}
8✔
730

731
      void run(const error_code& ec) {
20✔
732
         static auto test_case = &Test_No_Shutdown_Response::run;
20✔
733
         reenter(*this) {
20✔
734
            client()->reset_timeout("connect");
4✔
735
            yield net::async_connect(
8✔
736
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
737
            result().expect_success("connect", ec);
8✔
738

739
            client()->reset_timeout("handshake");
4✔
740
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
741
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
742
            result().expect_success("handshake", ec);
8✔
743

744
            // send a control message to make the server shut down
745
            client()->reset_timeout("send shutdown message");
4✔
746
            yield net::async_write(
8✔
747
               client()->stream(),
4✔
748
               net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
4✔
749
                           PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
750
               std::bind(test_case, shared_from_this(), _1));
8✔
751
            result().expect_success("send shutdown message", ec);
8✔
752

753
            // read the server's close-notify message
754
            client()->reset_timeout("read close_notify");
4✔
755
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
756
            result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
757
            result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
758

759
            // close the socket rather than shutting down
760
            client()->close_socket();
4✔
761

762
            teardown();
24✔
763
         }
20✔
764
      }
20✔
765
};
766

767
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
768
   public:
769
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
770
                                     const std::string& config_name,
771
                                     const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
772
                                     const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
773
            Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
8✔
774

775
      void run_synchronous_client() override {
4✔
776
         error_code ec;
4✔
777
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
778
         result().expect_success("connect", ec);
8✔
779

780
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
781
         result().expect_success("handshake", ec);
8✔
782

783
         net::write(client()->stream(),
4✔
784
                    net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
8✔
785
                                PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
786
                    ec);
787
         result().expect_success("send expect_short_read message", ec);
8✔
788

789
         net::read(client()->stream(), client()->buffer(), ec);
4✔
790
         result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
791
         result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
792

793
         // close the socket rather than shutting down
794
         client()->close_socket();
4✔
795

796
         teardown();
4✔
797
      }
4✔
798
};
799

800
class Test_Conversation_With_Move : public Test_Conversation {
4✔
801
   public:
802
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
803
                                  const std::string& config_name,
804
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
805
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
806
            Test_Conversation(ioc, config_name, client_policy, server_policy, "Test Conversation With Move") {
8✔
807
         server()->move_before_accept();
4✔
808
      }
4✔
809
};
810

811
/* In this test we provoke a handshake failure early on in the server and expect
812
 * the stream to handle it by providing the configured error code to the client.
813
 *
814
 * This is a regression test for #3778: Alerts during handshakes resulted in an
815
 * immediate closure of the socket without transmitting the Alert message first.
816
 */
817
class Test_Handshake_Failure : public TestBase,
818
                               public net::coroutine,
819
                               public std::enable_shared_from_this<Test_Handshake_Failure> {
820
   public:
821
      Test_Handshake_Failure(net::io_context& ioc,
4✔
822
                             const std::string& config_name,
823
                             const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
824
                             const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
825
            TestBase(ioc, client_policy, server_policy, "Test Handshake Failure", config_name) {
8✔
826
         server()->fail_on_handshake_message(Botan::TLS::Handshake_Type::ClientHello,
4✔
827
                                             Botan::TLS::Alert::HandshakeFailure);
828
      }
4✔
829

830
      void run(const error_code& ec) {
12✔
831
         static auto test_case = &Test_Handshake_Failure::run;
12✔
832
         reenter(*this) {
12✔
833
            client()->reset_timeout("connect");
4✔
834
            yield net::async_connect(
8✔
835
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
836
            result().expect_success("connect", ec);
8✔
837

838
            client()->reset_timeout("handshake");
4✔
839
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
840
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
841
            result().expect_ec("handshake", Botan::TLS::Alert::HandshakeFailure, ec);
4✔
842

843
            client()->close_socket();
4✔
844
            teardown();
16✔
845
         }
12✔
846
      }
12✔
847
};
848

849
class Test_Handshake_Failure_Sync : public Synchronous_Test {
4✔
850
   public:
851
      Test_Handshake_Failure_Sync(net::io_context& ioc,
4✔
852
                                  const std::string& config_name,
853
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
854
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
855
            Synchronous_Test(ioc, client_policy, server_policy, "Test Handshake Failure Sync", config_name) {
8✔
856
         server()->fail_on_handshake_message(Botan::TLS::Handshake_Type::ClientHello,
4✔
857
                                             Botan::TLS::Alert::HandshakeFailure);
858
      }
4✔
859

860
      void run_synchronous_client() override {
4✔
861
         error_code ec;
4✔
862
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
863
         result().expect_success("connect", ec);
8✔
864

865
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
866
         result().expect_ec("handshake", Botan::TLS::Alert::HandshakeFailure, ec);
4✔
867

868
         client()->close_socket();
4✔
869
         teardown();
4✔
870
      }
4✔
871
};
872

873
class SystemConfiguration {
874
   public:
875
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
876
            m_name(std::move(n)),
4✔
877
            m_client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
878
            m_server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
879

880
      template <typename TestT>
881
      void run(std::vector<Test::Result>& results) {
44✔
882
         net::io_context ioc;
44✔
883

884
         auto t = std::make_shared<TestT>(ioc, m_name, m_server_policy, m_client_policy);
44✔
885

886
         t->run(error_code{});
44✔
887

888
         while(true) {
889
            try {
890
               ioc.run();
44✔
891
               break;
892
            } catch(const Timeout_Exception&) { /* timeout is reported via Test::Result object */
×
893
            } catch(const boost::exception&) {
×
894
               t->fail("boost exception");
×
895
            } catch(const std::exception& ex) {
×
896
               t->fail(ex.what());
×
897
            }
898
         }
899

900
         t->finishAsynchronousWork();
44✔
901
         t->extend_results(results);
44✔
902
      }
44✔
903

904
   private:
905
      std::string m_name;
906

907
      std::shared_ptr<Botan::TLS::Text_Policy> m_client_policy;
908
      std::shared_ptr<Botan::TLS::Text_Policy> m_server_policy;
909
};
910

911
std::vector<SystemConfiguration> get_configurations() {
1✔
912
   return {
1✔
913
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
1✔
914
   #if defined(BOTAN_HAS_TLS_13)
915
         SystemConfiguration(
916
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
1✔
917
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
918
                             "allow_tls12=true\nallow_tls13=false",
919
                             "allow_tls12=true\nallow_tls13=true"),
1✔
920
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
921
                             "allow_tls12=true\nallow_tls13=true",
922
                             "allow_tls12=true\nallow_tls13=false"),
1✔
923
   #endif
924
   };
5✔
925
}
6✔
926

927
class Tls_Stream_Integration_Tests final : public Test {
×
928
   public:
929
      std::vector<Test::Result> run() override {
1✔
930
         std::vector<Test::Result> results;
1✔
931

932
         auto configs = get_configurations();
1✔
933
         for(auto& config : configs) {
5✔
934
            config.run<Test_Conversation>(results);
4✔
935
            config.run<Test_Eager_Close>(results);
4✔
936
            config.run<Test_Close_Without_Shutdown>(results);
4✔
937
            config.run<Test_No_Shutdown_Response>(results);
4✔
938
            config.run<Test_Handshake_Failure>(results);
4✔
939
            config.run<Test_Conversation_Sync>(results);
4✔
940
            config.run<Test_Eager_Close_Sync>(results);
4✔
941
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
942
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
943
            config.run<Test_Handshake_Failure_Sync>(results);
4✔
944
            config.run<Test_Conversation_With_Move>(results);
4✔
945
         }
946

947
         return results;
1✔
948
      }
1✔
949
};
950

951
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
952

953
}  // namespace
954

955
// NOLINTEND(*-avoid-bind)
956

957
#endif
958

959
}  // namespace Botan_Tests
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