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

randombit / botan / 16072710435

04 Jul 2025 11:26AM UTC coverage: 90.571% (-0.005%) from 90.576%
16072710435

push

github

web-flow
Merge pull request #4958 from randombit/jack/fix-clang-tidy-modernize-avoid-bind

Enable and fix clang-tidy warning modernize-avoid-bind

99057 of 109369 relevant lines covered (90.57%)

12345728.62 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
const auto k_timeout = std::chrono::seconds(30);
53
const auto k_endpoints = std::vector<tcp::endpoint>{tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}};
54

55
constexpr size_t MAX_MSG_LENGTH = 512;
56

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

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

65
class Timeout_Exception : public std::runtime_error {
66
      using std::runtime_error::runtime_error;
×
67
};
68

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

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

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

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

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

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

107
      Peer(const Peer& other) = delete;
108
      Peer(Peer&& other) = delete;
109
      Peer& operator=(const Peer& other) = delete;
110
      Peer& operator=(Peer&& other) = delete;
111

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

115
      net::mutable_buffer buffer() { return net::buffer(m_data, MAX_MSG_LENGTH); }
36✔
116

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

119
      std::string message() const { return std::string(m_data); }
12✔
120

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

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

132
         return MAX_MSG_LENGTH - bytes_transferred;
76✔
133
      }
134

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

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

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

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

158
      void cancel_timeout() { m_timeout_timer.cancel(); }
132✔
159

160
      ssl_stream& stream() { return *m_stream; }
204✔
161

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

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

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

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

182
      char m_data[MAX_MSG_LENGTH];
183
};
184

185
class Result_Wrapper {
44✔
186
   public:
187
      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
88✔
188

189
      Test::Result& result() { return m_result; }
88✔
190

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

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

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

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

208
   private:
209
      Test::Result m_result;
210
};
211

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

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

235
      void listen() {
44✔
236
         error_code ec;
44✔
237
         const auto& endpoint = k_endpoints.back();
44✔
238

239
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
240
         m_acceptor.open(endpoint.protocol(), ec);
44✔
241

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

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

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

251
         m_result.expect_success("listen", ec);
88✔
252

253
         reset_timeout("accept");
44✔
254
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
88✔
255
      }
44✔
256

257
      void expect_short_read() { m_short_read_expected = true; }
4✔
258

259
      void move_before_accept() { m_move_before_accept = true; }
4✔
260

261
      void fail_on_handshake_message(const Botan::TLS::Handshake_Type msg_type, Botan::TLS::AlertType alert) {
8✔
262
         callbacks()->fail_on_handshake_message(msg_type, alert);
8✔
263
         m_expected_handshake_failure = alert;
8✔
264
      }
8✔
265

266
      Result_Wrapper result() { return m_result; }
44✔
267

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

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

276
         if(m_move_before_accept) {
44✔
277
            // regression test for #2635
278
            ssl_stream s(std::move(socket), ctx(), callbacks());
8✔
279
            create_stream(std::make_unique<ssl_stream>(std::move(s)));
8✔
280
         } else {
4✔
281
            create_stream(std::make_unique<ssl_stream>(std::move(socket), ctx(), callbacks()));
80✔
282
         }
283

284
         reset_timeout("handshake");
44✔
285
         stream().async_handshake(Botan::TLS::Connection_Side::Server,
88✔
286
                                  std::bind(&Server::handle_handshake, shared_from_this(), _1));
88✔
287
      }
44✔
288

289
      void shutdown() {
8✔
290
         error_code shutdown_ec;
8✔
291
         stream().shutdown(shutdown_ec);
8✔
292
         m_result.expect_success("shutdown", shutdown_ec);
16✔
293
         handle_write(error_code{});
8✔
294
      }
8✔
295

296
      void handle_handshake(const error_code& ec) {
44✔
297
         if(!m_expected_handshake_failure) {
44✔
298
            m_result.expect_success("handshake", ec);
72✔
299
            handle_write(error_code{});
36✔
300
         } else {
301
            m_result.expect_ec("handshake", m_expected_handshake_failure.value(), ec);
8✔
302
            cancel_timeout();
8✔
303
         }
304
      }
44✔
305

306
      void handle_write(const error_code& ec) {
60✔
307
         m_result.expect_success("send_response", ec);
120✔
308
         reset_timeout("read_message");
60✔
309
         net::async_read(stream(),
60✔
310
                         buffer(),
60✔
311
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
312
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
313
      }
60✔
314

315
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
316
         if(m_short_read_expected) {
60✔
317
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
318
            cancel_timeout();
16✔
319
            return;
16✔
320
         }
321

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

334
         m_result.expect_success("read_message", ec);
48✔
335

336
         if(message() == PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE) {
24✔
337
            m_short_read_expected = true;
8✔
338
            shutdown();
8✔
339
            return;
8✔
340
         }
341

342
         if(message() == EXPECT_SHORT_READ_MESSAGE) {
16✔
343
            m_short_read_expected = true;
4✔
344
         }
345

346
         reset_timeout("send_response");
16✔
347
         net::async_write(
32✔
348
            stream(), buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
349
      }
350

351
      void handle_shutdown(const error_code& ec) {
20✔
352
         m_result.expect_success("shutdown", ec);
40✔
353
         cancel_timeout();
20✔
354
      }
20✔
355

356
   private:
357
      tcp::acceptor m_acceptor;
358
      Result_Wrapper m_result;
359
      std::optional<Botan::TLS::AlertType> m_expected_handshake_failure;
360
      bool m_short_read_expected;
361

362
      // regression test for #2635
363
      bool m_move_before_accept;
364
};
365

366
class Client : public Peer {
44✔
367
      static void accept_all(const std::vector<Botan::X509_Certificate>&,
36✔
368
                             const std::vector<std::optional<Botan::OCSP::Response>>&,
369
                             const std::vector<Botan::Certificate_Store*>&,
370
                             Botan::Usage_Type,
371
                             std::string_view,
372
                             const Botan::TLS::Policy&) {}
36✔
373

374
   public:
375
      Client(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) : Peer(policy, ioc) {
44✔
376
         ctx()->set_verify_callback(accept_all);
44✔
377
         create_stream(std::make_unique<ssl_stream>(ioc, ctx(), callbacks()));
88✔
378
      }
44✔
379

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

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

406
         m_server->listen();
44✔
407
      }
44✔
408

409
      virtual ~TestBase() = default;
132✔
410

411
      TestBase(const TestBase& other) = delete;
412
      TestBase(TestBase&& other) = delete;
413
      TestBase& operator=(const TestBase& other) = delete;
414
      TestBase& operator=(TestBase&& other) = delete;
415

416
      virtual void finishAsynchronousWork() {}
24✔
417

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

420
      void extend_results(std::vector<Test::Result>& results) {
44✔
421
         results.push_back(m_result.result());
44✔
422
         results.push_back(m_server->result().result());
44✔
423
      }
44✔
424

425
   protected:
426
      //! retire client and server instances after a job well done
427
      void teardown() { m_client->cancel_timeout(); }
28✔
428

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

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

433
      Result_Wrapper& result() { return m_result; }
168✔
434

435
   private:
436
      std::string m_name;
437

438
      std::shared_ptr<Client> m_client;
439
      std::shared_ptr<Server> m_server;
440

441
      Result_Wrapper m_result;
442
};
443

444
class Synchronous_Test : public TestBase {
20✔
445
   public:
446
      using TestBase::TestBase;
20✔
447

448
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
449

450
      void run(const error_code&) {
20✔
451
         m_client_thread = std::thread([this] {
20✔
452
            try {
20✔
453
               this->run_synchronous_client();
20✔
454
            } catch(const std::exception& ex) {
×
455
               result().test_failure(std::string("sync client failed with: ") + ex.what());
×
456
            } catch(const boost::exception&) {
×
457
               result().test_failure("sync client failed with boost exception");
×
458
            } catch(...) {
×
459
               result().test_failure("sync client failed with unknown error");
×
460
            }
×
461
         });
40✔
462
      }
20✔
463

464
      virtual void run_synchronous_client() = 0;
465

466
   private:
467
      std::thread m_client_thread;
468
};
469

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

487
      void run(const error_code& ec) {
56✔
488
         static auto test_case = &Test_Conversation::run;
56✔
489
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
490

491
         reenter(*this) {
56✔
492
            client()->reset_timeout("connect");
8✔
493
            yield net::async_connect(
16✔
494
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
495
            result().expect_success("connect", ec);
16✔
496

497
            client()->reset_timeout("handshake");
8✔
498
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
499
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
500
            result().expect_success("handshake", ec);
16✔
501

502
            client()->reset_timeout("send_message");
8✔
503
            yield net::async_write(client()->stream(),
24✔
504
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
505
                                   std::bind(test_case, shared_from_this(), _1));
16✔
506
            result().expect_success("send_message", ec);
16✔
507

508
            client()->reset_timeout("receive_response");
8✔
509
            yield net::async_read(client()->stream(),
24✔
510
                                  client()->buffer(),
8✔
511
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
8✔
512
                                  std::bind(test_case, shared_from_this(), _1));
16✔
513
            result().expect_success("receive_response", ec);
16✔
514
            result().confirm("correct message", client()->message() == message);
16✔
515

516
            client()->reset_timeout("shutdown");
8✔
517
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
518
            result().expect_success("shutdown", ec);
16✔
519

520
            client()->reset_timeout("await close_notify");
8✔
521
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
522
            result().confirm("received close_notify", client()->stream().shutdown_received());
24✔
523
            result().expect_ec("closed with EOF", net::error::eof, ec);
8✔
524

525
            teardown();
64✔
526
         }
56✔
527
      }
56✔
528
};
529

530
class Test_Conversation_Sync : public Synchronous_Test {
4✔
531
   public:
532
      Test_Conversation_Sync(net::io_context& ioc,
4✔
533
                             const std::string& config_name,
534
                             const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
535
                             const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
536
            Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
8✔
537

538
      void run_synchronous_client() override {
4✔
539
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
540
         error_code ec;
4✔
541

542
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
543
         result().expect_success("connect", ec);
8✔
544

545
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
546
         result().expect_success("handshake", ec);
8✔
547

548
         net::write(client()->stream(),
4✔
549
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
550
                    ec);
551
         result().expect_success("send_message", ec);
8✔
552

553
         net::read(
8✔
554
            client()->stream(), client()->buffer(), std::bind(&Client::received_zero_byte, client().get(), _1, _2), ec);
4✔
555
         result().expect_success("receive_response", ec);
8✔
556
         result().confirm("correct message", client()->message() == message);
8✔
557

558
         client()->stream().shutdown(ec);
4✔
559
         result().expect_success("shutdown", ec);
8✔
560

561
         net::read(client()->stream(), client()->buffer(), ec);
4✔
562
         result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
563
         result().expect_ec("closed with EOF", net::error::eof, ec);
4✔
564

565
         teardown();
8✔
566
      }
4✔
567
};
568

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

583
      void run(const error_code& ec) {
16✔
584
         static auto test_case = &Test_Eager_Close::run;
16✔
585
         reenter(*this) {
16✔
586
            client()->reset_timeout("connect");
4✔
587
            yield net::async_connect(
8✔
588
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
589
            result().expect_success("connect", ec);
8✔
590

591
            client()->reset_timeout("handshake");
4✔
592
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
593
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
594
            result().expect_success("handshake", ec);
8✔
595

596
            client()->reset_timeout("shutdown");
4✔
597
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
598
            result().expect_success("shutdown", ec);
8✔
599

600
            client()->close_socket();
4✔
601
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
602

603
            teardown();
20✔
604
         }
16✔
605
      }
16✔
606
};
607

608
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
609
   public:
610
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
611
                            const std::string& config_name,
612
                            const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
613
                            const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
614
            Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
8✔
615

616
      void run_synchronous_client() override {
4✔
617
         error_code ec;
4✔
618

619
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
620
         result().expect_success("connect", ec);
8✔
621

622
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
623
         result().expect_success("handshake", ec);
8✔
624

625
         client()->stream().shutdown(ec);
4✔
626
         result().expect_success("shutdown", ec);
8✔
627

628
         client()->close_socket();
4✔
629
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
630

631
         teardown();
4✔
632
      }
4✔
633
};
634

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

648
      void run(const error_code& ec) {
20✔
649
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
650
         reenter(*this) {
20✔
651
            client()->reset_timeout("connect");
4✔
652
            yield net::async_connect(
8✔
653
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
654
            result().expect_success("connect", ec);
8✔
655

656
            client()->reset_timeout("handshake");
4✔
657
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
658
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
659
            result().expect_success("handshake", ec);
8✔
660

661
            // send the control message to configure the server to expect a short-read
662
            client()->reset_timeout("send expect_short_read message");
4✔
663
            yield net::async_write(client()->stream(),
12✔
664
                                   net::buffer(EXPECT_SHORT_READ_MESSAGE.c_str(),
4✔
665
                                               EXPECT_SHORT_READ_MESSAGE.size() + 1),  // including \0 termination
4✔
666
                                   std::bind(test_case, shared_from_this(), _1));
8✔
667
            result().expect_success("send expect_short_read message", ec);
8✔
668

669
            // read the confirmation of the control message above
670
            client()->reset_timeout("receive_response");
4✔
671
            yield net::async_read(client()->stream(),
12✔
672
                                  client()->buffer(),
4✔
673
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
4✔
674
                                  std::bind(test_case, shared_from_this(), _1));
8✔
675
            result().expect_success("receive_response", ec);
8✔
676

677
            client()->close_socket();
4✔
678
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
679

680
            teardown();
24✔
681
         }
20✔
682
      }
20✔
683
};
684

685
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
686
   public:
687
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
688
                                       const std::string& config_name,
689
                                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
690
                                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
691
            Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
8✔
692

693
      void run_synchronous_client() override {
4✔
694
         error_code ec;
4✔
695
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
696
         result().expect_success("connect", ec);
8✔
697

698
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
699
         result().expect_success("handshake", ec);
8✔
700

701
         server()->expect_short_read();
4✔
702

703
         client()->close_socket();
4✔
704
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
12✔
705

706
         teardown();
4✔
707
      }
4✔
708
};
709

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

724
      void run(const error_code& ec) {
20✔
725
         static auto test_case = &Test_No_Shutdown_Response::run;
20✔
726
         reenter(*this) {
20✔
727
            client()->reset_timeout("connect");
4✔
728
            yield net::async_connect(
8✔
729
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
730
            result().expect_success("connect", ec);
8✔
731

732
            client()->reset_timeout("handshake");
4✔
733
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
734
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
735
            result().expect_success("handshake", ec);
8✔
736

737
            // send a control message to make the server shut down
738
            client()->reset_timeout("send shutdown message");
4✔
739
            yield net::async_write(
8✔
740
               client()->stream(),
4✔
741
               net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
4✔
742
                           PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
743
               std::bind(test_case, shared_from_this(), _1));
8✔
744
            result().expect_success("send shutdown message", ec);
8✔
745

746
            // read the server's close-notify message
747
            client()->reset_timeout("read close_notify");
4✔
748
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
749
            result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
750
            result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
751

752
            // close the socket rather than shutting down
753
            client()->close_socket();
4✔
754

755
            teardown();
24✔
756
         }
20✔
757
      }
20✔
758
};
759

760
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
761
   public:
762
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
763
                                     const std::string& config_name,
764
                                     const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
765
                                     const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
766
            Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
8✔
767

768
      void run_synchronous_client() override {
4✔
769
         error_code ec;
4✔
770
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
771
         result().expect_success("connect", ec);
8✔
772

773
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
774
         result().expect_success("handshake", ec);
8✔
775

776
         net::write(client()->stream(),
4✔
777
                    net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
8✔
778
                                PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
779
                    ec);
780
         result().expect_success("send expect_short_read message", ec);
8✔
781

782
         net::read(client()->stream(), client()->buffer(), ec);
4✔
783
         result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
784
         result().confirm("received close_notify", client()->stream().shutdown_received());
12✔
785

786
         // close the socket rather than shutting down
787
         client()->close_socket();
4✔
788

789
         teardown();
4✔
790
      }
4✔
791
};
792

793
class Test_Conversation_With_Move : public Test_Conversation {
4✔
794
   public:
795
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
796
                                  const std::string& config_name,
797
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
798
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
799
            Test_Conversation(ioc, config_name, client_policy, server_policy, "Test Conversation With Move") {
8✔
800
         server()->move_before_accept();
4✔
801
      }
4✔
802
};
803

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

823
      void run(const error_code& ec) {
12✔
824
         static auto test_case = &Test_Handshake_Failure::run;
12✔
825
         reenter(*this) {
12✔
826
            client()->reset_timeout("connect");
4✔
827
            yield net::async_connect(
8✔
828
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
829
            result().expect_success("connect", ec);
8✔
830

831
            client()->reset_timeout("handshake");
4✔
832
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
833
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
834
            result().expect_ec("handshake", Botan::TLS::Alert::HandshakeFailure, ec);
4✔
835

836
            client()->close_socket();
4✔
837
            teardown();
16✔
838
         }
12✔
839
      }
12✔
840
};
841

842
class Test_Handshake_Failure_Sync : public Synchronous_Test {
4✔
843
   public:
844
      Test_Handshake_Failure_Sync(net::io_context& ioc,
4✔
845
                                  const std::string& config_name,
846
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
847
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
848
            Synchronous_Test(ioc, client_policy, server_policy, "Test Handshake Failure Sync", config_name) {
8✔
849
         server()->fail_on_handshake_message(Botan::TLS::Handshake_Type::ClientHello,
4✔
850
                                             Botan::TLS::Alert::HandshakeFailure);
851
      }
4✔
852

853
      void run_synchronous_client() override {
4✔
854
         error_code ec;
4✔
855
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
856
         result().expect_success("connect", ec);
8✔
857

858
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
859
         result().expect_ec("handshake", Botan::TLS::Alert::HandshakeFailure, ec);
4✔
860

861
         client()->close_socket();
4✔
862
         teardown();
4✔
863
      }
4✔
864
};
865

866
class SystemConfiguration {
867
   public:
868
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
869
            m_name(std::move(n)),
4✔
870
            m_client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
871
            m_server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
872

873
      template <typename TestT>
874
      void run(std::vector<Test::Result>& results) {
44✔
875
         net::io_context ioc;
44✔
876

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

879
         t->run(error_code{});
44✔
880

881
         while(true) {
882
            try {
883
               ioc.run();
44✔
884
               break;
885
            } catch(const Timeout_Exception&) { /* timeout is reported via Test::Result object */
×
886
            } catch(const boost::exception&) {
×
887
               t->fail("boost exception");
×
888
            } catch(const std::exception& ex) {
×
889
               t->fail(ex.what());
×
890
            }
891
         }
892

893
         t->finishAsynchronousWork();
44✔
894
         t->extend_results(results);
44✔
895
      }
44✔
896

897
   private:
898
      std::string m_name;
899

900
      std::shared_ptr<Botan::TLS::Text_Policy> m_client_policy;
901
      std::shared_ptr<Botan::TLS::Text_Policy> m_server_policy;
902
};
903

904
std::vector<SystemConfiguration> get_configurations() {
1✔
905
   return {
1✔
906
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
1✔
907
   #if defined(BOTAN_HAS_TLS_13)
908
         SystemConfiguration(
909
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
1✔
910
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
911
                             "allow_tls12=true\nallow_tls13=false",
912
                             "allow_tls12=true\nallow_tls13=true"),
1✔
913
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
914
                             "allow_tls12=true\nallow_tls13=true",
915
                             "allow_tls12=true\nallow_tls13=false"),
1✔
916
   #endif
917
   };
5✔
918
}
6✔
919

920
class Tls_Stream_Integration_Tests final : public Test {
×
921
   public:
922
      std::vector<Test::Result> run() override {
1✔
923
         std::vector<Test::Result> results;
1✔
924

925
         auto configs = get_configurations();
1✔
926
         for(auto& config : configs) {
5✔
927
            config.run<Test_Conversation>(results);
4✔
928
            config.run<Test_Eager_Close>(results);
4✔
929
            config.run<Test_Close_Without_Shutdown>(results);
4✔
930
            config.run<Test_No_Shutdown_Response>(results);
4✔
931
            config.run<Test_Handshake_Failure>(results);
4✔
932
            config.run<Test_Conversation_Sync>(results);
4✔
933
            config.run<Test_Eager_Close_Sync>(results);
4✔
934
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
935
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
936
            config.run<Test_Handshake_Failure_Sync>(results);
4✔
937
            config.run<Test_Conversation_With_Move>(results);
4✔
938
         }
939

940
         return results;
1✔
941
      }
1✔
942
};
943

944
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
945

946
}  // namespace
947

948
// NOLINTEND(*-avoid-bind)
949

950
#endif
951

952
}  // 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