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

randombit / botan / 5378005141

26 Jun 2023 12:53PM UTC coverage: 91.741% (-0.001%) from 91.742%
5378005141

push

github

randombit
Disable pylint too-many-return-statements

78183 of 85221 relevant lines covered (91.74%)

12352794.33 hits per line

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

93.59
/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
   // first version to be compatible with Networking TS (N4656) and boost::beast
15
   #include <boost/version.hpp>
16
   #if BOOST_VERSION >= 106600
17

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

24
      #include <botan/asio_stream.h>
25
      #include <botan/auto_rng.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

34
namespace {
35

36
namespace net = boost::asio;
37

38
using tcp = net::ip::tcp;
39
using error_code = boost::system::error_code;
40
using ssl_stream = Botan::TLS::Stream<net::ip::tcp::socket>;
41
using namespace std::placeholders;
42
using Result = Botan_Tests::Test::Result;
43

44
const auto k_timeout = std::chrono::seconds(30);
45
const auto k_endpoints = std::vector<tcp::endpoint>{tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}};
46

47
enum { max_msg_length = 512 };
48

49
std::string server_cert() {
36✔
50
   return Botan_Tests::Test::data_dir() + "/x509/certstor/cert1.crt";
36✔
51
}
52

53
std::string server_key() {
36✔
54
   return Botan_Tests::Test::data_dir() + "/x509/certstor/key01.pem";
36✔
55
}
56

57
class Timeout_Exception : public std::runtime_error {
58
      using std::runtime_error::runtime_error;
×
59
};
60

61
class Peer {
62
   public:
63
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) :
36✔
64
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
65
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(true, "")),
36✔
66
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
67
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
68
            m_timeout_timer(ioc) {}
108✔
69

70
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy,
36✔
71
           net::io_context& ioc,
72
           const std::string& server_cert,
73
           const std::string& server_key) :
36✔
74
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
36✔
75
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(server_cert, server_key)),
36✔
76
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
77
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
78
            m_timeout_timer(ioc) {}
108✔
79

80
      Peer(const Peer& other) = delete;
81
      Peer(Peer&& other) = delete;
82
      Peer& operator=(const Peer& other) = delete;
83
      Peer& operator=(Peer&& other) = delete;
84

85
      // NOLINTNEXTLINE(*-exception-escape)
86
      virtual ~Peer() { cancel_timeout(); }
432✔
87

88
      net::mutable_buffer buffer() { return net::buffer(m_data, max_msg_length); }
36✔
89

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

92
      std::string message() const { return std::string(m_data); }
12✔
93

94
      // This is a CompletionCondition for net::async_read().
95
      // Our toy protocol always expects a single \0-terminated string.
96
      std::size_t received_zero_byte(const boost::system::error_code& error, std::size_t bytes_transferred) {
152✔
97
         if(error) {
152✔
98
            return 0;
99
         }
100

101
         if(bytes_transferred > 0 && m_data[bytes_transferred - 1] == '\0') {
116✔
102
            return 0;
103
         }
104

105
         return max_msg_length - bytes_transferred;
76✔
106
      }
107

108
      void on_timeout(std::function<void(const std::string&)> cb) { m_on_timeout = std::move(cb); }
72✔
109

110
      void reset_timeout(const std::string& message) {
296✔
111
         m_timeout_timer.expires_after(k_timeout);
296✔
112
         m_timeout_timer.async_wait([=, this](const error_code& ec) {
1,512✔
113
            if(ec != net::error::operation_aborted)  // timer cancelled
296✔
114
            {
115
               if(m_on_timeout) {
×
116
                  m_on_timeout(message);
×
117
               }
118

119
               // hard-close the underlying transport to maximize the
120
               // probability for all participating peers to error out
121
               // of their pending I/O operations
122
               if(m_stream) {
×
123
                  m_stream->lowest_layer().close();
×
124
               }
125

126
               throw Timeout_Exception("timeout occured: " + message);
×
127
            }
128
         });
296✔
129
      }
296✔
130

131
      void cancel_timeout() { m_timeout_timer.cancel(); }
108✔
132

133
      ssl_stream& stream() { return *m_stream; }
188✔
134

135
      void create_stream(std::unique_ptr<ssl_stream> stream) {
72✔
136
         BOTAN_ASSERT(!m_stream, "Stream is only assigned once");
72✔
137
         m_stream = std::move(stream);
72✔
138
      }
72✔
139

140
      std::shared_ptr<Botan::TLS::Context> ctx() { return m_ctx; }
216✔
141

142
   private:
143
      std::shared_ptr<Botan::AutoSeeded_RNG> m_rng;
144
      std::shared_ptr<Basic_Credentials_Manager> m_credentials_manager;
145
      std::shared_ptr<Botan::TLS::Session_Manager_Noop> m_session_mgr;
146
      std::shared_ptr<Botan::TLS::Context> m_ctx;
147
      std::unique_ptr<ssl_stream> m_stream;
148
      net::system_timer m_timeout_timer;
149
      std::function<void(const std::string&)> m_on_timeout;
150

151
      char m_data[max_msg_length];
152
};
153

154
class Result_Wrapper {
36✔
155
   public:
156
      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
144✔
157

158
      Result& result() { return m_result; }
72✔
159

160
      void expect_success(const std::string& msg, const error_code& ec) {
352✔
161
         error_code success;
352✔
162
         expect_ec(msg, success, ec);
352✔
163
      }
164

165
      void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec) {
408✔
166
         if(ec != expected) {
408✔
167
            m_result.test_failure(msg, "Unexpected error code: " + ec.message());
×
168
         } else {
169
            m_result.test_success(msg);
408✔
170
         }
171
      }
408✔
172

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

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

177
   private:
178
      Result m_result;
179
};
180

181
// Control messages
182
// The messages below can be used by the test clients in order to configure the server's behavior during a test
183
// case.
184
//
185
// Tell the server that the next read should result in a StreamTruncated error
186
const std::string EXPECT_SHORT_READ_MESSAGE = "SHORT_READ";
187
// Prepare the server for the test case "Shutdown No Response"
188
const std::string PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE = "SHUTDOWN_NOW";
189

190
class Server : public Peer,
191
               public std::enable_shared_from_this<Server> {
192
   public:
193
      Server(const std::shared_ptr<const Botan::TLS::Policy>& policy,
36✔
194
             net::io_context& ioc,
195
             const std::string& test_name) :
36✔
196
            Peer(policy, ioc, server_cert(), server_key()),
36✔
197
            m_acceptor(ioc),
36✔
198
            m_result("Server (" + test_name + ")"),
36✔
199
            m_short_read_expected(false),
36✔
200
            m_move_before_accept(false) {
108✔
201
         reset_timeout("startup");
36✔
202
      }
36✔
203

204
      void listen() {
36✔
205
         error_code ec;
36✔
206
         const auto& endpoint = k_endpoints.back();
36✔
207

208
         m_acceptor.open(endpoint.protocol(), ec);
36✔
209
         m_acceptor.set_option(net::socket_base::reuse_address(true), ec);
36✔
210
         m_acceptor.bind(endpoint, ec);
36✔
211
         m_acceptor.listen(net::socket_base::max_listen_connections, ec);
36✔
212

213
         m_result.expect_success("listen", ec);
72✔
214

215
         reset_timeout("accept");
36✔
216
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
72✔
217
      }
36✔
218

219
      void expect_short_read() { m_short_read_expected = true; }
4✔
220

221
      void move_before_accept() { m_move_before_accept = true; }
4✔
222

223
      Result_Wrapper result() { return m_result; }
36✔
224

225
   private:
226
      void start_session(const error_code& ec, tcp::socket socket) {
36✔
227
         // Note: If this fails with 'Operation canceled', it likely means the timer expired and the port is taken.
228
         m_result.expect_success("accept", ec);
72✔
229

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

233
         if(m_move_before_accept) {
36✔
234
            // regression test for #2635
235
            ssl_stream s(std::move(socket), ctx());
4✔
236
            create_stream(std::make_unique<ssl_stream>(std::move(s)));
8✔
237
         } else {
4✔
238
            create_stream(std::make_unique<ssl_stream>(std::move(socket), ctx()));
64✔
239
         }
240

241
         reset_timeout("handshake");
36✔
242
         stream().async_handshake(Botan::TLS::Connection_Side::Server,
72✔
243
                                  std::bind(&Server::handle_handshake, shared_from_this(), _1));
72✔
244
      }
36✔
245

246
      void shutdown() {
8✔
247
         error_code shutdown_ec;
8✔
248
         stream().shutdown(shutdown_ec);
8✔
249
         m_result.expect_success("shutdown", shutdown_ec);
16✔
250
         handle_write(error_code{});
8✔
251
      }
8✔
252

253
      void handle_handshake(const error_code& ec) {
36✔
254
         m_result.expect_success("handshake", ec);
72✔
255
         handle_write(error_code{});
36✔
256
      }
36✔
257

258
      void handle_write(const error_code& ec) {
60✔
259
         m_result.expect_success("send_response", ec);
120✔
260
         reset_timeout("read_message");
60✔
261
         net::async_read(stream(),
60✔
262
                         buffer(),
60✔
263
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
264
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
265
      }
60✔
266

267
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
268
         if(m_short_read_expected) {
60✔
269
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
270
            cancel_timeout();
16✔
271
            return;
16✔
272
         }
273

274
         if(ec) {
44✔
275
            if(stream().shutdown_received()) {
20✔
276
               m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
20✔
277
               reset_timeout("shutdown");
20✔
278
               stream().async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
20✔
279
            } else {
280
               m_result.test_failure("Unexpected error code: " + ec.message());
×
281
               cancel_timeout();
×
282
            }
283
            return;
20✔
284
         }
285

286
         m_result.expect_success("read_message", ec);
48✔
287

288
         if(message() == PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE) {
36✔
289
            m_short_read_expected = true;
8✔
290
            shutdown();
8✔
291
            return;
8✔
292
         }
293

294
         if(message() == EXPECT_SHORT_READ_MESSAGE) {
28✔
295
            m_short_read_expected = true;
4✔
296
         }
297

298
         reset_timeout("send_response");
16✔
299
         net::async_write(
32✔
300
            stream(), buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
301
      }
302

303
      void handle_shutdown(const error_code& ec) {
20✔
304
         m_result.expect_success("shutdown", ec);
40✔
305
         cancel_timeout();
20✔
306
      }
20✔
307

308
   private:
309
      tcp::acceptor m_acceptor;
310
      Result_Wrapper m_result;
311
      bool m_short_read_expected;
312

313
      // regression test for #2635
314
      bool m_move_before_accept;
315
};
316

317
class Client : public Peer {
36✔
318
      static void accept_all(const std::vector<Botan::X509_Certificate>&,
36✔
319
                             const std::vector<std::optional<Botan::OCSP::Response>>&,
320
                             const std::vector<Botan::Certificate_Store*>&,
321
                             Botan::Usage_Type,
322
                             std::string_view,
323
                             const Botan::TLS::Policy&) {}
36✔
324

325
   public:
326
      Client(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) : Peer(policy, ioc) {
36✔
327
         ctx()->set_verify_callback(accept_all);
36✔
328
         create_stream(std::make_unique<ssl_stream>(ioc, ctx()));
72✔
329
      }
36✔
330

331
      void close_socket() {
24✔
332
         // Shutdown on TCP level before closing the socket for portable behavior. Otherwise the peer will see a
333
         // connection_reset error rather than EOF on Windows.
334
         // See the remark in
335
         // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html
336
         stream().lowest_layer().shutdown(tcp::socket::shutdown_both);
20✔
337
         stream().lowest_layer().close();
24✔
338
      }
12✔
339
};
340

341
class TestBase {
342
   public:
343
      TestBase(net::io_context& ioc,
36✔
344
               const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
345
               const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
346
               const std::string& name,
347
               const std::string& config_name) :
36✔
348
            m_name(name + " (" + config_name + ")"),
72✔
349
            m_client(std::make_shared<Client>(client_policy, ioc)),
36✔
350
            m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
36✔
351
            m_result(m_name) {
108✔
352
         m_client->on_timeout(
36✔
353
            [=, this](const std::string& msg) { m_result.test_failure("timeout in client during: " + msg); });
×
354
         m_server->on_timeout(
36✔
355
            [=, this](const std::string& msg) { m_result.test_failure("timeout in server during: " + msg); });
×
356

357
         m_server->listen();
36✔
358
      }
36✔
359

360
      virtual ~TestBase() = default;
108✔
361

362
      TestBase(const TestBase& other) = delete;
363
      TestBase(TestBase&& other) = delete;
364
      TestBase& operator=(const TestBase& other) = delete;
365
      TestBase& operator=(TestBase&& other) = delete;
366

367
      virtual void finishAsynchronousWork() {}
20✔
368

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

371
      void extend_results(std::vector<Result>& results) {
36✔
372
         results.push_back(m_result.result());
36✔
373
         results.push_back(m_server->result().result());
36✔
374
      }
36✔
375

376
   protected:
377
      //! retire client and server instances after a job well done
378
      void teardown() { m_client->cancel_timeout(); }
24✔
379

380
      std::shared_ptr<Client>& client() { return m_client; }
44✔
381

382
      std::shared_ptr<Server>& server() { return m_server; }
8✔
383

384
      Result_Wrapper& result() { return m_result; }
156✔
385

386
   private:
387
      std::string m_name;
388

389
      std::shared_ptr<Client> m_client;
390
      std::shared_ptr<Server> m_server;
391

392
      Result_Wrapper m_result;
393
};
394

395
class Synchronous_Test : public TestBase {
16✔
396
   public:
397
      using TestBase::TestBase;
16✔
398

399
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
400

401
      void run(const error_code&) {
16✔
402
         m_client_thread = std::thread([this] {
16✔
403
            try {
16✔
404
               this->run_synchronous_client();
16✔
405
            } catch(const std::exception& ex) {
×
406
               result().test_failure(std::string("sync client failed with: ") + ex.what());
×
407
            } catch(const boost::exception&) {
×
408
               result().test_failure("sync client failed with boost exception");
×
409
            } catch(...) {
×
410
               result().test_failure("sync client failed with unknown error");
×
411
            }
×
412
         });
32✔
413
      }
16✔
414

415
      virtual void run_synchronous_client() = 0;
416

417
   private:
418
      std::thread m_client_thread;
419
};
420

421
/* In this test case both parties perform the handshake, exchange a message, and do a full shutdown.
422
 *
423
 * The client expects the server to echo the same message it sent. The client then initiates the shutdown. The server is
424
 * expected to receive a close_notify and complete its shutdown with an error_code Success, the client is expected to
425
 * receive a close_notify and complete its shutdown with an error_code EOF.
426
 */
427
class Test_Conversation : public TestBase,
428
                          public net::coroutine,
429
                          public std::enable_shared_from_this<Test_Conversation> {
430
   public:
431
      Test_Conversation(net::io_context& ioc,
8✔
432
                        const std::string& config_name,
433
                        const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
434
                        const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
435
                        const std::string& test_name = "Test Conversation") :
8✔
436
            TestBase(ioc, client_policy, server_policy, test_name, config_name) {}
8✔
437

438
      void run(const error_code& ec) {
56✔
439
         static auto test_case = &Test_Conversation::run;
56✔
440
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
441

442
         reenter(*this) {
56✔
443
            client()->reset_timeout("connect");
8✔
444
            yield net::async_connect(
16✔
445
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
446
            result().expect_success("connect", ec);
16✔
447

448
            client()->reset_timeout("handshake");
8✔
449
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
450
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
451
            result().expect_success("handshake", ec);
16✔
452

453
            client()->reset_timeout("send_message");
8✔
454
            yield net::async_write(client()->stream(),
24✔
455
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
456
                                   std::bind(test_case, shared_from_this(), _1));
16✔
457
            result().expect_success("send_message", ec);
16✔
458

459
            client()->reset_timeout("receive_response");
8✔
460
            yield net::async_read(client()->stream(),
24✔
461
                                  client()->buffer(),
8✔
462
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
8✔
463
                                  std::bind(test_case, shared_from_this(), _1));
16✔
464
            result().expect_success("receive_response", ec);
16✔
465
            result().confirm("correct message", client()->message() == message);
16✔
466

467
            client()->reset_timeout("shutdown");
8✔
468
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
469
            result().expect_success("shutdown", ec);
16✔
470

471
            client()->reset_timeout("await close_notify");
8✔
472
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
473
            result().confirm("received close_notify", client()->stream().shutdown_received());
16✔
474
            result().expect_ec("closed with EOF", net::error::eof, ec);
8✔
475

476
            teardown();
64✔
477
         }
56✔
478
      }
56✔
479
};
480

481
class Test_Conversation_Sync : public Synchronous_Test {
4✔
482
   public:
483
      Test_Conversation_Sync(net::io_context& ioc,
4✔
484
                             const std::string& config_name,
485
                             const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
486
                             const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
487
            Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
12✔
488

489
      void run_synchronous_client() override {
4✔
490
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
491
         error_code ec;
4✔
492

493
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
494
         result().expect_success("connect", ec);
8✔
495

496
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
497
         result().expect_success("handshake", ec);
8✔
498

499
         net::write(client()->stream(),
4✔
500
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
501
                    ec);
502
         result().expect_success("send_message", ec);
8✔
503

504
         net::read(
8✔
505
            client()->stream(), client()->buffer(), std::bind(&Client::received_zero_byte, client().get(), _1, _2), ec);
4✔
506
         result().expect_success("receive_response", ec);
8✔
507
         result().confirm("correct message", client()->message() == message);
8✔
508

509
         client()->stream().shutdown(ec);
4✔
510
         result().expect_success("shutdown", ec);
8✔
511

512
         net::read(client()->stream(), client()->buffer(), ec);
4✔
513
         result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
514
         result().expect_ec("closed with EOF", net::error::eof, ec);
4✔
515

516
         teardown();
8✔
517
      }
4✔
518
};
519

520
/* In this test case the client shuts down the SSL connection, but does not wait for the server's response before
521
 * closing the socket. Accordingly, it will not receive the server's close_notify alert. Instead, the async_read
522
 * operation will be aborted. The server should be able to successfully shutdown nonetheless.
523
 */
524
class Test_Eager_Close : public TestBase,
525
                         public net::coroutine,
526
                         public std::enable_shared_from_this<Test_Eager_Close> {
527
   public:
528
      Test_Eager_Close(net::io_context& ioc,
4✔
529
                       const std::string& config_name,
530
                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
531
                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
532
            TestBase(ioc, client_policy, server_policy, "Test Eager Close", config_name) {}
8✔
533

534
      void run(const error_code& ec) {
16✔
535
         static auto test_case = &Test_Eager_Close::run;
16✔
536
         reenter(*this) {
16✔
537
            client()->reset_timeout("connect");
4✔
538
            yield net::async_connect(
8✔
539
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
540
            result().expect_success("connect", ec);
8✔
541

542
            client()->reset_timeout("handshake");
4✔
543
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
544
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
545
            result().expect_success("handshake", ec);
8✔
546

547
            client()->reset_timeout("shutdown");
4✔
548
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
549
            result().expect_success("shutdown", ec);
8✔
550

551
            client()->close_socket();
4✔
552
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
553

554
            teardown();
20✔
555
         }
16✔
556
      }
16✔
557
};
558

559
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
560
   public:
561
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
562
                            const std::string& config_name,
563
                            const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
564
                            const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
565
            Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
12✔
566

567
      void run_synchronous_client() override {
4✔
568
         error_code ec;
4✔
569

570
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
571
         result().expect_success("connect", ec);
8✔
572

573
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
574
         result().expect_success("handshake", ec);
8✔
575

576
         client()->stream().shutdown(ec);
4✔
577
         result().expect_success("shutdown", ec);
8✔
578

579
         client()->close_socket();
4✔
580
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
581

582
         teardown();
4✔
583
      }
4✔
584
};
585

586
/* In this test case the client closes the socket without properly shutting down the connection.
587
 * The server should see a StreamTruncated error.
588
 */
589
class Test_Close_Without_Shutdown : public TestBase,
590
                                    public net::coroutine,
591
                                    public std::enable_shared_from_this<Test_Close_Without_Shutdown> {
592
   public:
593
      Test_Close_Without_Shutdown(net::io_context& ioc,
4✔
594
                                  const std::string& config_name,
595
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
596
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
597
            TestBase(ioc, client_policy, server_policy, "Test Close Without Shutdown", config_name) {}
8✔
598

599
      void run(const error_code& ec) {
20✔
600
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
601
         reenter(*this) {
20✔
602
            client()->reset_timeout("connect");
4✔
603
            yield net::async_connect(
8✔
604
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
605
            result().expect_success("connect", ec);
8✔
606

607
            client()->reset_timeout("handshake");
4✔
608
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
609
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
610
            result().expect_success("handshake", ec);
8✔
611

612
            // send the control message to configure the server to expect a short-read
613
            client()->reset_timeout("send expect_short_read message");
4✔
614
            yield net::async_write(client()->stream(),
12✔
615
                                   net::buffer(EXPECT_SHORT_READ_MESSAGE.c_str(),
4✔
616
                                               EXPECT_SHORT_READ_MESSAGE.size() + 1),  // including \0 termination
4✔
617
                                   std::bind(test_case, shared_from_this(), _1));
8✔
618
            result().expect_success("send expect_short_read message", ec);
8✔
619

620
            // read the confirmation of the control message above
621
            client()->reset_timeout("receive_response");
4✔
622
            yield net::async_read(client()->stream(),
12✔
623
                                  client()->buffer(),
4✔
624
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
4✔
625
                                  std::bind(test_case, shared_from_this(), _1));
8✔
626
            result().expect_success("receive_response", ec);
8✔
627

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

631
            teardown();
24✔
632
         }
20✔
633
      }
20✔
634
};
635

636
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
637
   public:
638
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
639
                                       const std::string& config_name,
640
                                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
641
                                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
642
            Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
12✔
643

644
      void run_synchronous_client() override {
4✔
645
         error_code ec;
4✔
646
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
647
         result().expect_success("connect", ec);
8✔
648

649
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
650
         result().expect_success("handshake", ec);
8✔
651

652
         server()->expect_short_read();
4✔
653

654
         client()->close_socket();
4✔
655
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
656

657
         teardown();
4✔
658
      }
4✔
659
};
660

661
/* In this test case the server shuts down the connection but the client doesn't send the corresponding close_notify
662
 * response. Instead, it closes the socket immediately.
663
 * The server should see a short-read error.
664
 */
665
class Test_No_Shutdown_Response : public TestBase,
666
                                  public net::coroutine,
667
                                  public std::enable_shared_from_this<Test_No_Shutdown_Response> {
668
   public:
669
      Test_No_Shutdown_Response(net::io_context& ioc,
4✔
670
                                const std::string& config_name,
671
                                const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
672
                                const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
673
            TestBase(ioc, client_policy, server_policy, "Test No Shutdown Response", config_name) {}
8✔
674

675
      void run(const error_code& ec) {
20✔
676
         static auto test_case = &Test_No_Shutdown_Response::run;
20✔
677
         reenter(*this) {
20✔
678
            client()->reset_timeout("connect");
4✔
679
            yield net::async_connect(
8✔
680
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
681
            result().expect_success("connect", ec);
8✔
682

683
            client()->reset_timeout("handshake");
4✔
684
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
685
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
686
            result().expect_success("handshake", ec);
8✔
687

688
            // send a control message to make the server shut down
689
            client()->reset_timeout("send shutdown message");
4✔
690
            yield net::async_write(
8✔
691
               client()->stream(),
4✔
692
               net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
4✔
693
                           PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
694
               std::bind(test_case, shared_from_this(), _1));
8✔
695
            result().expect_success("send shutdown message", ec);
8✔
696

697
            // read the server's close-notify message
698
            client()->reset_timeout("read close_notify");
4✔
699
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
700
            result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
701
            result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
702

703
            // close the socket rather than shutting down
704
            client()->close_socket();
4✔
705

706
            teardown();
24✔
707
         }
20✔
708
      }
20✔
709
};
710

711
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
712
   public:
713
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
714
                                     const std::string& config_name,
715
                                     const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
716
                                     const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
717
            Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
12✔
718

719
      void run_synchronous_client() override {
4✔
720
         error_code ec;
4✔
721
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
722
         result().expect_success("connect", ec);
8✔
723

724
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
725
         result().expect_success("handshake", ec);
8✔
726

727
         net::write(client()->stream(),
4✔
728
                    net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
8✔
729
                                PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
730
                    ec);
731
         result().expect_success("send expect_short_read message", ec);
8✔
732

733
         net::read(client()->stream(), client()->buffer(), ec);
4✔
734
         result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
735
         result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
736

737
         // close the socket rather than shutting down
738
         client()->close_socket();
4✔
739

740
         teardown();
4✔
741
      }
4✔
742
};
743

744
class Test_Conversation_With_Move : public Test_Conversation {
4✔
745
   public:
746
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
747
                                  const std::string& config_name,
748
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
749
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
750
            Test_Conversation(ioc, config_name, client_policy, server_policy, "Test Conversation With Move") {
8✔
751
         server()->move_before_accept();
4✔
752
      }
4✔
753
};
754

755
class SystemConfiguration {
756
   public:
757
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
758
            m_name(std::move(n)),
8✔
759
            m_client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
760
            m_server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
761

762
      template <typename TestT>
763
      void run(std::vector<Result>& results) {
36✔
764
         net::io_context ioc;
36✔
765

766
         auto t = std::make_shared<TestT>(ioc, m_name, m_server_policy, m_client_policy);
36✔
767

768
         t->run(error_code{});
36✔
769

770
         while(true) {
771
            try {
772
               ioc.run();
36✔
773
               break;
774
            } catch(const Timeout_Exception&) { /* timeout is reported via Test::Result object */
×
775
            } catch(const boost::exception&) {
×
776
               t->fail("boost exception");
×
777
            } catch(const std::exception& ex) {
×
778
               t->fail(ex.what());
×
779
            }
780
         }
781

782
         t->finishAsynchronousWork();
36✔
783
         t->extend_results(results);
36✔
784
      }
36✔
785

786
   private:
787
      std::string m_name;
788

789
      std::shared_ptr<Botan::TLS::Text_Policy> m_client_policy;
790
      std::shared_ptr<Botan::TLS::Text_Policy> m_server_policy;
791
};
792

793
std::vector<SystemConfiguration> get_configurations() {
1✔
794
   return {
1✔
795
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
796
      #if defined(BOTAN_HAS_TLS_13)
797
         SystemConfiguration(
798
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
799
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
800
                             "allow_tls12=true\nallow_tls13=false",
801
                             "allow_tls12=true\nallow_tls13=true"),
802
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
803
                             "allow_tls12=true\nallow_tls13=true",
804
                             "allow_tls12=true\nallow_tls13=false"),
805
      #endif
806
   };
13✔
807
}
808

809
}  // namespace
810

811
namespace Botan_Tests {
812

813
class Tls_Stream_Integration_Tests final : public Test {
×
814
   public:
815
      std::vector<Test::Result> run() override {
1✔
816
         std::vector<Test::Result> results;
1✔
817

818
         auto configs = get_configurations();
1✔
819
         for(auto& config : configs) {
5✔
820
            config.run<Test_Conversation>(results);
4✔
821
            config.run<Test_Eager_Close>(results);
4✔
822
            config.run<Test_Close_Without_Shutdown>(results);
4✔
823
            config.run<Test_No_Shutdown_Response>(results);
4✔
824
            config.run<Test_Conversation_Sync>(results);
4✔
825
            config.run<Test_Eager_Close_Sync>(results);
4✔
826
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
827
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
828
            config.run<Test_Conversation_With_Move>(results);
4✔
829
         }
830

831
         return results;
1✔
832
      }
1✔
833
};
834

835
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
836

837
}  // namespace Botan_Tests
838

839
   #endif  // BOOST_VERSION
840
#endif     // BOTAN_HAS_TLS && BOTAN_HAS_BOOST_ASIO
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

© 2025 Coveralls, Inc