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

randombit / botan / 37409379121

06 Oct 2026 03:02AM UTC coverage: 89.915% (-2.3%) from 92.214%
37409379121

push

github

web-flow
Merge pull request #5997 from randombit/jack/dl-key-validation

Tighten DL key range checks and make weak check_key cheaper

130419 of 145047 relevant lines covered (89.91%)

9048497.74 hits per line

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

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

8
#include "tests.h"
9

10
#include <botan/mem_ops.h>
11
#include <botan/internal/alignment_buffer.h>
12
#include <botan/internal/buffer_stuffer.h>
13
#include <botan/internal/concat_util.h>
14

15
#include <array>
16

17
namespace Botan_Tests {
18

19
namespace {
20

21
using StrongBuffer = Botan::Strong<std::vector<uint8_t>, struct StrongBuffer_>;
22

23
std::vector<Test::Result> test_buffer_slicer() {
1 ✔
24
   return {
1 ✔
25
      CHECK("Empty BufferSlicer",
26
            [](auto& result) {
1 ✔
27
               const std::vector<uint8_t> buffer(0);
1 ✔
28
               Botan::BufferSlicer s(buffer);
1 ✔
29
               result.test_is_true("empty slicer has no remaining bytes", s.remaining() == 0);
1 ✔
30
               result.test_is_true("empty slicer is empty()", s.empty());
1 ✔
31
               result.test_is_true("empty slicer can take() 0 bytes", s.take(0).empty());
1 ✔
32

33
               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
2 ✔
34
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
2 ✔
35
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
2 ✔
36
            }),
1 ✔
37

38
      CHECK("Read from BufferSlicer",
39
            [](auto& result) {
1 ✔
40
               const std::vector<uint8_t> buffer{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', ' ', '!'};
1 ✔
41
               Botan::BufferSlicer s(buffer);
1 ✔
42

43
               result.test_sz_eq("non-empty slicer has remaining bytes", s.remaining(), buffer.size());
1 ✔
44
               result.test_is_true("non-empty slicer is not empty()", !s.empty());
1 ✔
45

46
               const auto peeked = s.peek(5);
1 ✔
47
               result.require("has 5 bytes", peeked.size() == 5);
1 ✔
48
               result.test_u8_eq("peeked hello", peeked[0], uint8_t('h'));
1 ✔
49
               result.test_u8_eq("peeked hello", peeked[1], uint8_t('e'));
1 ✔
50
               result.test_u8_eq("peeked hello", peeked[2], uint8_t('l'));
1 ✔
51
               result.test_u8_eq("peeked hello", peeked[3], uint8_t('l'));
1 ✔
52
               result.test_u8_eq("peeked hello", peeked[4], uint8_t('o'));
1 ✔
53
               result.test_sz_eq("no bytes consumed", s.remaining(), buffer.size());
1 ✔
54

55
               const auto hello = s.take(5);
1 ✔
56
               result.require("has 5 bytes", hello.size() == 5);
1 ✔
57
               result.test_u8_eq("took hello", hello[0], uint8_t('h'));
1 ✔
58
               result.test_u8_eq("took hello", hello[1], uint8_t('e'));
1 ✔
59
               result.test_u8_eq("took hello", hello[2], uint8_t('l'));
1 ✔
60
               result.test_u8_eq("took hello", hello[3], uint8_t('l'));
1 ✔
61
               result.test_u8_eq("took hello", hello[4], uint8_t('o'));
1 ✔
62

63
               result.test_sz_eq("remaining bytes", s.remaining(), 8);
1 ✔
64

65
               s.skip(1);
1 ✔
66
               result.test_sz_eq("remaining bytes", s.remaining(), 7);
1 ✔
67

68
               const auto wor = s.copy_as_vector(3);
1 ✔
69
               result.require("has 3 bytes", wor.size() == 3);
1 ✔
70
               result.test_u8_eq("took wor...", wor[0], uint8_t('w'));
1 ✔
71
               result.test_u8_eq("took wor...", wor[1], uint8_t('o'));
1 ✔
72
               result.test_u8_eq("took wor...", wor[2], uint8_t('r'));
1 ✔
73
               result.test_sz_eq("remaining bytes", s.remaining(), 4);
1 ✔
74

75
               std::vector<uint8_t> ld(2);
1 ✔
76
               s.copy_into(ld);
1 ✔
77
               result.test_u8_eq("took ...ld", ld[0], uint8_t('l'));
1 ✔
78
               result.test_u8_eq("took ...ld", ld[1], uint8_t('d'));
1 ✔
79
               result.test_sz_eq("remaining bytes", s.remaining(), 2);
1 ✔
80

81
               s.skip(1);
1 ✔
82
               result.test_sz_eq("remaining bytes", s.remaining(), 1);
1 ✔
83

84
               const auto exclaim = s.take<1>();
1 ✔
85
               result.test_u8_eq("took ...!", exclaim[0], uint8_t('!'));
1 ✔
86
               result.test_is_true("has static extent", decltype(exclaim)::extent != std::dynamic_extent);
1 ✔
87
               result.test_is_true("static extent is 1", decltype(exclaim)::extent == 1);
1 ✔
88

89
               result.test_is_true("empty", s.empty());
1 ✔
90
               result.test_sz_eq("nothing remaining", s.remaining(), 0);
1 ✔
91

92
               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
2 ✔
93
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
2 ✔
94
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
2 ✔
95
            }),
2 ✔
96

97
      CHECK("Strong type support",
98
            [](auto& result) {
1 ✔
99
               const Botan::secure_vector<uint8_t> secure_buffer{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
1 ✔
100
               Botan::BufferSlicer s(secure_buffer);
1 ✔
101

102
               auto span1 = s.take(1);
1 ✔
103
               auto span2 = s.take<StrongBuffer>(2);
1 ✔
104
               auto vec1 = s.copy<StrongBuffer>(2);
1 ✔
105
               auto vec2 = s.copy_as_vector(2);
1 ✔
106
               auto vec3 = s.copy_as_secure_vector(2);
1 ✔
107
               StrongBuffer vec4(s.remaining());
1 ✔
108
               s.copy_into(vec4);
1 ✔
109

110
               const auto reproduce = Botan::concat<std::vector<uint8_t>>(span1, span2, vec1, vec2, vec3, vec4);
1 ✔
111
               result.test_bin_eq("sliced into various types", reproduce, secure_buffer);
1 ✔
112
            }),
6 ✔
113
   };
4 ✔
114
}
1 ✔
115

116
std::vector<Test::Result> test_buffer_stuffer() {
1 ✔
117
   return {
1 ✔
118
      CHECK("Empty BufferStuffer",
119
            [](auto& result) {
1 ✔
120
               std::vector<uint8_t> empty_buffer;
1 ✔
121
               Botan::BufferStuffer s(empty_buffer);
1 ✔
122

123
               result.test_sz_eq("has no capacity", s.remaining_capacity(), 0);
1 ✔
124
               result.test_is_true("is immediately full", s.full());
1 ✔
125
               result.test_is_true("can next() 0 bytes", s.next(0).empty());
1 ✔
126

127
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
2 ✔
128
               result.test_throws("cannot append bytes", [&]() {
2 ✔
129
                  std::vector<uint8_t> some_bytes(42);
1 ✔
130
                  s.append(some_bytes);
1 ✔
131
               });
×
132
            }),
1 ✔
133

134
      CHECK("Fill BufferStuffer",
135
            [](auto& result) {
1 ✔
136
               std::vector<uint8_t> sink(13);
1 ✔
137
               Botan::BufferStuffer s(sink);
1 ✔
138

139
               result.test_sz_eq("has some capacity", s.remaining_capacity(), sink.size());
1 ✔
140
               result.test_is_true("is not full", !s.full());
2 ✔
141

142
               const auto n1 = s.next(5);
1 ✔
143
               result.require("got requested bytes", n1.size() == 5);
1 ✔
144
               n1[0] = 'h';
1 ✔
145
               n1[1] = 'e';
1 ✔
146
               n1[2] = 'l';
1 ✔
147
               n1[3] = 'l';
1 ✔
148
               n1[4] = 'o';
1 ✔
149

150
               auto n2 = s.next<StrongBuffer>(3);
1 ✔
151
               result.require("got requested bytes", n2.size() == 3);
1 ✔
152

153
               n2.get()[0] = ' ';
1 ✔
154
               n2.get()[1] = 'w';
1 ✔
155
               n2.get()[2] = 'o';
1 ✔
156

157
               result.test_sz_eq("has 5 bytes remaining", s.remaining_capacity(), 5);
1 ✔
158

159
               std::vector<uint8_t> rld{'r', 'l', 'd'};
1 ✔
160
               s.append(rld);
1 ✔
161

162
               result.test_sz_eq("has 2 bytes remaining", s.remaining_capacity(), 2);
1 ✔
163

164
               const auto n3 = s.next<2>();
1 ✔
165
               result.require("got requested bytes", n3.size() == 2);
1 ✔
166
               result.require("is static extent", decltype(n3)::extent != std::dynamic_extent);
1 ✔
167
               result.require("static extent is 2", decltype(n3)::extent == 2);
1 ✔
168

169
               n3[0] = ' ';
1 ✔
170
               n3[1] = '!';
1 ✔
171

172
               result.test_is_true("is full", s.full());
1 ✔
173

174
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
2 ✔
175
               result.test_throws("cannot append bytes", [&]() {
2 ✔
176
                  std::vector<uint8_t> some_bytes(42);
1 ✔
177
                  s.append(some_bytes);
1 ✔
178
               });
×
179

180
               const std::string final_string(Botan::cast_uint8_ptr_to_char(sink.data()), sink.size());
1 ✔
181
               result.test_str_eq("final buffer is as expected", final_string, "hello world !");
1 ✔
182
            }),
3 ✔
183
   };
3 ✔
184
}
1 ✔
185

186
std::vector<Test::Result> test_alignment_buffer() {
1 ✔
187
   const std::array<uint8_t, 32> data = {1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16,
1 ✔
188
                                         17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
189
   const std::array<uint8_t, 16> first_half_data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
1 ✔
190
   const std::array<uint8_t, 16> second_half_data = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
1 ✔
191

192
   return {
1 ✔
193
      CHECK("Fresh Alignment Buffer",
194
            [](auto& result) {
1 ✔
195
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
196
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
197
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1 ✔
198
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1 ✔
199
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
200
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
201
            }),
1 ✔
202

203
      CHECK("Fill Alignment Buffer",
204
            [=](auto& result) {
1 ✔
205
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
206

207
               b.append(first_half_data);
1 ✔
208

209
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
210
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1 ✔
211
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1 ✔
212
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
213
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
214

215
               b.append(second_half_data);
1 ✔
216

217
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
218
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
1 ✔
219
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1 ✔
220
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
221
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
222
            }),
1 ✔
223

224
      CHECK("Consume Alignment Buffer",
225
            [=](auto& result) {
1 ✔
226
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
227

228
               b.append(data);
1 ✔
229

230
               result.require("ready_to_consume()", b.ready_to_consume());
1 ✔
231
               const auto out = b.consume();
1 ✔
232

233
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
234
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1 ✔
235
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1 ✔
236
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
237
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
238

239
               result.test_bin_eq("in == out", data, out);
1 ✔
240
            }),
1 ✔
241

242
      CHECK("Consume partially filled Alignment Buffer",
243
            [=](auto& result) {
1 ✔
244
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
245

246
               result.require("!ready_to_consume()", !b.ready_to_consume());
1 ✔
247
               const auto out_empty = b.consume_partial();
1 ✔
248
               result.test_sz_eq("consuming nothing resets buffer", b.elements_in_buffer(), 0);
1 ✔
249
               result.test_is_true("consumed empty buffer", out_empty.empty());
1 ✔
250

251
               b.append(first_half_data);
1 ✔
252
               result.require("!ready_to_consume()", !b.ready_to_consume());
1 ✔
253
               const auto out_half = b.consume_partial();
1 ✔
254
               result.test_sz_eq("consumed half-data resets buffer", b.elements_in_buffer(), 0);
1 ✔
255
               result.test_bin_eq("half-in == half-out", out_half, first_half_data);
1 ✔
256

257
               b.append(data);
1 ✔
258
               result.require("ready_to_consume()", b.ready_to_consume());
1 ✔
259
               const auto out_full = b.consume_partial();
1 ✔
260
               result.test_sz_eq("consumed full-data resets buffer", b.elements_in_buffer(), 0);
1 ✔
261
               result.test_bin_eq("full-in == full-out", out_full, data);
1 ✔
262
            }),
1 ✔
263

264
      CHECK("Clear Alignment Buffer",
265
            [=](auto& result) {
1 ✔
266
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
267

268
               b.append(first_half_data);
1 ✔
269

270
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
1 ✔
271
               b.clear();
1 ✔
272

273
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
274
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1 ✔
275
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1 ✔
276
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
277
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
278
            }),
1 ✔
279

280
      CHECK("Add Zero-Padding to Alignment Buffer",
281
            [=](auto& result) {
1 ✔
282
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
283

284
               b.append(first_half_data);
1 ✔
285

286
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
1 ✔
287
               b.fill_up_with_zeros();
1 ✔
288

289
               result.test_sz_eq("size()", b.size(), 32);
1 ✔
290
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
1 ✔
291
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1 ✔
292
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
293
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
294

295
               const auto out = b.consume();
1 ✔
296

297
               result.test_bin_eq("prefix", out.first(16), first_half_data);
1 ✔
298
               result.test_bin_eq("zero-padding", out.last(16), std::vector<uint8_t>(16, 0));
1 ✔
299

300
               // Regression test for GH #3734
301
               // fill_up_with_zeros() must work if called on a full (ready to consume) alignment buffer
302
               b.append(data);
1 ✔
303
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
304
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1 ✔
305

306
               b.fill_up_with_zeros();
1 ✔
307
               const auto out_without_padding = b.consume();
1 ✔
308

309
               result.test_bin_eq("no padding", out_without_padding, data);
1 ✔
310
            }),
1 ✔
311

312
      CHECK("Handle unaligned data in Alignment Buffer (no block-defer)",
313
            [=](auto& result) {
1 ✔
314
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
315

316
               Botan::BufferSlicer first_half(first_half_data);
1 ✔
317
               Botan::BufferSlicer second_half(second_half_data);
1 ✔
318

319
               const auto r1 = b.handle_unaligned_data(first_half);
1 ✔
320
               result.test_is_true("half a block is not returned", !r1.has_value());
1 ✔
321
               result.test_is_true("first input is consumed", first_half.empty());
1 ✔
322

323
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1 ✔
324
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1 ✔
325
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
326
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
327

328
               const auto r2 = b.handle_unaligned_data(second_half);
1 ✔
329
               result.require("second half completes block", r2.has_value());
1 ✔
330
               result.test_is_true("second input is consumed", second_half.empty());
1 ✔
331

332
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1 ✔
333
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1 ✔
334
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
335
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
336

337
               result.test_bin_eq("collected block is correct", r2.value(), data);
1 ✔
338
            }),
1 ✔
339

340
      CHECK("Aligned data is not buffered unnecessarily (no block-defer)",
341
            [=](auto& result) {
1 ✔
342
               Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
343

344
               Botan::BufferSlicer full_block_1(data);
1 ✔
345
               const auto r1 = b.handle_unaligned_data(full_block_1);
1 ✔
346
               result.test_is_true("aligned data is not buffered", !r1.has_value());
1 ✔
347
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
348
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
349
               result.test_sz_eq("aligned data is not consumed", full_block_1.remaining(), 32);
1 ✔
350

351
               Botan::BufferSlicer half_block(first_half_data);
1 ✔
352
               Botan::BufferSlicer full_block_2(data);
1 ✔
353
               const auto r2 = b.handle_unaligned_data(half_block);
1 ✔
354
               result.test_is_true("unaligned data is buffered", !r2.has_value());
1 ✔
355
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
356
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
357
               result.test_is_true("unaligned data is consumed", half_block.empty());
1 ✔
358

359
               const auto r3 = b.handle_unaligned_data(full_block_2);
1 ✔
360
               result.test_is_true("collected block is consumed", r3.has_value());
1 ✔
361
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
362
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
363
               result.test_sz_eq("input is consumed until alignment", full_block_2.remaining(), 16);
1 ✔
364
            }),
1 ✔
365

366
      CHECK("Handle unaligned data in Alignment Buffer (with block-defer)",
367
            [=](auto& result) {
1 ✔
368
               Botan::AlignmentBuffer<uint8_t, 32, Botan::AlignmentBufferFinalBlock::must_be_deferred> b;
1 ✔
369

370
               Botan::BufferSlicer first_half(first_half_data);
1 ✔
371
               Botan::BufferSlicer second_half(second_half_data);
1 ✔
372
               Botan::BufferSlicer third_half(first_half_data);
1 ✔
373

374
               const auto r1 = b.handle_unaligned_data(first_half);
1 ✔
375
               result.test_is_true("half a block is not returned", !r1.has_value());
1 ✔
376
               result.test_is_true("first input is consumed", first_half.empty());
1 ✔
377

378
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1 ✔
379
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1 ✔
380
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
381
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
382

383
               const auto r2 = b.handle_unaligned_data(second_half);
1 ✔
384
               result.require("second half completes block but is not returned", !r2.has_value());
1 ✔
385
               result.test_is_true("second input is consumed", second_half.empty());
1 ✔
386

387
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
1 ✔
388
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1 ✔
389
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
390
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
391

392
               const auto r3 = b.handle_unaligned_data(third_half);
1 ✔
393
               result.require("extra data pushes out block", r3.has_value());
1 ✔
394
               result.test_sz_eq("third input is not consumed", third_half.remaining(), 16);
1 ✔
395

396
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1 ✔
397
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1 ✔
398
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
399
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
400

401
               result.test_bin_eq("collected block is correct", r3.value(), data);
1 ✔
402
            }),
1 ✔
403

404
      CHECK("Aligned data is not buffered unnecessarily (with block-defer)",
405
            [=](auto& result) {
1 ✔
406
               Botan::AlignmentBuffer<uint8_t, 32, Botan::AlignmentBufferFinalBlock::must_be_deferred> b;
1 ✔
407

408
               Botan::BufferSlicer full_block_1(data);
1 ✔
409
               const auto r1 = b.handle_unaligned_data(full_block_1);
1 ✔
410
               result.test_is_true("exactly aligned data is buffered", !r1.has_value());
1 ✔
411
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
412
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
413
               result.test_is_true("exactly aligned block is consumed", full_block_1.empty());
1 ✔
414

415
               Botan::BufferSlicer empty_input({});
1 ✔
416
               const auto r2 = b.handle_unaligned_data(empty_input);
1 ✔
417
               result.require("empty input does not push out buffer", !r2.has_value());
1 ✔
418
               result.test_is_true("!in_alignment()", !b.in_alignment());
1 ✔
419
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1 ✔
420

421
               const uint8_t extra_byte = 1;
1 ✔
422
               Botan::BufferSlicer one_extra_byte({&extra_byte, 1});
1 ✔
423
               const auto r3 = b.handle_unaligned_data(one_extra_byte);
1 ✔
424
               result.require("more data pushes out buffer", r3.has_value());
1 ✔
425
               result.test_is_true("in_alignment()", b.in_alignment());
1 ✔
426
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1 ✔
427
               result.test_sz_eq("no input data is consumed", one_extra_byte.remaining(), 1);
1 ✔
428

429
               result.test_bin_eq("collected block is correct", r3.value(), data);
1 ✔
430
            }),
1 ✔
431

432
      CHECK("Aligned data passthrough (no block-defer)",
433
            [=](auto& result) {
1 ✔
434
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
435
               result.require("buffer is in alignment", b.in_alignment());
1 ✔
436

437
               Botan::BufferSlicer half_block(first_half_data);
1 ✔
438
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1 ✔
439
               result.test_is_true("not enough data for alignment processing", s1.empty());
1 ✔
440
               result.test_sz_eq("not enough data for alignment processing", r1, 0);
1 ✔
441
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1 ✔
442

443
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1 ✔
444
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1 ✔
445
               const auto [s2, r2] = b.aligned_data_to_process(one_and_a_half_block);
1 ✔
446
               result.test_sz_eq("data of one block for processing", s2.size(), 32);
1 ✔
447
               result.test_sz_eq("one block for processing", r2, 1);
1 ✔
448
               result.test_bin_eq("one block for processing", s2, data);
1 ✔
449
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1 ✔
450

451
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1 ✔
452
               Botan::BufferSlicer two_blocks(two_blocks_data);
1 ✔
453
               const auto [s3, r3] = b.aligned_data_to_process(two_blocks);
1 ✔
454
               result.test_sz_eq("data of two block for processing", s3.size(), 64);
1 ✔
455
               result.test_sz_eq("two blocks for processing", r3, 2);
1 ✔
456
               result.test_bin_eq("two blocks for processing", s3, two_blocks_data);
1 ✔
457
               result.test_sz_eq("aligned data is fully consumed", two_blocks.remaining(), 0);
1 ✔
458
            }),
2 ✔
459

460
      CHECK("Aligned data blockwise (no block-defer)",
461
            [=](auto& result) {
1 ✔
462
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1 ✔
463
               result.require("buffer is in alignment", b.in_alignment());
1 ✔
464

465
               Botan::BufferSlicer half_block(first_half_data);
1 ✔
466
               const auto s1 = b.next_aligned_block_to_process(half_block);
1 ✔
467
               result.test_is_true("not enough data for alignment processing", !s1.has_value());
1 ✔
468
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1 ✔
469

470
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1 ✔
471
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1 ✔
472
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1 ✔
473
               result.require("one block for processing", s2.has_value());
1 ✔
474
               result.test_sz_eq("data of one block for processing", s2->size(), 32);
1 ✔
475
               result.test_bin_eq("sliced data", s2.value(), data);
1 ✔
476
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1 ✔
477

478
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1 ✔
479
               Botan::BufferSlicer two_blocks(two_blocks_data);
1 ✔
480
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1 ✔
481
               result.require("first block for processing", s3_1.has_value());
1 ✔
482
               result.test_sz_eq("data of first block for processing", s3_1->size(), 32);
1 ✔
483
               result.test_bin_eq("sliced data", s3_1.value(), data);
1 ✔
484
               result.test_sz_eq("first block is consumed", two_blocks.remaining(), 32);
1 ✔
485

486
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1 ✔
487
               result.require("second block for processing", s3_2.has_value());
1 ✔
488
               result.test_sz_eq("data of second block for processing", s3_2->size(), 32);
1 ✔
489
               result.test_bin_eq("sliced data", s3_2.value(), data);
1 ✔
490
               result.test_sz_eq("second block is consumed", two_blocks.remaining(), 0);
1 ✔
491
            }),
2 ✔
492

493
      CHECK("Aligned data passthrough (with block-defer)",
494
            [=](auto& result) {
1 ✔
495
               const Botan::AlignmentBuffer<uint8_t, 32, Botan::AlignmentBufferFinalBlock::must_be_deferred> b;
1 ✔
496
               result.require("buffer is in alignment", b.in_alignment());
1 ✔
497

498
               Botan::BufferSlicer half_block(first_half_data);
1 ✔
499
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1 ✔
500
               result.test_is_true("not enough data for alignment processing", s1.empty());
1 ✔
501
               result.test_sz_eq("not enough data for alignment processing", r1, 0);
1 ✔
502
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1 ✔
503

504
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1 ✔
505
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1 ✔
506
               const auto [s2, r2] = b.aligned_data_to_process(one_and_a_half_block);
1 ✔
507
               result.test_sz_eq("data of one block for processing", s2.size(), 32);
1 ✔
508
               result.test_sz_eq("one block for processing", r2, 1);
1 ✔
509
               result.test_bin_eq("sliced data", s2, data);
1 ✔
510
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1 ✔
511

512
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1 ✔
513
               Botan::BufferSlicer two_blocks(two_blocks_data);
1 ✔
514
               const auto [s3, r3] = b.aligned_data_to_process(two_blocks);
1 ✔
515
               result.test_sz_eq("data of first block for processing", s3.size(), 32);
1 ✔
516
               result.test_sz_eq("one block for processing", r3, 1);
1 ✔
517
               result.test_bin_eq("sliced data", s3, data);
1 ✔
518
               result.test_sz_eq("aligned data is partially consumed", two_blocks.remaining(), 32);
1 ✔
519
            }),
2 ✔
520

521
      CHECK("Aligned data blockwise (with block-defer)",
522
            [=](auto& result) {
1 ✔
523
               const Botan::AlignmentBuffer<uint8_t, 32, Botan::AlignmentBufferFinalBlock::must_be_deferred> b;
1 ✔
524
               result.require("buffer is in alignment", b.in_alignment());
1 ✔
525

526
               Botan::BufferSlicer half_block(first_half_data);
1 ✔
527
               const auto s1 = b.next_aligned_block_to_process(half_block);
1 ✔
528
               result.test_is_true("not enough data for alignment processing", !s1.has_value());
1 ✔
529
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1 ✔
530

531
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1 ✔
532
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1 ✔
533
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1 ✔
534
               result.require("one block for processing", s2.has_value());
1 ✔
535
               result.test_sz_eq("data of one block for processing", s2->size(), 32);
1 ✔
536
               result.test_bin_eq("sliced data", s2.value(), data);
1 ✔
537
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1 ✔
538

539
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1 ✔
540
               Botan::BufferSlicer two_blocks(two_blocks_data);
1 ✔
541
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1 ✔
542
               result.require("first block for processing", s3_1.has_value());
1 ✔
543
               result.test_sz_eq("data of first block for processing", s3_1->size(), 32);
1 ✔
544
               result.test_bin_eq("sliced data", s3_1.value(), data);
1 ✔
545
               result.test_sz_eq("first block is consumed", two_blocks.remaining(), 32);
1 ✔
546

547
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1 ✔
548
               result.test_is_true("second block is not passed through", !s3_2.has_value());
1 ✔
549
               result.test_sz_eq("second block is not consumed", two_blocks.remaining(), 32);
1 ✔
550
            }),
2 ✔
551
   };
15 ✔
552
}
1 ✔
553

554
std::vector<Test::Result> test_concat() {
1 ✔
555
   return {
1 ✔
556
      CHECK("empty concat",
557
            [](Test::Result& result) {
1 ✔
558
               // only define a dynamic output type, but no input to be concat'ed
559
               const auto empty1 = Botan::concat<std::vector<uint8_t>>();
1 ✔
560
               result.test_is_true("empty concat 1", empty1.empty());
1 ✔
561

562
               // pass an empty input buffer to be concat'ed
563
               const auto empty2 = Botan::concat(std::vector<uint8_t>());
1 ✔
564
               result.test_is_true("empty concat 2", empty2.empty());
1 ✔
565

566
               // pass multiple empty input buffers to be concat'ed
567
               const auto empty3 = Botan::concat(std::vector<uint8_t>(), Botan::secure_vector<uint8_t>());
1 ✔
568
               result.test_is_true("empty concat 3", empty3.empty());
1 ✔
569

570
               // pass multiple empty input buffers to be concat'ed without auto-detection of the output buffer
571
               const auto empty4 = Botan::concat<std::array<uint8_t, 0>>(
1 ✔
572
                  std::vector<uint8_t>(), std::array<uint8_t, 0>(), Botan::secure_vector<uint8_t>());
1 ✔
573
               result.test_is_true("empty concat 4", empty4.empty());
1 ✔
574
            }),
1 ✔
575

576
      CHECK("auto-detected output type",
577
            [](Test::Result& result) {
1 ✔
578
               // define a static output type without any input parameters
579
               const auto t0 = Botan::concat<std::array<uint8_t, 0>>();
1 ✔
580
               result.test_is_true("type 0", std::is_same_v<std::array<uint8_t, 0>, std::remove_cvref_t<decltype(t0)>>);
1 ✔
581

582
               // define a dynamic output type without any input parameters
583
               const auto t1 = Botan::concat<std::vector<uint8_t>>();
1 ✔
584
               result.test_is_true("type 1", std::is_same_v<std::vector<uint8_t>, std::remove_cvref_t<decltype(t1)>>);
1 ✔
585

586
               // pass a single dynamic input buffer and auto-detect result type
587
               const auto t2 = Botan::concat(std::vector<uint8_t>());
1 ✔
588
               result.test_is_true("type 2", std::is_same_v<std::vector<uint8_t>, std::remove_cvref_t<decltype(t2)>>);
1 ✔
589

590
               // pass multiple dynamic input buffers and auto-detect result type
591
               const auto t3 = Botan::concat(Botan::secure_vector<uint8_t>(), std::vector<uint8_t>());
1 ✔
592
               result.test_is_true("type 3",
1 ✔
593
                                   std::is_same_v<Botan::secure_vector<uint8_t>, std::remove_cvref_t<decltype(t3)>>);
594

595
               // pass a mixture of dynamic and static input buffers and auto-detect result type
596
               const auto t4 =
1 ✔
597
                  Botan::concat(std::vector<uint8_t>(), std::array<uint8_t, 0>(), Botan::secure_vector<uint8_t>());
1 ✔
598
               result.test_is_true("type 4", std::is_same_v<std::vector<uint8_t>, std::remove_cvref_t<decltype(t4)>>);
1 ✔
599

600
               // pass only static input buffers and auto-detect result type
601
               const std::array<uint8_t, 5> some_buffer{};
1 ✔
602
               const auto t5 =
1 ✔
603
                  Botan::concat(std::array<uint8_t, 1>(), std::array<uint8_t, 3>(), std::span{some_buffer});
3 ✔
604
               result.test_is_true("type 5", std::is_same_v<std::array<uint8_t, 9>, std::remove_cvref_t<decltype(t5)>>);
1 ✔
605
            }),
1 ✔
606

607
      CHECK("concatenate",
608
            [](Test::Result& result) {
1 ✔
609
               constexpr std::array<uint8_t, 5> a1 = {1, 2, 3, 4, 5};
1 ✔
610
               const std::vector<uint8_t> v1{6, 7, 8, 9, 10};
1 ✔
611

612
               // concatenate a single buffer
613
               const auto concat0 = Botan::concat<Botan::secure_vector<uint8_t>>(v1);
1 ✔
614
               result.test_bin_eq("concat 0", concat0, "060708090A");
1 ✔
615

616
               // concatenate into an dynamically allocated output buffer
617
               const auto concat1 = Botan::concat<std::vector<uint8_t>>(a1, v1);
1 ✔
618
               result.test_bin_eq("concat 1", concat1, "0102030405060708090A");
1 ✔
619

620
               // concatenate into a statically sized output buffer
621
               const auto concat2 = Botan::concat<std::array<uint8_t, 10>>(v1, a1);
1 ✔
622
               result.test_bin_eq("concat 2", concat2, "060708090A0102030405");
1 ✔
623

624
               // concatenate into a statically sized output buffer, that is auto-detected
625
               const auto concat3 = Botan::concat(a1, std::span<const uint8_t, 5>(v1));
1 ✔
626
               result.test_bin_eq("concat 3", concat3, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
1 ✔
627
               result.test_is_true("correct type 3",
1 ✔
628
                                   std::is_same_v<std::array<uint8_t, 10>, std::remove_cvref_t<decltype(concat3)>>);
629

630
               // concatenate into a statically sized output buffer, that is auto-detected, at compile time
631
               constexpr auto concat4 = Botan::concat(a1, a1);
1 ✔
632
               result.test_bin_eq("concat 4", concat4, {1, 2, 3, 4, 5, 1, 2, 3, 4, 5});
1 ✔
633
               result.test_is_true("correct type 4",
1 ✔
634
                                   std::is_same_v<std::array<uint8_t, 10>, std::remove_cvref_t<decltype(concat4)>>);
635
            }),
3 ✔
636

637
      CHECK("dynamic length-check",
638
            [](Test::Result& result) {
1 ✔
639
               std::vector<uint8_t> v1{1, 2, 3, 4, 5};
1 ✔
640
               std::vector<uint8_t> v2{6, 7, 8, 9, 10};
1 ✔
641

642
               result.test_throws("concatenate into a statically sized type with insufficient space",
1 ✔
643
                                  [&]() { Botan::concat<std::array<uint8_t, 4>>(v1, v2); });
2 ✔
644
               result.test_throws("concatenate into a statically sized type with too much space",
1 ✔
645
                                  [&]() { Botan::concat<std::array<uint8_t, 20>>(v1, v2); });
2 ✔
646
            }),
2 ✔
647

648
      CHECK("concatenate strong types",
649
            [](Test::Result& result) {
1 ✔
650
               using StrongV = Botan::Strong<std::vector<uint8_t>, struct StrongV_>;
1 ✔
651
               using StrongA = Botan::Strong<std::array<uint8_t, 4>, struct StrongA_>;
1 ✔
652

653
               StrongV v1(std::vector<uint8_t>{1, 2, 3, 4, 5});
1 ✔
654
               StrongA a2;
1 ✔
655
               a2[0] = 6;
1 ✔
656
               a2[1] = 7;
1 ✔
657
               a2[2] = 8;
1 ✔
658
               a2[3] = 9;
1 ✔
659

660
               // concat strong types into a verbatim type
661
               auto concat1 = Botan::concat<std::vector<uint8_t>>(v1, a2);
1 ✔
662
               result.test_bin_eq("concat 1", concat1, "010203040506070809");
1 ✔
663

664
               // concat strong types into a dynamically allocated strong type
665
               auto concat2 = Botan::concat<StrongV>(a2, v1);
1 ✔
666
               result.test_bin_eq("concat 2", concat2.get(), "060708090102030405");
1 ✔
667
            }),
3 ✔
668
   };
6 ✔
669
}
1 ✔
670

671
BOTAN_REGISTER_TEST_FN(
672
   "utils", "buffer_utilities", test_buffer_slicer, test_buffer_stuffer, test_alignment_buffer, test_concat);
673

674
}  // namespace
675

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