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

randombit / botan / 30974838434

04 Aug 2026 05:33PM UTC coverage: 89.682% (+0.02%) from 89.662%
30974838434

push

github

web-flow
Merge pull request #5793 from randombit/jack/dtls-bounded-retry

Bound DTLS handshake retry attempts and properly abandon timed out states

119137 of 132844 relevant lines covered (89.68%)

10371831.54 hits per line

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

97.04
/src/tests/test_dtls12.cpp
1
/*
2
* (C) 2014,2015,2018,2026 Jack Lloyd
3
*     2016 Matthias Gierlings
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
5
*     2017 Harry Reimann, Rohde & Schwarz Cybersecurity
6
*     2023 René Meusel, Rohde & Schwarz Cybersecurity
7
*     2026 René Meusel, Rohde & Schwarz Networks and Cybersecurity
8
*     2025 Lars Dürkop, CARIAD SE
9
*     2026 Thiesius
10
*
11
* Botan is released under the Simplified BSD License (see license.txt)
12
*/
13

14
#include "tests.h"
15

16
#include <array>
17
#include <chrono>
18
#include <memory>
19
#include <optional>
20
#include <string>
21
#include <vector>
22

23
#if defined(BOTAN_TARGET_OS_HAS_THREADS)
24
   #include <thread>
25
#endif
26

27
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_12)
28

29
   #include <botan/credentials_manager.h>
30
   #include <botan/rng.h>
31
   #include <botan/tls_callbacks.h>
32
   #include <botan/tls_client.h>
33
   #include <botan/tls_exceptn.h>
34
   #include <botan/tls_policy.h>
35
   #include <botan/tls_server.h>
36
   #include <botan/tls_session_manager_memory.h>
37
   #include <botan/tls_session_manager_noop.h>
38
   #include <botan/tls_session_manager_stateless.h>
39

40
#endif
41

42
namespace Botan_Tests {
43

44
namespace {
45

46
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_12)
47

48
/*
49
These tests exercise the DTLS core without opening real UDP sockets. The test
50
callbacks collect bytes emitted by TLS::Client/TLS::Server, and deliver() feeds
51
those bytes into the peer's received_data(). Clearing, splitting, copying, or
52
delaying those buffers simulates datagram loss, retransmission, duplicates, and
53
partial flights while still running the real DTLS record, handshake, epoch, and
54
retransmission logic.
55
*/
56

57
class DTLS_Test_Callbacks final : public Botan::TLS::Callbacks {
56✔
58
   public:
59
      DTLS_Test_Callbacks(Test::Result& result, std::vector<uint8_t>& outbound, std::vector<uint8_t>& received) :
56✔
60
            m_result(result), m_outbound(outbound), m_received(received) {}
56✔
61

62
      void tls_emit_data(std::span<const uint8_t> bits) override {
397✔
63
         m_outbound.insert(m_outbound.end(), bits.begin(), bits.end());
397✔
64
      }
397✔
65

66
      void tls_record_received(uint64_t /*seq*/, std::span<const uint8_t> bits) override {
11✔
67
         m_received.insert(m_received.end(), bits.begin(), bits.end());
11✔
68
      }
11✔
69

70
      void tls_alert(Botan::TLS::Alert alert) override {
×
71
         ++m_alerts;
×
72
         m_last_alert = alert;
×
73

74
         if(alert.is_fatal() && !m_tolerate_fatal_alerts) {
×
75
            m_result.test_failure("unexpected fatal alert: " + alert.type_string());
×
76
         }
77
      }
×
78

79
      // A DTLS server requires a non-empty peer identity to bind the cookie to.
80
      std::string tls_peer_network_identity() override { return "test-peer"; }
58✔
81

82
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
54✔
83
         m_session_was_resumption = session.was_resumption();
54✔
84
         ++m_sessions_established;
54✔
85
      }
54✔
86

87
      // Returns the normal clock unless advance_clock_ms has been called, in
88
      // which case the timer becomes controlled entirely by the test
89
      uint64_t tls_current_monotonic_clock_ms() override {
308✔
90
         // TODO(C++23) std::optional::or_else
91
         if(m_virtual_now_ms.has_value()) {
308✔
92
            return m_virtual_now_ms.value();
11✔
93
         } else {
94
            return Botan::TLS::Callbacks::tls_current_monotonic_clock_ms();
297✔
95
         }
96
      }
97

98
      // A test using a synthetic clock should call advance_clock_ms before
99
      // sending any flights, as otherwise the clock may seem to move backwards
100
      // from the perspective of the handshake timer
101
      void advance_clock_ms(uint64_t delta) { m_virtual_now_ms = m_virtual_now_ms.value_or(0) + delta; }
6✔
102

103
      // For endpoints a test expects to fail; without this a fatal alert marks
104
      // the whole result failed.
105
      void tolerate_fatal_alerts() { m_tolerate_fatal_alerts = true; }
×
106

107
      size_t sessions_established() const { return m_sessions_established; }
5✔
108

109
      size_t alerts_received() const { return m_alerts; }
110

111
      std::optional<Botan::TLS::Alert> last_alert() const { return m_last_alert; }
112

113
      std::optional<bool> session_was_resumption() const { return m_session_was_resumption; }
10✔
114

115
   private:
116
      Test::Result& m_result;
117
      std::vector<uint8_t>& m_outbound;
118
      std::vector<uint8_t>& m_received;
119
      size_t m_sessions_established = 0;
120
      size_t m_alerts = 0;
121
      std::optional<Botan::TLS::Alert> m_last_alert;
122
      std::optional<bool> m_session_was_resumption;
123
      std::optional<uint64_t> m_virtual_now_ms;
124
      bool m_tolerate_fatal_alerts = false;
125
};
126

127
class DTLS_PSK_Credentials final : public Botan::Credentials_Manager {
48✔
128
   public:
129
      Botan::SymmetricKey psk(const std::string& type,
136✔
130
                              const std::string& context,
131
                              const std::string& /*identity*/) override {
132
         if(type == "tls-server" && context == "session-ticket") {
136✔
133
            return Botan::SymmetricKey("AABBCCDDEEFF012345678012345678");
×
134
         }
135

136
         if(type == "tls-server" && context == "dtls-cookie-secret") {
136✔
137
            ++m_dtls_cookie_secret_requests;
85✔
138
            return m_cookie_secret_rotated ? Botan::SymmetricKey("0123456789ABCDEF0123456789ABCDEF")
85✔
139
                                           : Botan::SymmetricKey("4AEA5EAD279CADEB537A594DA0E9DE3A");
85✔
140
         }
141

142
         if(context == "localhost" && (type == "tls-client" || type == "tls-server")) {
51✔
143
            return Botan::SymmetricKey("20B602D1475F2DF888FCB60D2AE03AFD");
51✔
144
         }
145

146
         throw Test_Error("No PSK set for " + type + "/" + context);
×
147
      }
148

149
      size_t dtls_cookie_secret_requests() const { return m_dtls_cookie_secret_requests; }
1✔
150

151
      void rotate_cookie_secret() { m_cookie_secret_rotated = true; }
1✔
152

153
   private:
154
      size_t m_dtls_cookie_secret_requests = 0;
155
      bool m_cookie_secret_rotated = false;
156
};
157

158
// Settings a test wants to differ from the shared PSK-over-DTLS baseline. The
159
// timeouts are short so that the retransmission tests do not wait on a real
160
// clock; everything else follows Botan::TLS::Policy, which the unset optionals
161
// below defer to.
162
struct DTLS_Policy_Options final {
2✔
163
      std::optional<size_t> max_retransmissions;
164
      std::optional<std::string> cipher;
165
      bool allow_epoch0_restart = true;
166
      bool reuse_session_tickets = false;
167
      size_t initial_timeout_ms = 1;
168
      size_t maximum_timeout_ms = 8;
169
};
170

171
class DTLS_PSK_Policy final : public Botan::TLS::Policy {
24✔
172
   public:
173
      DTLS_PSK_Policy() = default;
22✔
174

175
      explicit DTLS_PSK_Policy(DTLS_Policy_Options opts) : m_opts(std::move(opts)) {}
2✔
176

177
      std::optional<size_t> dtls_maximum_retransmissions() const override {
65✔
178
         // TODO(C++23) std::optional::or_else
179
         if(m_opts.max_retransmissions.has_value()) {
65✔
180
            return m_opts.max_retransmissions;
5✔
181
         } else {
182
            return Botan::TLS::Policy::dtls_maximum_retransmissions();
60✔
183
         }
184
      }
185

186
      std::vector<std::string> allowed_macs() const override { return {"AEAD"}; }
1,083✔
187

188
      std::vector<std::string> allowed_key_exchange_methods() const override { return {"PSK"}; }
60✔
189

190
      std::vector<std::string> allowed_ciphers() const override {
5,883✔
191
         if(m_opts.cipher.has_value()) {
5,883✔
192
            return {m_opts.cipher.value()};
×
193
         }
194
         return Botan::TLS::Policy::allowed_ciphers();
5,883✔
195
      }
×
196

197
      bool allow_tls12() const override { return false; }
92✔
198

199
      bool allow_dtls12() const override { return true; }
302✔
200

201
      bool allow_dtls_epoch0_restart() const override { return m_opts.allow_epoch0_restart; }
112✔
202

203
      bool allow_server_initiated_renegotiation() const override { return true; }
1✔
204

205
      bool allow_client_initiated_renegotiation() const override { return true; }
2✔
206

207
      bool reuse_session_tickets() const override { return m_opts.reuse_session_tickets; }
29✔
208

209
      size_t dtls_initial_timeout() const override { return m_opts.initial_timeout_ms; }
65✔
210

211
      size_t dtls_maximum_timeout() const override { return m_opts.maximum_timeout_ms; }
65✔
212

213
   private:
214
      DTLS_Policy_Options m_opts;
215
};
216

217
// The variants the tests below need. Everything not named here follows the
218
// baseline above.
219

220
std::shared_ptr<DTLS_PSK_Policy> dtls_policy_with_max_retransmissions(size_t limit) {
2✔
221
   DTLS_Policy_Options opts;
2✔
222
   opts.max_retransmissions = limit;
2✔
223
   return std::make_shared<DTLS_PSK_Policy>(std::move(opts));
2✔
224
}
2✔
225

226
std::unique_ptr<Botan::TLS::Client> make_dtls_client(const std::shared_ptr<Botan::TLS::Callbacks>& callbacks,
20✔
227
                                                     const std::shared_ptr<Botan::TLS::Session_Manager>& sessions,
228
                                                     const std::shared_ptr<Botan::Credentials_Manager>& creds,
229
                                                     const std::shared_ptr<const Botan::TLS::Policy>& policy,
230
                                                     const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
231
   return std::make_unique<Botan::TLS::Client>(callbacks,
20✔
232
                                               sessions,
233
                                               creds,
234
                                               policy,
235
                                               rng,
236
                                               Botan::TLS::Server_Information("localhost"),
20✔
237
                                               Botan::TLS::Protocol_Version::latest_dtls_version());
60✔
238
}
239

240
std::unique_ptr<Botan::TLS::Server> make_dtls_server(const std::shared_ptr<Botan::TLS::Callbacks>& callbacks,
19✔
241
                                                     const std::shared_ptr<Botan::TLS::Session_Manager>& sessions,
242
                                                     const std::shared_ptr<Botan::Credentials_Manager>& creds,
243
                                                     const std::shared_ptr<const Botan::TLS::Policy>& policy,
244
                                                     const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
245
   return std::make_unique<Botan::TLS::Server>(callbacks, sessions, creds, policy, rng, true /* is_datagram */);
38✔
246
}
247

248
void deliver(Test::Result& result,
170✔
249
             const std::string& label,
250
             std::vector<uint8_t>& outbound,
251
             Botan::TLS::Channel& peer) {
252
   if(!result.test_is_true(label + " has data", !outbound.empty())) {
170✔
253
      return;
×
254
   }
255

256
   std::vector<uint8_t> input;
170✔
257
   std::swap(input, outbound);
170✔
258
   result.test_no_throw(label, [&] { peer.received_data(input.data(), input.size()); });
510✔
259
}
170✔
260

261
void deliver_copy(Test::Result& result,
13✔
262
                  const std::string& label,
263
                  const std::vector<uint8_t>& outbound,
264
                  Botan::TLS::Channel& peer) {
265
   if(!result.test_is_true(label + " has data", !outbound.empty())) {
13✔
266
      return;
267
   }
268

269
   result.test_no_throw(label, [&] { peer.received_data(outbound.data(), outbound.size()); });
39✔
270
}
271

272
// Hand each waiting buffer to its peer until `done` holds or both run dry.
273
// Bounded so that a regression cannot hang the suite.
274
template <typename Predicate>
275
bool pump_records(std::vector<uint8_t>& c2s,
276
                  Botan::TLS::Channel& server,
277
                  std::vector<uint8_t>& s2c,
278
                  Botan::TLS::Channel& client,
279
                  Predicate done,
280
                  size_t max_rounds = 64) {
281
   for(size_t i = 0; i != max_rounds; ++i) {
282
      if(done()) {
283
         return true;
284
      }
285

286
      if(!c2s.empty()) {
287
         std::vector<uint8_t> in;
288
         std::swap(c2s, in);
289
         server.received_data(in.data(), in.size());
290
      } else if(!s2c.empty()) {
291
         std::vector<uint8_t> in;
292
         std::swap(s2c, in);
293
         client.received_data(in.data(), in.size());
294
      } else {
295
         break;
296
      }
297
   }
298

299
   return done();
300
}
301

302
// One client/server pair plus the buffers carrying records between them. The
303
// buffers are declared ahead of the callbacks that write into them.
304
struct DTLS_Association final {
19✔
305
      // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
306
      std::vector<uint8_t> c2s;
307
      std::vector<uint8_t> s2c;
308
      std::vector<uint8_t> client_recv;
309
      std::vector<uint8_t> server_recv;
310
      std::shared_ptr<DTLS_Test_Callbacks> client_cb;
311
      std::shared_ptr<DTLS_Test_Callbacks> server_cb;
312
      std::shared_ptr<Botan::TLS::Session_Manager> client_sessions;
313
      std::shared_ptr<Botan::TLS::Session_Manager> server_sessions;
314
      std::shared_ptr<DTLS_PSK_Credentials> creds;
315
      // Retained so that a test can attach a further client to this server.
316
      std::shared_ptr<const Botan::TLS::Policy> client_policy;
317
      std::unique_ptr<Botan::TLS::Server> server;
318
      std::unique_ptr<Botan::TLS::Client> client;
319

320
      // NOLINTEND(misc-non-private-member-variables-in-classes)
321

322
      bool both_active() const { return client->is_active() && server->is_active(); }
5✔
323

324
      // Hand one waiting buffer to its peer. Returns false when both are empty.
325
      bool pump_one() {
326
         if(!c2s.empty()) {
327
            std::vector<uint8_t> in;
328
            std::swap(c2s, in);
329
            server->received_data(in.data(), in.size());
330
            return true;
331
         }
332
         if(!s2c.empty()) {
333
            std::vector<uint8_t> in;
334
            std::swap(s2c, in);
335
            client->received_data(in.data(), in.size());
336
            return true;
337
         }
338
         return false;
339
      }
340

341
      template <typename Predicate>
342
      bool pump_until(Predicate done, size_t max_rounds = 64) {
343
         return pump_records(c2s, *server, s2c, *client, done, max_rounds);
344
      }
345

346
      bool pump(size_t max_rounds = 64) {
347
         return pump_until([this] { return both_active(); }, max_rounds);
348
      }
349
};
350

351
// Options for the association builder. `stateless_tickets` selects a server
352
// that issues session tickets rather than session IDs, which is the case the
353
// ServerHello session ID cannot identify.
354
struct DTLS_Association_Options final {
2✔
355
      std::shared_ptr<const Botan::TLS::Policy> policy;
356
      std::shared_ptr<const Botan::TLS::Policy> client_policy;
357
      std::shared_ptr<Botan::TLS::Session_Manager> client_sessions;
358
      bool stateless_tickets = false;
359
      // For tests whose subject is a fatal alert, which otherwise fails the
360
      // result the moment the callbacks see it.
361
      bool tolerate_fatal_alerts = false;
362
};
363

364
std::unique_ptr<DTLS_Association> make_association(Test::Result& result,
19✔
365
                                                   const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
366
                                                   DTLS_Association_Options opts = {}) {
367
   auto assoc = std::make_unique<DTLS_Association>();
19✔
368

369
   auto policy = opts.policy ? opts.policy : std::make_shared<DTLS_PSK_Policy>();
36✔
370
   assoc->client_policy = opts.client_policy ? opts.client_policy : policy;
19✔
371
   assoc->creds = std::make_shared<DTLS_PSK_Credentials>();
19✔
372

373
   if(opts.stateless_tickets) {
19✔
374
      assoc->server_sessions = std::make_shared<Botan::TLS::Session_Manager_Stateless>(assoc->creds, rng);
×
375
   } else {
376
      assoc->server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
19✔
377
   }
378

379
   assoc->client_sessions = opts.client_sessions ? std::move(opts.client_sessions)
38✔
380
                                                 : std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
37✔
381

382
   assoc->server_cb = std::make_shared<DTLS_Test_Callbacks>(result, assoc->s2c, assoc->server_recv);
19✔
383
   assoc->server = make_dtls_server(assoc->server_cb, assoc->server_sessions, assoc->creds, policy, rng);
38✔
384

385
   assoc->client_cb = std::make_shared<DTLS_Test_Callbacks>(result, assoc->c2s, assoc->client_recv);
19✔
386
   assoc->client = make_dtls_client(assoc->client_cb, assoc->client_sessions, assoc->creds, assoc->client_policy, rng);
38✔
387

388
   if(opts.tolerate_fatal_alerts) {
19✔
389
      assoc->server_cb->tolerate_fatal_alerts();
×
390
      assoc->client_cb->tolerate_fatal_alerts();
×
391
   }
392

393
   return assoc;
19✔
394
}
19✔
395

396
// Both endpoints under a policy other than the baseline, which is by far the
397
// most common reason to reach for the options above.
398
std::unique_ptr<DTLS_Association> make_association(Test::Result& result,
2✔
399
                                                   const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
400
                                                   std::shared_ptr<const Botan::TLS::Policy> policy) {
401
   DTLS_Association_Options opts;
2✔
402
   opts.policy = std::move(policy);
2✔
403
   return make_association(result, rng, std::move(opts));
4✔
404
}
2✔
405

406
// The full cookie exchange and handshake, step by step, so that a failure names
407
// the flight it stalled on. Tests that only need an established association can
408
// use DTLS_Association::pump() instead.
409
bool complete_dtls_handshake(Test::Result& result, DTLS_Association& assoc, const std::string& prefix = "") {
5✔
410
   deliver(result, prefix + "client hello 1", assoc.c2s, *assoc.server);
5✔
411
   deliver(result, prefix + "hello verify request", assoc.s2c, *assoc.client);
5✔
412
   deliver(result, prefix + "client hello 2", assoc.c2s, *assoc.server);
5✔
413
   deliver(result, prefix + "server handshake flight", assoc.s2c, *assoc.client);
5✔
414
   deliver(result, prefix + "client final flight", assoc.c2s, *assoc.server);
5✔
415
   deliver(result, prefix + "server final flight", assoc.s2c, *assoc.client);
5✔
416

417
   return result.test_is_true(prefix + "handshake completed", assoc.both_active());
5✔
418
}
419

420
// A DTLS record with a plaintext body, ie one the record layer will either
421
// reject or (at epoch 0) hand straight to the handshake layer.
422
std::vector<uint8_t> unprotected_dtls_record(Botan::TLS::Record_Type type,
3✔
423
                                             uint64_t sequence,
424
                                             std::span<const uint8_t> payload) {
425
   std::vector<uint8_t> record;
3✔
426
   record.reserve(13 + payload.size());
3✔
427

428
   record.push_back(static_cast<uint8_t>(type));
3✔
429
   record.push_back(0xFE);
3✔
430
   record.push_back(0xFD);
3✔
431
   for(size_t i = 0; i != 8; ++i) {
27✔
432
      record.push_back(static_cast<uint8_t>(sequence >> (56 - 8 * i)));
24✔
433
   }
434
   record.push_back(static_cast<uint8_t>(payload.size() >> 8));
3✔
435
   record.push_back(static_cast<uint8_t>(payload.size()));
3✔
436
   record.insert(record.end(), payload.begin(), payload.end());
3✔
437

438
   return record;
3✔
439
}
×
440

441
// A 12-byte DTLS handshake header followed by `fragment`, which may be a piece
442
// of the message or all of it.
443
std::vector<uint8_t> dtls_handshake_fragment(Botan::TLS::Handshake_Type type,
1✔
444
                                             size_t message_length,
445
                                             uint16_t message_sequence,
446
                                             size_t fragment_offset,
447
                                             std::span<const uint8_t> fragment) {
448
   const auto push_be24 = [](std::vector<uint8_t>& out, size_t value) {
4✔
449
      out.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
3✔
450
      out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
3✔
451
      out.push_back(static_cast<uint8_t>(value & 0xFF));
3✔
452
   };
3✔
453

454
   std::vector<uint8_t> msg;
1✔
455
   msg.reserve(12 + fragment.size());
1✔
456

457
   msg.push_back(static_cast<uint8_t>(type));
1✔
458
   push_be24(msg, message_length);
1✔
459
   msg.push_back(static_cast<uint8_t>(message_sequence >> 8));
1✔
460
   msg.push_back(static_cast<uint8_t>(message_sequence));
1✔
461
   push_be24(msg, fragment_offset);
1✔
462
   push_be24(msg, fragment.size());
1✔
463
   msg.insert(msg.end(), fragment.begin(), fragment.end());
1✔
464

465
   return msg;
1✔
466
}
×
467

468
// A record carrying only a handshake header: it declares `message_length` bytes
469
// but delivers none of them.
470
std::vector<uint8_t> empty_dtls_handshake_fragment(Botan::TLS::Handshake_Type type,
1✔
471
                                                   size_t message_length,
472
                                                   uint16_t message_sequence) {
473
   return unprotected_dtls_record(
1✔
474
      Botan::TLS::Record_Type::Handshake, 0, dtls_handshake_fragment(type, message_length, message_sequence, 0, {}));
2✔
475
}
476

477
/*
478
These tests exercise the DTLS core without opening real UDP sockets. The test
479
callbacks collect bytes emitted by TLS::Client/TLS::Server, and deliver() feeds
480
those bytes into the peer's received_data(). Clearing, splitting, copying, or
481
delaying those buffers simulates datagram loss, retransmission, duplicates, and
482
partial flights while still running the real DTLS record, handshake, epoch, and
483
retransmission logic.
484
*/
485
class DTLS_Core_Regression_Tests final : public Test {
1✔
486
   private:
487
      static bool split_first_dtls_record(Test::Result& result,
6✔
488
                                          const std::vector<uint8_t>& records,
489
                                          std::vector<uint8_t>& first_record,
490
                                          std::vector<uint8_t>& remaining_records) {
491
         constexpr size_t dtls_header_len = 13;
6✔
492

493
         if(!result.test_is_true("DTLS records include a complete header", records.size() >= dtls_header_len)) {
6✔
494
            return false;
495
         }
496

497
         const size_t record_len = static_cast<size_t>((static_cast<uint16_t>(records[11]) << 8) | records[12]);
6✔
498
         const size_t total_len = dtls_header_len + record_len;
6✔
499

500
         if(!result.test_is_true("DTLS records include a complete first record", records.size() >= total_len)) {
6✔
501
            return false;
502
         }
503

504
         first_record.assign(records.begin(), records.begin() + total_len);
6✔
505
         remaining_records.assign(records.begin() + total_len, records.end());
6✔
506
         return result.test_is_true("DTLS server flight has more than one record", !remaining_records.empty());
6✔
507
      }
508

509
      // Pack the handshake payloads of two DTLS records into a single record,
510
      // reusing the first record's header. RFC 6347 4.2.3 allows this for
511
      // messages "part of the same flight"; a stale duplicate is not, which is
512
      // the point of the test using it.
513
      static std::vector<uint8_t> coalesce_dtls_records(const std::vector<uint8_t>& first,
514
                                                        const std::vector<uint8_t>& second) {
515
         constexpr size_t dtls_header_len = 13;
516

517
         const size_t first_len = (static_cast<size_t>(first[11]) << 8) | first[12];
518
         const size_t second_len = (static_cast<size_t>(second[11]) << 8) | second[12];
519

520
         std::vector<uint8_t> record(first.begin(), first.begin() + dtls_header_len);
521
         record[11] = static_cast<uint8_t>((first_len + second_len) >> 8);
522
         record[12] = static_cast<uint8_t>(first_len + second_len);
523
         record.insert(record.end(), first.begin() + dtls_header_len, first.begin() + dtls_header_len + first_len);
524
         record.insert(record.end(), second.begin() + dtls_header_len, second.begin() + dtls_header_len + second_len);
525
         return record;
526
      }
527

528
      static std::vector<Botan::TLS::Handshake_Type> dtls_handshake_types(const std::vector<uint8_t>& records) {
11✔
529
         // Lightweight test-only DTLS record inspection. This deliberately
530
         // looks only at unencrypted handshake records, so labels like
531
         // "retransmitted HelloVerifyRequest" are backed by wire data without
532
         // turning the test into a second DTLS implementation.
533
         constexpr size_t dtls_header_len = 13;
11✔
534
         constexpr size_t dtls_handshake_header_len = 12;
11✔
535

536
         std::vector<Botan::TLS::Handshake_Type> types;
11✔
537

538
         size_t offset = 0;
11✔
539
         while(offset + dtls_header_len <= records.size()) {
24✔
540
            const auto record_type = static_cast<Botan::TLS::Record_Type>(records[offset]);
13✔
541
            const size_t record_len =
13✔
542
               static_cast<size_t>((static_cast<uint16_t>(records[offset + 11]) << 8) | records[offset + 12]);
13✔
543
            const size_t record_end = offset + dtls_header_len + record_len;
13✔
544

545
            if(record_end > records.size()) {
13✔
546
               break;
547
            }
548

549
            if(record_type == Botan::TLS::Record_Type::Handshake) {
13✔
550
               size_t hs_offset = offset + dtls_header_len;
551
               while(hs_offset + dtls_handshake_header_len <= record_end) {
26✔
552
                  const auto handshake_type = static_cast<Botan::TLS::Handshake_Type>(records[hs_offset]);
13✔
553
                  const size_t fragment_len = (static_cast<size_t>(records[hs_offset + 9]) << 16) |
13✔
554
                                              (static_cast<size_t>(records[hs_offset + 10]) << 8) |
13✔
555
                                              records[hs_offset + 11];
13✔
556
                  const size_t handshake_end = hs_offset + dtls_handshake_header_len + fragment_len;
13✔
557

558
                  if(handshake_end > record_end) {
13✔
559
                     break;
560
                  }
561

562
                  types.push_back(handshake_type);
13✔
563
                  hs_offset = handshake_end;
13✔
564
               }
565
            }
566

567
            offset = record_end;
568
         }
569

570
         return types;
11✔
571
      }
×
572

573
      static std::vector<Botan::TLS::Record_Type> dtls_record_types(const std::vector<uint8_t>& records) {
6✔
574
         constexpr size_t dtls_header_len = 13;
6✔
575

576
         std::vector<Botan::TLS::Record_Type> types;
6✔
577

578
         size_t offset = 0;
6✔
579
         while(offset + dtls_header_len <= records.size()) {
14✔
580
            const auto record_type = static_cast<Botan::TLS::Record_Type>(records[offset]);
8✔
581
            const size_t record_len =
8✔
582
               static_cast<size_t>((static_cast<uint16_t>(records[offset + 11]) << 8) | records[offset + 12]);
8✔
583
            const size_t record_end = offset + dtls_header_len + record_len;
8✔
584

585
            if(record_end > records.size()) {
8✔
586
               break;
587
            }
588

589
            types.push_back(record_type);
8✔
590
            offset = record_end;
8✔
591
         }
592

593
         return types;
6✔
594
      }
×
595

596
      static bool contains_dtls_handshake_type(const std::vector<uint8_t>& records,
11✔
597
                                               Botan::TLS::Handshake_Type expected) {
598
         for(auto type : dtls_handshake_types(records)) {
13✔
599
            if(type == expected) {
13✔
600
               return true;
11✔
601
            }
602
         }
11✔
603

604
         return false;
×
605
      }
606

607
      static bool contains_dtls_record_type(const std::vector<uint8_t>& records, Botan::TLS::Record_Type expected) {
6✔
608
         for(auto type : dtls_record_types(records)) {
9✔
609
            if(type == expected) {
7✔
610
               return true;
4✔
611
            }
612
         }
6✔
613

614
         return false;
2✔
615
      }
616

617
      // A complete, unfragmented DTLS handshake message: 12-byte header plus body.
618
      template <typename Predicate>
619
      static bool wait_until(Predicate predicate) {
17✔
620
         // DTLS timeouts are clock based. Poll briefly instead of sleeping for
621
         // an exact duration so slow/debug builds and timer granularity do not
622
         // make retransmission tests flaky.
623
         const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
17✔
624

625
         while(std::chrono::steady_clock::now() < deadline) {
22✔
626
            if(predicate()) {
22✔
627
               return true;
628
            }
629

630
   #if defined(BOTAN_TARGET_OS_HAS_THREADS)
631
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
5✔
632
   #endif
633
         }
634

635
         return predicate();
×
636
      }
637

638
      static void wait_for_timeout_retransmit(Test::Result& result,
17✔
639
                                              Botan::TLS::Channel& channel,
640
                                              std::vector<uint8_t>& outbound) {
641
         if(!wait_until([&] {
17✔
642
               const auto timeout = channel.next_retransmission_timeout();
22✔
643
               return timeout.has_value() && timeout->count() == 0 && channel.timeout_check() && !outbound.empty();
22✔
644
            })) {
645
            result.test_failure("DTLS retransmit was not produced");
×
646
         }
647
      }
17✔
648

649
      static Test::Result test_timeout_check_paces_retransmissions() {
1✔
650
         Test::Result result("DTLS timeout_check retransmit pacing");
1✔
651

652
         auto rng = Test::new_shared_rng("dtls-core-timeout-pacing");
1✔
653
         auto assoc = make_association(result, rng);
1✔
654
         auto& client = *assoc->client;
1✔
655
         auto& server = *assoc->server;
1✔
656
         auto& c2s = assoc->c2s;
1✔
657
         auto& s2c = assoc->s2c;
1✔
658

659
         deliver(result, "client hello 1", c2s, server);
1✔
660
         if(!result.test_is_true("hello verify request was produced", !s2c.empty())) {
1✔
661
            return result;
662
         }
663
         s2c.clear();  // simulate losing the HelloVerifyRequest
1✔
664

665
         wait_for_timeout_retransmit(result, client, c2s);
1✔
666
         const auto retransmit_size = c2s.size();
1✔
667

668
         result.test_is_true("next retransmission timeout remains available",
3✔
669
                             client.next_retransmission_timeout().has_value());
1✔
670
         result.test_is_false("immediate second timeout is suppressed", client.timeout_check());
1✔
671
         result.test_sz_eq("no immediate second retransmit", c2s.size(), retransmit_size);
1✔
672

673
         return result;
1✔
674
      }
2✔
675

676
      static Test::Result test_retransmitted_epoch_transition_flight_includes_ccs() {
1✔
677
         Test::Result result("DTLS retransmitted epoch-1 flight includes CCS");
1✔
678

679
         auto rng = Test::new_shared_rng("dtls-core-retransmitted-ccs");
1✔
680
         auto assoc = make_association(result, rng);
1✔
681
         auto& client = *assoc->client;
1✔
682
         auto& server = *assoc->server;
1✔
683
         auto& c2s = assoc->c2s;
1✔
684
         auto& s2c = assoc->s2c;
1✔
685

686
         deliver(result, "client hello 1", c2s, server);
1✔
687
         deliver(result, "hello verify request", s2c, client);
1✔
688
         deliver(result, "client hello 2", c2s, server);
1✔
689
         deliver(result, "server handshake flight", s2c, client);
1✔
690
         deliver(result, "client final flight", c2s, server);
1✔
691

692
         result.test_is_true("server became active", server.is_active());
1✔
693
         if(!result.test_is_true("server final flight was produced", !s2c.empty())) {
1✔
694
            return result;
695
         }
696
         s2c.clear();  // simulate losing the server final flight
1✔
697

698
         wait_for_timeout_retransmit(result, client, c2s);
1✔
699
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
700

701
         result.test_is_true("retransmitted final flight includes CCS",
1✔
702
                             contains_dtls_record_type(s2c, Botan::TLS::Record_Type::ChangeCipherSpec));
1✔
703
         result.test_is_true("retransmitted final flight includes Finished record",
1✔
704
                             contains_dtls_record_type(s2c, Botan::TLS::Record_Type::Handshake));
1✔
705

706
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
707
         result.test_is_true("client became active", client.is_active());
1✔
708

709
         return result;
1✔
710
      }
2✔
711

712
      static Test::Result test_lost_hello_verify_request_retransmits() {
1✔
713
         Test::Result result("DTLS lost HelloVerifyRequest retransmits");
1✔
714

715
         auto rng = Test::new_shared_rng("dtls-core-lost-hvr");
1✔
716
         auto assoc = make_association(result, rng);
1✔
717
         auto& client = *assoc->client;
1✔
718
         auto& server = *assoc->server;
1✔
719
         auto& c2s = assoc->c2s;
1✔
720
         auto& s2c = assoc->s2c;
1✔
721

722
         deliver(result, "client hello 1", c2s, server);
1✔
723
         if(!result.test_is_true("hello verify request was produced", !s2c.empty())) {
1✔
724
            return result;
725
         }
726
         result.test_is_true("server response is HelloVerifyRequest",
1✔
727
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
728
         result.test_is_false("server does not arm a HelloVerifyRequest retransmission timer",
3✔
729
                              server.next_retransmission_timeout().has_value());
1✔
730
         s2c.clear();  // simulate losing the HelloVerifyRequest
1✔
731

732
         wait_for_timeout_retransmit(result, client, c2s);
1✔
733
         deliver(result, "client hello 1 retransmit", c2s, server);
1✔
734
         if(!result.test_is_true("server retransmitted hello verify request", !s2c.empty())) {
1✔
735
            return result;
736
         }
737
         result.test_is_true("server retransmission is HelloVerifyRequest",
1✔
738
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
739

740
         deliver(result, "retransmitted hello verify request", s2c, client);
1✔
741
         deliver(result, "client hello 2", c2s, server);
1✔
742
         deliver(result, "server handshake flight", s2c, client);
1✔
743
         deliver(result, "client final flight", c2s, server);
1✔
744
         deliver(result, "server final flight", s2c, client);
1✔
745

746
         result.test_is_true("client became active", client.is_active());
1✔
747
         result.test_is_true("server became active", server.is_active());
1✔
748

749
         return result;
1✔
750
      }
2✔
751

752
      static Test::Result test_duplicate_hello_verify_request_is_tolerated() {
1✔
753
         Test::Result result("DTLS duplicate HelloVerifyRequest is tolerated");
1✔
754

755
         auto rng = Test::new_shared_rng("dtls-core-duplicate-hvr");
1✔
756
         auto assoc = make_association(result, rng);
1✔
757
         auto& client = *assoc->client;
1✔
758
         auto& server = *assoc->server;
1✔
759
         auto& c2s = assoc->c2s;
1✔
760
         auto& s2c = assoc->s2c;
1✔
761

762
         deliver(result, "client hello 1", c2s, server);
1✔
763

764
         const std::vector<uint8_t> hello_verify = s2c;
1✔
765
         if(!result.test_is_true("hello verify request was produced", !hello_verify.empty())) {
1✔
766
            return result;
767
         }
768
         result.test_is_true(
1✔
769
            "server response is HelloVerifyRequest",
770
            contains_dtls_handshake_type(hello_verify, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
771
         s2c.clear();
1✔
772

773
         deliver_copy(result, "hello verify request", hello_verify, client);
1✔
774
         if(!result.test_is_true("client hello 2 was produced", !c2s.empty())) {
1✔
775
            return result;
776
         }
777
         std::vector<uint8_t> client_hello_2;
1✔
778
         std::swap(client_hello_2, c2s);
1✔
779

780
         deliver_copy(result, "duplicate hello verify request", hello_verify, client);
1✔
781
         result.test_is_true("duplicate hello verify request is ignored", c2s.empty());
1✔
782

783
         deliver_copy(result, "client hello 2 after duplicate hvr", client_hello_2, server);
1✔
784
         deliver(result, "server handshake flight", s2c, client);
1✔
785
         deliver(result, "client final flight", c2s, server);
1✔
786
         deliver(result, "server final flight", s2c, client);
1✔
787

788
         result.test_is_true("client became active", client.is_active());
1✔
789
         result.test_is_true("server became active", server.is_active());
1✔
790

791
         return result;
1✔
792
      }
3✔
793

794
      static Test::Result test_partial_server_flight_does_not_advance_client() {
1✔
795
         Test::Result result("DTLS partial server flight waits for ServerHelloDone");
1✔
796

797
         auto rng = Test::new_shared_rng("dtls-core-partial-server-flight");
1✔
798
         auto assoc = make_association(result, rng);
1✔
799
         auto& client = *assoc->client;
1✔
800
         auto& server = *assoc->server;
1✔
801
         auto& c2s = assoc->c2s;
1✔
802
         auto& s2c = assoc->s2c;
1✔
803

804
         deliver(result, "client hello 1", c2s, server);
1✔
805
         deliver(result, "hello verify request", s2c, client);
1✔
806
         deliver(result, "client hello 2", c2s, server);
1✔
807

808
         std::vector<uint8_t> first_record;
1✔
809
         std::vector<uint8_t> remaining_records;
1✔
810
         if(!split_first_dtls_record(result, s2c, first_record, remaining_records)) {
1✔
811
            return result;
812
         }
813
         s2c.clear();
1✔
814

815
         result.test_is_true("partial server flight starts with ServerHello",
1✔
816
                             contains_dtls_handshake_type(first_record, Botan::TLS::Handshake_Type::ServerHello));
1✔
817
         result.test_is_true(
1✔
818
            "partial server flight remainder has ServerHelloDone",
819
            contains_dtls_handshake_type(remaining_records, Botan::TLS::Handshake_Type::ServerHelloDone));
1✔
820

821
         deliver_copy(result, "partial server flight first record", first_record, client);
1✔
822
         result.test_is_false("client waits for the rest of the server flight", client.is_active());
1✔
823
         result.test_is_true("client does not send final flight early", c2s.empty());
1✔
824

825
         deliver_copy(result, "partial server flight remaining records", remaining_records, client);
1✔
826
         if(!result.test_is_true("client final flight was produced after ServerHelloDone", !c2s.empty())) {
1✔
827
            return result;
828
         }
829

830
         deliver(result, "client final flight", c2s, server);
1✔
831
         deliver(result, "server final flight", s2c, client);
1✔
832

833
         result.test_is_true("client became active", client.is_active());
1✔
834
         result.test_is_true("server became active", server.is_active());
1✔
835

836
         return result;
1✔
837
      }
3✔
838

839
      static Test::Result test_lost_server_flight_retransmits() {
1✔
840
         Test::Result result("DTLS lost server flight retransmits");
1✔
841

842
         auto rng = Test::new_shared_rng("dtls-core-lost-server-flight");
1✔
843
         auto assoc = make_association(result, rng);
1✔
844
         auto& client = *assoc->client;
1✔
845
         auto& server = *assoc->server;
1✔
846
         auto& c2s = assoc->c2s;
1✔
847
         auto& s2c = assoc->s2c;
1✔
848

849
         deliver(result, "client hello 1", c2s, server);
1✔
850
         deliver(result, "hello verify request", s2c, client);
1✔
851
         deliver(result, "client hello 2", c2s, server);
1✔
852

853
         if(!result.test_is_true("server handshake flight was produced", !s2c.empty())) {
1✔
854
            return result;
855
         }
856
         s2c.clear();  // simulate losing the server flight
1✔
857

858
         wait_for_timeout_retransmit(result, client, c2s);
1✔
859
         deliver(result, "client hello 2 retransmit", c2s, server);
1✔
860
         if(!result.test_is_true("server retransmitted handshake flight", !s2c.empty())) {
1✔
861
            return result;
862
         }
863

864
         deliver(result, "retransmitted server handshake flight", s2c, client);
1✔
865
         deliver(result, "client final flight", c2s, server);
1✔
866
         deliver(result, "server final flight", s2c, client);
1✔
867

868
         result.test_is_true("client became active", client.is_active());
1✔
869
         result.test_is_true("server became active", server.is_active());
1✔
870

871
         return result;
1✔
872
      }
2✔
873

874
      // RFC 5246 7.4.1.1 says a HelloRequest is ignored by a client that is
875
      // already negotiating. Answering with a fatal alert instead killed a
876
      // server whose own HelloRequest merely crossed with our renegotiation.
877
      static Test::Result test_hello_request_during_handshake_is_ignored() {
1✔
878
         Test::Result result("DTLS HelloRequest crossing a client renegotiation");
1✔
879

880
         auto rng = Test::new_shared_rng("dtls-core-crossed-hello-request");
1✔
881
         auto assoc = make_association(result, rng);
1✔
882
         auto& client = *assoc->client;
1✔
883
         auto& server = *assoc->server;
1✔
884
         auto& c2s = assoc->c2s;
1✔
885
         auto& s2c = assoc->s2c;
1✔
886

887
         if(!complete_dtls_handshake(result, *assoc)) {
1✔
888
            return result;
889
         }
890

891
         // Both sides ask to renegotiate at once. Hold the client's ClientHello
892
         // back so the two requests genuinely cross.
893
         result.test_no_throw("client starts renegotiation", [&] { client.renegotiate(true); });
2✔
894
         c2s.clear();
1✔
895

896
         result.test_no_throw("server sends its own HelloRequest", [&] { server.renegotiate(false); });
2✔
897
         result.test_is_true("server emitted a HelloRequest", !s2c.empty());
1✔
898

899
         result.test_no_throw("crossed HelloRequest is ignored", [&] {
1✔
900
            std::vector<uint8_t> in;
1✔
901
            std::swap(s2c, in);
1✔
902
            client.received_data(in.data(), in.size());
1✔
903
         });
1✔
904
         result.test_is_true("client produced no response to HelloRequest", c2s.empty());
1✔
905
         result.test_is_true("client remains active", client.is_active());
1✔
906
         result.test_is_false("client did not close", client.is_closed());
1✔
907

908
         return result;
1✔
909
      }
2✔
910

911
      // RFC 6347 4.2.1: "Note to implementors: This may result in clients
912
      // receiving multiple HelloVerifyRequest messages with different cookies.
913
      // Clients SHOULD handle this by sending a new ClientHello with a cookie
914
      // in response to the new HelloVerifyRequest."
915
      static Test::Result test_rotated_cookie_secret_produces_fresh_hello_verify_request() {
1✔
916
         Test::Result result("DTLS fresh HelloVerifyRequest after cookie secret rotation");
1✔
917

918
         auto rng = Test::new_shared_rng("dtls-core-rotated-cookie-secret");
1✔
919
         auto assoc = make_association(result, rng);
1✔
920
         auto& client = *assoc->client;
1✔
921
         auto& server = *assoc->server;
1✔
922
         auto& c2s = assoc->c2s;
1✔
923
         auto& s2c = assoc->s2c;
1✔
924

925
         deliver(result, "client hello 1", c2s, server);
1✔
926
         result.test_is_true("server sent HelloVerifyRequest",
1✔
927
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
928
         deliver(result, "hello verify request", s2c, client);
1✔
929

930
         // The cookie the client is about to present is now signed under a
931
         // secret the server no longer uses.
932
         assoc->creds->rotate_cookie_secret();
1✔
933

934
         deliver(result, "client hello 2 carrying the stale cookie", c2s, server);
1✔
935
         result.test_is_true("server re-challenges with a fresh HelloVerifyRequest",
1✔
936
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
937

938
         deliver(result, "fresh hello verify request", s2c, client);
1✔
939
         if(!result.test_is_true("client answers the fresh HelloVerifyRequest", !c2s.empty())) {
1✔
940
            return result;
941
         }
942

943
         deliver(result, "client hello 3 carrying the rotated cookie", c2s, server);
1✔
944
         deliver(result, "server handshake flight", s2c, client);
1✔
945
         deliver(result, "client final flight", c2s, server);
1✔
946
         deliver(result, "server final flight", s2c, client);
1✔
947

948
         result.test_is_true("client became active", client.is_active());
1✔
949
         result.test_is_true("server became active", server.is_active());
1✔
950

951
         return result;
1✔
952
      }
2✔
953

954
      // Poll the retransmission timer forward until the handshake gives up.
955
      static bool run_out_the_clock(DTLS_Test_Callbacks& cb, Botan::TLS::Channel& channel) {
2✔
956
         for(int i = 0; i < 200; ++i) {
6✔
957
            cb.advance_clock_ms(50);
6✔
958
            try {
6✔
959
               channel.timeout_check();
6✔
960
            } catch(const Botan::TLS::TLS_Exception&) {
2✔
961
               return true;
2✔
962
            }
2✔
963
         }
964
         return false;
965
      }
966

967
      // Reaching the retransmission limit left the expired timer and its IO
968
      // installed, so every later timeout check threw again from unchanged
969
      // state and renegotiate() could never start another attempt.
970
      static Test::Result test_timed_out_initial_handshake_closes_the_channel() {
1✔
971
         Test::Result result("DTLS unanswered handshake closes the channel");
1✔
972

973
         auto rng = Test::new_shared_rng("dtls-core-timeout-abandon");
1✔
974
         auto assoc = make_association(result, rng, dtls_policy_with_max_retransmissions(2));
2✔
975
         auto& client = *assoc->client;
1✔
976

977
         // Nothing is ever delivered to the server, so the client retransmits
978
         // until its budget is spent.
979
         result.test_is_true("initial handshake gave up", run_out_the_clock(*assoc->client_cb, client));
1✔
980
         result.test_is_true("channel is closed", client.is_closed());
1✔
981
         result.test_no_throw("later polls are quiet", [&] { client.timeout_check(); });
2✔
982

983
         return result;
2✔
984
      }
2✔
985

986
      // A renegotiation that dies before any ChangeCipherSpec leaves the
987
      // established association intact, and another attempt can be made.
988
      static Test::Result test_timed_out_renegotiation_keeps_the_association() {
1✔
989
         Test::Result result("DTLS timed out renegotiation keeps the association");
1✔
990

991
         auto rng = Test::new_shared_rng("dtls-core-timeout-abandon-reneg");
1✔
992
         auto assoc = make_association(result, rng, dtls_policy_with_max_retransmissions(2));
2✔
993
         auto& client = *assoc->client;
1✔
994
         auto& c2s = assoc->c2s;
1✔
995

996
         if(!complete_dtls_handshake(result, *assoc)) {
1✔
997
            return result;
998
         }
999

1000
         client.renegotiate(true);
1✔
1001
         c2s.clear();  // the renegotiation ClientHello never arrives
1✔
1002

1003
         result.test_is_true("renegotiation gave up", run_out_the_clock(*assoc->client_cb, client));
1✔
1004
         result.test_is_true("association survived", client.is_active());
1✔
1005
         result.test_is_false("channel not closed", client.is_closed());
1✔
1006

1007
         result.test_no_throw("another renegotiation can start", [&] { client.renegotiate(true); });
2✔
1008
         result.test_is_true("it emitted a ClientHello", !c2s.empty());
1✔
1009

1010
         return result;
1✔
1011
      }
2✔
1012

1013
      static Test::Result test_duplicate_server_flight_defers_to_timer() {
1✔
1014
         Test::Result result("DTLS duplicate server flight defers replay to timer");
1✔
1015

1016
         auto rng = Test::new_shared_rng("dtls-core-retransmitted-server-flight");
1✔
1017
         auto assoc = make_association(result, rng);
1✔
1018
         auto& client = *assoc->client;
1✔
1019
         auto& server = *assoc->server;
1✔
1020
         auto& c2s = assoc->c2s;
1✔
1021
         auto& s2c = assoc->s2c;
1✔
1022

1023
         deliver(result, "client hello 1", c2s, server);
1✔
1024
         deliver(result, "hello verify request", s2c, client);
1✔
1025
         deliver(result, "client hello 2", c2s, server);
1✔
1026
         deliver(result, "server handshake flight", s2c, client);
1✔
1027
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1028
            return result;
1029
         }
1030
         c2s.clear();  // simulate losing the client's response flight
1✔
1031

1032
         wait_for_timeout_retransmit(result, server, s2c);
1✔
1033

1034
         std::vector<uint8_t> first_server_record;
1✔
1035
         std::vector<uint8_t> remaining_server_records;
1✔
1036
         if(!split_first_dtls_record(result, s2c, first_server_record, remaining_server_records)) {
1✔
1037
            return result;
1038
         }
1039
         s2c.clear();
1✔
1040

1041
         result.test_is_true(
1✔
1042
            "first retransmitted server record is ServerHello",
1043
            contains_dtls_handshake_type(first_server_record, Botan::TLS::Handshake_Type::ServerHello));
1✔
1044
         result.test_is_true(
1✔
1045
            "remaining retransmitted records include ServerHelloDone",
1046
            contains_dtls_handshake_type(remaining_server_records, Botan::TLS::Handshake_Type::ServerHelloDone));
1✔
1047

1048
         deliver_copy(result, "retransmitted ServerHello record", first_server_record, client);
1✔
1049
         result.test_is_true("non-terminal record does not replay client flight", c2s.empty());
1✔
1050

1051
         deliver_copy(result, "rest of retransmitted server flight", remaining_server_records, client);
1✔
1052
         result.test_is_true("duplicated server flight does not immediately replay client flight", c2s.empty());
1✔
1053

1054
         // A pending handshake deliberately does not take RFC 6347 4.2.4's
1055
         // "read retransmit" exit; see process_previous_handshake_fragment for
1056
         // why reordering makes that unsafe. The flight is still recoverable
1057
         // from the local timer.
1058
         wait_for_timeout_retransmit(result, client, c2s);
1✔
1059
         result.test_is_true("client timer replays the lost response flight", !c2s.empty());
1✔
1060

1061
         return result;
1✔
1062
      }
3✔
1063

1064
      static Test::Result test_lost_server_final_flight_retransmits() {
1✔
1065
         Test::Result result("DTLS lost server final flight retransmits");
1✔
1066

1067
         auto rng = Test::new_shared_rng("dtls-core-lost-server-final");
1✔
1068
         auto assoc = make_association(result, rng);
1✔
1069
         auto& client = *assoc->client;
1✔
1070
         auto& server = *assoc->server;
1✔
1071
         auto& c2s = assoc->c2s;
1✔
1072
         auto& s2c = assoc->s2c;
1✔
1073

1074
         deliver(result, "client hello 1", c2s, server);
1✔
1075
         deliver(result, "hello verify request", s2c, client);
1✔
1076
         deliver(result, "client hello 2", c2s, server);
1✔
1077
         deliver(result, "server handshake flight", s2c, client);
1✔
1078
         deliver(result, "client final flight", c2s, server);
1✔
1079

1080
         result.test_is_true("server became active", server.is_active());
1✔
1081
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1082
         if(!result.test_is_true("server final flight was produced", !s2c.empty())) {
1✔
1083
            return result;
1084
         }
1085

1086
         std::vector<uint8_t> delayed_ccs;
1✔
1087
         std::vector<uint8_t> delayed_finished;
1✔
1088
         if(!split_first_dtls_record(result, s2c, delayed_ccs, delayed_finished)) {
1✔
1089
            return result;
1090
         }
1091
         s2c.clear();  // simulate delaying the original server final flight
1✔
1092

1093
         result.test_is_false("finished server has no proactive retransmission timer",
3✔
1094
                              server.next_retransmission_timeout().has_value());
1✔
1095
         wait_for_timeout_retransmit(result, client, c2s);
1✔
1096
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
1097
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
1098

1099
         result.test_is_true("client became active", client.is_active());
1✔
1100
         result.test_is_false("active client has no retransmission timer",
3✔
1101
                              client.next_retransmission_timeout().has_value());
1✔
1102

1103
         // The original Finished may arrive after the retransmitted flight
1104
         // activated the client. It has an unseen DTLS record sequence number
1105
         // but an old handshake message_seq and must be discarded silently.
1106
         deliver(result, "delayed original server Finished", delayed_finished, client);
1✔
1107
         result.test_is_true("client remains active after delayed Finished", client.is_active());
1✔
1108
         result.test_is_true("client does not respond to delayed Finished", c2s.empty());
1✔
1109

1110
         return result;
1✔
1111
      }
3✔
1112

1113
      static Test::Result test_stale_client_hello_does_not_replace_active_handshake() {
1✔
1114
         Test::Result result("DTLS stale ClientHello after server activation");
1✔
1115

1116
         auto rng = Test::new_shared_rng("dtls-core-stale-client-hello");
1✔
1117
         auto assoc = make_association(result, rng);
1✔
1118
         auto& client = *assoc->client;
1✔
1119
         auto& server = *assoc->server;
1✔
1120
         auto& c2s = assoc->c2s;
1✔
1121
         auto& s2c = assoc->s2c;
1✔
1122

1123
         deliver(result, "client hello 1", c2s, server);
1✔
1124
         deliver(result, "hello verify request", s2c, client);
1✔
1125
         const auto cookie_client_hello = c2s;
1✔
1126
         deliver(result, "client hello 2", c2s, server);
1✔
1127
         deliver(result, "server handshake flight", s2c, client);
1✔
1128
         deliver(result, "client final flight", c2s, server);
1✔
1129

1130
         result.test_is_true("server became active", server.is_active());
1✔
1131
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1132
         s2c.clear();  // simulate losing the server final flight
1✔
1133

1134
         // A delayed retransmission of the cookie-bearing ClientHello has
1135
         // message_seq 1. It belongs to the completed handshake, whereas a
1136
         // genuinely new association starts at message_seq 0.
1137
         deliver_copy(result, "stale client hello 2", cookie_client_hello, server);
1✔
1138
         result.test_is_true("server remains active", server.is_active());
1✔
1139
         result.test_is_true("stale ClientHello replays the final server flight", !s2c.empty());
1✔
1140
         s2c.clear();
1✔
1141

1142
         wait_for_timeout_retransmit(result, client, c2s);
1✔
1143
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
1144
         result.test_is_true("client final flight still receives a response", !s2c.empty());
1✔
1145

1146
         return result;
1✔
1147
      }
2✔
1148

1149
      static Test::Result test_epoch0_client_hello_retransmit_while_restart_pending() {
1✔
1150
         Test::Result result("DTLS epoch-zero ClientHello retransmit while restart pending");
1✔
1151

1152
         auto rng = Test::new_shared_rng("dtls-core-pending-epoch0-restart");
1✔
1153
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1154
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1155
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1156
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_Noop>();
1✔
1157

1158
         std::vector<uint8_t> s2c;
1✔
1159
         std::vector<uint8_t> server_recv;
1✔
1160
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1161
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1162

1163
         std::vector<uint8_t> c1_c2s;
1✔
1164
         std::vector<uint8_t> client1_recv;
1✔
1165
         auto client1_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c1_c2s, client1_recv);
1✔
1166
         Botan::TLS::Client client1(client1_callbacks,
1✔
1167
                                    client_sessions,
1168
                                    creds,
1169
                                    policy,
1170
                                    rng,
1171
                                    Botan::TLS::Server_Information("localhost"),
1✔
1172
                                    Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1173

1174
         deliver(result, "client 1 hello", c1_c2s, server);
1✔
1175
         deliver(result, "client 1 hello verify request", s2c, client1);
1✔
1176
         deliver(result, "client 1 cookie-bearing hello", c1_c2s, server);
1✔
1177
         deliver(result, "client 1 server handshake flight", s2c, client1);
1✔
1178
         deliver(result, "client 1 final flight", c1_c2s, server);
1✔
1179
         deliver(result, "client 1 server final flight", s2c, client1);
1✔
1180

1181
         result.test_is_true("first client became active", client1.is_active());
1✔
1182
         result.test_is_true("server became active for first client", server.is_active());
1✔
1183
         result.test_sz_eq("server established one session", server_callbacks->sessions_established(), 1);
1✔
1184

1185
         std::vector<uint8_t> c2_c2s;
1✔
1186
         std::vector<uint8_t> client2_recv;
1✔
1187
         auto client2_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2_c2s, client2_recv);
1✔
1188
         Botan::TLS::Client client2(client2_callbacks,
1✔
1189
                                    client_sessions,
1190
                                    creds,
1191
                                    policy,
1192
                                    rng,
1193
                                    Botan::TLS::Server_Information("localhost"),
1✔
1194
                                    Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1195

1196
         const auto epoch0_client_hello = c2_c2s;
1✔
1197
         deliver(result, "client 2 epoch-zero hello", c2_c2s, server);
1✔
1198
         const auto hello_verify_request = s2c;
1✔
1199
         const auto cookie_secret_requests = creds->dtls_cookie_secret_requests();
1✔
1200
         s2c.clear();
1✔
1201

1202
         // HelloVerifyRequest is not retained as retransmittable flight data.
1203
         // A repeated initial ClientHello recreates the cookie response.
1204
         deliver_copy(result, "retransmitted client 2 epoch-zero hello", epoch0_client_hello, server);
1✔
1205
         result.test_is_true("pending restart replays HelloVerifyRequest",
1✔
1206
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
1207
         result.test_sz_eq("retransmitted ClientHello does not establish another session",
1✔
1208
                           server_callbacks->sessions_established(),
1209
                           1);
1210
         result.test_sz_eq("retransmitted ClientHello recreates the cookie response",
1✔
1211
                           creds->dtls_cookie_secret_requests(),
1212
                           cookie_secret_requests + 1);
1213
         result.test_is_true("previous association remains active during restart", server.is_active());
1✔
1214
         s2c.clear();
1✔
1215

1216
         deliver_copy(result, "client 2 hello verify request", hello_verify_request, client2);
1✔
1217
         deliver(result, "client 2 cookie-bearing hello", c2_c2s, server);
1✔
1218
         deliver(result, "client 2 server handshake flight", s2c, client2);
1✔
1219
         deliver(result, "client 2 final flight", c2_c2s, server);
1✔
1220
         deliver(result, "client 2 server final flight", s2c, client2);
1✔
1221

1222
         result.test_is_true("second client became active", client2.is_active());
1✔
1223
         result.test_is_true("server became active for second client", server.is_active());
1✔
1224
         result.test_sz_eq("server established the replacement session", server_callbacks->sessions_established(), 2);
1✔
1225

1226
         return result;
1✔
1227
      }
10✔
1228

1229
      static Test::Result test_retransmitted_final_flight_then_application_data(bool expect_resumption) {
2✔
1230
         Test::Result result(expect_resumption ? "DTLS resumed handshake accepts app data"
2✔
1231
                                               : "DTLS final flight retransmit before app data");
3✔
1232

1233
         auto rng = Test::new_shared_rng(expect_resumption ? "dtls-core-resumption" : "dtls-core-full");
3✔
1234
         auto policy = std::make_shared<DTLS_PSK_Policy>();
2✔
1235
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
2✔
1236
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
2✔
1237
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
2✔
1238

1239
         if(expect_resumption) {
2✔
1240
            run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
5✔
1241
         }
1242

1243
         return run_handshake(result, rng, policy, creds, client_sessions, server_sessions, expect_resumption);
12✔
1244
      }
10✔
1245

1246
      static Test::Result test_resumed_client_final_flight_retransmits_after_activation() {
1✔
1247
         Test::Result result("DTLS resumed client final flight retransmits after activation");
1✔
1248

1249
         auto rng = Test::new_shared_rng("dtls-core-resumed-client-active");
1✔
1250
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1251
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1252
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1253
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1254

1255
         run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
4✔
1256

1257
         std::vector<uint8_t> c2s;
1✔
1258
         std::vector<uint8_t> s2c;
1✔
1259
         std::vector<uint8_t> client_recv;
1✔
1260
         std::vector<uint8_t> server_recv;
1✔
1261

1262
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1263
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
1✔
1264

1265
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1266
         Botan::TLS::Client client(client_callbacks,
1✔
1267
                                   client_sessions,
1268
                                   creds,
1269
                                   policy,
1270
                                   rng,
1271
                                   Botan::TLS::Server_Information("localhost"),
1✔
1272
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1273

1274
         deliver(result, "resumed client hello 1", c2s, server);
1✔
1275
         deliver(result, "resumed hello verify request", s2c, client);
1✔
1276
         deliver(result, "resumed client hello 2", c2s, server);
1✔
1277
         deliver(result, "resumed server handshake flight", s2c, client);
1✔
1278

1279
         result.test_is_true("client is active after resumed server flight", client.is_active());
1✔
1280
         result.test_is_false("server is still waiting for client final flight", server.is_active());
1✔
1281
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1282
            return result;
1283
         }
1284

1285
         std::vector<uint8_t> discarded_ccs;
1✔
1286
         std::vector<uint8_t> delayed_finished;
1✔
1287
         if(!split_first_dtls_record(result, c2s, discarded_ccs, delayed_finished)) {
1✔
1288
            return result;
1289
         }
1290
         c2s.clear();  // simulate delaying the original client final flight
1✔
1291

1292
         result.test_is_false("finished client has no proactive retransmission timer",
3✔
1293
                              client.next_retransmission_timeout().has_value());
1✔
1294
         wait_for_timeout_retransmit(result, server, s2c);
1✔
1295
         deliver(result, "retransmitted resumed server flight", s2c, client);
1✔
1296
         deliver(result, "retransmitted resumed client final flight", c2s, server);
1✔
1297

1298
         result.test_is_true("server became active from retransmitted client final flight", server.is_active());
1✔
1299

1300
         // As in the full handshake, a delayed copy of the original terminal
1301
         // Finished must not turn normal DTLS reordering into a fatal alert.
1302
         deliver(result, "delayed original client Finished", delayed_finished, server);
1✔
1303
         result.test_is_true("server remains active after delayed Finished", server.is_active());
1✔
1304
         result.test_is_true("server does not respond to delayed Finished", s2c.empty());
1✔
1305

1306
         const std::vector<uint8_t> app_data = {0xB0, 0x7A, 0x11};
1✔
1307
         result.test_no_throw("server sends application data after completing resumed handshake",
1✔
1308
                              [&] { server.send(app_data); });
2✔
1309
         deliver(result, "server application data", s2c, client);
1✔
1310
         result.test_bin_eq("client received server application data", client_recv, app_data);
1✔
1311

1312
         return result;
1✔
1313
      }
11✔
1314

1315
      static Test::Result test_reordered_retransmitted_final_flight() {
1✔
1316
         Test::Result result("DTLS reordered retransmitted final flight");
1✔
1317

1318
         auto rng = Test::new_shared_rng("dtls-core-reordered-retransmitted-final");
1✔
1319
         auto assoc = make_association(result, rng);
1✔
1320
         auto& client = *assoc->client;
1✔
1321
         auto& server = *assoc->server;
1✔
1322
         auto& c2s = assoc->c2s;
1✔
1323
         auto& s2c = assoc->s2c;
1✔
1324

1325
         deliver(result, "client hello 1", c2s, server);
1✔
1326
         deliver(result, "hello verify request", s2c, client);
1✔
1327
         deliver(result, "client hello 2", c2s, server);
1✔
1328
         deliver(result, "server handshake flight", s2c, client);
1✔
1329
         deliver(result, "client final flight", c2s, server);
1✔
1330

1331
         result.test_is_true("server became active", server.is_active());
1✔
1332
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1333
         s2c.clear();  // simulate losing the server's last flight
1✔
1334

1335
         wait_for_timeout_retransmit(result, client, c2s);
1✔
1336

1337
         std::vector<uint8_t> client_key_exchange;
1✔
1338
         std::vector<uint8_t> ccs_and_finished;
1✔
1339
         if(!split_first_dtls_record(result, c2s, client_key_exchange, ccs_and_finished)) {
1✔
1340
            return result;
1341
         }
1342

1343
         std::vector<uint8_t> ccs;
1✔
1344
         std::vector<uint8_t> finished;
1✔
1345
         if(!split_first_dtls_record(result, ccs_and_finished, ccs, finished)) {
1✔
1346
            return result;
1347
         }
1348
         c2s.clear();
1✔
1349

1350
         result.test_is_true(
1✔
1351
            "first retransmitted record is ClientKeyExchange",
1352
            contains_dtls_handshake_type(client_key_exchange, Botan::TLS::Handshake_Type::ClientKeyExchange));
1✔
1353
         result.test_is_true("second retransmitted record is CCS",
1✔
1354
                             contains_dtls_record_type(ccs, Botan::TLS::Record_Type::ChangeCipherSpec));
1✔
1355
         result.test_is_true("third retransmitted record is Finished",
1✔
1356
                             contains_dtls_record_type(finished, Botan::TLS::Record_Type::Handshake));
1✔
1357

1358
         deliver_copy(result, "retransmitted ClientKeyExchange", client_key_exchange, server);
1✔
1359
         result.test_is_true("non-terminal retransmitted record produces no response", s2c.empty());
1✔
1360

1361
         deliver_copy(result, "retransmitted Finished before CCS", finished, server);
1✔
1362
         result.test_is_true("partial retransmitted flight produces no response", s2c.empty());
1✔
1363

1364
         deliver_copy(result, "retransmitted CCS after Finished", ccs, server);
1✔
1365
         result.test_is_true("complete reordered flight retransmits server flight", !s2c.empty());
1✔
1366

1367
         return result;
1✔
1368
      }
5✔
1369

1370
      static Test::Result test_renegotiation(bool server_initiated) {
2✔
1371
         Test::Result result(server_initiated ? "DTLS server-initiated renegotiation"
2✔
1372
                                              : "DTLS client-initiated renegotiation");
3✔
1373

1374
         auto rng = Test::new_shared_rng(server_initiated ? "dtls-core-server-renegotiation"
2✔
1375
                                                          : "dtls-core-client-renegotiation");
3✔
1376
         auto assoc = make_association(result, rng);
2✔
1377
         auto& client = *assoc->client;
2✔
1378
         auto& server = *assoc->server;
2✔
1379
         auto& c2s = assoc->c2s;
2✔
1380
         auto& s2c = assoc->s2c;
2✔
1381

1382
         complete_dtls_handshake(result, *assoc);
2✔
1383

1384
         if(server_initiated) {
2✔
1385
            result.test_no_throw("server requests renegotiation", [&] { server.renegotiate(true); });
2✔
1386
            deliver(result, "hello request", s2c, client);
2✔
1387
         } else {
1388
            result.test_no_throw("client requests renegotiation", [&] { client.renegotiate(true); });
2✔
1389
         }
1390

1391
         // A renegotiation ClientHello is already protected under the active
1392
         // epoch. Replaying it must not infer a preceding CCS merely because
1393
         // the cached handshake message has a non-zero epoch.
1394
         c2s.clear();  // simulate losing the renegotiation ClientHello
2✔
1395
         wait_for_timeout_retransmit(result, client, c2s);
2✔
1396
         result.test_is_false("retransmitted renegotiation ClientHello does not include CCS",
2✔
1397
                              contains_dtls_record_type(c2s, Botan::TLS::Record_Type::ChangeCipherSpec));
2✔
1398
         deliver(result, "renegotiation client hello", c2s, server);
2✔
1399
         deliver(result, "renegotiation server handshake flight", s2c, client);
2✔
1400
         deliver(result, "renegotiation client final flight", c2s, server);
2✔
1401
         deliver(result, "renegotiation server final flight", s2c, client);
2✔
1402

1403
         result.test_is_true("client remains active after renegotiation", client.is_active());
2✔
1404
         result.test_is_true("server remains active after renegotiation", server.is_active());
2✔
1405
         result.test_is_false("finished client has no proactive retransmission timer",
6✔
1406
                              client.next_retransmission_timeout().has_value());
2✔
1407
         result.test_is_false("finished server has no proactive retransmission timer",
6✔
1408
                              server.next_retransmission_timeout().has_value());
2✔
1409

1410
         return result;
4✔
1411
      }
4✔
1412

1413
      static Test::Result test_empty_old_handshake_fragment_does_not_retransmit() {
1✔
1414
         Test::Result result("DTLS empty old handshake fragment does not retransmit");
1✔
1415

1416
         auto rng = Test::new_shared_rng("dtls-core-empty-old-fragment");
1✔
1417
         auto assoc = make_association(result, rng);
1✔
1418
         auto& client = *assoc->client;
1✔
1419
         auto& server = *assoc->server;
1✔
1420
         auto& s2c = assoc->s2c;
1✔
1421

1422
         complete_dtls_handshake(result, *assoc);
1✔
1423

1424
         result.test_is_true("client became active", client.is_active());
1✔
1425
         result.test_is_true("server became active", server.is_active());
1✔
1426

1427
         const auto empty_fragment = empty_dtls_handshake_fragment(Botan::TLS::Handshake_Type::Finished, 12, 0);
1✔
1428

1429
         result.test_no_throw("empty old fragment is ignored",
1✔
1430
                              [&] { server.received_data(empty_fragment.data(), empty_fragment.size()); });
2✔
1431
         result.test_is_true("server did not retransmit final flight", s2c.empty());
1✔
1432

1433
         return result;
1✔
1434
      }
2✔
1435

1436
      static Test::Result test_spoofed_epoch0_records_do_not_abort_or_poison_retransmission() {
1✔
1437
         Test::Result result("DTLS spoofed epoch 0 records do not abort or poison retransmission");
1✔
1438

1439
         auto rng = Test::new_shared_rng("dtls-core-spoofed-epoch0");
1✔
1440
         auto assoc = make_association(result, rng);
1✔
1441
         auto& client = *assoc->client;
1✔
1442
         auto& server = *assoc->server;
1✔
1443
         auto& c2s = assoc->c2s;
1✔
1444
         auto& s2c = assoc->s2c;
1✔
1445

1446
         deliver(result, "client hello 1", c2s, server);
1✔
1447
         deliver(result, "hello verify request", s2c, client);
1✔
1448
         deliver(result, "client hello 2", c2s, server);
1✔
1449
         deliver(result, "server handshake flight", s2c, client);
1✔
1450
         deliver(result, "client final flight", c2s, server);
1✔
1451

1452
         result.test_is_true("server became active", server.is_active());
1✔
1453
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1454
         s2c.clear();  // simulate losing the server's final flight
1✔
1455

1456
         const std::array<uint8_t, 2> fatal_alert = {2, 40};
1✔
1457
         const auto spoofed_alert =
1✔
1458
            unprotected_dtls_record(Botan::TLS::Record_Type::Alert, 0x0000FFFFFFFFFFFF, fatal_alert);
1✔
1459
         result.test_no_throw("spoofed epoch 0 fatal alert is ignored",
1✔
1460
                              [&] { server.received_data(spoofed_alert.data(), spoofed_alert.size()); });
2✔
1461
         result.test_is_true("server remains active after spoofed alert", server.is_active());
1✔
1462
         result.test_is_true("server does not respond to spoofed alert", s2c.empty());
1✔
1463

1464
         std::array<uint8_t, 12> invalid_handshake = {};
1✔
1465
         invalid_handshake[0] = 0xFF;
1✔
1466
         const auto spoofed_handshake =
1✔
1467
            unprotected_dtls_record(Botan::TLS::Record_Type::Handshake, 0x0000FFFFFFFFFFFE, invalid_handshake);
1✔
1468
         result.test_no_throw("invalid epoch 0 handshake is ignored",
1✔
1469
                              [&] { server.received_data(spoofed_handshake.data(), spoofed_handshake.size()); });
2✔
1470
         result.test_is_true("server remains active after invalid handshake", server.is_active());
1✔
1471
         result.test_is_true("server does not respond to invalid handshake", s2c.empty());
1✔
1472

1473
         wait_for_timeout_retransmit(result, client, c2s);
1✔
1474
         deliver(result, "genuine client final flight retransmit", c2s, server);
1✔
1475
         result.test_is_true("server retransmits final flight", !s2c.empty());
1✔
1476
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
1477

1478
         result.test_is_true("client became active", client.is_active());
1✔
1479
         result.test_is_true("server remains active", server.is_active());
1✔
1480

1481
         return result;
1✔
1482
      }
3✔
1483

1484
      static Test::Result test_resumed_final_flight_and_app_data_in_one_receive() {
1✔
1485
         Test::Result result("DTLS resumed final flight and app data in one receive");
1✔
1486

1487
         auto rng = Test::new_shared_rng("dtls-core-resumed-final-and-app-data");
1✔
1488
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1489
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1490
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1491
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1492

1493
         run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
4✔
1494

1495
         std::vector<uint8_t> c2s;
1✔
1496
         std::vector<uint8_t> s2c;
1✔
1497
         std::vector<uint8_t> client_recv;
1✔
1498
         std::vector<uint8_t> server_recv;
1✔
1499

1500
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1501
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
1✔
1502

1503
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1504
         Botan::TLS::Client client(client_callbacks,
1✔
1505
                                   client_sessions,
1506
                                   creds,
1507
                                   policy,
1508
                                   rng,
1509
                                   Botan::TLS::Server_Information("localhost"),
1✔
1510
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1511

1512
         deliver(result, "resumed client hello 1", c2s, server);
1✔
1513
         deliver(result, "resumed hello verify request", s2c, client);
1✔
1514
         deliver(result, "resumed client hello 2", c2s, server);
1✔
1515
         deliver(result, "resumed server handshake flight", s2c, client);
1✔
1516

1517
         result.test_is_true("client is active after resumed server flight", client.is_active());
1✔
1518
         result.test_is_false("server is still waiting for client final flight", server.is_active());
1✔
1519
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1520
            return result;
1521
         }
1522

1523
         const std::vector<uint8_t> app_data = {0x47, 0x82, 0x01};
1✔
1524
         result.test_no_throw("client sends application data before server processes final flight",
1✔
1525
                              [&] { client.send(app_data); });
2✔
1526

1527
         deliver(result, "resumed client final flight and application data", c2s, server);
1✔
1528

1529
         result.test_is_true("server became active", server.is_active());
1✔
1530
         result.test_bin_eq("server received client application data", server_recv, app_data);
1✔
1531

1532
         return result;
1✔
1533
      }
9✔
1534

1535
      static Test::Result run_handshake(Test::Result& result,
5✔
1536
                                        const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
1537
                                        const std::shared_ptr<Botan::TLS::Policy>& policy,
1538
                                        const std::shared_ptr<Botan::Credentials_Manager>& creds,
1539
                                        const std::shared_ptr<Botan::TLS::Session_Manager>& client_sessions,
1540
                                        const std::shared_ptr<Botan::TLS::Session_Manager>& server_sessions,
1541
                                        bool expect_resumption) {
1542
         std::vector<uint8_t> c2s;
5✔
1543
         std::vector<uint8_t> s2c;
5✔
1544
         std::vector<uint8_t> client_recv;
5✔
1545
         std::vector<uint8_t> server_recv;
5✔
1546

1547
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
5✔
1548
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
5✔
1549

1550
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
10✔
1551
         Botan::TLS::Client client(client_callbacks,
5✔
1552
                                   client_sessions,
1553
                                   creds,
1554
                                   policy,
1555
                                   rng,
1556
                                   Botan::TLS::Server_Information("localhost"),
5✔
1557
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
25✔
1558

1559
         deliver(result, "client hello 1", c2s, server);
5✔
1560
         deliver(result, "hello verify request", s2c, client);
5✔
1561
         deliver(result, "client hello 2", c2s, server);
5✔
1562
         deliver(result, "server handshake flight", s2c, client);
5✔
1563

1564
         // For a full handshake this is the client's final flight. The server's
1565
         // response is intentionally withheld to make the client retransmit it with
1566
         // fresh DTLS record sequence numbers, matching the scenario in #2498.
1567
         deliver(result, "client final flight", c2s, server);
5✔
1568
         result.test_is_true("server became active", server.is_active());
5✔
1569

1570
         if(expect_resumption) {
5✔
1571
            result.test_is_true("client became active during resumed handshake", client.is_active());
1✔
1572
         } else {
1573
            if(!result.test_is_true("server final flight is pending", !s2c.empty())) {
4✔
1574
               return result;
×
1575
            }
1576

1577
            wait_for_timeout_retransmit(result, client, c2s);
4✔
1578
            deliver(result, "client final flight retransmit", c2s, server);
4✔
1579

1580
            deliver(result, "server final flight", s2c, client);
8✔
1581
         }
1582

1583
         result.test_is_true("client became active", client.is_active());
5✔
1584

1585
         if(client_callbacks->session_was_resumption().has_value()) {
5✔
1586
            result.test_bool_eq(
10✔
1587
               "client resumption state", client_callbacks->session_was_resumption().value(), expect_resumption);
5✔
1588
         }
1589
         if(server_callbacks->session_was_resumption().has_value()) {
5✔
1590
            result.test_bool_eq(
10✔
1591
               "server resumption state", server_callbacks->session_was_resumption().value(), expect_resumption);
5✔
1592
         }
1593

1594
         const std::vector<uint8_t> app_data = {0xD7, 0x15, 0xA9};
5✔
1595
         result.test_no_throw("client sends application data after retransmitted final flight",
5✔
1596
                              [&] { client.send(app_data); });
10✔
1597
         deliver(result, "application data", c2s, server);
5✔
1598
         result.test_bin_eq("server received application data", server_recv, app_data);
5✔
1599

1600
         return result;
5✔
1601
      }
20✔
1602

1603
   public:
1604
      std::vector<Test::Result> run() override {
1✔
1605
         return {test_timeout_check_paces_retransmissions(),
1✔
1606
                 test_retransmitted_epoch_transition_flight_includes_ccs(),
1607
                 test_lost_hello_verify_request_retransmits(),
1608
                 test_duplicate_hello_verify_request_is_tolerated(),
1609
                 test_rotated_cookie_secret_produces_fresh_hello_verify_request(),
1610
                 test_partial_server_flight_does_not_advance_client(),
1611
                 test_lost_server_flight_retransmits(),
1612
                 test_duplicate_server_flight_defers_to_timer(),
1613
                 test_hello_request_during_handshake_is_ignored(),
1614
                 test_timed_out_initial_handshake_closes_the_channel(),
1615
                 test_timed_out_renegotiation_keeps_the_association(),
1616
                 test_lost_server_final_flight_retransmits(),
1617
                 test_stale_client_hello_does_not_replace_active_handshake(),
1618
                 test_epoch0_client_hello_retransmit_while_restart_pending(),
1619
                 test_reordered_retransmitted_final_flight(),
1620
                 test_empty_old_handshake_fragment_does_not_retransmit(),
1621
                 test_spoofed_epoch0_records_do_not_abort_or_poison_retransmission(),
1622
                 test_retransmitted_final_flight_then_application_data(false),
1623
                 test_retransmitted_final_flight_then_application_data(true),
1624
                 test_resumed_client_final_flight_retransmits_after_activation(),
1625
                 test_resumed_final_flight_and_app_data_in_one_receive(),
1626
                 test_renegotiation(false),
1627
                 test_renegotiation(true)};
24✔
1628
      }
1✔
1629
};
1630

1631
BOTAN_REGISTER_TEST("tls", "tls_dtls_core_regressions", DTLS_Core_Regression_Tests);
1632

1633
// RFC 6347 4.2.8: a client that goes silent may be replaced by a new one
1634
// arriving on the same 5-tuple, which the server has to take up as a fresh
1635
// association rather than as traffic on the old one.
1636
class DTLS_Reconnection_Test : public Test {
1✔
1637
   public:
1638
      std::vector<Test::Result> run() override {
1✔
1639
         Test::Result result("DTLS reconnection");
1✔
1640

1641
         auto rng = Test::new_shared_rng(this->test_name());
1✔
1642

1643
         // Neither client caches sessions, so the second one starts a genuinely
1644
         // new handshake rather than trying to resume.
1645
         DTLS_Association_Options opts;
1✔
1646
         opts.client_sessions = std::make_shared<Botan::TLS::Session_Manager_Noop>();
1✔
1647
         auto assoc = make_association(result, rng, opts);
1✔
1648

1649
         auto& server = *assoc->server;
1✔
1650

1651
         // Run one client to completion against the shared server and exchange a
1652
         // datagram in each direction. Bounded so a regression cannot hang.
1653
         const auto exchange = [&](Botan::TLS::Client& client,
3✔
1654
                                   std::vector<uint8_t>& c2s,
1655
                                   std::vector<uint8_t>& client_recv,
1656
                                   uint8_t to_server_byte,
1657
                                   uint8_t to_client_byte,
1658
                                   const std::string& label) {
1659
            const std::vector<uint8_t> to_server(16, to_server_byte);
2✔
1660
            const std::vector<uint8_t> to_client(16, to_client_byte);
2✔
1661
            bool client_sent = false;
2✔
1662
            bool server_sent = false;
2✔
1663

1664
            for(size_t round = 0; round != 64; ++round) {
22✔
1665
               if(!c2s.empty()) {
22✔
1666
                  std::vector<uint8_t> input;
8✔
1667
                  std::swap(c2s, input);
8✔
1668
                  server.received_data(input.data(), input.size());
8✔
1669
               } else if(!assoc->s2c.empty()) {
22✔
1670
                  std::vector<uint8_t> input;
8✔
1671
                  std::swap(assoc->s2c, input);
8✔
1672
                  client.received_data(input.data(), input.size());
8✔
1673
               } else if(!client_sent && client.is_active()) {
14✔
1674
                  client.send(to_server);
2✔
1675
                  client_sent = true;
2✔
1676
               } else if(!server_sent && server.is_active()) {
4✔
1677
                  server.send(to_client);
2✔
1678
                  server_sent = true;
2✔
1679
               } else if(!assoc->server_recv.empty() && !client_recv.empty()) {
2✔
1680
                  result.test_bin_eq("message from " + label, assoc->server_recv, to_server);
2✔
1681
                  result.test_bin_eq("message to " + label, client_recv, to_client);
2✔
1682
                  return true;
2✔
1683
               } else {
1684
                  break;  // out of input with nothing left to send
1685
               }
1686
            }
1687

1688
            result.test_failure("the " + label + " exchange did not complete");
×
1689
            return false;
×
1690
         };
4✔
1691

1692
         if(!exchange(*assoc->client, assoc->c2s, assoc->client_recv, 0xC1, 0x42, "client1")) {
1✔
1693
            return {result};
×
1694
         }
1695
         result.test_sz_eq("client1 established a session", assoc->client_cb->sessions_established(), size_t(1));
1✔
1696
         result.test_sz_eq("server established a session", assoc->server_cb->sessions_established(), size_t(1));
1✔
1697

1698
         // Now client1 goes silent and a new client connects to the same server
1699
         // context, as happens when a client source port is reused.
1700
         assoc->server_recv.clear();
1✔
1701
         assoc->s2c.clear();
1✔
1702

1703
         std::vector<uint8_t> c2_c2s;
1✔
1704
         std::vector<uint8_t> client2_recv;
1✔
1705
         auto client2_cb = std::make_shared<DTLS_Test_Callbacks>(result, c2_c2s, client2_recv);
1✔
1706
         auto client2 = make_dtls_client(client2_cb, assoc->client_sessions, assoc->creds, assoc->client_policy, rng);
2✔
1707

1708
         if(!exchange(*client2, c2_c2s, client2_recv, 0xC2, 0x66, "client2")) {
1✔
1709
            return {result};
×
1710
         }
1711
         result.test_sz_eq("client2 established a session", client2_cb->sessions_established(), size_t(1));
1✔
1712
         result.test_sz_eq("server established a second session", assoc->server_cb->sessions_established(), size_t(2));
1✔
1713

1714
         return {result};
2✔
1715
      }
5✔
1716
};
1717

1718
BOTAN_REGISTER_TEST("tls", "tls_dtls_reconnect", DTLS_Reconnection_Test);
1719

1720
#endif
1721

1722
}  // namespace
1723

1724
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc