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

randombit / botan / 5356326050

23 Jun 2023 01:05PM UTC coverage: 91.728% (-0.008%) from 91.736%
5356326050

Pull #3595

github

web-flow
Merge a5b917599 into 92171c524
Pull Request #3595: Improve clang-tidy coverage

78163 of 85212 relevant lines covered (91.73%)

12690161.35 hits per line

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

93.56
/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

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

27
      #include <boost/asio.hpp>
28
      #include <boost/exception/exception.hpp>
29

30
      #include "../cli/tls_helpers.h"  // for Basic_Credentials_Manager
31

32
namespace {
33

34
namespace net = boost::asio;
35

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

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

45
enum { max_msg_length = 512 };
46

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

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

55
class Timeout_Exception : public std::runtime_error {
56
      using std::runtime_error::runtime_error;
×
57
};
58

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

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

78
      virtual ~Peer() { cancel_timeout(); }
432✔
79

80
      net::mutable_buffer buffer() { return net::buffer(m_data, max_msg_length); }
36✔
81

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

84
      std::string message() const { return std::string(m_data); }
12✔
85

86
      // This is a CompletionCondition for net::async_read().
87
      // Our toy protocol always expects a single \0-terminated string.
88
      std::size_t received_zero_byte(const boost::system::error_code& error, std::size_t bytes_transferred) {
152✔
89
         if(error) {
152✔
90
            return 0;
91
         }
92

93
         if(bytes_transferred > 0 && m_data[bytes_transferred - 1] == '\0') {
116✔
94
            return 0;
95
         }
96

97
         return max_msg_length - bytes_transferred;
76✔
98
      }
99

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

102
      void reset_timeout(const std::string& message) {
296✔
103
         m_timeout_timer.expires_after(k_timeout);
296✔
104
         m_timeout_timer.async_wait([=, this](const error_code& ec) {
1,512✔
105
            if(ec != net::error::operation_aborted)  // timer cancelled
296✔
106
            {
107
               if(m_on_timeout) {
×
108
                  m_on_timeout(message);
×
109
               }
110

111
               // hard-close the underlying transport to maximize the
112
               // probability for all participating peers to error out
113
               // of their pending I/O operations
114
               if(m_stream) {
×
115
                  m_stream->lowest_layer().close();
×
116
               }
117

118
               throw Timeout_Exception("timeout occured: " + message);
×
119
            }
120
         });
296✔
121
      }
296✔
122

123
      void cancel_timeout() { m_timeout_timer.cancel(); }
108✔
124

125
   protected:
126
      std::shared_ptr<Botan::AutoSeeded_RNG> m_rng;
127
      std::shared_ptr<Basic_Credentials_Manager> m_credentials_manager;
128
      std::shared_ptr<Botan::TLS::Session_Manager_Noop> m_session_mgr;
129
      std::shared_ptr<Botan::TLS::Context> m_ctx;
130
      std::unique_ptr<ssl_stream> m_stream;
131
      net::system_timer m_timeout_timer;
132
      std::function<void(const std::string&)> m_on_timeout;
133

134
      char m_data[max_msg_length];
135
};
136

137
class Result_Wrapper {
36✔
138
   public:
139
      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
144✔
140

141
      Result& result() { return m_result; }
72✔
142

143
      void expect_success(const std::string& msg, const error_code& ec) {
352✔
144
         error_code success;
352✔
145
         expect_ec(msg, success, ec);
352✔
146
      }
147

148
      void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec) {
408✔
149
         if(ec != expected) {
408✔
150
            m_result.test_failure(msg, "Unexpected error code: " + ec.message());
×
151
         } else {
152
            m_result.test_success(msg);
408✔
153
         }
154
      }
408✔
155

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

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

160
   private:
161
      Result m_result;
162
};
163

164
class Server : public Peer,
165
               public std::enable_shared_from_this<Server> {
166
   public:
167
      Server(std::shared_ptr<const Botan::TLS::Policy> policy, net::io_context& ioc, const std::string& test_name) :
36✔
168
            Peer(std::move(policy), ioc, server_cert(), server_key()),
36✔
169
            m_acceptor(ioc),
72✔
170
            m_result("Server (" + test_name + ")"),
36✔
171
            m_short_read_expected(false),
36✔
172
            m_move_before_accept(false) {
108✔
173
         reset_timeout("startup");
36✔
174
      }
36✔
175

176
      // Control messages
177
      // The messages below can be used by the test clients in order to configure the server's behavior during a test
178
      // case.
179
      //
180
      // Tell the server that the next read should result in a StreamTruncated error
181
      std::string expect_short_read_message = "SHORT_READ";
182
      // Prepare the server for the test case "Shutdown No Response"
183
      std::string prepare_shutdown_no_response_message = "SHUTDOWN_NOW";
184

185
      void listen() {
36✔
186
         error_code ec;
36✔
187
         const auto& endpoint = k_endpoints.back();
36✔
188

189
         m_acceptor.open(endpoint.protocol(), ec);
36✔
190
         m_acceptor.set_option(net::socket_base::reuse_address(true), ec);
36✔
191
         m_acceptor.bind(endpoint, ec);
36✔
192
         m_acceptor.listen(net::socket_base::max_listen_connections, ec);
36✔
193

194
         m_result.expect_success("listen", ec);
72✔
195

196
         reset_timeout("accept");
36✔
197
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
72✔
198
      }
36✔
199

200
      void expect_short_read() { m_short_read_expected = true; }
4✔
201

202
      void move_before_accept() { m_move_before_accept = true; }
4✔
203

204
      Result_Wrapper result() { return m_result; }
36✔
205

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

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

214
         if(m_move_before_accept) {
36✔
215
            // regression test for #2635
216
            ssl_stream s(std::move(socket), m_ctx);
4✔
217
            m_stream = std::make_unique<ssl_stream>(std::move(s));
8✔
218
         } else {
4✔
219
            m_stream = std::make_unique<ssl_stream>(std::move(socket), m_ctx);
32✔
220
         }
221

222
         reset_timeout("handshake");
36✔
223
         m_stream->async_handshake(Botan::TLS::Connection_Side::Server,
72✔
224
                                   std::bind(&Server::handle_handshake, shared_from_this(), _1));
72✔
225
      }
36✔
226

227
      void shutdown() {
8✔
228
         error_code shutdown_ec;
8✔
229
         m_stream->shutdown(shutdown_ec);
8✔
230
         m_result.expect_success("shutdown", shutdown_ec);
16✔
231
         handle_write(error_code{});
8✔
232
      }
8✔
233

234
      void handle_handshake(const error_code& ec) {
36✔
235
         m_result.expect_success("handshake", ec);
72✔
236
         handle_write(error_code{});
36✔
237
      }
36✔
238

239
      void handle_write(const error_code& ec) {
60✔
240
         m_result.expect_success("send_response", ec);
120✔
241
         reset_timeout("read_message");
60✔
242
         net::async_read(*m_stream,
60✔
243
                         buffer(),
60✔
244
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
245
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
246
      }
60✔
247

248
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
249
         if(m_short_read_expected) {
60✔
250
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
251
            cancel_timeout();
16✔
252
            return;
16✔
253
         }
254

255
         if(ec) {
44✔
256
            if(m_stream->shutdown_received()) {
20✔
257
               m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
20✔
258
               reset_timeout("shutdown");
20✔
259
               m_stream->async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
20✔
260
            } else {
261
               m_result.test_failure("Unexpected error code: " + ec.message());
×
262
               cancel_timeout();
×
263
            }
264
            return;
20✔
265
         }
266

267
         m_result.expect_success("read_message", ec);
48✔
268

269
         if(message() == prepare_shutdown_no_response_message) {
36✔
270
            m_short_read_expected = true;
8✔
271
            shutdown();
8✔
272
            return;
8✔
273
         }
274

275
         if(message() == expect_short_read_message) {
28✔
276
            m_short_read_expected = true;
4✔
277
         }
278

279
         reset_timeout("send_response");
16✔
280
         net::async_write(
32✔
281
            *m_stream, buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
282
      }
283

284
      void handle_shutdown(const error_code& ec) {
20✔
285
         m_result.expect_success("shutdown", ec);
40✔
286
         cancel_timeout();
20✔
287
      }
20✔
288

289
   private:
290
      tcp::acceptor m_acceptor;
291
      Result_Wrapper m_result;
292
      bool m_short_read_expected;
293

294
      // regression test for #2635
295
      bool m_move_before_accept;
296
};
297

298
class Client : public Peer {
36✔
299
      static void accept_all(const std::vector<Botan::X509_Certificate>&,
36✔
300
                             const std::vector<std::optional<Botan::OCSP::Response>>&,
301
                             const std::vector<Botan::Certificate_Store*>&,
302
                             Botan::Usage_Type,
303
                             std::string_view,
304
                             const Botan::TLS::Policy&) {}
36✔
305

306
   public:
307
      Client(std::shared_ptr<const Botan::TLS::Policy> policy, net::io_context& ioc) : Peer(std::move(policy), ioc) {
36✔
308
         m_ctx->set_verify_callback(accept_all);
36✔
309
         m_stream = std::make_unique<ssl_stream>(ioc, m_ctx);
36✔
310
      }
36✔
311

312
      ssl_stream& stream() { return *m_stream; }
164✔
313

314
      void close_socket() {
24✔
315
         // Shutdown on TCP level before closing the socket for portable behavior. Otherwise the peer will see a
316
         // connection_reset error rather than EOF on Windows.
317
         // See the remark in
318
         // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html
319
         m_stream->lowest_layer().shutdown(tcp::socket::shutdown_both);
20✔
320
         m_stream->lowest_layer().close();
24✔
321
      }
12✔
322
};
323

324
      #include <boost/asio/yield.hpp>
325

326
class TestBase {
327
   public:
328
      TestBase(net::io_context& ioc,
36✔
329
               const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
330
               const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
331
               const std::string& name,
332
               const std::string& config_name) :
36✔
333
            m_name(name + " (" + config_name + ")"),
72✔
334
            m_client(std::make_shared<Client>(client_policy, ioc)),
36✔
335
            m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
36✔
336
            m_result(m_name) {
108✔
337
         m_client->on_timeout(
36✔
338
            [=, this](const std::string& msg) { m_result.test_failure("timeout in client during: " + msg); });
×
339
         m_server->on_timeout(
36✔
340
            [=, this](const std::string& msg) { m_result.test_failure("timeout in server during: " + msg); });
×
341

342
         m_server->listen();
36✔
343
      }
36✔
344

345
      virtual ~TestBase() = default;
108✔
346

347
      virtual void finishAsynchronousWork() {}
20✔
348

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

351
      void extend_results(std::vector<Result>& results) {
36✔
352
         results.push_back(m_result.result());
36✔
353
         results.push_back(m_server->result().result());
36✔
354
      }
36✔
355

356
   protected:
357
      //! retire client and server instances after a job well done
358
      void teardown() { m_client->cancel_timeout(); }
24✔
359

360
   protected:
361
      std::string m_name;
362

363
      std::shared_ptr<Client> m_client;
364
      std::shared_ptr<Server> m_server;
365

366
      Result_Wrapper m_result;
367
};
368

369
class Synchronous_Test : public TestBase {
16✔
370
   public:
371
      using TestBase::TestBase;
16✔
372

373
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
374

375
      void run(const error_code&) {
16✔
376
         m_client_thread = std::thread([this] {
16✔
377
            try {
16✔
378
               this->run_synchronous_client();
16✔
379
            } catch(const std::exception& ex) {
×
380
               m_result.test_failure(std::string("sync client failed with: ") + ex.what());
×
381
            } catch(const boost::exception&) {
×
382
               m_result.test_failure("sync client failed with boost exception");
×
383
            } catch(...) {
×
384
               m_result.test_failure("sync client failed with unknown error");
×
385
            }
×
386
         });
32✔
387
      }
16✔
388

389
      virtual void run_synchronous_client() = 0;
390

391
   private:
392
      std::thread m_client_thread;
393
};
394

395
/* In this test case both parties perform the handshake, exchange a message, and do a full shutdown.
396
 *
397
 * The client expects the server to echo the same message it sent. The client then initiates the shutdown. The server is
398
 * expected to receive a close_notify and complete its shutdown with an error_code Success, the client is expected to
399
 * receive a close_notify and complete its shutdown with an error_code EOF.
400
 */
401
class Test_Conversation : public TestBase,
402
                          public net::coroutine,
403
                          public std::enable_shared_from_this<Test_Conversation> {
404
   public:
405
      Test_Conversation(net::io_context& ioc,
8✔
406
                        const std::string& config_name,
407
                        std::shared_ptr<const Botan::TLS::Policy> client_policy,
408
                        std::shared_ptr<const Botan::TLS::Policy> server_policy,
409
                        const std::string& test_name = "Test Conversation") :
8✔
410
            TestBase(ioc, std::move(client_policy), std::move(server_policy), test_name, config_name) {}
8✔
411

412
      void run(const error_code& ec) {
56✔
413
         static auto test_case = &Test_Conversation::run;
56✔
414
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
415

416
         reenter(*this) {
56✔
417
            m_client->reset_timeout("connect");
8✔
418
            yield net::async_connect(
16✔
419
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
420
            m_result.expect_success("connect", ec);
16✔
421

422
            m_client->reset_timeout("handshake");
8✔
423
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
424
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
425
            m_result.expect_success("handshake", ec);
16✔
426

427
            m_client->reset_timeout("send_message");
8✔
428
            yield net::async_write(m_client->stream(),
24✔
429
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
430
                                   std::bind(test_case, shared_from_this(), _1));
16✔
431
            m_result.expect_success("send_message", ec);
16✔
432

433
            m_client->reset_timeout("receive_response");
8✔
434
            yield net::async_read(m_client->stream(),
24✔
435
                                  m_client->buffer(),
8✔
436
                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
8✔
437
                                  std::bind(test_case, shared_from_this(), _1));
16✔
438
            m_result.expect_success("receive_response", ec);
16✔
439
            m_result.confirm("correct message", m_client->message() == message);
16✔
440

441
            m_client->reset_timeout("shutdown");
8✔
442
            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
443
            m_result.expect_success("shutdown", ec);
16✔
444

445
            m_client->reset_timeout("await close_notify");
8✔
446
            yield net::async_read(m_client->stream(), m_client->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
447
            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
16✔
448
            m_result.expect_ec("closed with EOF", net::error::eof, ec);
8✔
449

450
            teardown();
64✔
451
         }
56✔
452
      }
56✔
453
};
454

455
class Test_Conversation_Sync : public Synchronous_Test {
4✔
456
   public:
457
      Test_Conversation_Sync(net::io_context& ioc,
4✔
458
                             const std::string& config_name,
459
                             std::shared_ptr<const Botan::TLS::Policy> client_policy,
460
                             std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
461
            Synchronous_Test(
462
               ioc, std::move(client_policy), std::move(server_policy), "Test Conversation Sync", config_name) {}
8✔
463

464
      void run_synchronous_client() override {
4✔
465
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
466
         error_code ec;
4✔
467

468
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
469
         m_result.expect_success("connect", ec);
8✔
470

471
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
472
         m_result.expect_success("handshake", ec);
8✔
473

474
         net::write(m_client->stream(),
4✔
475
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
476
                    ec);
477
         m_result.expect_success("send_message", ec);
8✔
478

479
         net::read(
8✔
480
            m_client->stream(), m_client->buffer(), std::bind(&Client::received_zero_byte, m_client.get(), _1, _2), ec);
4✔
481
         m_result.expect_success("receive_response", ec);
8✔
482
         m_result.confirm("correct message", m_client->message() == message);
8✔
483

484
         m_client->stream().shutdown(ec);
4✔
485
         m_result.expect_success("shutdown", ec);
8✔
486

487
         net::read(m_client->stream(), m_client->buffer(), ec);
4✔
488
         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
489
         m_result.expect_ec("closed with EOF", net::error::eof, ec);
4✔
490

491
         teardown();
8✔
492
      }
4✔
493
};
494

495
/* In this test case the client shuts down the SSL connection, but does not wait for the server's response before
496
 * closing the socket. Accordingly, it will not receive the server's close_notify alert. Instead, the async_read
497
 * operation will be aborted. The server should be able to successfully shutdown nonetheless.
498
 */
499
class Test_Eager_Close : public TestBase,
500
                         public net::coroutine,
501
                         public std::enable_shared_from_this<Test_Eager_Close> {
502
   public:
503
      Test_Eager_Close(net::io_context& ioc,
4✔
504
                       const std::string& config_name,
505
                       std::shared_ptr<const Botan::TLS::Policy> client_policy,
506
                       std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
507
            TestBase(ioc, std::move(client_policy), std::move(server_policy), "Test Eager Close", config_name) {}
8✔
508

509
      void run(const error_code& ec) {
16✔
510
         static auto test_case = &Test_Eager_Close::run;
16✔
511
         reenter(*this) {
16✔
512
            m_client->reset_timeout("connect");
4✔
513
            yield net::async_connect(
8✔
514
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
515
            m_result.expect_success("connect", ec);
8✔
516

517
            m_client->reset_timeout("handshake");
4✔
518
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
519
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
520
            m_result.expect_success("handshake", ec);
8✔
521

522
            m_client->reset_timeout("shutdown");
4✔
523
            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
524
            m_result.expect_success("shutdown", ec);
8✔
525

526
            m_client->close_socket();
4✔
527
            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
528

529
            teardown();
20✔
530
         }
16✔
531
      }
16✔
532
};
533

534
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
535
   public:
536
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
537
                            const std::string& config_name,
538
                            std::shared_ptr<const Botan::TLS::Policy> client_policy,
539
                            std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
540
            Synchronous_Test(
541
               ioc, std::move(client_policy), std::move(server_policy), "Test Eager Close Sync", config_name) {}
8✔
542

543
      void run_synchronous_client() override {
4✔
544
         error_code ec;
4✔
545

546
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
547
         m_result.expect_success("connect", ec);
8✔
548

549
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
550
         m_result.expect_success("handshake", ec);
8✔
551

552
         m_client->stream().shutdown(ec);
4✔
553
         m_result.expect_success("shutdown", ec);
8✔
554

555
         m_client->close_socket();
4✔
556
         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
557

558
         teardown();
4✔
559
      }
4✔
560
};
561

562
/* In this test case the client closes the socket without properly shutting down the connection.
563
 * The server should see a StreamTruncated error.
564
 */
565
class Test_Close_Without_Shutdown : public TestBase,
566
                                    public net::coroutine,
567
                                    public std::enable_shared_from_this<Test_Close_Without_Shutdown> {
568
   public:
569
      Test_Close_Without_Shutdown(net::io_context& ioc,
4✔
570
                                  const std::string& config_name,
571
                                  std::shared_ptr<const Botan::TLS::Policy> client_policy,
572
                                  std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
573
            TestBase(
574
               ioc, std::move(client_policy), std::move(server_policy), "Test Close Without Shutdown", config_name) {}
8✔
575

576
      void run(const error_code& ec) {
20✔
577
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
578
         reenter(*this) {
20✔
579
            m_client->reset_timeout("connect");
4✔
580
            yield net::async_connect(
8✔
581
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
582
            m_result.expect_success("connect", ec);
8✔
583

584
            m_client->reset_timeout("handshake");
4✔
585
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
586
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
587
            m_result.expect_success("handshake", ec);
8✔
588

589
            // send the control message to configure the server to expect a short-read
590
            m_client->reset_timeout("send expect_short_read message");
4✔
591
            yield net::async_write(
8✔
592
               m_client->stream(),
4✔
593
               net::buffer(m_server->expect_short_read_message.c_str(),
4✔
594
                           m_server->expect_short_read_message.size() + 1),  // including \0 termination
4✔
595
               std::bind(test_case, shared_from_this(), _1));
8✔
596
            m_result.expect_success("send expect_short_read message", ec);
8✔
597

598
            // read the confirmation of the control message above
599
            m_client->reset_timeout("receive_response");
4✔
600
            yield net::async_read(m_client->stream(),
12✔
601
                                  m_client->buffer(),
4✔
602
                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
4✔
603
                                  std::bind(test_case, shared_from_this(), _1));
8✔
604
            m_result.expect_success("receive_response", ec);
8✔
605

606
            m_client->close_socket();
4✔
607
            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
608

609
            teardown();
24✔
610
         }
20✔
611
      }
20✔
612
};
613

614
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
615
   public:
616
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
617
                                       const std::string& config_name,
618
                                       std::shared_ptr<const Botan::TLS::Policy> client_policy,
619
                                       std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
620
            Synchronous_Test(ioc,
621
                             std::move(client_policy),
4✔
622
                             std::move(server_policy),
4✔
623
                             "Test Close Without Shutdown Sync",
624
                             config_name) {}
12✔
625

626
      void run_synchronous_client() override {
4✔
627
         error_code ec;
4✔
628
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
629
         m_result.expect_success("connect", ec);
8✔
630

631
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
632
         m_result.expect_success("handshake", ec);
8✔
633

634
         m_server->expect_short_read();
4✔
635

636
         m_client->close_socket();
4✔
637
         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
638

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

643
/* In this test case the server shuts down the connection but the client doesn't send the corresponding close_notify
644
 * response. Instead, it closes the socket immediately.
645
 * The server should see a short-read error.
646
 */
647
class Test_No_Shutdown_Response : public TestBase,
648
                                  public net::coroutine,
649
                                  public std::enable_shared_from_this<Test_No_Shutdown_Response> {
650
   public:
651
      Test_No_Shutdown_Response(net::io_context& ioc,
4✔
652
                                const std::string& config_name,
653
                                std::shared_ptr<const Botan::TLS::Policy> client_policy,
654
                                std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
655
            TestBase(
656
               ioc, std::move(client_policy), std::move(server_policy), "Test No Shutdown Response", config_name) {}
8✔
657

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

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

671
            // send a control message to make the server shut down
672
            m_client->reset_timeout("send shutdown message");
4✔
673
            yield net::async_write(
8✔
674
               m_client->stream(),
4✔
675
               net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
4✔
676
                           m_server->prepare_shutdown_no_response_message.size() + 1),  // including \0 termination
4✔
677
               std::bind(test_case, shared_from_this(), _1));
8✔
678
            m_result.expect_success("send shutdown message", ec);
8✔
679

680
            // read the server's close-notify message
681
            m_client->reset_timeout("read close_notify");
4✔
682
            yield net::async_read(m_client->stream(), m_client->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
683
            m_result.expect_ec("read gives EOF", net::error::eof, ec);
4✔
684
            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
685

686
            // close the socket rather than shutting down
687
            m_client->close_socket();
4✔
688

689
            teardown();
24✔
690
         }
20✔
691
      }
20✔
692
};
693

694
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
695
   public:
696
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
697
                                     const std::string& config_name,
698
                                     std::shared_ptr<const Botan::TLS::Policy> client_policy,
699
                                     std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
700
            Synchronous_Test(
701
               ioc, std::move(client_policy), std::move(server_policy), "Test No Shutdown Response Sync", config_name) {
4✔
702
      }
4✔
703

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

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

712
         net::write(m_client->stream(),
8✔
713
                    net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
4✔
714
                                m_server->prepare_shutdown_no_response_message.size() + 1),  // including \0 termination
4✔
715
                    ec);
716
         m_result.expect_success("send expect_short_read message", ec);
8✔
717

718
         net::read(m_client->stream(), m_client->buffer(), ec);
4✔
719
         m_result.expect_ec("read gives EOF", net::error::eof, ec);
4✔
720
         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
721

722
         // close the socket rather than shutting down
723
         m_client->close_socket();
4✔
724

725
         teardown();
4✔
726
      }
4✔
727
};
728

729
class Test_Conversation_With_Move : public Test_Conversation {
4✔
730
   public:
731
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
732
                                  std::string config_name,
733
                                  std::shared_ptr<const Botan::TLS::Policy> client_policy,
734
                                  std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
735
            Test_Conversation(ioc,
736
                              std::move(config_name),
4✔
737
                              std::move(client_policy),
4✔
738
                              std::move(server_policy),
4✔
739
                              "Test Conversation With Move") {
12✔
740
         m_server->move_before_accept();
4✔
741
      }
4✔
742
};
743

744
      #include <boost/asio/unyield.hpp>
745
      #include <utility>
746

747
struct SystemConfiguration {
748
      std::string name;
749

750
      std::shared_ptr<Botan::TLS::Text_Policy> client_policy;
751
      std::shared_ptr<Botan::TLS::Text_Policy> server_policy;
752

753
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
754
            name(std::move(n)),
8✔
755
            client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
756
            server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
757

758
      template <typename TestT>
759
      void run(std::vector<Result>& results) {
36✔
760
         net::io_context ioc;
36✔
761

762
         auto t = std::make_shared<TestT>(ioc, name, server_policy, client_policy);
36✔
763

764
         t->run(error_code{});
36✔
765

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

778
         t->finishAsynchronousWork();
36✔
779
         t->extend_results(results);
36✔
780
      }
36✔
781
};
782

783
std::vector<SystemConfiguration> get_configurations() {
1✔
784
   return {
1✔
785
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
786
      #if defined(BOTAN_HAS_TLS_13)
787
         SystemConfiguration(
788
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
789
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
790
                             "allow_tls12=true\nallow_tls13=false",
791
                             "allow_tls12=true\nallow_tls13=true"),
792
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
793
                             "allow_tls12=true\nallow_tls13=true",
794
                             "allow_tls12=true\nallow_tls13=false"),
795
      #endif
796
   };
13✔
797
}
798

799
}  // namespace
800

801
namespace Botan_Tests {
802

803
class Tls_Stream_Integration_Tests final : public Test {
×
804
   public:
805
      std::vector<Test::Result> run() override {
1✔
806
         std::vector<Test::Result> results;
1✔
807

808
         auto configs = get_configurations();
1✔
809
         for(auto& config : configs) {
5✔
810
            config.run<Test_Conversation>(results);
4✔
811
            config.run<Test_Eager_Close>(results);
4✔
812
            config.run<Test_Close_Without_Shutdown>(results);
4✔
813
            config.run<Test_No_Shutdown_Response>(results);
4✔
814
            config.run<Test_Conversation_Sync>(results);
4✔
815
            config.run<Test_Eager_Close_Sync>(results);
4✔
816
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
817
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
818
            config.run<Test_Conversation_With_Move>(results);
4✔
819
         }
820

821
         return results;
1✔
822
      }
1✔
823
};
824

825
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
826

827
}  // namespace Botan_Tests
828

829
   #endif  // BOOST_VERSION
830
#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