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

randombit / botan / 30328435581

27 Jul 2026 06:39PM UTC coverage: 89.428% (-0.005%) from 89.433%
30328435581

push

github

web-flow
Merge pull request #5765 from randombit/jack/python-docs

Add doc comments to Python bindings that had none

115095 of 128702 relevant lines covered (89.43%)

10581809.56 hits per line

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

93.13
/src/tests/test_stream.cpp
1
/*
2
* (C) 2014,2015,2016 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_STREAM_CIPHER)
10
   #include <botan/exceptn.h>
11
   #include <botan/rng.h>
12
   #include <botan/stream_cipher.h>
13
   #include <botan/internal/fmt.h>
14
#endif
15

16
namespace Botan_Tests {
17

18
#if defined(BOTAN_HAS_STREAM_CIPHER)
19

20
namespace {
21

22
class Stream_Cipher_Tests final : public Text_Based_Test {
×
23
   public:
24
      Stream_Cipher_Tests() : Text_Based_Test("stream", "Key,Out", "In,Nonce,Seek") {}
2✔
25

26
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
4,402✔
27
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
4,402✔
28
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
4,402✔
29
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
4,402✔
30
         const uint64_t seek = vars.get_opt_u64("Seek", 0);
4,402✔
31
         std::vector<uint8_t> input = vars.get_opt_bin("In");
4,402✔
32

33
         if(input.empty()) {
4,402✔
34
            input.resize(expected.size());
2,700✔
35
         }
36

37
         Test::Result result(algo);
4,402✔
38

39
         const std::vector<std::string> providers = provider_filter(Botan::StreamCipher::providers(algo));
4,402✔
40

41
         if(providers.empty()) {
4,402✔
42
            result.note_missing("stream cipher " + algo);
2✔
43
            return result;
2✔
44
         }
45

46
         for(const auto& provider_ask : providers) {
8,800✔
47
            auto cipher = Botan::StreamCipher::create(algo, provider_ask);
4,400✔
48

49
            if(!cipher) {
4,400✔
50
               result.test_failure(Botan::fmt("Stream cipher {} supported by {} but not found", algo, provider_ask));
×
51
               continue;
×
52
            }
53

54
            const std::string provider(cipher->provider());
4,400✔
55
            result.test_str_not_empty("provider", provider);
4,400✔
56
            result.test_str_eq(provider, cipher->name(), algo);
4,400✔
57

58
            result.test_is_true("default iv length is valid", cipher->valid_iv_length(cipher->default_iv_length()));
4,400✔
59

60
            result.test_is_true("advertised buffer size is > 0", cipher->buffer_size() > 0);
4,400✔
61

62
            if(cipher->default_iv_length() == 0) {
4,400✔
63
               result.test_is_true("if default iv length is zero, no iv supported", nonce.empty());
2,365✔
64

65
               // This should still succeed
66
               cipher->set_iv(nullptr, 0);
2,365✔
67
            }
68

69
            try {
4,400✔
70
               std::vector<uint8_t> buf(128);
4,400✔
71
               cipher->cipher1(buf.data(), buf.size());
4,400✔
72
               result.test_failure("Was able to encrypt without a key being set");
×
73
            } catch(Botan::Invalid_State&) {
4,400✔
74
               result.test_success("Trying to encrypt with no key set fails");
4,400✔
75
            }
4,400✔
76

77
            const bool supports_seek = cipher->supports_seek();
4,400✔
78

79
            if(supports_seek) {
4,400✔
80
               try {
2,018✔
81
                  cipher->seek(0);
2,018✔
82
                  result.test_failure("Was able to seek without a key being set");
×
83
               } catch(Botan::Invalid_State&) {
2,018✔
84
                  result.test_success("Trying to seek with no key set fails");
2,018✔
85
               }
2,018✔
86
            } else {
87
               result.test_throws<Botan::Not_Implemented>("seek() throws Not_Implemented when supports_seek() is false",
2,382✔
88
                                                          [&]() { cipher->seek(0); });
4,764✔
89
            }
90

91
            if(!cipher->valid_iv_length(nonce.size())) {
4,400✔
92
               throw Test_Error("Invalid nonce for " + algo);
×
93
            }
94

95
            bool accepted_nonce_early = false;
4,400✔
96
            if(!nonce.empty()) {
4,400✔
97
               try {
2,029✔
98
                  cipher->set_iv(nonce.data(), nonce.size());
2,029✔
99
                  accepted_nonce_early = true;
100
               } catch(Botan::Invalid_State&) {}
2,029✔
101
            }
102

103
            /*
104
            * Different providers may have additional restrictions on key sizes.
105
            * Avoid testing the cipher with a key size that it does not natively support.
106
            */
107
            if(!cipher->valid_keylength(key.size())) {
4,400✔
108
               result.test_note("Skipping test with provider " + provider + " as it does not support key length " +
×
109
                                std::to_string(key.size()));
×
110
               continue;
×
111
            }
112

113
            result.test_is_false("key not set", cipher->has_keying_material());
4,400✔
114
            cipher->set_key(key);
4,400✔
115
            result.test_is_true("key set", cipher->has_keying_material());
4,400✔
116

117
            /*
118
            Test invalid nonce sizes. this assumes no implemented cipher supports a nonce of 65000
119
            */
120
            const size_t large_nonce_size = 65000;
4,400✔
121
            result.test_is_true("Stream cipher does not support very large nonce",
4,400✔
122
                                cipher->valid_iv_length(large_nonce_size) == false);
4,400✔
123

124
            result.test_throws("Throws if invalid nonce size given",
4,400✔
125
                               [&]() { cipher->set_iv(nullptr, large_nonce_size); });
8,800✔
126

127
            /*
128
            If the set_nonce call earlier succeeded, then we require that it also
129
            worked (ie saved the nonce for later use) even though the key was
130
            not set. So, don't set the nonce now, to ensure the previous call
131
            had an effect.
132
            */
133
            if(!nonce.empty() && accepted_nonce_early == false) {
4,400✔
134
               cipher->set_iv(nonce.data(), nonce.size());
2,029✔
135
            }
136

137
            if(seek != 0) {
4,400✔
138
               cipher->seek(seek);
1,043✔
139
            }
140

141
            // Test that clone works and does not affect parent object
142
            auto clone = cipher->new_object();
4,400✔
143
            result.test_is_true("Clone has different pointer", cipher.get() != clone.get());
4,400✔
144
            result.test_str_eq("Clone has same name", cipher->name(), clone->name());
4,400✔
145
            clone->set_key(this->rng().random_vec(cipher->maximum_keylength()));
4,400✔
146

147
            {
4,400✔
148
               std::vector<uint8_t> buf = input;
4,400✔
149
               cipher->encrypt(buf);
4,400✔
150
               result.test_bin_eq(provider + " encrypt", buf, expected);
8,800✔
151
            }
×
152

153
            /*
154
            * Verify that seek is idempotent
155
            */
156
            if(supports_seek && seek > 0) {
4,400✔
157
               if(!nonce.empty()) {
1,043✔
158
                  cipher->set_iv(nonce.data(), nonce.size());
1,043✔
159
               }
160
               cipher->seek(seek);
1,043✔
161
               cipher->seek(0);
1,043✔
162
               cipher->seek(seek);
1,043✔
163
               std::vector<uint8_t> seek_buf = input;
1,043✔
164
               cipher->encrypt(seek_buf);
1,043✔
165
               result.test_bin_eq(provider + " seek is idempotent", seek_buf, expected);
1,043✔
166

167
               // After seeking, seek(0) must reset back to original keystream.
168
               cipher->seek(0);
1,043✔
169
               std::vector<uint8_t> seek0_buf(input.size());
1,043✔
170
               cipher->encrypt(seek0_buf);
1,043✔
171

172
               auto fresh = cipher->new_object();
1,043✔
173
               fresh->set_key(key);
1,043✔
174
               if(!nonce.empty()) {
1,043✔
175
                  fresh->set_iv(nonce.data(), nonce.size());
1,043✔
176
               }
177
               std::vector<uint8_t> fresh_buf(input.size());
1,043✔
178
               fresh->encrypt(fresh_buf);
1,043✔
179
               result.test_bin_eq(provider + " seek(0) after high seek round-trips", fresh_buf, seek0_buf);
2,086✔
180
            }
4,172✔
181

182
            {
4,400✔
183
               if(nonce.empty()) {
4,400✔
184
                  cipher->set_key(key);
2,371✔
185
               } else {
186
                  cipher->set_iv(nonce.data(), nonce.size());
2,029✔
187
               }
188
               if(seek != 0) {
4,400✔
189
                  cipher->seek(seek);
1,043✔
190
               }
191
               std::vector<uint8_t> buf = input;
4,400✔
192
               cipher->encrypt(buf);
4,400✔
193
               result.test_bin_eq(provider + " encrypt 2", buf, expected);
8,800✔
194
            }
×
195

196
            if(!nonce.empty()) {
4,400✔
197
               cipher->set_iv(nonce.data(), nonce.size());
2,029✔
198
               if(seek != 0) {
2,029✔
199
                  cipher->seek(seek);
1,043✔
200
               }
201
               std::vector<uint8_t> buf = input;
2,029✔
202
               cipher->encrypt(buf);
2,029✔
203
               result.test_bin_eq(provider + " second encrypt", buf, expected);
4,058✔
204
            }
2,029✔
205

206
            {
4,400✔
207
               cipher->set_key(key);
4,400✔
208

209
               cipher->set_iv(nonce.data(), nonce.size());
4,400✔
210

211
               if(seek != 0) {
4,400✔
212
                  cipher->seek(seek);
1,043✔
213
               }
214

215
               std::vector<uint8_t> buf(input.size(), 0xAB);
4,400✔
216

217
               uint8_t* buf_ptr = buf.data();
4,400✔
218
               size_t buf_len = buf.size();
4,400✔
219

220
               while(buf_len > 0) {
11,406✔
221
                  const size_t next = std::min<size_t>(buf_len, this->rng().next_byte());
7,006✔
222
                  cipher->write_keystream(buf_ptr, next);
7,006✔
223
                  buf_ptr += next;
7,006✔
224
                  buf_len -= next;
7,006✔
225
               }
226

227
               for(size_t i = 0; i != input.size(); ++i) {
451,638✔
228
                  buf[i] ^= input[i];
447,238✔
229
               }
230
               result.test_bin_eq(provider + " write_keystream", buf, expected);
8,800✔
231
            }
×
232

233
            {
4,400✔
234
               // A single large request exercises any internal multi-block refill paths
235
               cipher->set_key(key);
4,400✔
236

237
               cipher->set_iv(nonce.data(), nonce.size());
4,400✔
238

239
               if(seek != 0) {
4,400✔
240
                  cipher->seek(seek);
1,043✔
241
               }
242

243
               std::vector<uint8_t> buf(input.size());
4,400✔
244
               cipher->write_keystream(buf.data(), buf.size());
4,400✔
245

246
               for(size_t i = 0; i != input.size(); ++i) {
451,638✔
247
                  buf[i] ^= input[i];
447,238✔
248
               }
249
               result.test_bin_eq(provider + " write_keystream one-shot", buf, expected);
8,800✔
250
            }
×
251

252
            result.test_is_true("key set", cipher->has_keying_material());
4,400✔
253
            cipher->clear();
4,400✔
254
            result.test_is_false("key not set", cipher->has_keying_material());
4,400✔
255

256
            try {
4,400✔
257
               std::vector<uint8_t> buf(128);
4,400✔
258
               cipher->cipher1(buf.data(), buf.size());
4,400✔
259
               result.test_failure("Was able to encrypt without a key being set (after clear)");
×
260
            } catch(Botan::Invalid_State&) {
4,400✔
261
               result.test_success("Trying to encrypt with no key set (after clear) fails");
4,400✔
262
            }
4,400✔
263
         }
8,800✔
264

265
         return result;
266
      }
41,354✔
267
};
268

269
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("stream", "stream_ciphers", Stream_Cipher_Tests);
270

271
class Stream_Cipher_Seek_Tests final : public Test {
1✔
272
   public:
273
      std::vector<Test::Result> run() override {
1✔
274
         std::vector<Test::Result> results;
1✔
275
         results.push_back(test_idempotent_and_round_trip());
2✔
276
         results.push_back(test_strict_counter_limits());
2✔
277
         return results;
1✔
278
      }
×
279

280
   private:
281
      static std::unique_ptr<Botan::StreamCipher> create_with_iv(std::string_view algo, size_t iv_len) {
25✔
282
         auto cipher = Botan::StreamCipher::create(algo);
25✔
283

284
         if(cipher) {
25✔
285
            std::vector<uint8_t> key(cipher->maximum_keylength(), 0);
25✔
286
            std::vector<uint8_t> iv(iv_len, 0);
25✔
287
            cipher->set_key(key);
25✔
288
            if(iv_len > 0) {
25✔
289
               cipher->set_iv(iv);
25✔
290
            }
291
         }
50✔
292

293
         return cipher;
25✔
294
      }
×
295

296
      Test::Result test_idempotent_and_round_trip() {
1✔
297
         Test::Result result("StreamCipher seek idempotence and round-trip at high offsets");
1✔
298

299
         struct Case {
12✔
300
               std::string algo;
301
               size_t iv_len;
302
               uint64_t seek_bytes;
303
         };
304

305
         // Seeks chosen so the high counter word is non-zero
306
         const std::vector<Case> cases = {
1✔
307
            {"ChaCha(20)", 8, (uint64_t{1} << 32) * 64},
308
            {"ChaCha(20)", 8, (uint64_t{1} << 32) * 64 + 5 * 64 + 17},
309
            {"ChaCha(20)", 24, (uint64_t{1} << 32) * 64},
310
            {"Salsa20", 8, (uint64_t{1} << 32) * 64},
311
            {"Salsa20", 8, (uint64_t{1} << 32) * 64 + 5 * 64 + 17},
312
            {"Salsa20", 24, (uint64_t{1} << 32) * 64},
313
         };
8✔
314

315
         for(const auto& c : cases) {
7✔
316
            const std::string tag = Botan::fmt("{} iv={} seek={}", c.algo, c.iv_len, c.seek_bytes);
6✔
317

318
            auto a = create_with_iv(c.algo, c.iv_len);
6✔
319
            if(!a) {
6✔
320
               result.note_missing(c.algo);
×
321
               continue;
×
322
            }
323

324
            constexpr size_t sample_bytes = 128;
6✔
325

326
            // Take reference value: seek to offset, output sample_bytes bytes of keystream.
327
            a->seek(c.seek_bytes);
6✔
328
            const auto ks_a = a->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
329

330
            auto b = create_with_iv(c.algo, c.iv_len);
6✔
331
            b->seek(c.seek_bytes);
6✔
332
            b->seek(c.seek_bytes);
6✔
333
            const auto ks_b = b->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
334
            result.test_bin_eq(tag + " idempotent", ks_a, ks_b);
6✔
335

336
            // seek(0) after a high seek must reproduce the keystream of a fresh cipher.
337
            auto fresh = create_with_iv(c.algo, c.iv_len);
6✔
338
            const auto ks_fresh = fresh->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
339

340
            auto rt = create_with_iv(c.algo, c.iv_len);
6✔
341
            rt->seek(c.seek_bytes);
6✔
342
            (void)rt->keystream_bytes<std::vector<uint8_t>>(64);
6✔
343
            rt->seek(0);
6✔
344
            const auto ks_rt = rt->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
345
            result.test_bin_eq(tag + " seek(0) round-trip", ks_rt, ks_fresh);
12✔
346
         }
48✔
347

348
         return result;
1✔
349
      }
3✔
350

351
      Test::Result test_strict_counter_limits() {
1✔
352
         Test::Result result("StreamCipher seek rejection past counter limits");
1✔
353

354
         if(auto chacha = create_with_iv("ChaCha(20)", 12)) {
1✔
355
            // Last addressable byte: block 2^32 - 1, offset 63.
356
            const uint64_t max_ok = (uint64_t{1} << 32) * 64 - 1;
1✔
357
            result.test_no_throw("ChaCha 12-byte nonce seek at counter limit", [&]() { chacha->seek(max_ok); });
2✔
358

359
            // First rejected byte: block 2^32, offset 0.
360
            const uint64_t seek_limit = (uint64_t{1} << 32) * 64;
1✔
361

362
            chacha->seek(seek_limit - 1);  // ok
1✔
363

364
            result.test_throws<Botan::Invalid_Argument>("ChaCha 12-byte nonce seek past counter limit throws",
1✔
365
                                                        [&]() { chacha->seek(seek_limit); });
2✔
366

367
            // Test a seek way past that limit:
368
            result.test_throws<Botan::Invalid_Argument>("ChaCha 12-byte nonce seek well past counter limit throws",
1✔
369
                                                        [&]() { chacha->seek((uint64_t{1} << 40) * 64); });
2✔
370
         }
×
371

372
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
373
            std::vector<uint8_t> key(16, 0);
1✔
374
            std::vector<uint8_t> iv(16, 0xFF);
1✔
375
            ctr_be->set_key(key);
1✔
376
            ctr_be->set_iv(iv);
1✔
377

378
            constexpr uint64_t ctr32_max = (uint64_t{1} << 32) * 16 - 1;
1✔
379

380
            result.test_no_throw("CTR-BE(AES,4) seek at counter limit", [&]() { ctr_be->seek(ctr32_max); });
2✔
381

382
            result.test_throws<Botan::Invalid_Argument>("CTR-BE(AES,4) seek past 2^32 blocks throws",
1✔
383
                                                        [&]() { ctr_be->seek(ctr32_max + 1); });
2✔
384
         }
2✔
385

386
         // With a 64-bit counter, you can go anywhere you want
387
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,8)")) {
1✔
388
            std::vector<uint8_t> key(16, 0);
1✔
389
            std::vector<uint8_t> iv(16, 0);
1✔
390
            ctr_be->set_key(key);
1✔
391
            ctr_be->set_iv(iv);
1✔
392
            result.test_no_throw("CTR-BE(AES,8) high seek accepted", [&]() { ctr_be->seek((uint64_t{1} << 40) * 16); });
2✔
393
         }
2✔
394

395
         return result;
1✔
396
      }
×
397
};
398

399
BOTAN_REGISTER_TEST("stream", "stream_cipher_seek", Stream_Cipher_Seek_Tests);
400

401
class Stream_Cipher_Keystream_Cap_Tests final : public Test {
1✔
402
   public:
403
      std::vector<Test::Result> run() override {
1✔
404
         std::vector<Test::Result> results;
1✔
405
         results.push_back(test_remaining_getter());
2✔
406
         results.push_back(test_exhaustion());
2✔
407
         return results;
1✔
408
      }
×
409

410
   private:
411
      Test::Result test_remaining_getter() {
1✔
412
         Test::Result result("StreamCipher::remaining_keystream_bytes");
1✔
413

414
         if(auto chacha = Botan::StreamCipher::create("ChaCha(20)")) {
1✔
415
            // Unkeyed cipher: nullopt regardless
416
            result.test_is_true("Unkeyed ChaCha returns nullopt", !chacha->remaining_keystream_bytes().has_value());
1✔
417

418
            // With a 64-bit counter, you can go anywhere you want
419
            const std::vector<uint8_t> key(32, 0);
1✔
420
            const std::vector<uint8_t> iv8(8, 0);
1✔
421
            chacha->set_key(key);
1✔
422
            chacha->set_iv(iv8);
1✔
423
            result.test_is_true("ChaCha 8-byte nonce returns nullopt",
1✔
424
                                !chacha->remaining_keystream_bytes().has_value());
1✔
425

426
            const std::vector<uint8_t> iv24(24, 0);
1✔
427
            chacha->set_iv(iv24);
1✔
428
            result.test_is_true("ChaCha 24-byte nonce returns nullopt",
1✔
429
                                !chacha->remaining_keystream_bytes().has_value());
1✔
430

431
            // 96-bit nonce: cap = 2^32 * 64 = 2^38 bytes from a fresh IV.
432
            const std::vector<uint8_t> iv12(12, 0);
1✔
433
            chacha->set_key(key);
1✔
434
            chacha->set_iv(iv12);
1✔
435
            constexpr auto cap = uint64_t{1} << 38;
1✔
436
            const auto remaining = chacha->remaining_keystream_bytes();
1✔
437
            result.test_is_true("ChaCha 12-byte nonce returns a value", remaining.has_value());
1✔
438
            result.test_u64_eq("ChaCha 12-byte nonce fresh capacity", *remaining, cap);
1✔
439

440
            // Consume some bytes, the available keystream decreases
441
            std::vector<uint8_t> buf(100);
1✔
442
            chacha->write_keystream(buf);
1✔
443
            result.test_opt_u64_eq(
1✔
444
               "ChaCha 12-byte nonce after 100 byte write", chacha->remaining_keystream_bytes(), cap - buf.size());
1✔
445

446
            // After seek the count tracks the new offset
447
            chacha->seek(cap - 64);
1✔
448
            result.test_opt_u64_eq("ChaCha 12-byte nonce after near-end seek", chacha->remaining_keystream_bytes(), 64);
1✔
449
         }
5✔
450

451
         // CTR-BE with 64-bit counter
452
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,8)")) {
1✔
453
            const std::vector<uint8_t> key(16, 0);
1✔
454
            const std::vector<uint8_t> iv(16, 0);
1✔
455
            ctr_be->set_key(key);
1✔
456
            ctr_be->set_iv(iv);
1✔
457
            result.test_opt_is_null("CTR-BE(AES,8) remaining_keystream_bytes", ctr_be->remaining_keystream_bytes());
1✔
458
         }
2✔
459

460
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
461
            const std::vector<uint8_t> key(16, 0);
1✔
462
            const std::vector<uint8_t> iv(16, 0);
1✔
463
            ctr_be->set_key(key);
1✔
464
            ctr_be->set_iv(iv);
1✔
465

466
            constexpr auto cap = (uint64_t{1} << 32) * 16;
1✔
467
            const auto remaining = ctr_be->remaining_keystream_bytes();
1✔
468
            result.test_is_true("CTR-BE(AES,4) returns a value", remaining.has_value());
1✔
469
            result.test_u64_eq("CTR-BE(AES,4) fresh capacity", *remaining, cap);
1✔
470
         }
2✔
471

472
         // Ciphers without seek also return nullopt.
473
         if(auto rc4 = Botan::StreamCipher::create("RC4")) {
1✔
474
            std::vector<uint8_t> key(16, 0);
1✔
475
            rc4->set_key(key);
1✔
476
            result.test_is_true("RC4 returns nullopt", !rc4->remaining_keystream_bytes().has_value());
1✔
477
         }
1✔
478

479
         return result;
1✔
480
      }
×
481

482
      Test::Result test_exhaustion() {
1✔
483
         Test::Result result("StreamCipher keystream exhaustion");
1✔
484

485
         /*
486
         * ChaCha 96-bit nonce, near the cap: consume the last 200
487
         * bytes successfully, then any further byte must throw.
488
         */
489
         if(auto chacha = Botan::StreamCipher::create("ChaCha(20)")) {
1✔
490
            const std::vector<uint8_t> key(32, 0);
1✔
491
            const std::vector<uint8_t> iv(12, 0xFF);
1✔
492
            chacha->set_key(key);
1✔
493
            chacha->set_iv(iv);
1✔
494
            constexpr uint64_t cap = uint64_t{1} << 38;
1✔
495
            chacha->seek(cap - 200);
1✔
496

497
            std::vector<uint8_t> buf(200);
1✔
498
            result.test_no_throw("ChaCha 12-byte nonce: consume up to cap", [&]() { chacha->write_keystream(buf); });
2✔
499
            result.test_opt_u64_eq(
1✔
500
               "ChaCha 12-byte nonce: remaining is 0 at cap", chacha->remaining_keystream_bytes(), 0);
1✔
501

502
            std::vector<uint8_t> one(1);
1✔
503
            result.test_throws<Botan::Invalid_State>("ChaCha 12-byte nonce: write past cap throws",
1✔
504
                                                     [&]() { chacha->write_keystream(one); });
2✔
505

506
            chacha->seek(cap - 100);
1✔
507

508
            const auto orig = buf;
1✔
509
            result.test_throws<Botan::Invalid_State>("ChaCha 12-byte nonce: oversize encrypt throws",
1✔
510
                                                     [&]() { chacha->encrypt(buf); });
2✔
511
            result.test_bin_eq("ChaCha 12-byte nonce: oversize encrypt leaves buffer untouched", buf, orig);
1✔
512
         }
5✔
513

514
         /*
515
         * CTR-BE(AES,4) near the end of the counter cycle (set up by
516
         * a high seek): the cap is 2^36 bytes regardless of IV, and
517
         * we approach it from the bottom via seek. Consume the last
518
         * 64 bytes successfully, then the 65th must throw without
519
         * writing.
520
         */
521
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
522
            const std::vector<uint8_t> key(16, 0);
1✔
523
            const std::vector<uint8_t> iv(16, 0xFF);
1✔
524
            ctr_be->set_key(key);
1✔
525
            ctr_be->set_iv(iv);
1✔
526

527
            constexpr uint64_t cap = (uint64_t{1} << 32) * 16;
1✔
528

529
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), cap);
1✔
530

531
            ctr_be->seek(cap - 64);
1✔
532
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), 64);
1✔
533

534
            std::vector<uint8_t> buf(64);
1✔
535
            result.test_no_throw("CTR-BE(AES,4): consume last 64 bytes before counter cycle",
1✔
536
                                 [&]() { ctr_be->write_keystream(buf); });
2✔
537
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), 0);
1✔
538

539
            const auto orig = buf;
1✔
540
            result.test_throws<Botan::Invalid_State>("CTR-BE(AES,4): write past cap throws",
1✔
541
                                                     [&]() { ctr_be->encrypt(buf); });
2✔
542
            result.test_bin_eq("CTR-BE(AES,4): throw leaves buffer untouched", buf, orig);
1✔
543
         }
4✔
544

545
         return result;
1✔
546
      }
×
547
};
548

549
BOTAN_REGISTER_TEST("stream", "stream_cipher_keystream_cap", Stream_Cipher_Keystream_Cap_Tests);
550

551
}  // namespace
552

553
#endif
554

555
}  // 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