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

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

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

98.98
/src/tests/test_tls_session_manager.cpp
1
/*
2
* (C) 2022 Jack Lloyd
3
* (C) 2022 René Meusel - Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_TLS)
11

12
   #include <botan/credentials_manager.h>
13
   #include <botan/rng.h>
14
   #include <botan/tls_callbacks.h>
15
   #include <botan/tls_policy.h>
16
   #include <botan/tls_session_manager_hybrid.h>
17
   #include <botan/tls_session_manager_memory.h>
18
   #include <botan/tls_session_manager_stateless.h>
19
   #include <botan/internal/fmt.h>
20
   #include <array>
21
   #include <chrono>
22

23
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
24
      #include <botan/tls_session_manager_sqlite.h>
25
   #endif
26

27
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
28
      #include <version>
29
      #if defined(__cpp_lib_filesystem)
30
         #include <filesystem>
31
      #endif
32
   #endif
33

34
// This file contains a number of `Botan::TLS::Version_Code::TLS_V**` protocol
35
// version specifications. This is to work around a compiler bug in GCC 11.3
36
// where `Botan::TLS::Protocol_Version::TLS_V12` would lead to an "Internal
37
// Compiler Error" when used in the affected context.
38
//
39
// TODO: remove the workaround once GCC 11 is not supported anymore.
40

41
namespace Botan_Tests {
42

43
class Test_Credentials_Manager : public Botan::Credentials_Manager {
7✔
44
   public:
45
      Botan::secure_vector<uint8_t> session_ticket_key() override {
55✔
46
         return Botan::hex_decode_locked("DEADFACECAFEBAAD");
55✔
47
      }
48
};
49

50
class Other_Test_Credentials_Manager : public Botan::Credentials_Manager {
1✔
51
   public:
52
      Botan::secure_vector<uint8_t> session_ticket_key() override {
1✔
53
         return Botan::hex_decode_locked("CAFEBAADFACEBABE");
1✔
54
      }
55
};
56

57
class Empty_Credentials_Manager : public Botan::Credentials_Manager {};
4✔
58

59
class Session_Manager_Callbacks : public Botan::TLS::Callbacks {
3✔
60
   public:
61
      void tls_emit_data(std::span<const uint8_t> /*data*/) override { BOTAN_ASSERT_NOMSG(false); }
×
62

63
      void tls_record_received(uint64_t /*record*/, std::span<const uint8_t> /*data*/) override {
×
64
         BOTAN_ASSERT_NOMSG(false);
×
65
      }
66

67
      void tls_alert(Botan::TLS::Alert /*alert*/) override { BOTAN_ASSERT_NOMSG(false); }
×
68

69
      void tls_session_established(const Botan::TLS::Session_Summary& /*summary*/) override {
×
70
         BOTAN_ASSERT_NOMSG(false);
×
71
      }
72

73
      std::chrono::system_clock::time_point tls_current_timestamp() override {
225✔
74
         return std::chrono::system_clock::now() + std::chrono::hours(m_ticks);
225✔
75
      }
76

77
      void tick() { ++m_ticks; }
13✔
78

79
   private:
80
      uint64_t m_ticks = 0;
81
};
82

83
class Session_Manager_Policy : public Botan::TLS::Policy {
6✔
84
   public:
85
      std::chrono::seconds session_ticket_lifetime() const override { return std::chrono::minutes(30); }
198✔
86

87
      bool reuse_session_tickets() const override { return m_allow_session_reuse; }
27✔
88

89
      size_t maximum_session_tickets_per_client_hello() const override { return m_session_limit; }
58✔
90

91
      void set_session_limit(size_t l) { m_session_limit = l; }
6✔
92

93
      void set_allow_session_reuse(bool b) { m_allow_session_reuse = b; }
4✔
94

95
   private:
96
      size_t m_session_limit = 1000;  // basically 'no limit'
97
      bool m_allow_session_reuse = true;
98
};
99

100
namespace {
101

102
decltype(auto) random_id(Botan::RandomNumberGenerator& rng) {
60✔
103
   return rng.random_vec<Botan::TLS::Session_ID>(32);
58✔
104
}
105

106
decltype(auto) random_ticket(Botan::RandomNumberGenerator& rng) {
29✔
107
   return rng.random_vec<Botan::TLS::Session_Ticket>(32);
27✔
108
}
109

110
decltype(auto) random_opaque_handle(Botan::RandomNumberGenerator& rng) {
1✔
111
   return rng.random_vec<Botan::TLS::Opaque_Session_Handle>(32);
1✔
112
}
113

114
const Botan::TLS::Server_Information& server_info() {
151✔
115
   static const Botan::TLS::Server_Information si("botan.randombit.net");
151✔
116
   return si;
151✔
117
}
118

119
decltype(auto) default_session(Botan::TLS::Connection_Side side,
118✔
120
                               Botan::TLS::Callbacks& cbs,
121
                               Botan::TLS::Protocol_Version version = Botan::TLS::Protocol_Version::TLS_V12) {
122
   if(version.is_pre_tls_13()) {
118✔
123
      return Botan::TLS::Session(
108✔
124
         {}, version, 0x009C, side, true, true, {}, server_info(), 0, cbs.tls_current_timestamp());
216✔
125
   } else {
126
   #if defined(BOTAN_HAS_TLS_13)
127
      return Botan::TLS::Session({},
10✔
128
                                 std::nullopt,
129
                                 0,
130
                                 std::chrono::seconds(1024),
10✔
131
                                 Botan::TLS::Protocol_Version::TLS_V13,
132
                                 Botan::TLS::Ciphersuite::from_name("AES_128_GCM_SHA256")->ciphersuite_code(),
10✔
133
                                 side,
134
                                 {},
135
                                 nullptr,
136
                                 server_info(),
137
                                 cbs.tls_current_timestamp());
20✔
138
   #else
139
      throw Test_Error("TLS 1.3 is not available in this build");
140
   #endif
141
   }
142
}
143

144
using namespace std::literals;
145

146
std::vector<Test::Result> test_session_manager_in_memory() {
1✔
147
   auto rng = Test::new_shared_rng(__func__);
1✔
148

149
   const Botan::TLS::Session_ID default_id = random_id(*rng);
1✔
150

151
   std::optional<Botan::TLS::Session_Manager_In_Memory> mgr;
1✔
152

153
   Session_Manager_Callbacks cbs;
1✔
154
   Session_Manager_Policy plcy;
1✔
155

156
   return {
1✔
157
      CHECK("creation", [&](auto&) { mgr.emplace(rng, 5); }),
1✔
158

159
      CHECK("empty cache does not obtain anything",
160
            [&](auto& result) {
1✔
161
               result.confirm("no session found via server info", mgr->find(server_info(), cbs, plcy).empty());
2✔
162

163
               Botan::TLS::Session_ID const mock_id = random_id(*rng);
1✔
164
               auto mock_ticket = rng->random_vec<Botan::TLS::Session_Ticket>(128);
1✔
165

166
               result.confirm("no session found via ID", !mgr->retrieve(mock_id, cbs, plcy));
4✔
167
               result.confirm("no session found via ID", !mgr->retrieve(mock_ticket, cbs, plcy));
4✔
168
            }),
2✔
169

170
      CHECK("clearing empty cache",
171
            [&](auto& result) { result.test_eq("does not delete anything", mgr->remove_all(), 0); }),
2✔
172

173
      CHECK("establish new session",
174
            [&](auto& result) {
1✔
175
               auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
1✔
176
               if(result.confirm("establishment was successful", handle.has_value())) {
2✔
177
                  result.require("session id was set", handle->id().has_value());
2✔
178
                  result.confirm("session ticket was empty", !handle->ticket().has_value());
2✔
179
                  result.test_is_eq("session id is correct", handle->id().value(), default_id);
3✔
180
               }
181
            }),
1✔
182

183
      CHECK("obtain session from server info",
184
            [&](auto& result) {
1✔
185
               auto sessions = mgr->find(server_info(), cbs, plcy);
1✔
186
               if(result.confirm("session was found successfully", sessions.size() == 1)) {
2✔
187
                  result.test_is_eq("protocol version was echoed",
1✔
188
                                    sessions[0].session.version(),
1✔
189
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
190
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
1✔
191
                  result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), default_id);
3✔
192
                  result.confirm("not a ticket", !sessions[0].handle.ticket().has_value());
2✔
193
               }
194
            }),
1✔
195

196
      CHECK("obtain session from ID",
197
            [&](auto& result) {
1✔
198
               auto session = mgr->retrieve(default_id, cbs, plcy);
3✔
199
               if(result.confirm("session was found successfully", session.has_value())) {
2✔
200
                  result.test_is_eq("protocol version was echoed",
1✔
201
                                    session->version(),
1✔
202
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
203
                  result.test_is_eq("ciphersuite was echoed", session->ciphersuite_code(), uint16_t(0x009C));
2✔
204
               }
205
            }),
1✔
206

207
      CHECK("obtain session from ID disguised as opaque handle",
208
            [&](auto& result) {
1✔
209
               auto session = mgr->retrieve(Botan::TLS::Opaque_Session_Handle(default_id), cbs, plcy);
2✔
210
               if(result.confirm("session was found successfully", session.has_value())) {
2✔
211
                  result.test_is_eq("protocol version was echoed",
1✔
212
                                    session->version(),
1✔
213
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
214
                  result.test_is_eq("ciphersuite was echoed", session->ciphersuite_code(), uint16_t(0x009C));
2✔
215
               }
216
            }),
1✔
217

218
      CHECK("obtain session from ticket == id does not work",
219
            [&](auto& result) {
1✔
220
               auto session = mgr->retrieve(Botan::TLS::Session_Ticket(default_id), cbs, plcy);
2✔
221
               result.confirm("session was not found", !session.has_value());
2✔
222
            }),
1✔
223

224
      CHECK("invalid ticket causes std::nullopt",
225
            [&](auto& result) {
1✔
226
               auto no_session = mgr->retrieve(random_ticket(*rng), cbs, plcy);
2✔
227
               result.confirm("std::nullopt on bogus ticket", !no_session.has_value());
2✔
228
            }),
1✔
229

230
      CHECK("invalid ID causes std::nullopt",
231
            [&](auto& result) {
1✔
232
               auto no_session = mgr->retrieve(random_id(*rng), cbs, plcy);
2✔
233
               result.confirm("std::nullopt on bogus ID", !no_session.has_value());
2✔
234
            }),
1✔
235

236
      CHECK("remove_all",
237
            [&](auto& result) {
1✔
238
               result.test_eq("removed one element", mgr->remove_all(), 1);
1✔
239
               result.test_eq("should be empty now", mgr->remove_all(), 0);
1✔
240
            }),
1✔
241

242
      CHECK("add session with ID",
243
            [&](auto& result) {
1✔
244
               Botan::TLS::Session_ID const new_id = random_id(*rng);
1✔
245

246
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_id);
3✔
247
               result.require("obtain via ID", mgr->retrieve(new_id, cbs, plcy).has_value());
5✔
248

249
               auto sessions = mgr->find(server_info(), cbs, plcy);
1✔
250
               if(result.confirm("found via server info", sessions.size() == 1)) {
2✔
251
                  result.test_is_eq("protocol version was echoed",
1✔
252
                                    sessions[0].session.version(),
1✔
253
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
254
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
255
                  result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), new_id);
3✔
256
                  result.confirm("ticket was not stored", !sessions[0].handle.ticket().has_value());
2✔
257
               }
258

259
               mgr->remove_all();
1✔
260
            }),
2✔
261

262
      CHECK("add session with ticket",
263
            [&](auto& result) {
1✔
264
               Botan::TLS::Session_Ticket const new_ticket = random_ticket(*rng);
1✔
265

266
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_ticket);
3✔
267
               // cannot be obtained by (non-existent) ID or randomly generated ticket
268

269
               auto sessions = mgr->find(server_info(), cbs, plcy);
1✔
270
               if(result.confirm("found via server info", sessions.size() == 1)) {
2✔
271
                  result.test_is_eq("protocol version was echoed",
1✔
272
                                    sessions[0].session.version(),
1✔
273
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
274
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
275
                  result.confirm("ID was not stored", !sessions[0].handle.id().has_value());
2✔
276
                  result.test_is_eq("ticket was echoed", sessions[0].handle.ticket().value(), new_ticket);
3✔
277
               }
278

279
               mgr->remove_all();
1✔
280
            }),
2✔
281

282
      CHECK("removing by ID or opaque handle",
283
            [&](auto& result) {
1✔
284
               Botan::TLS::Session_Manager_In_Memory local_mgr(rng);
1✔
285

286
               const auto new_session1 =
1✔
287
                  local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
288
               const auto new_session2 = local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
289
               result.require(
1✔
290
                  "saving worked",
291
                  new_session1.has_value() && new_session1->id().has_value() && !new_session1->ticket().has_value());
4✔
292
               result.require(
1✔
293
                  "saving worked",
294
                  new_session2.has_value() && new_session2->id().has_value() && !new_session2->ticket().has_value());
4✔
295

296
               result.test_is_eq(
1✔
297
                  "can find via server info", local_mgr.find(server_info(), cbs, plcy).size(), size_t(2));
2✔
298

299
               result.test_is_eq("one was deleted", local_mgr.remove(default_id), size_t(1));
4✔
300
               result.confirm("cannot obtain via default ID anymore",
1✔
301
                              !local_mgr.retrieve(default_id, cbs, plcy).has_value());
4✔
302
               result.test_is_eq(
1✔
303
                  "can find less via server info", local_mgr.find(server_info(), cbs, plcy).size(), size_t(1));
2✔
304

305
               result.test_is_eq("last one was deleted",
2✔
306
                                 local_mgr.remove(Botan::TLS::Opaque_Session_Handle(new_session2->id().value())),
3✔
307
                                 size_t(1));
1✔
308
               result.confirm("cannot obtain via ID anymore",
1✔
309
                              !local_mgr.retrieve(new_session2->id().value(), cbs, plcy).has_value());
4✔
310
               result.confirm("cannot find via server info", local_mgr.find(server_info(), cbs, plcy).empty());
2✔
311
            }),
2✔
312

313
      CHECK("removing by ticket or opaque handle",
314
            [&](auto& result) {
1✔
315
               Botan::TLS::Session_Manager_In_Memory local_mgr(rng);
1✔
316

317
               Botan::TLS::Session_Ticket const ticket1 = random_ticket(*rng);
1✔
318
               Botan::TLS::Session_Ticket const ticket2 = random_ticket(*rng);
1✔
319
               Botan::TLS::Session_Ticket ticket3 = random_ticket(*rng);
1✔
320

321
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket1);
2✔
322
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket2);
2✔
323
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket3);
2✔
324
               result.test_is_eq(
1✔
325
                  "can find them via server info ", local_mgr.find(server_info(), cbs, plcy).size(), size_t(3));
2✔
326

327
               result.test_is_eq("remove one session by ticket", local_mgr.remove(ticket2), size_t(1));
4✔
328
               result.test_is_eq(
1✔
329
                  "can find two via server info", local_mgr.find(server_info(), cbs, plcy).size(), size_t(2));
2✔
330

331
               result.test_is_eq("remove one session by opaque handle",
2✔
332
                                 local_mgr.remove(Botan::TLS::Opaque_Session_Handle(ticket3.get())),
2✔
333
                                 size_t(1));
1✔
334
               result.test_is_eq(
1✔
335
                  "can find only one via server info", local_mgr.find(server_info(), cbs, plcy).size(), size_t(1));
2✔
336
            }),
3✔
337

338
      CHECK(
339
         "session purging",
340
         [&](auto& result) {
1✔
341
            result.require("max sessions is 5", mgr->capacity() == 5);
1✔
342

343
            // fill the Session_Manager up fully
344
            std::vector<Botan::TLS::Session_Handle> handles;
1✔
345
            handles.reserve(mgr->capacity());
1✔
346
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
347
               handles.push_back(
5✔
348
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
15✔
349
            }
350

351
            for(const auto& handle : handles) {
6✔
352
               result.confirm("session still there", mgr->retrieve(handle, cbs, plcy).has_value());
15✔
353
            }
354

355
            // add one more session (causing a first purge to happen)
356
            handles.push_back(
1✔
357
               mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
3✔
358

359
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
2✔
360
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
361
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
15✔
362
            }
363

364
            // remove a session to cause a 'gap' in the FIFO
365
            mgr->remove(handles[4]);
1✔
366
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
2✔
367
            result.confirm("deleted session gone", !mgr->retrieve(handles[4], cbs, plcy).has_value());
2✔
368
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
369
               result.confirm("session still there", i == 4 || mgr->retrieve(handles[i], cbs, plcy).has_value());
14✔
370
            }
371

372
            // insert enough new sessions to fully purge the ones currently held
373
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
374
               handles.push_back(
5✔
375
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
15✔
376
            }
377

378
            for(size_t i = 0; i < handles.size() - mgr->capacity(); ++i) {
7✔
379
               result.confirm("session gone", !mgr->retrieve(handles[i], cbs, plcy).has_value());
12✔
380
            }
381

382
            for(size_t i = handles.size() - mgr->capacity(); i < handles.size(); ++i) {
6✔
383
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
15✔
384
            }
385

386
            // clear it all out
387
            result.test_eq("rest of the sessions removed", mgr->remove_all(), size_t(5));
1✔
388
         }),
1✔
389
   };
17✔
390
}
5✔
391

392
std::vector<Test::Result> test_session_manager_choose_ticket() {
1✔
393
   #if defined(BOTAN_HAS_TLS_13)
394
   Session_Manager_Callbacks cbs;
1✔
395
   Session_Manager_Policy plcy;
1✔
396

397
   auto rng = Test::new_shared_rng(__func__);
1✔
398

399
   auto default_session = [&](const std::string& suite,
9✔
400
                              Botan::TLS::Callbacks& mycbs,
401
                              Botan::TLS::Protocol_Version version = Botan::TLS::Protocol_Version::TLS_V13) {
402
      return (version.is_pre_tls_13())
8✔
403
                ? Botan::TLS::Session({},
8✔
404
                                      version,
405
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
1✔
406
                                      Botan::TLS::Connection_Side::Server,
407
                                      true,
408
                                      true,
409
                                      {},
410
                                      server_info(),
411
                                      0,
412
                                      mycbs.tls_current_timestamp())
1✔
413
                : Botan::TLS::Session({},
414
                                      std::nullopt,
415
                                      0,
416
                                      std::chrono::seconds(1024),
7✔
417
                                      version,
418
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
7✔
419
                                      Botan::TLS::Connection_Side::Server,
420
                                      {},
421
                                      nullptr,
422
                                      server_info(),
423
                                      mycbs.tls_current_timestamp());
23✔
424
   };
425

426
   auto ticket = [&](std::span<const uint8_t> identity) {
16✔
427
      return Botan::TLS::PskIdentity(std::vector(identity.begin(), identity.end()), 0);
15✔
428
   };
429

430
   return {
1✔
431
      CHECK("empty manager has nothing to choose from",
432
            [&](auto& result) {
1✔
433
               Botan::TLS::Session_Manager_In_Memory mgr(rng);
1✔
434

435
               Botan::TLS::Session_Ticket random_session_ticket = random_ticket(*rng);
1✔
436

437
               result.confirm("empty ticket list, no session",
1✔
438
                              !mgr.choose_from_offered_tickets({}, "SHA-256", cbs, plcy).has_value());
2✔
439
               result.confirm(
1✔
440
                  "empty session manager, no session",
441
                  !mgr.choose_from_offered_tickets(std::vector{ticket(random_session_ticket)}, "SHA-256", cbs, plcy)
3✔
442
                      .has_value());
3✔
443
            }),
3✔
444

445
      CHECK("choose ticket by ID",
446
            [&](auto& result) {
1✔
447
               Botan::TLS::Session_Manager_In_Memory mgr(rng);
1✔
448
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
449

450
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
451
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
452

453
               // choose from a list of tickets that contains only handles[0]
454
               auto session1 =
2✔
455
                  mgr.choose_from_offered_tickets(std::vector{ticket(handles[0].id().value())}, "SHA-256", cbs, plcy);
3✔
456
               result.require("ticket was chosen and produced a session (1)", session1.has_value());
1✔
457
               result.test_is_eq("chosen offset", session1->second, uint16_t(0));
1✔
458

459
               // choose from a list of tickets that contains only handles[1]
460
               auto session2 =
2✔
461
                  mgr.choose_from_offered_tickets(std::vector{ticket(handles[1].id().value())}, "SHA-256", cbs, plcy);
3✔
462
               result.require("ticket was chosen and produced a session (2)", session2.has_value());
1✔
463
               result.test_is_eq("chosen offset", session2->second, uint16_t(0));
1✔
464

465
               // choose from a list of tickets that contains a random ticket and handles[1]
466
               auto session3 = mgr.choose_from_offered_tickets(
2✔
467
                  std::vector{ticket(random_ticket(*rng)), ticket(handles[1].id().value())}, "SHA-256", cbs, plcy);
5✔
468
               result.require("ticket was chosen and produced a session (3)", session3.has_value());
1✔
469
               result.test_is_eq("chosen second offset", session3->second, uint16_t(1));
2✔
470
            }),
13✔
471

472
      CHECK("choose ticket by ticket",
473
            [&](auto& result) {
1✔
474
               auto creds = std::make_shared<Test_Credentials_Manager>();
475
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
476
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
477

478
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
479
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
480

481
               // choose from a list of tickets that contains only handles[0]
482
               auto session1 = mgr.choose_from_offered_tickets(
2✔
483
                  std::vector{ticket(handles[0].ticket().value())}, "SHA-256", cbs, plcy);
4✔
484
               result.require("ticket was chosen and produced a session (1)", session1.has_value());
1✔
485
               result.test_is_eq("chosen offset", session1->second, uint16_t(0));
1✔
486

487
               // choose from a list of tickets that contains only handles[1]
488
               auto session2 = mgr.choose_from_offered_tickets(
2✔
489
                  std::vector{ticket(handles[1].ticket().value())}, "SHA-256", cbs, plcy);
4✔
490
               result.require("ticket was chosen and produced a session (2)", session2.has_value());
1✔
491
               result.test_is_eq("chosen offset", session2->second, uint16_t(0));
1✔
492

493
               // choose from a list of tickets that contains a random ticket and handles[1]
494
               auto session3 = mgr.choose_from_offered_tickets(
2✔
495
                  std::vector{ticket(random_ticket(*rng)), ticket(handles[1].ticket().value())}, "SHA-256", cbs, plcy);
5✔
496
               result.require("ticket was chosen and produced a session (3)", session3.has_value());
1✔
497
               result.test_is_eq("chosen second offset", session3->second, uint16_t(1));
2✔
498
            }),
14✔
499

500
      CHECK("choose ticket based on requested hash function",
501
            [&](auto& result) {
1✔
502
               auto creds = std::make_shared<Test_Credentials_Manager>();
503
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
504
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
505

506
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
507
               handles.push_back(mgr.establish(default_session("AES_256_GCM_SHA384", cbs)).value());
3✔
508

509
               auto session = mgr.choose_from_offered_tickets(std::vector{ticket(random_ticket(*rng)),
5✔
510
                                                                          ticket(handles[0].ticket().value()),
1✔
511
                                                                          ticket(handles[1].ticket().value())},
1✔
512
                                                              "SHA-384",
513
                                                              cbs,
514
                                                              plcy);
515
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
516
               result.test_is_eq("chosen second offset", session->second, uint16_t(2));
2✔
517
            }),
7✔
518

519
      CHECK("choose ticket based on protocol version",
520
            [&](auto& result) {
1✔
521
               auto creds = std::make_shared<Test_Credentials_Manager>();
522
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
523
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
524

525
               handles.push_back(
1✔
526
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V12)).value());
3✔
527
               handles.push_back(
1✔
528
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V13)).value());
3✔
529

530
               auto session = mgr.choose_from_offered_tickets(std::vector{ticket(random_ticket(*rng)),
5✔
531
                                                                          ticket(handles[0].ticket().value()),
1✔
532
                                                                          ticket(handles[1].ticket().value())},
1✔
533
                                                              "SHA-256",
534
                                                              cbs,
535
                                                              plcy);
536
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
537
               result.test_is_eq("chosen second offset (TLS 1.3 ticket)", session->second, uint16_t(2));
2✔
538
            }),
7✔
539
   };
7✔
540
   #else
541
   return {};
542
   #endif
543
}
2✔
544

545
std::vector<Test::Result> test_session_manager_stateless() {
1✔
546
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
547

548
   auto rng = Test::new_shared_rng(__func__);
1✔
549

550
   Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
551

552
   Session_Manager_Callbacks cbs;
1✔
553
   Session_Manager_Policy plcy;
1✔
554

555
   return {
1✔
556
      CHECK("establish with default parameters",
557
            [&](auto& result) {
1✔
558
               result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
559
               auto ticket = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
560
               result.confirm("returned ticket", ticket.has_value() && ticket->is_ticket());
2✔
561
            }),
1✔
562

563
      CHECK("establish with disabled tickets",
564
            [&](auto& result) {
1✔
565
               result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
566
               auto ticket =
1✔
567
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), std::nullopt, true);
1✔
568
               result.confirm("returned std::nullopt", !ticket.has_value());
2✔
569
            }),
1✔
570

571
      CHECK("establish without ticket key in credentials manager",
572
            [&](auto& result) {
1✔
573
               Botan::TLS::Session_Manager_Stateless local_mgr(std::make_shared<Empty_Credentials_Manager>(), rng);
2✔
574

575
               result.confirm("won't emit tickets", !local_mgr.emits_session_tickets());
2✔
576
               auto ticket = local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
577
               result.confirm("returned std::nullopt", !ticket.has_value());
2✔
578
            }),
1✔
579

580
      CHECK("retrieve via ticket",
581
            [&](auto& result) {
1✔
582
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
583
               auto ticket2 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
584
               result.require("tickets created successfully", ticket1.has_value() && ticket2.has_value());
1✔
585

586
               Botan::TLS::Session_Manager_Stateless local_mgr(creds, rng);
1✔
587
               result.confirm("can retrieve ticket 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
588
               result.confirm("can retrieve ticket 2 from different manager but sam credentials",
1✔
589
                              local_mgr.retrieve(ticket2.value(), cbs, plcy).has_value());
1✔
590
            }),
3✔
591

592
      CHECK("retrieve via ID does not work",
593
            [&](auto& result) {
1✔
594
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
595
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
596

597
               result.confirm("retrieval by ID does not work", !mgr.retrieve(random_id(*rng), cbs, plcy).has_value());
3✔
598
            }),
1✔
599

600
      CHECK("retrieve via opaque handle does work",
601
            [&](auto& result) {
1✔
602
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
603
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
604

605
               result.confirm("retrieval by opaque handle",
1✔
606
                              mgr.retrieve(ticket1->opaque_handle(), cbs, plcy).has_value());
3✔
607
            }),
1✔
608

609
      CHECK("no retrieve without or with wrong ticket key",
610
            [&](auto& result) {
1✔
611
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
612
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
613

614
               Botan::TLS::Session_Manager_Stateless local_mgr1(std::make_shared<Empty_Credentials_Manager>(), rng);
2✔
615

616
               Botan::TLS::Session_Manager_Stateless local_mgr2(std::make_shared<Other_Test_Credentials_Manager>(),
2✔
617
                                                                rng);
618

619
               result.confirm("no successful retrieval (without key)",
1✔
620
                              !local_mgr1.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
621
               result.confirm("no successful retrieval (with wrong key)",
1✔
622
                              !local_mgr2.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
623
               result.confirm("successful retrieval", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
624
            }),
2✔
625

626
      CHECK("Clients cannot be stateless",
627
            [&](auto& result) {
1✔
628
               result.test_throws("::store() does not work with ID", [&] {
2✔
629
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id(*rng));
3✔
630
               });
631
               result.test_throws("::store() does not work with ticket", [&] {
2✔
632
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket(*rng));
3✔
633
               });
634
               result.test_throws("::store() does not work with opaque handle", [&] {
2✔
635
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_opaque_handle(*rng));
3✔
636
               });
637

638
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
639
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
640
               result.confirm("finding tickets does not work", mgr.find(server_info(), cbs, plcy).empty());
2✔
641
            }),
1✔
642

643
      CHECK("remove is a NOOP",
644
            [&](auto& result) {
1✔
645
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
646
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
647

648
               result.test_is_eq("remove the ticket", mgr.remove(ticket1.value()), size_t(0));
1✔
649
               result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
650

651
               result.test_is_eq("remove the ticket", mgr.remove_all(), size_t(0));
1✔
652
               result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
3✔
653
            }),
1✔
654

655
      CHECK(
656
         "retrieval via ticket reconstructs the start_time stamp",
657
         [&](auto& result) {
1✔
658
            auto session_before = default_session(Botan::TLS::Connection_Side::Server, cbs);
1✔
659
            auto ticket = mgr.establish(session_before);
2✔
660
            result.require("got a ticket", ticket.has_value() && ticket->is_ticket());
1✔
661
            auto session_after = mgr.retrieve(ticket.value(), cbs, plcy);
1✔
662
            result.require("got the session back", session_after.has_value());
2✔
663

664
            result.test_is_eq(
1✔
665
               "timestamps match",
666
               std::chrono::duration_cast<std::chrono::seconds>(session_before.start_time().time_since_epoch()).count(),
2✔
667
               std::chrono::duration_cast<std::chrono::seconds>(session_after->start_time().time_since_epoch())
1✔
668
                  .count());
2✔
669
         }),
2✔
670
   };
11✔
671
}
4✔
672

673
std::vector<Test::Result> test_session_manager_hybrid() {
1✔
674
   auto rng = Test::new_shared_rng(__func__);
1✔
675

676
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
677
   Session_Manager_Callbacks cbs;
1✔
678
   Session_Manager_Policy plcy;
1✔
679

680
   // Runs the passed-in hybrid manager test lambdas for all available stateful
681
   // managers. The `make_manager()` helper is passed into the test code and
682
   // transparently constructs a hybrid manager with the respective internal
683
   // stateful manager.
684
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
4✔
685
      std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>> const
686
         stateful_manager_factories = {
9✔
687
            {"In Memory",
688
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
9✔
689
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10);
6✔
690
             }},
691
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
692
            {"SQLite",
693
             [&rng]() -> std::
9✔
694
                         unique_ptr<Botan::TLS::Session_Manager> {
695
                            return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
696
                               "secure_pw", rng, Test::temp_file_name("tls_session_manager_sqlite"), 10);
6✔
697
                         }},
698
   #endif
699
         };
700

701
      std::vector<Test::Result> results;
3✔
702
      using namespace std::placeholders;
703
      for(const auto& factory_and_name : stateful_manager_factories) {
15✔
704
         const auto& stateful_manager_name = factory_and_name.first;
6✔
705
         const auto& stateful_manager_factory = factory_and_name.second;
6✔
706
         auto make_manager = [stateful_manager_factory, &creds, &rng](bool prefer_tickets) {
42✔
707
            return Botan::TLS::Session_Manager_Hybrid(stateful_manager_factory(), creds, rng, prefer_tickets);
48✔
708
         };
709

710
         auto nm = Botan::fmt("{} ({})", name, stateful_manager_name);
6✔
711
         auto fn = std::bind(lambda, make_manager, _1);  // NOLINT(*-avoid-bind)
6✔
712
         results.push_back(CHECK(nm.c_str(), fn));
18✔
713
      }
714
      return results;
3✔
715
   };
7✔
716

717
   return Test::flatten_result_lists({
3✔
718
      CHECK_all("ticket vs ID preference in establishment",
1✔
719
                [&](const auto& make_manager, auto& result) {
2✔
720
                   auto mgr_prefers_tickets = make_manager(true);
2✔
721
                   auto ticket1 =
2✔
722
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
723
                   result.confirm("emits a ticket", ticket1.has_value() && ticket1->is_ticket());
4✔
724

725
                   auto mgr_prefers_ids = make_manager(false);
2✔
726
                   auto ticket2 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
727
                   result.confirm("emits an ID", ticket2.has_value() && ticket2->is_id());
4✔
728

729
                   auto ticket3 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
2✔
730
                                                            std::nullopt,
731
                                                            true /* TLS 1.2 no ticket support */);
732
                   result.confirm("emits an ID", ticket3.has_value() && ticket3->is_id());
4✔
733

734
                   auto ticket4 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
2✔
735
                                                            std::nullopt,
736
                                                            true /* TLS 1.2 no ticket support */);
737
                   result.confirm("emits an ID", ticket4.has_value() && ticket4->is_id());
4✔
738
                }),
8✔
739

740
      CHECK_all("ticket vs ID preference in retrieval",
1✔
741
                [&](const auto& make_manager, auto& result) {
2✔
742
                   auto mgr_prefers_tickets = make_manager(true);
2✔
743
                   auto mgr_prefers_ids = make_manager(false);
2✔
744

745
                   auto id1 = mgr_prefers_tickets.underlying_stateful_manager()->establish(
2✔
746
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
747
                   auto id2 = mgr_prefers_ids.underlying_stateful_manager()->establish(
2✔
748
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
749
                   auto ticket =
2✔
750
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
751

752
                   result.require("establishments worked", id1.has_value() && id2.has_value() && ticket.has_value());
2✔
753

754
                   result.confirm("mgr1 + ID1", mgr_prefers_tickets.retrieve(id1.value(), cbs, plcy).has_value());
4✔
755
                   result.confirm("mgr1 + ID2", !mgr_prefers_tickets.retrieve(id2.value(), cbs, plcy).has_value());
4✔
756
                   result.confirm("mgr2 + ID1", !mgr_prefers_ids.retrieve(id1.value(), cbs, plcy).has_value());
4✔
757
                   result.confirm("mgr2 + ID2", mgr_prefers_ids.retrieve(id2.value(), cbs, plcy).has_value());
4✔
758
                   result.confirm("mgr1 + ticket", mgr_prefers_tickets.retrieve(ticket.value(), cbs, plcy).has_value());
4✔
759
                   result.confirm("mgr2 + ticket", mgr_prefers_ids.retrieve(ticket.value(), cbs, plcy).has_value());
6✔
760
                }),
6✔
761

762
      CHECK_all("no session tickets if hybrid manager cannot create them",
1✔
763
                [&](const auto& make_manager, auto& result) {
2✔
764
                   Botan::TLS::Session_Manager_Hybrid empty_mgr(
8✔
765
                      std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10),
4✔
766
                      std::make_shared<Empty_Credentials_Manager>(),
767
                      rng);
768
                   auto mgr_prefers_tickets = make_manager(true);
2✔
769
                   auto mgr_prefers_ids = make_manager(false);
2✔
770

771
                   result.confirm("does not emit tickets", !empty_mgr.emits_session_tickets());
4✔
772
                   result.confirm("does emit tickets 1", mgr_prefers_tickets.emits_session_tickets());
4✔
773
                   result.confirm("does emit tickets 2", mgr_prefers_ids.emits_session_tickets());
4✔
774
                }),
2✔
775
   });
4✔
776
}
4✔
777

778
namespace {
779

780
class Temporary_Database_File {
781
   private:
782
      std::string m_temp_file;
783

784
   public:
785
      explicit Temporary_Database_File(const std::string& db_file) :
1✔
786
            m_temp_file(Test::data_file_as_temporary_copy(db_file)) {
1✔
787
         if(m_temp_file.empty()) {
1✔
788
            throw Test_Error("Failed to create temporary database file");
×
789
         }
790
      }
1✔
791

792
      ~Temporary_Database_File() {
1✔
793
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
794
         if(!m_temp_file.empty()) {
1✔
795
            std::filesystem::remove(m_temp_file);
1✔
796
         }
797
   #endif
798
      }
1✔
799

800
      const std::string& get() const { return m_temp_file; }
801

802
      Temporary_Database_File(const Temporary_Database_File&) = delete;
803
      Temporary_Database_File& operator=(const Temporary_Database_File&) = delete;
804
      Temporary_Database_File(Temporary_Database_File&&) = delete;
805
      Temporary_Database_File& operator=(Temporary_Database_File&&) = delete;
806
};
807

808
}  // namespace
809

810
std::vector<Test::Result> test_session_manager_sqlite() {
1✔
811
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
812
   auto rng = Test::new_shared_rng(__func__);
1✔
813
   Session_Manager_Callbacks cbs;
1✔
814
   Session_Manager_Policy plcy;
1✔
815

816
   return {
1✔
817
      CHECK("migrate session database scheme (purges database)",
818
            [&](auto& result) {
1✔
819
               Temporary_Database_File const dbfile("tls-sessions/botan-2.19.3.sqlite");
1✔
820

821
               // legacy database (encrypted with 'thetruthisoutthere') containing:
822
               //    $ sqlite3 src/tests/data/tls-sessions/botan-2.19.3.sqlite  'SELECT * FROM tls_sessions;'
823
               //    63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714|1673606906|cloudflare.com|443|...
824
               //    63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB|1673606915|randombit.net|443|...
825
               Botan::TLS::Session_Manager_SQLite legacy_db("thetruthisoutthere", rng, dbfile.get());
1✔
826

827
               result.confirm("Session_ID for randombit.net is gone",
1✔
828
                              !legacy_db
829
                                  .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
830
                                               "63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB")),
831
                                            cbs,
832
                                            plcy)
833
                                  .has_value());
3✔
834
               result.confirm("Session_ID for cloudflare.com is gone",
1✔
835
                              !legacy_db
836
                                  .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
837
                                               "63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714")),
838
                                            cbs,
839
                                            plcy)
840
                                  .has_value());
3✔
841
               result.confirm("no more session for randombit.net",
1✔
842
                              legacy_db.find(Botan::TLS::Server_Information("randombit.net", 443), cbs, plcy).empty());
2✔
843
               result.confirm("no more session for cloudflare.com",
1✔
844
                              legacy_db.find(Botan::TLS::Server_Information("cloudflare.com", 443), cbs, plcy).empty());
2✔
845

846
               result.test_is_eq("empty database won't get more empty", legacy_db.remove_all(), size_t(0));
2✔
847
            }),
1✔
848

849
      CHECK("clearing empty database",
850
            [&](auto& result) {
1✔
851
               Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere", rng, Test::temp_file_name("empty.sqlite"));
1✔
852
               result.test_eq("does not delete anything", mgr.remove_all(), 0);
2✔
853
            }),
1✔
854

855
      CHECK("establish new session",
856
            [&](auto& result) {
1✔
857
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
858
                  "thetruthisoutthere", rng, Test::temp_file_name("new_session.sqlite"));
1✔
859
               auto some_random_id = random_id(*rng);
1✔
860
               auto some_random_handle =
1✔
861
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
862
               result.require("establishment was successful", some_random_handle.has_value());
2✔
863
               result.require("session id was set", some_random_handle->id().has_value());
2✔
864
               result.test_is_eq("session id is correct", some_random_handle->id().value(), some_random_id);
3✔
865

866
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
867
               result.require("establishment was successful", some_virtual_handle.has_value());
2✔
868
               result.require("session id was set", some_virtual_handle->id().has_value());
2✔
869
            }),
3✔
870

871
      CHECK("retrieve session by ID",
872
            [&](auto& result) {
1✔
873
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
874
                  "thetruthisoutthere", rng, Test::temp_file_name("retrieve_by_id.sqlite"));
1✔
875
               auto some_random_id = random_id(*rng);
1✔
876
               auto some_random_handle =
1✔
877
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
878
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
879

880
               result.require("establishment was successful",
1✔
881
                              some_random_handle->is_id() && some_virtual_handle->is_id());
1✔
882

883
               auto session1 = mgr.retrieve(some_random_handle.value(), cbs, plcy);
1✔
884
               if(result.confirm("found session by user-provided ID", session1.has_value())) {
2✔
885
                  result.test_is_eq("protocol version was echoed",
1✔
886
                                    session1->version(),
1✔
887
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
888
                  result.test_is_eq("ciphersuite was echoed", session1->ciphersuite_code(), uint16_t(0x009C));
2✔
889
               }
890

891
               auto session2 = mgr.retrieve(some_virtual_handle.value(), cbs, plcy);
1✔
892
               if(result.confirm("found session by manager-generated ID", session2.has_value())) {
2✔
893
                  result.test_is_eq("protocol version was echoed",
1✔
894
                                    session2->version(),
1✔
895
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
896
                  result.test_is_eq("ciphersuite was echoed", session2->ciphersuite_code(), uint16_t(0x009C));
2✔
897
               }
898

899
               auto session3 = mgr.retrieve(random_id(*rng), cbs, plcy);
2✔
900
               result.confirm("random ID creates empty result", !session3.has_value());
2✔
901
            }),
6✔
902

903
      CHECK("retrieval via ticket creates empty result",
904
            [&](auto& result) {
1✔
905
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
906
                  "thetruthisoutthere", rng, Test::temp_file_name("retrieve_by_ticket.sqlite"));
1✔
907
               auto some_random_handle =
1✔
908
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng));
1✔
909
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
910

911
               result.confirm("std::nullopt on random ticket",
1✔
912
                              !mgr.retrieve(random_ticket(*rng), cbs, plcy).has_value());
3✔
913
            }),
2✔
914

915
      CHECK("storing sessions and finding them by server info",
916
            [&](auto& result) {
1✔
917
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
918
                  "thetruthisoutthere", rng, Test::temp_file_name("store_and_find.sqlite"));
1✔
919
               auto id = random_id(*rng);
1✔
920
               auto ticket = random_ticket(*rng);
1✔
921
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
922
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
923

924
               auto found_sessions = mgr.find(server_info(), cbs, plcy);
1✔
925
               if(result.test_is_eq("found both sessions", found_sessions.size(), size_t(2))) {
1✔
926
                  for(const auto& [session, handle] : found_sessions) {
3✔
927
                     result.confirm("ID matches", !handle.is_id() || handle.id().value() == id);
5✔
928
                     result.confirm("ticket matches", !handle.is_ticket() || handle.ticket().value() == ticket);
5✔
929
                  }
930
               }
931
            }),
3✔
932

933
      CHECK("removing sessions",
934
            [&](auto& result) {
1✔
935
               Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere", rng, Test::temp_file_name("remove.sqlite"));
1✔
936
               auto id = random_id(*rng);
1✔
937
               auto ticket = random_ticket(*rng);
1✔
938
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
939
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
940
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id(*rng));
2✔
941
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket(*rng));
2✔
942

943
               result.test_is_eq("deletes one session by ID", mgr.remove(id), size_t(1));
4✔
944
               result.test_is_eq("deletes one session by ticket", mgr.remove(ticket), size_t(1));
4✔
945

946
               auto found_sessions = mgr.find(server_info(), cbs, plcy);
1✔
947
               if(result.test_is_eq("found some other sessions", found_sessions.size(), size_t(2))) {
1✔
948
                  for(const auto& [session, handle] : found_sessions) {
3✔
949
                     result.confirm("ID does not match", !handle.is_id() || handle.id().value() != id);
6✔
950
                     result.confirm("ticket does not match", !handle.is_ticket() || handle.ticket().value() != ticket);
6✔
951
                  }
952
               }
953

954
               result.test_is_eq("removing the rest of the sessions", mgr.remove_all(), size_t(2));
1✔
955
            }),
3✔
956

957
      CHECK("old sessions are purged when needed",
958
            [&](auto& result) {
1✔
959
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
960
                  "thetruthisoutthere", rng, Test::temp_file_name("purging.sqlite"), 1);
1✔
961

962
               std::vector<Botan::TLS::Session_ID> ids = {random_id(*rng), random_id(*rng), random_id(*rng)};
5✔
963
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[0]);
2✔
964
               result.require("new ID exists", mgr.retrieve(ids[0], cbs, plcy).has_value());
5✔
965

966
               // Session timestamps are saved with second-resolution. If more than
967
               // one session has the same (coarse) timestamp it is undefined which
968
               // will be purged first. The clock tick ensures that session's
969
               // timestamps are unique.
970
               cbs.tick();
1✔
971
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[1]);
2✔
972
               result.require("first ID is gone", !mgr.retrieve(ids[0], cbs, plcy).has_value());
4✔
973
               result.require("new ID exists", mgr.retrieve(ids[1], cbs, plcy).has_value());
5✔
974

975
               cbs.tick();
1✔
976
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[2]);
2✔
977
               result.require("second ID is gone", !mgr.retrieve(ids[1], cbs, plcy).has_value());
4✔
978
               result.require("new ID exists", mgr.retrieve(ids[2], cbs, plcy).has_value());
5✔
979

980
               result.test_is_eq("only one entry exists", mgr.remove_all(), size_t(1));
1✔
981
            }),
2✔
982

983
      CHECK("session purging can be disabled",
984
            [&](auto& result) {
1✔
985
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
986
                  "thetruthisoutthere", rng, Test::temp_file_name("purging.sqlite"), 0 /* no pruning! */);
1✔
987

988
               for(size_t i = 0; i < 25; ++i) {
26✔
989
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng));
50✔
990
               }
991

992
               result.test_is_eq("no entries were purged along the way", mgr.remove_all(), size_t(25));
2✔
993
            }),
1✔
994
   };
10✔
995
   #else
996
   return {};
997
   #endif
998
}
3✔
999

1000
std::vector<Test::Result> tls_session_manager_expiry() {
1✔
1001
   auto rng = Test::new_shared_rng(__func__);
1✔
1002
   Session_Manager_Callbacks cbs;
1✔
1003
   Session_Manager_Policy plcy;
1✔
1004

1005
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
6✔
1006
      const std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>>
1007
         stateful_manager_factories = {
20✔
1008
            {"In Memory",
1009
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
10✔
1010
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10);
5✔
1011
             }},
1012
            {"Stateless",
1013
             [&]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
7✔
1014
                return std::make_unique<Botan::TLS::Session_Manager_Stateless>(
1015
                   std::make_shared<Test_Credentials_Manager>(), rng);
2✔
1016
             }},
1017
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
1018
            {"SQLite",
1019
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
10✔
1020
                return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
1021
                   "secure_pw", rng, Test::temp_file_name("tls_session_manager_sqlite"), 10);
5✔
1022
             }},
1023
   #endif
1024
         };
1025

1026
      std::vector<Test::Result> results;
5✔
1027
      results.reserve(stateful_manager_factories.size());
5✔
1028
      using namespace std::placeholders;
1029
      for(const auto& [sub_name, factory] : stateful_manager_factories) {
20✔
1030
         auto nm = Botan::fmt("{} ({})", name, sub_name);
15✔
1031
         auto fn = std::bind(lambda, sub_name, factory, _1);  // NOLINT(*-avoid-bind)
15✔
1032
         results.push_back(CHECK(nm.c_str(), fn));
30✔
1033
      }
1034
      return results;
5✔
1035
   };
11✔
1036

1037
   return Test::flatten_result_lists({
5✔
1038
      CHECK_all("sessions expire",
1✔
1039
                [&](const auto&, const auto& factory, auto& result) {
3✔
1040
                   auto mgr = factory();
3✔
1041

1042
                   auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
3✔
1043
                   result.require("saved successfully", handle.has_value());
6✔
1044
                   result.require("session was found", mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1045
                   cbs.tick();
3✔
1046
                   result.confirm("session has expired", !mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1047
                   result.test_is_eq("session was deleted when it expired", mgr->remove_all(), size_t(0));
6✔
1048
                }),
6✔
1049

1050
         CHECK_all("expired sessions are not found",
1✔
1051
                   [&](const std::string& type, const auto& factory, auto& result) {
3✔
1052
                      if(type == "Stateless") {
3✔
1053
                         return;  // this manager can neither store nor find anything
1✔
1054
                      }
1055

1056
                      auto mgr = factory();
2✔
1057

1058
                      auto handle_old = random_id(*rng);
2✔
1059
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_old);
6✔
1060
                      result.require("session was found", mgr->retrieve(handle_old, cbs, plcy).has_value());
10✔
1061

1062
                      cbs.tick();
2✔
1063
                      auto handle_new = random_id(*rng);
2✔
1064
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_new);
6✔
1065
                      result.require("session was found", mgr->retrieve(handle_new, cbs, plcy).has_value());
10✔
1066

1067
                      auto sessions_and_handles = mgr->find(server_info(), cbs, plcy);
2✔
1068
                      result.require("sessions are found", !sessions_and_handles.empty());
2✔
1069
                      result.test_is_eq("exactly one session is found", sessions_and_handles.size(), size_t(1));
4✔
1070
                      result.test_is_eq(
4✔
1071
                         "the new session is found", sessions_and_handles.front().handle.id().value(), handle_new);
4✔
1072

1073
                      result.test_is_eq("old session was deleted when it expired", mgr->remove_all(), size_t(1));
2✔
1074
                   }),
8✔
1075

1076
         CHECK_all(
1✔
1077
            "session tickets are not reused",
1078
            [&](const std::string& type, const auto& factory, auto& result) {
3✔
1079
               if(type == "Stateless") {
3✔
1080
                  return;  // this manager can neither store nor find anything
1✔
1081
               }
1082

1083
               auto mgr = factory();
2✔
1084

1085
               auto handle_1 = random_id(*rng);
2✔
1086
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1087
                          handle_1);
1088
               auto handle_2 = random_ticket(*rng);
2✔
1089
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1090
                          handle_2);
1091

1092
   #if defined(BOTAN_HAS_TLS_13)
1093
               auto handle_3 = random_id(*rng);
2✔
1094
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1095
                          handle_3);
1096
               auto handle_4 = random_ticket(*rng);
2✔
1097
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1098
                          handle_4);
1099
   #endif
1100

1101
               plcy.set_allow_session_reuse(false);
2✔
1102

1103
               auto sessions_and_handles1 = mgr->find(server_info(), cbs, plcy);
2✔
1104
               result.require("all sessions are found", sessions_and_handles1.size() > 1);
4✔
1105

1106
               auto sessions_and_handles2 = mgr->find(server_info(), cbs, plcy);
2✔
1107
               result.test_is_eq("only one session is found", sessions_and_handles2.size(), size_t(1));
4✔
1108
               result.confirm("found session is the Session_ID", sessions_and_handles2.front().handle.is_id());
4✔
1109
               result.test_is_eq(
4✔
1110
                  "found session is the Session_ID", sessions_and_handles2.front().handle.id().value(), handle_1);
6✔
1111
               result.confirm("found session is TLS 1.2",
4✔
1112
                              sessions_and_handles2.front().session.version().is_pre_tls_13());
2✔
1113
            }),
12✔
1114

1115
         CHECK_all("number of found tickets is capped",
1✔
1116
                   [&](const std::string& type, const auto& factory, auto& result) {
3✔
1117
                      if(type == "Stateless") {
3✔
1118
                         return;  // this manager can neither store nor find anything
1✔
1119
                      }
1120

1121
                      auto mgr = factory();
2✔
1122

1123
                      std::array<Botan::TLS::Session_Ticket, 5> tickets;
2✔
1124
                      for(auto& ticket : tickets) {
12✔
1125
                         ticket = random_ticket(*rng);
10✔
1126
                         mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
30✔
1127
                      }
1128

1129
                      plcy.set_allow_session_reuse(true);
2✔
1130

1131
                      plcy.set_session_limit(1);
2✔
1132
                      result.test_is_eq("find one",
2✔
1133
                                        mgr->find(server_info(), cbs, plcy).size(),
4✔
1134
                                        plcy.maximum_session_tickets_per_client_hello());
2✔
1135

1136
                      plcy.set_session_limit(3);
2✔
1137
                      result.test_is_eq("find three",
2✔
1138
                                        mgr->find(server_info(), cbs, plcy).size(),
4✔
1139
                                        plcy.maximum_session_tickets_per_client_hello());
2✔
1140

1141
                      plcy.set_session_limit(10);
2✔
1142
                      result.test_is_eq("find all five", mgr->find(server_info(), cbs, plcy).size(), size_t(5));
4✔
1143
                   }),
4✔
1144

1145
   #if defined(BOTAN_HAS_TLS_13)
1146
         CHECK_all("expired tickets are not selected for PSK resumption",
1✔
1147
                   [&](const auto&, const auto& factory, auto& result) {
3✔
1148
                      auto ticket = [&](const Botan::TLS::Session_Handle& handle) {
12✔
1149
                         return Botan::TLS::PskIdentity(handle.opaque_handle().get(), 0);
12✔
1150
                      };
1151

1152
                      auto mgr = factory();
3✔
1153

1154
                      auto old_handle = mgr->establish(
3✔
1155
                         default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1156
                      cbs.tick();
3✔
1157
                      auto new_handle = mgr->establish(
3✔
1158
                         default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1159
                      result.require("both sessions are stored", old_handle.has_value() && new_handle.has_value());
6✔
1160

1161
                      auto session_and_index = mgr->choose_from_offered_tickets(
12✔
1162
                         std::vector{ticket(old_handle.value()), ticket(new_handle.value())}, "SHA-256", cbs, plcy);
6✔
1163
                      result.require("a ticket was chosen", session_and_index.has_value());
3✔
1164
                      result.test_is_eq("the new ticket was chosen", session_and_index->second, uint16_t(1));
3✔
1165

1166
                      cbs.tick();
3✔
1167

1168
                      auto nothing = mgr->choose_from_offered_tickets(
12✔
1169
                         std::vector{ticket(new_handle.value()), ticket(old_handle.value())}, "SHA-256", cbs, plcy);
6✔
1170
                      result.require("all tickets are expired", !nothing.has_value());
6✔
1171
                   }),
33✔
1172
   #endif
1173
   });
6✔
1174
}
3✔
1175

1176
}  // namespace
1177

1178
BOTAN_REGISTER_TEST_FN("tls",
1179
                       "tls_session_manager",
1180
                       test_session_manager_in_memory,
1181
                       test_session_manager_choose_ticket,
1182
                       test_session_manager_stateless,
1183
                       test_session_manager_hybrid,
1184
                       test_session_manager_sqlite,
1185
                       tls_session_manager_expiry);
1186

1187
}  // namespace Botan_Tests
1188

1189
#endif  // BOTAN_HAS_TLS
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