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

randombit / botan / 13274522654

11 Feb 2025 11:26PM UTC coverage: 91.645% (-0.007%) from 91.652%
13274522654

push

github

web-flow
Merge pull request #4647 from randombit/jack/internal-assert-and-mem-ops

Avoid using mem_ops.h or assert.h in public headers

94854 of 103501 relevant lines covered (91.65%)

11334975.77 hits per line

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

98.85
/src/tests/unit_asio_stream.cpp
1
/*
2
* TLS ASIO Stream Unit Tests
3
* (C) 2018-2020 Jack Lloyd
4
*     2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include "tests.h"
10

11
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_ASIO_STREAM)
12

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

16
      #include <botan/asio_stream.h>
17
      #include <botan/tls_callbacks.h>
18
      #include <botan/tls_session_manager_noop.h>
19

20
      #include <boost/beast/_experimental/test/stream.hpp>
21
      #include <boost/bind.hpp>
22
      #include <utility>
23

24
namespace Botan_Tests {
25

26
namespace net = boost::asio;
27
using error_code = boost::system::error_code;
28

29
constexpr uint8_t TEST_DATA[] =
30
   "The story so far: In the beginning the Universe was created. "
31
   "This has made a lot of people very angry and been widely regarded as a bad move.";
32
constexpr std::size_t TEST_DATA_SIZE = 142;
33
static_assert(sizeof(TEST_DATA) == TEST_DATA_SIZE, "size of TEST_DATA must match TEST_DATA_SIZE");
34

35
/**
36
 * Mocked Botan::TLS::Channel. Pretends to perform TLS operations and triggers appropriate callbacks in StreamCore.
37
 */
38
class MockChannel {
36✔
39
   public:
40
      MockChannel(std::shared_ptr<Botan::TLS::Callbacks> core) :
26✔
41
            m_callbacks(std::move(core)),
26✔
42
            m_bytes_till_complete_record(TEST_DATA_SIZE),
26✔
43
            m_active(false),
26✔
44
            m_close_notify_received(false) {}
26✔
45

46
   public:
47
      std::size_t received_data(std::span<const uint8_t> data) {
6✔
48
         if(m_bytes_till_complete_record <= data.size()) {
6✔
49
            m_callbacks->tls_record_received(0, TEST_DATA);
6✔
50
            m_active = true;  // claim to be active once a full record has been received (for handshake test)
6✔
51
            return 0;
6✔
52
         }
53
         m_bytes_till_complete_record -= data.size();
×
54
         return m_bytes_till_complete_record;
×
55
      }
56

57
      void send(std::span<const uint8_t> buf) { m_callbacks->tls_emit_data(buf); }
15✔
58

59
      bool is_active() const { return m_active; }
8✔
60

61
      bool is_handshake_complete() const { return m_active; }
13✔
62

63
      bool is_closed_for_reading() const { return m_close_notify_received; }
64

65
      bool is_closed_for_writing() const { return m_close_notify_received; }
66

67
      void received_close_notify() {
2✔
68
         m_close_notify_received = true;
2✔
69
         m_callbacks->tls_alert(Botan::TLS::AlertType::CloseNotify);
2✔
70
         const auto close_notify_record = Botan::hex_decode("15030300020100");
2✔
71
         send(close_notify_record);
2✔
72
      }
2✔
73

74
   private:
75
      std::shared_ptr<Botan::TLS::Callbacks> m_callbacks;
76
      std::size_t m_bytes_till_complete_record;  // number of bytes still to read before tls record is completed
77
      bool m_active;
78
      bool m_close_notify_received;
79
};
80

81
class ThrowingMockChannel : public MockChannel {
12✔
82
   public:
83
      static boost::system::error_code expected_ec() { return Botan::TLS::Alert::UnexpectedMessage; }
12✔
84

85
      ThrowingMockChannel(std::shared_ptr<Botan::TLS::Callbacks> core) : MockChannel(std::move(core)) {}
6✔
86

87
      std::size_t received_data(std::span<const uint8_t>) { throw Botan::TLS::Unexpected_Message("test_error"); }
4✔
88

89
      void send(std::span<const uint8_t>) { throw Botan::TLS::Unexpected_Message("test_error"); }
2✔
90
};
91

92
class CancellingMockChannel : public MockChannel {
4✔
93
   public:
94
      CancellingMockChannel(std::shared_ptr<Botan::TLS::Callbacks> core) : MockChannel(std::move(core)) {}
2✔
95

96
      std::size_t received_data(std::span<const uint8_t>) {
2✔
97
         received_close_notify();
2✔
98
         return 0;
×
99
      }
100
};
101

102
// Unfortunately, boost::beast::test::stream keeps lowest_layer_type private and
103
// only friends boost::asio::ssl::stream. We need to make our own.
104
class TestStream : public boost::beast::test::stream {
27✔
105
   public:
106
      // NOLINTNEXTLINE(modernize-type-traits)
107
      using boost::beast::test::stream::stream;
27✔
108
      using lowest_layer_type = boost::beast::test::stream;
109
};
110

111
using FailCount = boost::beast::test::fail_count;
112

113
class AsioStream : public Botan::TLS::Stream<TestStream, MockChannel> {
×
114
   public:
115
      template <typename... Args>
116
      AsioStream(std::shared_ptr<Botan::TLS::Context> context, Args&&... args) :
18✔
117
            Stream(context, std::forward<Args>(args)...) {
18✔
118
         m_native_handle = std::make_unique<MockChannel>(m_core);
18✔
119
      }
18✔
120
};
121

122
class ThrowingAsioStream : public Botan::TLS::Stream<TestStream, ThrowingMockChannel> {
12✔
123
   public:
124
      template <typename... Args>
125
      ThrowingAsioStream(std::shared_ptr<Botan::TLS::Context> context, Args&&... args) :
6✔
126
            Stream(context, std::forward<Args>(args)...) {
6✔
127
         m_native_handle = std::make_unique<ThrowingMockChannel>(m_core);
6✔
128
      }
6✔
129
};
130

131
class CancellingAsioStream : public Botan::TLS::Stream<TestStream, CancellingMockChannel> {
4✔
132
   public:
133
      template <typename... Args>
134
      CancellingAsioStream(std::shared_ptr<Botan::TLS::Context> context, Args&&... args) :
2✔
135
            Stream(context, std::forward<Args>(args)...) {
2✔
136
         m_native_handle = std::make_unique<CancellingMockChannel>(m_core);
2✔
137
      }
2✔
138
};
139

140
/**
141
 * Synchronous tests for Botan::Stream.
142
 *
143
 * This test validates the asynchronous behavior Botan::Stream, including its utility classes StreamCore and Async_*_Op.
144
 * The stream's channel, i.e. TLS_Client or TLS_Server, is mocked and pretends to perform TLS operations (noop) and
145
 * provides the test data to the stream.
146
 * The underlying network socket, claiming it read / wrote a number of bytes.
147
 */
148
class Asio_Stream_Tests final : public Test {
×
149
      std::shared_ptr<Botan::TLS::Context> get_context() {
27✔
150
         return std::make_shared<Botan::TLS::Context>(std::make_shared<Botan::Credentials_Manager>(),
54✔
151
                                                      std::make_shared<Botan::Null_RNG>(),
27✔
152
                                                      std::make_shared<Botan::TLS::Session_Manager_Noop>(),
27✔
153
                                                      std::make_shared<Botan::TLS::Default_Policy>());
54✔
154
      }
155

156
      // use memcmp to check if the data in a is a prefix of the data in b
157
      bool contains(const void* a, const void* b, const std::size_t size) { return memcmp(a, b, size) == 0; }
8✔
158

159
      boost::string_view test_data() const {
14✔
160
         return boost::string_view(reinterpret_cast<const char*>(TEST_DATA), TEST_DATA_SIZE);
14✔
161
      }
162

163
      void test_sync_handshake(std::vector<Test::Result>& results) {
1✔
164
         net::io_context ioc;
1✔
165
         auto ctx = get_context();
1✔
166
         AsioStream ssl(ctx, ioc, test_data());
1✔
167

168
         ssl.handshake(Botan::TLS::Connection_Side::Client);
1✔
169

170
         Test::Result result("sync TLS handshake");
1✔
171
         result.test_eq("feeds data into channel until active", ssl.native_handle()->is_active(), true);
1✔
172
         results.push_back(result);
1✔
173
      }
2✔
174

175
      void test_sync_handshake_error(std::vector<Test::Result>& results) {
1✔
176
         net::io_context ioc;
1✔
177
         // fail right away
178
         FailCount fc{0, net::error::no_recovery};
1✔
179
         TestStream remote{ioc};
1✔
180

181
         auto ctx = get_context();
1✔
182
         AsioStream ssl(ctx, ioc, fc);
1✔
183
         ssl.next_layer().connect(remote);
1✔
184

185
         // mimic handshake initialization
186
         ssl.native_handle()->send(TEST_DATA);
1✔
187

188
         error_code ec;
1✔
189
         ssl.handshake(Botan::TLS::Connection_Side::Client, ec);
1✔
190

191
         Test::Result result("sync TLS handshake error");
1✔
192
         result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
193
         result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
194
         results.push_back(result);
1✔
195
      }
2✔
196

197
      void test_sync_handshake_cancellation(std::vector<Test::Result>& results) {
1✔
198
         net::io_context ioc;
1✔
199
         TestStream remote{ioc};
1✔
200

201
         auto ctx = get_context();
1✔
202
         CancellingAsioStream ssl(ctx, ioc, test_data());
1✔
203
         ssl.next_layer().connect(remote);
1✔
204

205
         // mimic handshake initialization
206
         ssl.native_handle()->send(TEST_DATA);
1✔
207

208
         error_code ec;
1✔
209
         ssl.handshake(Botan::TLS::Connection_Side::Client, ec);
1✔
210

211
         Test::Result result("sync TLS handshake cancellation");
1✔
212
         result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
213
         result.test_eq("does not finish handshake", ssl.native_handle()->is_handshake_complete(), false);
1✔
214
         result.confirm("cancelled handshake means EOF", ec == net::error::eof);
2✔
215
         results.push_back(result);
1✔
216
      }
2✔
217

218
      void test_sync_handshake_throw(std::vector<Test::Result>& results) {
1✔
219
         net::io_context ioc;
1✔
220
         TestStream remote{ioc};
1✔
221

222
         auto ctx = get_context();
1✔
223
         ThrowingAsioStream ssl(ctx, ioc, test_data());
1✔
224
         ssl.next_layer().connect(remote);
1✔
225

226
         error_code ec;
1✔
227
         ssl.handshake(Botan::TLS::Connection_Side::Client, ec);
1✔
228

229
         Test::Result result("sync TLS handshake error");
1✔
230
         result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
231
         result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
232
         results.push_back(result);
1✔
233
      }
2✔
234

235
      void test_async_handshake(std::vector<Test::Result>& results) {
1✔
236
         net::io_context ioc;
1✔
237
         TestStream remote{ioc};
1✔
238

239
         auto ctx = get_context();
1✔
240
         AsioStream ssl(ctx, ioc, test_data());
1✔
241
         ssl.next_layer().connect(remote);
1✔
242

243
         // mimic handshake initialization
244
         ssl.native_handle()->send(TEST_DATA);
1✔
245

246
         Test::Result result("async TLS handshake");
1✔
247

248
         auto handler = [&](const error_code&) {
2✔
249
            result.confirm("reads from socket", ssl.next_layer().nread() > 0);
2✔
250
            result.confirm("writes from socket", ssl.next_layer().nwrite() > 0);
2✔
251
            result.test_eq("feeds data into channel until active", ssl.native_handle()->is_active(), true);
1✔
252
         };
1✔
253

254
         ssl.async_handshake(Botan::TLS::Connection_Side::Client, handler);
1✔
255

256
         ssl.next_layer().close_remote();
1✔
257
         ioc.run();
1✔
258
         results.push_back(result);
1✔
259
      }
2✔
260

261
      void test_async_handshake_error(std::vector<Test::Result>& results) {
1✔
262
         net::io_context ioc;
1✔
263
         // fail right away
264
         FailCount fc{0, net::error::no_recovery};
1✔
265
         TestStream remote{ioc};
1✔
266

267
         auto ctx = get_context();
1✔
268
         AsioStream ssl(ctx, ioc, fc);
1✔
269
         ssl.next_layer().connect(remote);
1✔
270

271
         // mimic handshake initialization
272
         ssl.native_handle()->send(TEST_DATA);
1✔
273

274
         Test::Result result("async TLS handshake error");
1✔
275

276
         auto handler = [&](const error_code& ec) {
2✔
277
            result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
278
            result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
279
         };
1✔
280

281
         ssl.async_handshake(Botan::TLS::Connection_Side::Client, handler);
1✔
282

283
         ioc.run();
1✔
284
         results.push_back(result);
1✔
285
      }
2✔
286

287
      void test_async_handshake_cancellation(std::vector<Test::Result>& results) {
1✔
288
         net::io_context ioc;
1✔
289
         TestStream remote{ioc};
1✔
290

291
         auto ctx = get_context();
1✔
292
         CancellingAsioStream ssl(ctx, ioc, test_data());
1✔
293
         ssl.next_layer().connect(remote);
1✔
294

295
         // mimic handshake initialization
296
         ssl.native_handle()->send(TEST_DATA);
1✔
297

298
         Test::Result result("async TLS handshake cancellation");
1✔
299

300
         auto handler = [&](const error_code& ec) {
2✔
301
            result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
302
            result.test_eq("does not finish handshake", ssl.native_handle()->is_handshake_complete(), false);
1✔
303
            result.confirm("cancelled handshake means EOF", ec == net::error::eof);
2✔
304
         };
1✔
305

306
         ssl.async_handshake(Botan::TLS::Connection_Side::Client, handler);
1✔
307

308
         ioc.run();
1✔
309
         results.push_back(result);
1✔
310
      }
2✔
311

312
      void test_async_handshake_throw(std::vector<Test::Result>& results) {
1✔
313
         net::io_context ioc;
1✔
314
         TestStream remote{ioc};
1✔
315

316
         auto ctx = get_context();
1✔
317
         ThrowingAsioStream ssl(ctx, ioc, test_data());
1✔
318
         ssl.next_layer().connect(remote);
1✔
319

320
         Test::Result result("async TLS handshake throw");
1✔
321

322
         auto handler = [&](const error_code& ec) {
2✔
323
            result.test_eq("does not activate channel", ssl.native_handle()->is_active(), false);
1✔
324
            result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
325
         };
1✔
326

327
         ssl.async_handshake(Botan::TLS::Connection_Side::Client, handler);
1✔
328

329
         ioc.run();
1✔
330
         results.push_back(result);
1✔
331
      }
2✔
332

333
      void test_sync_read_some_success(std::vector<Test::Result>& results) {
1✔
334
         net::io_context ioc;
1✔
335

336
         auto ctx = get_context();
1✔
337
         AsioStream ssl(ctx, ioc, test_data());
1✔
338

339
         const std::size_t buf_size = 128;
1✔
340
         uint8_t buf[buf_size];
1✔
341
         error_code ec;
1✔
342

343
         auto bytes_transferred = net::read(ssl, net::mutable_buffer(buf, sizeof(buf)), ec);
1✔
344

345
         Test::Result result("sync read_some success");
1✔
346
         result.confirm("reads the correct data", contains(buf, TEST_DATA, buf_size));
2✔
347
         result.test_eq("reads the correct amount of data", bytes_transferred, buf_size);
1✔
348
         result.confirm("does not report an error", !ec);
2✔
349

350
         results.push_back(result);
1✔
351
      }
2✔
352

353
      void test_sync_read_some_buffer_sequence(std::vector<Test::Result>& results) {
1✔
354
         net::io_context ioc;
1✔
355

356
         auto ctx = get_context();
1✔
357
         AsioStream ssl(ctx, ioc, test_data());
1✔
358
         error_code ec;
1✔
359

360
         std::vector<net::mutable_buffer> data;
1✔
361
         uint8_t buf1[TEST_DATA_SIZE / 2];
1✔
362
         uint8_t buf2[TEST_DATA_SIZE / 2];
1✔
363
         data.emplace_back(net::mutable_buffer(buf1, TEST_DATA_SIZE / 2));
1✔
364
         data.emplace_back(net::mutable_buffer(buf2, TEST_DATA_SIZE / 2));
1✔
365

366
         auto bytes_transferred = net::read(ssl, data, ec);
1✔
367

368
         Test::Result result("sync read_some buffer sequence");
1✔
369

370
         result.confirm("reads the correct data",
2✔
371
                        contains(buf1, TEST_DATA, TEST_DATA_SIZE / 2) &&
1✔
372
                           contains(buf2, TEST_DATA + TEST_DATA_SIZE / 2, TEST_DATA_SIZE / 2));
1✔
373
         result.test_eq("reads the correct amount of data", bytes_transferred, TEST_DATA_SIZE);
1✔
374
         result.confirm("does not report an error", !ec);
2✔
375

376
         results.push_back(result);
1✔
377
      }
2✔
378

379
      void test_sync_read_some_error(std::vector<Test::Result>& results) {
1✔
380
         net::io_context ioc;
1✔
381
         // fail right away
382
         FailCount fc{0, net::error::no_recovery};
1✔
383
         TestStream remote{ioc};
1✔
384

385
         auto ctx = get_context();
1✔
386
         AsioStream ssl(ctx, ioc, fc);
1✔
387
         ssl.next_layer().connect(remote);
1✔
388

389
         uint8_t buf[128];
1✔
390
         error_code ec;
1✔
391

392
         auto bytes_transferred = net::read(ssl, net::mutable_buffer(buf, sizeof(buf)), ec);
1✔
393

394
         Test::Result result("sync read_some error");
1✔
395
         result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
396
         result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
397

398
         results.push_back(result);
1✔
399
      }
2✔
400

401
      void test_sync_read_some_throw(std::vector<Test::Result>& results) {
1✔
402
         net::io_context ioc;
1✔
403
         TestStream remote{ioc};
1✔
404

405
         auto ctx = get_context();
1✔
406
         ThrowingAsioStream ssl(ctx, ioc, test_data());
1✔
407
         ssl.next_layer().connect(remote);
1✔
408

409
         uint8_t buf[128];
1✔
410
         error_code ec;
1✔
411

412
         auto bytes_transferred = net::read(ssl, net::mutable_buffer(buf, sizeof(buf)), ec);
1✔
413

414
         Test::Result result("sync read_some throw");
1✔
415
         result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
416
         result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
417

418
         results.push_back(result);
1✔
419
      }
2✔
420

421
      void test_sync_read_zero_buffer(std::vector<Test::Result>& results) {
1✔
422
         net::io_context ioc;
1✔
423

424
         auto ctx = get_context();
1✔
425
         AsioStream ssl(ctx, ioc);
1✔
426

427
         const std::size_t buf_size = 128;
1✔
428
         uint8_t buf[buf_size];
1✔
429
         error_code ec;
1✔
430

431
         auto bytes_transferred = net::read(ssl, net::mutable_buffer(buf, std::size_t(0)), ec);
1✔
432

433
         Test::Result result("sync read_some into zero-size buffer");
1✔
434
         result.test_eq("reads the correct amount of data", bytes_transferred, 0);
1✔
435
         // This relies on an implementation detail of TestStream: A "real" asio::tcp::stream
436
         // would block here. TestStream sets error_code::eof.
437
         result.confirm("does not report an error", !ec);
2✔
438

439
         results.push_back(result);
1✔
440
      }
2✔
441

442
      void test_async_read_some_success(std::vector<Test::Result>& results) {
1✔
443
         net::io_context ioc;
1✔
444
         TestStream remote{ioc};
1✔
445

446
         auto ctx = get_context();
1✔
447
         AsioStream ssl(ctx, ioc, test_data());
1✔
448
         uint8_t data[TEST_DATA_SIZE];
1✔
449

450
         Test::Result result("async read_some success");
1✔
451

452
         auto read_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
453
            result.confirm("reads the correct data", contains(data, TEST_DATA, TEST_DATA_SIZE));
2✔
454
            result.test_eq("reads the correct amount of data", bytes_transferred, TEST_DATA_SIZE);
1✔
455
            result.confirm("does not report an error", !ec);
2✔
456
         };
1✔
457

458
         net::mutable_buffer buf{data, TEST_DATA_SIZE};
1✔
459
         net::async_read(ssl, buf, read_handler);
1✔
460

461
         ssl.next_layer().close_remote();
1✔
462
         ioc.run();
1✔
463
         results.push_back(result);
1✔
464
      }
2✔
465

466
      void test_async_read_some_buffer_sequence(std::vector<Test::Result>& results) {
1✔
467
         net::io_context ioc;
1✔
468
         auto ctx = get_context();
1✔
469
         AsioStream ssl(ctx, ioc, test_data());
1✔
470

471
         std::vector<net::mutable_buffer> data;
1✔
472
         uint8_t buf1[TEST_DATA_SIZE / 2];
1✔
473
         uint8_t buf2[TEST_DATA_SIZE / 2];
1✔
474
         data.emplace_back(net::mutable_buffer(buf1, TEST_DATA_SIZE / 2));
1✔
475
         data.emplace_back(net::mutable_buffer(buf2, TEST_DATA_SIZE / 2));
1✔
476

477
         Test::Result result("async read_some buffer sequence");
1✔
478

479
         auto read_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
480
            result.confirm("reads the correct data",
2✔
481
                           contains(buf1, TEST_DATA, TEST_DATA_SIZE / 2) &&
1✔
482
                              contains(buf2, TEST_DATA + TEST_DATA_SIZE / 2, TEST_DATA_SIZE / 2));
1✔
483
            result.test_eq("reads the correct amount of data", bytes_transferred, TEST_DATA_SIZE);
1✔
484
            result.confirm("does not report an error", !ec);
2✔
485
         };
1✔
486

487
         net::async_read(ssl, data, read_handler);
1✔
488

489
         ssl.next_layer().close_remote();
1✔
490
         ioc.run();
1✔
491
         results.push_back(result);
1✔
492
      }
2✔
493

494
      void test_async_read_some_error(std::vector<Test::Result>& results) {
1✔
495
         net::io_context ioc;
1✔
496
         // fail right away
497
         FailCount fc{0, net::error::no_recovery};
1✔
498
         auto ctx = get_context();
1✔
499
         AsioStream ssl(ctx, ioc, fc);
1✔
500
         uint8_t data[TEST_DATA_SIZE];
1✔
501

502
         Test::Result result("async read_some error");
1✔
503

504
         auto read_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
505
            result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
506
            result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
507
         };
1✔
508

509
         net::mutable_buffer buf{data, TEST_DATA_SIZE};
1✔
510
         net::async_read(ssl, buf, read_handler);
1✔
511

512
         ssl.next_layer().close_remote();
1✔
513
         ioc.run();
1✔
514
         results.push_back(result);
1✔
515
      }
2✔
516

517
      void test_async_read_some_throw(std::vector<Test::Result>& results) {
1✔
518
         net::io_context ioc;
1✔
519
         auto ctx = get_context();
1✔
520
         ThrowingAsioStream ssl(ctx, ioc, test_data());
1✔
521
         uint8_t data[TEST_DATA_SIZE];
1✔
522

523
         Test::Result result("async read_some throw");
1✔
524

525
         auto read_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
526
            result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
527
            result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
528
         };
1✔
529

530
         net::mutable_buffer buf{data, TEST_DATA_SIZE};
1✔
531
         net::async_read(ssl, buf, read_handler);
1✔
532

533
         ssl.next_layer().close_remote();
1✔
534
         ioc.run();
1✔
535
         results.push_back(result);
1✔
536
      }
2✔
537

538
      void test_async_read_zero_buffer(std::vector<Test::Result>& results) {
1✔
539
         net::io_context ioc;
1✔
540
         TestStream remote{ioc};
1✔
541

542
         auto ctx = get_context();
1✔
543
         AsioStream ssl(ctx, ioc);
1✔
544
         uint8_t data[TEST_DATA_SIZE];
1✔
545

546
         Test::Result result("async read_some into zero-size buffer");
1✔
547

548
         auto read_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
549
            result.test_eq("reads the correct amount of data", bytes_transferred, 0);
1✔
550
            // This relies on an implementation detail of TestStream: A "real" asio::tcp::stream
551
            // would block here. TestStream sets error_code::eof.
552
            result.confirm("does not report an error", !ec);
2✔
553
         };
1✔
554

555
         net::mutable_buffer buf{data, std::size_t(0)};
1✔
556
         net::async_read(ssl, buf, read_handler);
1✔
557

558
         ssl.next_layer().close_remote();
1✔
559
         ioc.run();
1✔
560
         results.push_back(result);
1✔
561
      }
2✔
562

563
      void test_sync_write_some_success(std::vector<Test::Result>& results) {
1✔
564
         net::io_context ioc;
1✔
565
         TestStream remote{ioc};
1✔
566

567
         auto ctx = get_context();
1✔
568
         AsioStream ssl(ctx, ioc);
1✔
569
         ssl.next_layer().connect(remote);
1✔
570
         error_code ec;
1✔
571

572
         auto bytes_transferred = net::write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), ec);
1✔
573

574
         Test::Result result("sync write_some success");
1✔
575
         result.confirm("writes the correct data", remote.str() == test_data());
3✔
576
         result.test_eq("writes the correct amount of data", bytes_transferred, TEST_DATA_SIZE);
1✔
577
         result.confirm("does not report an error", !ec);
2✔
578

579
         results.push_back(result);
1✔
580
      }
2✔
581

582
      void test_sync_no_handshake(std::vector<Test::Result>& results) {
1✔
583
         net::io_context ioc;
1✔
584
         TestStream remote{ioc};
1✔
585

586
         auto ctx = get_context();
1✔
587
         Botan::TLS::Stream<TestStream> ssl(ctx, ioc);  // Note that we're not using MockChannel here
1✔
588
         ssl.next_layer().connect(remote);
1✔
589
         error_code ec;
1✔
590

591
         net::write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), ec);
1✔
592

593
         Test::Result result("sync write_some without handshake fails gracefully");
1✔
594
         result.confirm("reports an error", ec.failed());
3✔
595

596
         results.push_back(result);
1✔
597
      }
2✔
598

599
      void test_sync_write_some_buffer_sequence(std::vector<Test::Result>& results) {
1✔
600
         net::io_context ioc;
1✔
601
         TestStream remote{ioc};
1✔
602

603
         auto ctx = get_context();
1✔
604
         AsioStream ssl(ctx, ioc);
1✔
605
         ssl.next_layer().connect(remote);
1✔
606
         error_code ec;
1✔
607

608
         // this should be Botan::TLS::MAX_PLAINTEXT_SIZE + 1024 + 1
609
         std::array<uint8_t, 17 * 1024 + 1> random_data;
1✔
610
         random_data.fill('4');  // chosen by fair dice roll
1✔
611
         random_data.back() = '5';
1✔
612

613
         std::vector<net::const_buffer> data;
1✔
614
         data.emplace_back(net::const_buffer(random_data.data(), 1));
1✔
615
         for(std::size_t i = 1; i < random_data.size(); i += 1024) {
18✔
616
            data.emplace_back(net::const_buffer(random_data.data() + i, 1024));
17✔
617
         }
618

619
         auto bytes_transferred = net::write(ssl, data, ec);
1✔
620

621
         Test::Result result("sync write_some buffer sequence");
1✔
622

623
         result.confirm("[precondition] MAX_PLAINTEXT_SIZE is still smaller than random_data.size()",
2✔
624
                        Botan::TLS::MAX_PLAINTEXT_SIZE < random_data.size());
625

626
         result.confirm("writes the correct data",
2✔
627
                        contains(remote.buffer().data().data(), random_data.data(), random_data.size()));
1✔
628
         result.test_eq("writes the correct amount of data", bytes_transferred, random_data.size());
1✔
629
         result.test_eq("correct number of writes", ssl.next_layer().nwrite(), 2);
1✔
630
         result.confirm("does not report an error", !ec);
2✔
631

632
         results.push_back(result);
1✔
633
      }
2✔
634

635
      void test_sync_write_some_error(std::vector<Test::Result>& results) {
1✔
636
         net::io_context ioc;
1✔
637
         // fail right away
638
         FailCount fc{0, net::error::no_recovery};
1✔
639
         TestStream remote{ioc};
1✔
640

641
         auto ctx = get_context();
1✔
642
         AsioStream ssl(ctx, ioc, fc);
1✔
643
         ssl.next_layer().connect(remote);
1✔
644

645
         error_code ec;
1✔
646

647
         auto bytes_transferred = net::write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), ec);
1✔
648

649
         Test::Result result("sync write_some error");
1✔
650
         result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
651
         result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
652

653
         results.push_back(result);
1✔
654
      }
2✔
655

656
      void test_sync_write_some_throw(std::vector<Test::Result>& results) {
1✔
657
         net::io_context ioc;
1✔
658
         TestStream remote{ioc};
1✔
659

660
         auto ctx = get_context();
1✔
661
         ThrowingAsioStream ssl(ctx, ioc);
1✔
662
         ssl.next_layer().connect(remote);
1✔
663
         error_code ec;
1✔
664

665
         auto bytes_transferred = net::write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), ec);
1✔
666

667
         Test::Result result("sync write_some throw");
1✔
668
         result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
669
         result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
670

671
         results.push_back(result);
1✔
672
      }
2✔
673

674
      void test_async_write_some_success(std::vector<Test::Result>& results) {
1✔
675
         net::io_context ioc;
1✔
676
         TestStream remote{ioc};
1✔
677

678
         auto ctx = get_context();
1✔
679
         AsioStream ssl(ctx, ioc);
1✔
680
         ssl.next_layer().connect(remote);
1✔
681

682
         Test::Result result("async write_some success");
1✔
683

684
         auto write_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
685
            result.confirm("writes the correct data", remote.str() == test_data());
3✔
686
            result.test_eq("writes the correct amount of data", bytes_transferred, TEST_DATA_SIZE);
1✔
687
            result.confirm("does not report an error", !ec);
2✔
688
         };
1✔
689

690
         net::async_write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), write_handler);
1✔
691

692
         ioc.run();
1✔
693
         results.push_back(result);
1✔
694
      }
2✔
695

696
      void test_async_write_some_buffer_sequence(std::vector<Test::Result>& results) {
1✔
697
         net::io_context ioc;
1✔
698
         TestStream remote{ioc};
1✔
699

700
         auto ctx = get_context();
1✔
701
         AsioStream ssl(ctx, ioc);
1✔
702
         ssl.next_layer().connect(remote);
1✔
703

704
         // this should be Botan::TLS::MAX_PLAINTEXT_SIZE + 1024 + 1
705
         std::array<uint8_t, 17 * 1024 + 1> random_data;
1✔
706
         random_data.fill('4');  // chosen by fair dice roll
1✔
707
         random_data.back() = '5';
1✔
708

709
         std::vector<net::const_buffer> src;
1✔
710
         src.emplace_back(net::const_buffer(random_data.data(), 1));
1✔
711
         for(std::size_t i = 1; i < random_data.size(); i += 1024) {
18✔
712
            src.emplace_back(net::const_buffer(random_data.data() + i, 1024));
17✔
713
         }
714

715
         Test::Result result("async write_some buffer sequence");
1✔
716

717
         result.confirm("[precondition] MAX_PLAINTEXT_SIZE is still smaller than random_data.size()",
2✔
718
                        Botan::TLS::MAX_PLAINTEXT_SIZE < random_data.size());
719

720
         auto write_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
721
            result.confirm("writes the correct data",
2✔
722
                           contains(remote.buffer().data().data(), random_data.data(), random_data.size()));
1✔
723
            result.test_eq("writes the correct amount of data", bytes_transferred, random_data.size());
1✔
724
            result.test_eq("correct number of writes", ssl.next_layer().nwrite(), 2);
1✔
725
            result.confirm("does not report an error", !ec);
2✔
726
         };
1✔
727

728
         net::async_write(ssl, src, write_handler);
1✔
729

730
         ioc.run();
1✔
731
         results.push_back(result);
1✔
732
      }
2✔
733

734
      void test_async_write_some_error(std::vector<Test::Result>& results) {
1✔
735
         net::io_context ioc;
1✔
736
         // fail right away
737
         FailCount fc{0, net::error::no_recovery};
1✔
738
         TestStream remote{ioc};
1✔
739

740
         auto ctx = get_context();
1✔
741
         AsioStream ssl(ctx, ioc, fc);
1✔
742
         ssl.next_layer().connect(remote);
1✔
743

744
         Test::Result result("async write_some error");
1✔
745

746
         auto write_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
747
            result.test_eq("committed some bytes to the core", bytes_transferred, TEST_DATA_SIZE);
1✔
748
            result.confirm("propagates error code", ec == net::error::no_recovery);
2✔
749
         };
1✔
750

751
         net::async_write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), write_handler);
1✔
752

753
         ioc.run();
1✔
754
         results.push_back(result);
1✔
755
      }
2✔
756

757
      void test_async_write_throw(std::vector<Test::Result>& results) {
1✔
758
         net::io_context ioc;
1✔
759
         TestStream remote{ioc};
1✔
760

761
         auto ctx = get_context();
1✔
762
         ThrowingAsioStream ssl(ctx, ioc);
1✔
763
         ssl.next_layer().connect(remote);
1✔
764

765
         Test::Result result("async write_some throw");
1✔
766

767
         auto write_handler = [&](const error_code& ec, std::size_t bytes_transferred) {
2✔
768
            result.test_eq("didn't transfer anything", bytes_transferred, 0);
1✔
769
            result.confirm("propagates error code", ec == ThrowingMockChannel::expected_ec());
2✔
770
         };
1✔
771

772
         net::async_write(ssl, net::const_buffer(TEST_DATA, TEST_DATA_SIZE), write_handler);
1✔
773

774
         ioc.run();
1✔
775
         results.push_back(result);
1✔
776
      }
2✔
777

778
   public:
779
      std::vector<Test::Result> run() override {
1✔
780
         std::vector<Test::Result> results;
1✔
781

782
         test_sync_no_handshake(results);
1✔
783

784
         test_sync_handshake(results);
1✔
785
         test_sync_handshake_error(results);
1✔
786
         test_sync_handshake_cancellation(results);
1✔
787
         test_sync_handshake_throw(results);
1✔
788

789
         test_async_handshake(results);
1✔
790
         test_async_handshake_error(results);
1✔
791
         test_async_handshake_cancellation(results);
1✔
792
         test_async_handshake_throw(results);
1✔
793

794
         test_sync_read_some_success(results);
1✔
795
         test_sync_read_some_buffer_sequence(results);
1✔
796
         test_sync_read_some_error(results);
1✔
797
         test_sync_read_some_throw(results);
1✔
798
         test_sync_read_zero_buffer(results);
1✔
799

800
         test_async_read_some_success(results);
1✔
801
         test_async_read_some_buffer_sequence(results);
1✔
802
         test_async_read_some_error(results);
1✔
803
         test_async_read_some_throw(results);
1✔
804
         test_async_read_zero_buffer(results);
1✔
805

806
         test_sync_write_some_success(results);
1✔
807
         test_sync_write_some_buffer_sequence(results);
1✔
808
         test_sync_write_some_error(results);
1✔
809
         test_sync_write_some_throw(results);
1✔
810

811
         test_async_write_some_success(results);
1✔
812
         test_async_write_some_buffer_sequence(results);
1✔
813
         test_async_write_some_error(results);
1✔
814
         test_async_write_throw(results);
1✔
815

816
         return results;
1✔
817
      }
×
818
};
819

820
BOTAN_REGISTER_TEST("tls", "tls_asio_stream", Asio_Stream_Tests);
821

822
}  // namespace Botan_Tests
823

824
   #endif
825
#endif  // BOTAN_HAS_TLS && BOTAN_HAS_TLS_ASIO_STREAM
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