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

randombit / botan / 21712952425

05 Feb 2026 01:16PM UTC coverage: 90.076% (+0.005%) from 90.071%
21712952425

Pull #5287

github

web-flow
Merge 1b320c06e into 8c9623340
Pull Request #5287: Split out BufferSlicer and BufferStuffer to their own headers

102242 of 113507 relevant lines covered (90.08%)

11534589.25 hits per line

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

99.57
/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/concepts.h>
11
#include <botan/mem_ops.h>
12
#include <botan/internal/alignment_buffer.h>
13
#include <botan/internal/buffer_stuffer.h>
14
#include <botan/internal/stl_util.h>
15

16
#include <array>
17

18
namespace Botan_Tests {
19

20
namespace {
21

22
template <typename T>
23
std::vector<uint8_t> v(const T& container) {
1✔
24
   return {container.begin(), container.end()};
26✔
25
}
26

27
using StrongBuffer = Botan::Strong<std::vector<uint8_t>, struct StrongBuffer_>;
28

29
std::vector<Test::Result> test_buffer_slicer() {
1✔
30
   return {
1✔
31
      CHECK("Empty BufferSlicer",
32
            [](auto& result) {
1✔
33
               const std::vector<uint8_t> buffer(0);
1✔
34
               Botan::BufferSlicer s(buffer);
1✔
35
               result.confirm("empty slicer has no remaining bytes", s.remaining() == 0);
2✔
36
               result.confirm("empty slicer is empty()", s.empty());
2✔
37
               result.confirm("empty slicer can take() 0 bytes", s.take(0).empty());
2✔
38

39
               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
3✔
40
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
3✔
41
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
4✔
42
            }),
1✔
43

44
      CHECK("Read from BufferSlicer",
45
            [](auto& result) {
1✔
46
               const std::vector<uint8_t> buffer{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', ' ', '!'};
1✔
47
               Botan::BufferSlicer s(buffer);
1✔
48

49
               result.test_eq("non-empty slicer has remaining bytes", s.remaining(), buffer.size());
2✔
50
               result.confirm("non-empty slicer is not empty()", !s.empty());
2✔
51

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

60
               result.test_eq("remaining bytes", s.remaining(), 8);
2✔
61

62
               s.skip(1);
1✔
63
               result.test_eq("remaining bytes", s.remaining(), 7);
2✔
64

65
               const auto wor = s.copy_as_vector(3);
1✔
66
               result.require("has 3 bytes", wor.size() == 3);
1✔
67
               result.test_is_eq("took wor...", wor[0], uint8_t('w'));
1✔
68
               result.test_is_eq("took wor...", wor[1], uint8_t('o'));
1✔
69
               result.test_is_eq("took wor...", wor[2], uint8_t('r'));
2✔
70
               result.test_eq("remaining bytes", s.remaining(), 4);
2✔
71

72
               std::vector<uint8_t> ld(2);
1✔
73
               s.copy_into(ld);
1✔
74
               result.test_is_eq("took ...ld", ld[0], uint8_t('l'));
1✔
75
               result.test_is_eq("took ...ld", ld[1], uint8_t('d'));
2✔
76
               result.test_eq("remaining bytes", s.remaining(), 2);
2✔
77

78
               s.skip(1);
1✔
79
               result.test_eq("remaining bytes", s.remaining(), 1);
1✔
80

81
               const auto exclaim = s.take<1>();
1✔
82
               result.test_is_eq("took ...!", exclaim[0], uint8_t('!'));
2✔
83
               result.confirm("has static extent", decltype(exclaim)::extent != std::dynamic_extent);
2✔
84
               result.confirm("static extent is 1", decltype(exclaim)::extent == 1);
2✔
85

86
               result.confirm("empty", s.empty());
2✔
87
               result.test_eq("nothing remaining", s.remaining(), 0);
1✔
88

89
               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
3✔
90
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
3✔
91
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
4✔
92
            }),
3✔
93

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

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

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

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

120
               result.test_eq("has no capacity", s.remaining_capacity(), 0);
2✔
121
               result.confirm("is immediately full", s.full());
2✔
122
               result.confirm("can next() 0 bytes", s.next(0).empty());
2✔
123

124
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
3✔
125
               result.test_throws("cannot append bytes", [&]() {
4✔
126
                  std::vector<uint8_t> some_bytes(42);
1✔
127
                  s.append(some_bytes);
1✔
128
               });
×
129
            }),
1✔
130

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

136
               result.test_eq("has some capacity", s.remaining_capacity(), sink.size());
2✔
137
               result.confirm("is not full", !s.full());
2✔
138

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

147
               auto n2 = s.next<StrongBuffer>(3);
1✔
148
               result.require("got requested bytes", n2.size() == 3);
1✔
149

150
               n2.get()[0] = ' ';
1✔
151
               n2.get()[1] = 'w';
1✔
152
               n2.get()[2] = 'o';
1✔
153

154
               result.test_eq("has 5 bytes remaining", s.remaining_capacity(), 5);
1✔
155

156
               std::vector<uint8_t> rld{'r', 'l', 'd'};
1✔
157
               s.append(rld);
1✔
158

159
               result.test_eq("has 2 bytes remaining", s.remaining_capacity(), 2);
1✔
160

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

166
               n3[0] = ' ';
1✔
167
               n3[1] = '!';
1✔
168

169
               result.confirm("is full", s.full());
2✔
170

171
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
3✔
172
               result.test_throws("cannot append bytes", [&]() {
4✔
173
                  std::vector<uint8_t> some_bytes(42);
1✔
174
                  s.append(some_bytes);
1✔
175
               });
×
176

177
               const std::string final_string(Botan::cast_uint8_ptr_to_char(sink.data()), sink.size());
1✔
178
               result.test_eq("final buffer is as expected", final_string, "hello world !");
2✔
179
            }),
3✔
180
   };
3✔
181
}
1✔
182

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

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

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

204
               b.append(first_half_data);
1✔
205

206
               result.test_eq("size()", b.size(), 32);
2✔
207
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
2✔
208
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
2✔
209
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
210
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
211

212
               b.append(second_half_data);
1✔
213

214
               result.test_eq("size()", b.size(), 32);
2✔
215
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
2✔
216
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
2✔
217
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
218
               result.confirm("ready_to_consume()", b.ready_to_consume());
2✔
219
            }),
1✔
220

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

225
               b.append(data);
1✔
226

227
               result.require("ready_to_consume()", b.ready_to_consume());
1✔
228
               const auto out = b.consume();
1✔
229

230
               result.test_eq("size()", b.size(), 32);
2✔
231
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
2✔
232
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
2✔
233
               result.confirm("in_alignment()", b.in_alignment());
2✔
234
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
235

236
               result.test_is_eq("in == out", v(data), v(out));
4✔
237
            }),
1✔
238

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

243
               result.require("!ready_to_consume()", !b.ready_to_consume());
2✔
244
               const auto out_empty = b.consume_partial();
1✔
245
               result.test_eq("consuming nothing resets buffer", b.elements_in_buffer(), 0);
2✔
246
               result.confirm("consumed empty buffer", out_empty.empty());
2✔
247

248
               b.append(first_half_data);
1✔
249
               result.require("!ready_to_consume()", !b.ready_to_consume());
2✔
250
               const auto out_half = b.consume_partial();
1✔
251
               result.test_eq("consumed half-data resets buffer", b.elements_in_buffer(), 0);
1✔
252
               result.test_is_eq("half-in == half-out", v(out_half), v(first_half_data));
3✔
253

254
               b.append(data);
1✔
255
               result.require("ready_to_consume()", b.ready_to_consume());
2✔
256
               const auto out_full = b.consume_partial();
1✔
257
               result.test_eq("consumed full-data resets buffer", b.elements_in_buffer(), 0);
1✔
258
               result.test_is_eq("full-in == full-out", v(out_full), v(data));
4✔
259
            }),
1✔
260

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

265
               b.append(first_half_data);
1✔
266

267
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
2✔
268
               b.clear();
1✔
269

270
               result.test_eq("size()", b.size(), 32);
2✔
271
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
2✔
272
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
2✔
273
               result.confirm("in_alignment()", b.in_alignment());
2✔
274
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
275
            }),
1✔
276

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

281
               b.append(first_half_data);
1✔
282

283
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
2✔
284
               b.fill_up_with_zeros();
1✔
285

286
               result.test_eq("size()", b.size(), 32);
2✔
287
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
2✔
288
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
2✔
289
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
290
               result.confirm("ready_to_consume()", b.ready_to_consume());
2✔
291

292
               const auto out = b.consume();
1✔
293

294
               result.test_is_eq("prefix", v(out.first(16)), v(first_half_data));
4✔
295
               result.test_is_eq("zero-padding", v(out.last(16)), std::vector<uint8_t>(16, 0));
3✔
296

297
               // Regression test for GH #3734
298
               // fill_up_with_zeros() must work if called on a full (ready to consume) alignment buffer
299
               b.append(data);
1✔
300
               result.confirm("ready_to_consume()", b.ready_to_consume());
2✔
301
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
2✔
302

303
               b.fill_up_with_zeros();
1✔
304
               const auto out_without_padding = b.consume();
1✔
305

306
               result.test_is_eq("no padding", v(out_without_padding), v(data));
4✔
307
            }),
1✔
308

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

313
               Botan::BufferSlicer first_half(first_half_data);
1✔
314
               Botan::BufferSlicer second_half(second_half_data);
1✔
315

316
               const auto r1 = b.handle_unaligned_data(first_half);
1✔
317
               result.confirm("half a block is not returned", !r1.has_value());
2✔
318
               result.confirm("first input is consumed", first_half.empty());
2✔
319

320
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
2✔
321
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
2✔
322
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
323
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
324

325
               const auto r2 = b.handle_unaligned_data(second_half);
1✔
326
               result.require("second half completes block", r2.has_value());
2✔
327
               result.confirm("second input is consumed", second_half.empty());
2✔
328

329
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
2✔
330
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
2✔
331
               result.confirm("in_alignment()", b.in_alignment());
2✔
332
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
333

334
               result.test_is_eq("collected block is correct", v(r2.value()), v(data));
4✔
335
            }),
1✔
336

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

341
               Botan::BufferSlicer full_block_1(data);
1✔
342
               const auto r1 = b.handle_unaligned_data(full_block_1);
1✔
343
               result.confirm("aligned data is not buffered", !r1.has_value());
2✔
344
               result.confirm("in_alignment()", b.in_alignment());
2✔
345
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
346
               result.test_eq("aligned data is not consumed", full_block_1.remaining(), 32);
1✔
347

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

356
               const auto r3 = b.handle_unaligned_data(full_block_2);
1✔
357
               result.confirm("collected block is consumed", r3.has_value());
2✔
358
               result.confirm("in_alignment()", b.in_alignment());
2✔
359
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
360
               result.test_eq("input is consumed until alignment", full_block_2.remaining(), 16);
2✔
361
            }),
1✔
362

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

367
               Botan::BufferSlicer first_half(first_half_data);
1✔
368
               Botan::BufferSlicer second_half(second_half_data);
1✔
369
               Botan::BufferSlicer third_half(first_half_data);
1✔
370

371
               const auto r1 = b.handle_unaligned_data(first_half);
1✔
372
               result.confirm("half a block is not returned", !r1.has_value());
2✔
373
               result.confirm("first input is consumed", first_half.empty());
2✔
374

375
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
2✔
376
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
2✔
377
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
378
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
379

380
               const auto r2 = b.handle_unaligned_data(second_half);
1✔
381
               result.require("second half completes block but is not returned", !r2.has_value());
2✔
382
               result.confirm("second input is consumed", second_half.empty());
2✔
383

384
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
2✔
385
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
2✔
386
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
387
               result.confirm("ready_to_consume()", b.ready_to_consume());
2✔
388

389
               const auto r3 = b.handle_unaligned_data(third_half);
1✔
390
               result.require("extra data pushes out block", r3.has_value());
2✔
391
               result.test_eq("third input is not consumed", third_half.remaining(), 16);
2✔
392

393
               result.test_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
2✔
394
               result.test_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
2✔
395
               result.confirm("in_alignment()", b.in_alignment());
2✔
396
               result.confirm("!ready_to_consume()", !b.ready_to_consume());
2✔
397

398
               result.test_is_eq("collected block is correct", v(r3.value()), v(data));
4✔
399
            }),
1✔
400

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

405
               Botan::BufferSlicer full_block_1(data);
1✔
406
               const auto r1 = b.handle_unaligned_data(full_block_1);
1✔
407
               result.confirm("exactly aligned data is buffered", !r1.has_value());
2✔
408
               result.confirm("!in_alignment()", !b.in_alignment());
2✔
409
               result.confirm("ready_to_consume()", b.ready_to_consume());
2✔
410
               result.confirm("exactly aligned block is consumed", full_block_1.empty());
2✔
411

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

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

426
               result.test_is_eq("collected block is correct", v(r3.value()), v(data));
4✔
427
            }),
1✔
428

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

434
               Botan::BufferSlicer half_block(first_half_data);
1✔
435
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1✔
436
               result.confirm("not enough data for alignment processing", s1.empty());
2✔
437
               result.test_eq("not enough data for alignment processing", r1, 0);
2✔
438
               result.test_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
439

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

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

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

462
               Botan::BufferSlicer half_block(first_half_data);
1✔
463
               const auto s1 = b.next_aligned_block_to_process(half_block);
1✔
464
               result.confirm("not enough data for alignment processing", !s1.has_value());
2✔
465
               result.test_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
466

467
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
468
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
469
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1✔
470
               result.require("one block for processing", s2.has_value());
2✔
471
               result.test_eq("data of one block for processing", s2->size(), 32);
1✔
472
               result.test_is_eq(v(s2.value()), v(data));
3✔
473
               result.test_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
474

475
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
476
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
477
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1✔
478
               result.require("first block for processing", s3_1.has_value());
2✔
479
               result.test_eq("data of first block for processing", s3_1->size(), 32);
1✔
480
               result.test_is_eq(v(s3_1.value()), v(data));
3✔
481
               result.test_eq("first block is consumed", two_blocks.remaining(), 32);
1✔
482

483
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1✔
484
               result.require("second block for processing", s3_2.has_value());
2✔
485
               result.test_eq("data of second block for processing", s3_2->size(), 32);
1✔
486
               result.test_is_eq(v(s3_2.value()), v(data));
3✔
487
               result.test_eq("second block is consumed", two_blocks.remaining(), 0);
2✔
488
            }),
2✔
489

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

495
               Botan::BufferSlicer half_block(first_half_data);
1✔
496
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1✔
497
               result.confirm("not enough data for alignment processing", s1.empty());
2✔
498
               result.test_eq("not enough data for alignment processing", r1, 0);
2✔
499
               result.test_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
500

501
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
502
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
503
               const auto [s2, r2] = b.aligned_data_to_process(one_and_a_half_block);
1✔
504
               result.test_eq("data of one block for processing", s2.size(), 32);
1✔
505
               result.test_eq("one block for processing", r2, 1);
1✔
506
               result.test_is_eq(v(s2), v(data));
3✔
507
               result.test_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
508

509
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
510
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
511
               const auto [s3, r3] = b.aligned_data_to_process(two_blocks);
1✔
512
               result.test_eq("data of first block for processing", s3.size(), 32);
1✔
513
               result.test_eq("one block for processing", r3, 1);
1✔
514
               result.test_is_eq(v(s3), v(data));
3✔
515
               result.test_eq("aligned data is partially consumed", two_blocks.remaining(), 32);
2✔
516
            }),
2✔
517

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

523
               Botan::BufferSlicer half_block(first_half_data);
1✔
524
               const auto s1 = b.next_aligned_block_to_process(half_block);
1✔
525
               result.confirm("not enough data for alignment processing", !s1.has_value());
2✔
526
               result.test_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
527

528
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
529
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
530
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1✔
531
               result.require("one block for processing", s2.has_value());
2✔
532
               result.test_eq("data of one block for processing", s2->size(), 32);
1✔
533
               result.test_is_eq(v(s2.value()), v(data));
3✔
534
               result.test_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
535

536
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
537
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
538
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1✔
539
               result.require("first block for processing", s3_1.has_value());
2✔
540
               result.test_eq("data of first block for processing", s3_1->size(), 32);
1✔
541
               result.test_is_eq(v(s3_1.value()), v(data));
3✔
542
               result.test_eq("first block is consumed", two_blocks.remaining(), 32);
1✔
543

544
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1✔
545
               result.confirm("second block is not passed through", !s3_2.has_value());
2✔
546
               result.test_eq("second block is not consumed", two_blocks.remaining(), 32);
2✔
547
            }),
2✔
548
   };
15✔
549
}
1✔
550

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

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

563
               // pass multiple empty input buffers to be concat'ed
564
               const auto empty3 = Botan::concat(std::vector<uint8_t>(), Botan::secure_vector<uint8_t>());
1✔
565
               result.confirm("empty concat 3", empty3.empty());
2✔
566

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

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

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

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

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

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

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

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

608
               // concatenate a single buffer
609
               const auto concat0 = Botan::concat<Botan::secure_vector<uint8_t>>(v1);
1✔
610
               result.test_is_eq("concat 0", concat0, {6, 7, 8, 9, 10});
2✔
611

612
               // concatenate into an dynamically allocated output buffer
613
               const auto concat1 = Botan::concat<std::vector<uint8_t>>(a1, v1);
1✔
614
               result.test_is_eq("concat 1", concat1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
2✔
615

616
               // concatenate into a statically sized output buffer
617
               const auto concat2 = Botan::concat<std::array<uint8_t, 10>>(v1, a1);
1✔
618
               result.test_is_eq("concat 2", concat2, {6, 7, 8, 9, 10, 1, 2, 3, 4, 5});
1✔
619

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

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

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

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

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

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

656
               // concat strong types into a verbatim type
657
               auto concat1 = Botan::concat<std::vector<uint8_t>>(v1, a2);
1✔
658
               result.test_is_eq("concat 1", concat1, {1, 2, 3, 4, 5, 6, 7, 8, 9});
2✔
659

660
               // concat strong types into a dynamically allocated strong type
661
               auto concat2 = Botan::concat<StrongV>(a2, v1);
1✔
662
               result.test_is_eq("concat 2", concat2.get(), {6, 7, 8, 9, 1, 2, 3, 4, 5});
3✔
663
            }),
3✔
664
   };
6✔
665
}
1✔
666

667
BOTAN_REGISTER_TEST_FN(
668
   "utils", "buffer_utilities", test_buffer_slicer, test_buffer_stuffer, test_alignment_buffer, test_concat);
669

670
}  // namespace
671

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

© 2026 Coveralls, Inc