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

randombit / botan / 22073759886

16 Feb 2026 06:28PM UTC coverage: 90.025% (-0.02%) from 90.044%
22073759886

Pull #5348

github

web-flow
Merge 476498cbe into cff0123e7
Pull Request #5348: Improve use of test predicates

102324 of 113662 relevant lines covered (90.02%)

11521139.1 hits per line

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

95.01
/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 occurred: " + 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_view name) : m_result(name) {}
88✔
190

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

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

198
      void expect_ec(std::string_view 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 test_is_true(std::string_view msg, bool condition) { m_result.test_is_true(msg, condition); }
36✔
207

208
      void test_str_eq(std::string_view what, std::string_view produced, std::string_view expected) {
12✔
209
         m_result.test_str_eq(what, produced, expected);
12✔
210
      }
12✔
211

212
      void test_failure(std::string_view msg) { m_result.test_failure(msg); }
×
213

214
   private:
215
      Test::Result m_result;
216
};
217

218
// NOLINTBEGIN(cert-err58-cpp)
219

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

229
// NOLINTEND(cert-err58-cpp)
230

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

245
      void listen() {
44✔
246
         error_code ec;
44✔
247
         const auto& endpoint = k_endpoints.back();
44✔
248

249
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
250
         m_acceptor.open(endpoint.protocol(), ec);
44✔
251

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

255
         // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
256
         m_acceptor.bind(endpoint, ec);
44✔
257

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

261
         m_result.expect_success("listen", ec);
44✔
262

263
         reset_timeout("accept");
44✔
264
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
88✔
265
      }
44✔
266

267
      void expect_short_read() { m_short_read_expected = true; }
4✔
268

269
      void move_before_accept() { m_move_before_accept = true; }
4✔
270

271
      void fail_on_handshake_message(const Botan::TLS::Handshake_Type msg_type, Botan::TLS::AlertType alert) {
8✔
272
         callbacks()->fail_on_handshake_message(msg_type, alert);
8✔
273
         m_expected_handshake_failure = alert;
8✔
274
      }
8✔
275

276
      Result_Wrapper result() { return m_result; }
44✔
277

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

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

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

294
         reset_timeout("handshake");
44✔
295
         stream().async_handshake(Botan::TLS::Connection_Side::Server,
88✔
296
                                  std::bind(&Server::handle_handshake, shared_from_this(), _1));
88✔
297
      }
44✔
298

299
      void shutdown() {
8✔
300
         error_code shutdown_ec;
8✔
301
         stream().shutdown(shutdown_ec);
8✔
302
         m_result.expect_success("shutdown", shutdown_ec);
8✔
303
         handle_write(error_code{});
8✔
304
      }
8✔
305

306
      void handle_handshake(const error_code& ec) {
44✔
307
         if(!m_expected_handshake_failure) {
44✔
308
            m_result.expect_success("handshake", ec);
36✔
309
            handle_write(error_code{});
36✔
310
         } else {
311
            m_result.expect_ec("handshake", m_expected_handshake_failure.value(), ec);
8✔
312
            cancel_timeout();
8✔
313
         }
314
      }
44✔
315

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

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

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

344
         m_result.expect_success("read_message", ec);
24✔
345

346
         if(message() == PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE) {
24✔
347
            m_short_read_expected = true;
8✔
348
            shutdown();
8✔
349
            return;
8✔
350
         }
351

352
         if(message() == EXPECT_SHORT_READ_MESSAGE) {
16✔
353
            m_short_read_expected = true;
4✔
354
         }
355

356
         reset_timeout("send_response");
16✔
357
         net::async_write(
32✔
358
            stream(), buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
359
      }
360

361
      void handle_shutdown(const error_code& ec) {
20✔
362
         m_result.expect_success("shutdown", ec);
20✔
363
         cancel_timeout();
20✔
364
      }
20✔
365

366
   private:
367
      tcp::acceptor m_acceptor;
368
      Result_Wrapper m_result;
369
      std::optional<Botan::TLS::AlertType> m_expected_handshake_failure;
370
      bool m_short_read_expected;
371

372
      // regression test for #2635
373
      bool m_move_before_accept;
374
};
375

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

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

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

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

417
         m_server->listen();
44✔
418
      }
44✔
419

420
      virtual ~TestBase() = default;
132✔
421

422
      TestBase(const TestBase& other) = delete;
423
      TestBase(TestBase&& other) = delete;
424
      TestBase& operator=(const TestBase& other) = delete;
425
      TestBase& operator=(TestBase&& other) = delete;
426

427
      virtual void finishAsynchronousWork() {}
24✔
428

429
      void fail(std::string_view msg) { m_result.test_failure(msg); }
×
430

431
      void extend_results(std::vector<Test::Result>& results) {
44✔
432
         results.push_back(m_result.result());
44✔
433
         results.push_back(m_server->result().result());
44✔
434
      }
44✔
435

436
   protected:
437
      //! retire client and server instances after a job well done
438
      void teardown() { m_client->cancel_timeout(); }
28✔
439

440
      std::shared_ptr<Client>& client() { return m_client; }
60✔
441

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

444
      Result_Wrapper& result() { return m_result; }
168✔
445

446
   private:
447
      std::string m_name;
448

449
      std::shared_ptr<Client> m_client;
450
      std::shared_ptr<Server> m_server;
451

452
      Result_Wrapper m_result;
453
};
454

455
class Synchronous_Test : public TestBase {
20✔
456
   public:
457
      using TestBase::TestBase;
20✔
458

459
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
460

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

475
      virtual void run_synchronous_client() = 0;
476

477
   private:
478
      std::thread m_client_thread;
479
};
480

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

498
      void run(const error_code& ec) {
56✔
499
         static auto test_case = &Test_Conversation::run;
56✔
500
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
501

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

508
            client()->reset_timeout("handshake");
8✔
509
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
510
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
511
            result().expect_success("handshake", ec);
8✔
512

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

519
            client()->reset_timeout("receive_response");
8✔
520
            yield net::async_read(client()->stream(),
24✔
521
                                  client()->buffer(),
8✔
522
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
8✔
523
                                  std::bind(test_case, shared_from_this(), _1));
16✔
524
            result().expect_success("receive_response", ec);
8✔
525
            result().test_str_eq("correct message", client()->message(), message);
16✔
526

527
            client()->reset_timeout("shutdown");
8✔
528
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
529
            result().expect_success("shutdown", ec);
8✔
530

531
            client()->reset_timeout("await close_notify");
8✔
532
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
533
            result().test_is_true("received close_notify", client()->stream().shutdown_received());
16✔
534
            result().expect_ec("closed with EOF", net::error::eof, ec);
8✔
535

536
            teardown();
64✔
537
         }
56✔
538
      }
56✔
539
};
540

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

549
      void run_synchronous_client() override {
4✔
550
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
551
         error_code ec;
4✔
552

553
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
554
         result().expect_success("connect", ec);
4✔
555

556
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
557
         result().expect_success("handshake", ec);
4✔
558

559
         net::write(client()->stream(),
4✔
560
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
561
                    ec);
562
         result().expect_success("send_message", ec);
4✔
563

564
         net::read(
8✔
565
            client()->stream(), client()->buffer(), std::bind(&Client::received_zero_byte, client().get(), _1, _2), ec);
4✔
566
         result().expect_success("receive_response", ec);
4✔
567
         result().test_str_eq("correct message", client()->message(), message);
8✔
568

569
         client()->stream().shutdown(ec);
4✔
570
         result().expect_success("shutdown", ec);
4✔
571

572
         net::read(client()->stream(), client()->buffer(), ec);
4✔
573
         result().test_is_true("received close_notify", client()->stream().shutdown_received());
8✔
574
         result().expect_ec("closed with EOF", net::error::eof, ec);
4✔
575

576
         teardown();
8✔
577
      }
4✔
578
};
579

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

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

602
            client()->reset_timeout("handshake");
4✔
603
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
604
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
605
            result().expect_success("handshake", ec);
4✔
606

607
            client()->reset_timeout("shutdown");
4✔
608
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
609
            result().expect_success("shutdown", ec);
4✔
610

611
            client()->close_socket();
4✔
612
            result().test_is_true("did not receive close_notify", !client()->stream().shutdown_received());
8✔
613

614
            teardown();
20✔
615
         }
16✔
616
      }
16✔
617
};
618

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

627
      void run_synchronous_client() override {
4✔
628
         error_code ec;
4✔
629

630
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
631
         result().expect_success("connect", ec);
4✔
632

633
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
634
         result().expect_success("handshake", ec);
4✔
635

636
         client()->stream().shutdown(ec);
4✔
637
         result().expect_success("shutdown", ec);
4✔
638

639
         client()->close_socket();
4✔
640
         result().test_is_true("did not receive close_notify", !client()->stream().shutdown_received());
8✔
641

642
         teardown();
4✔
643
      }
4✔
644
};
645

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

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

667
            client()->reset_timeout("handshake");
4✔
668
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
669
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
670
            result().expect_success("handshake", ec);
4✔
671

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

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

688
            client()->close_socket();
4✔
689
            result().test_is_true("did not receive close_notify", !client()->stream().shutdown_received());
8✔
690

691
            teardown();
24✔
692
         }
20✔
693
      }
20✔
694
};
695

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

704
      void run_synchronous_client() override {
4✔
705
         error_code ec;
4✔
706
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
707
         result().expect_success("connect", ec);
4✔
708

709
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
710
         result().expect_success("handshake", ec);
4✔
711

712
         server()->expect_short_read();
4✔
713

714
         client()->close_socket();
4✔
715
         result().test_is_true("did not receive close_notify", !client()->stream().shutdown_received());
8✔
716

717
         teardown();
4✔
718
      }
4✔
719
};
720

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

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

743
            client()->reset_timeout("handshake");
4✔
744
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
745
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
746
            result().expect_success("handshake", ec);
4✔
747

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

757
            // read the server's close-notify message
758
            client()->reset_timeout("read close_notify");
4✔
759
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
760
            result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
761
            result().test_is_true("received close_notify", client()->stream().shutdown_received());
8✔
762

763
            // close the socket rather than shutting down
764
            client()->close_socket();
4✔
765

766
            teardown();
24✔
767
         }
20✔
768
      }
20✔
769
};
770

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

779
      void run_synchronous_client() override {
4✔
780
         error_code ec;
4✔
781
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
782
         result().expect_success("connect", ec);
4✔
783

784
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
785
         result().expect_success("handshake", ec);
4✔
786

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

793
         net::read(client()->stream(), client()->buffer(), ec);
4✔
794
         result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
795
         result().test_is_true("received close_notify", client()->stream().shutdown_received());
8✔
796

797
         // close the socket rather than shutting down
798
         client()->close_socket();
4✔
799

800
         teardown();
4✔
801
      }
4✔
802
};
803

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

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

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

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

847
            client()->close_socket();
4✔
848
            teardown();
16✔
849
         }
12✔
850
      }
12✔
851
};
852

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

864
      void run_synchronous_client() override {
4✔
865
         error_code ec;
4✔
866
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
867
         result().expect_success("connect", ec);
4✔
868

869
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
870
         result().expect_ec("handshake", Botan::TLS::Alert::HandshakeFailure, ec);
4✔
871

872
         client()->close_socket();
4✔
873
         teardown();
4✔
874
      }
4✔
875
};
876

877
class SystemConfiguration {
878
   public:
879
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
880
            m_name(std::move(n)),
4✔
881
            m_client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
882
            m_server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
883

884
      template <typename TestT>
885
      void run(std::vector<Test::Result>& results) {
44✔
886
         net::io_context ioc;
44✔
887

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

890
         t->run(error_code{});
44✔
891

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

904
         t->finishAsynchronousWork();
44✔
905
         t->extend_results(results);
44✔
906
      }
44✔
907

908
   private:
909
      std::string m_name;
910

911
      std::shared_ptr<Botan::TLS::Text_Policy> m_client_policy;
912
      std::shared_ptr<Botan::TLS::Text_Policy> m_server_policy;
913
};
914

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

931
class Tls_Stream_Integration_Tests final : public Test {
1✔
932
   public:
933
      std::vector<Test::Result> run() override {
1✔
934
         std::vector<Test::Result> results;
1✔
935

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

951
         return results;
1✔
952
      }
1✔
953
};
954

955
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
956

957
}  // namespace
958

959
// NOLINTEND(*-avoid-bind)
960

961
#endif
962

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