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

randombit / botan / 22039041177

15 Feb 2026 04:24PM UTC coverage: 90.054% (-0.005%) from 90.059%
22039041177

push

github

web-flow
Merge pull request #5341 from randombit/jack/test-h-bin

Cleanup test predicates on binary strings

102341 of 113644 relevant lines covered (90.05%)

11487063.99 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/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 hello = s.take(5);
1✔
47
               result.require("has 5 bytes", hello.size() == 5);
1✔
48
               result.test_u8_eq("took hello", hello[0], uint8_t('h'));
1✔
49
               result.test_u8_eq("took hello", hello[1], uint8_t('e'));
1✔
50
               result.test_u8_eq("took hello", hello[2], uint8_t('l'));
1✔
51
               result.test_u8_eq("took hello", hello[3], uint8_t('l'));
1✔
52
               result.test_u8_eq("took hello", hello[4], uint8_t('o'));
1✔
53

54
               result.test_sz_eq("remaining bytes", s.remaining(), 8);
1✔
55

56
               s.skip(1);
1✔
57
               result.test_sz_eq("remaining bytes", s.remaining(), 7);
1✔
58

59
               const auto wor = s.copy_as_vector(3);
1✔
60
               result.require("has 3 bytes", wor.size() == 3);
1✔
61
               result.test_u8_eq("took wor...", wor[0], uint8_t('w'));
1✔
62
               result.test_u8_eq("took wor...", wor[1], uint8_t('o'));
1✔
63
               result.test_u8_eq("took wor...", wor[2], uint8_t('r'));
1✔
64
               result.test_sz_eq("remaining bytes", s.remaining(), 4);
1✔
65

66
               std::vector<uint8_t> ld(2);
1✔
67
               s.copy_into(ld);
1✔
68
               result.test_u8_eq("took ...ld", ld[0], uint8_t('l'));
1✔
69
               result.test_u8_eq("took ...ld", ld[1], uint8_t('d'));
1✔
70
               result.test_sz_eq("remaining bytes", s.remaining(), 2);
1✔
71

72
               s.skip(1);
1✔
73
               result.test_sz_eq("remaining bytes", s.remaining(), 1);
1✔
74

75
               const auto exclaim = s.take<1>();
1✔
76
               result.test_u8_eq("took ...!", exclaim[0], uint8_t('!'));
1✔
77
               result.test_is_true("has static extent", decltype(exclaim)::extent != std::dynamic_extent);
1✔
78
               result.test_is_true("static extent is 1", decltype(exclaim)::extent == 1);
1✔
79

80
               result.test_is_true("empty", s.empty());
1✔
81
               result.test_sz_eq("nothing remaining", s.remaining(), 0);
1✔
82

83
               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
2✔
84
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
2✔
85
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
2✔
86
            }),
2✔
87

88
      CHECK("Strong type support",
89
            [](auto& result) {
1✔
90
               const Botan::secure_vector<uint8_t> secure_buffer{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
1✔
91
               Botan::BufferSlicer s(secure_buffer);
1✔
92

93
               auto span1 = s.take(1);
1✔
94
               auto span2 = s.take<StrongBuffer>(2);
1✔
95
               auto vec1 = s.copy<StrongBuffer>(2);
1✔
96
               auto vec2 = s.copy_as_vector(2);
1✔
97
               auto vec3 = s.copy_as_secure_vector(2);
1✔
98
               StrongBuffer vec4(s.remaining());
1✔
99
               s.copy_into(vec4);
1✔
100

101
               const auto reproduce = Botan::concat<std::vector<uint8_t>>(span1, span2, vec1, vec2, vec3, vec4);
1✔
102
               result.test_bin_eq("sliced into various types", reproduce, secure_buffer);
1✔
103
            }),
6✔
104
   };
4✔
105
}
1✔
106

107
std::vector<Test::Result> test_buffer_stuffer() {
1✔
108
   return {
1✔
109
      CHECK("Empty BufferStuffer",
110
            [](auto& result) {
1✔
111
               std::vector<uint8_t> empty_buffer;
1✔
112
               Botan::BufferStuffer s(empty_buffer);
1✔
113

114
               result.test_sz_eq("has no capacity", s.remaining_capacity(), 0);
1✔
115
               result.test_is_true("is immediately full", s.full());
1✔
116
               result.test_is_true("can next() 0 bytes", s.next(0).empty());
1✔
117

118
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
2✔
119
               result.test_throws("cannot append bytes", [&]() {
2✔
120
                  std::vector<uint8_t> some_bytes(42);
1✔
121
                  s.append(some_bytes);
1✔
122
               });
×
123
            }),
1✔
124

125
      CHECK("Fill BufferStuffer",
126
            [](auto& result) {
1✔
127
               std::vector<uint8_t> sink(13);
1✔
128
               Botan::BufferStuffer s(sink);
1✔
129

130
               result.test_sz_eq("has some capacity", s.remaining_capacity(), sink.size());
1✔
131
               result.test_is_true("is not full", !s.full());
1✔
132

133
               auto n1 = s.next(5);
1✔
134
               result.require("got requested bytes", n1.size() == 5);
1✔
135
               n1[0] = 'h';
1✔
136
               n1[1] = 'e';
1✔
137
               n1[2] = 'l';
1✔
138
               n1[3] = 'l';
1✔
139
               n1[4] = 'o';
1✔
140

141
               auto n2 = s.next<StrongBuffer>(3);
1✔
142
               result.require("got requested bytes", n2.size() == 3);
1✔
143

144
               n2.get()[0] = ' ';
1✔
145
               n2.get()[1] = 'w';
1✔
146
               n2.get()[2] = 'o';
1✔
147

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

150
               std::vector<uint8_t> rld{'r', 'l', 'd'};
1✔
151
               s.append(rld);
1✔
152

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

155
               auto n3 = s.next<2>();
1✔
156
               result.require("got requested bytes", n3.size() == 2);
1✔
157
               result.require("is static extent", decltype(n3)::extent != std::dynamic_extent);
1✔
158
               result.require("static extent is 2", decltype(n3)::extent == 2);
1✔
159

160
               n3[0] = ' ';
1✔
161
               n3[1] = '!';
1✔
162

163
               result.test_is_true("is full", s.full());
1✔
164

165
               result.test_throws("cannot next() anything", [&]() { s.next(1); });
2✔
166
               result.test_throws("cannot append bytes", [&]() {
2✔
167
                  std::vector<uint8_t> some_bytes(42);
1✔
168
                  s.append(some_bytes);
1✔
169
               });
×
170

171
               const std::string final_string(Botan::cast_uint8_ptr_to_char(sink.data()), sink.size());
1✔
172
               result.test_str_eq("final buffer is as expected", final_string, "hello world !");
1✔
173
            }),
3✔
174
   };
3✔
175
}
1✔
176

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

183
   return {
1✔
184
      CHECK("Fresh Alignment Buffer",
185
            [](auto& result) {
1✔
186
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
187
               result.test_sz_eq("size()", b.size(), 32);
1✔
188
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1✔
189
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1✔
190
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
191
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
192
            }),
1✔
193

194
      CHECK("Fill Alignment Buffer",
195
            [=](auto& result) {
1✔
196
               Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
197

198
               b.append(first_half_data);
1✔
199

200
               result.test_sz_eq("size()", b.size(), 32);
1✔
201
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1✔
202
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1✔
203
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
204
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
205

206
               b.append(second_half_data);
1✔
207

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

215
      CHECK("Consume Alignment Buffer",
216
            [=](auto& result) {
1✔
217
               Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
218

219
               b.append(data);
1✔
220

221
               result.require("ready_to_consume()", b.ready_to_consume());
1✔
222
               const auto out = b.consume();
1✔
223

224
               result.test_sz_eq("size()", b.size(), 32);
1✔
225
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1✔
226
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1✔
227
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
228
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
229

230
               result.test_bin_eq("in == out", data, out);
1✔
231
            }),
1✔
232

233
      CHECK("Consume partially filled Alignment Buffer",
234
            [=](auto& result) {
1✔
235
               Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
236

237
               result.require("!ready_to_consume()", !b.ready_to_consume());
1✔
238
               const auto out_empty = b.consume_partial();
1✔
239
               result.test_sz_eq("consuming nothing resets buffer", b.elements_in_buffer(), 0);
1✔
240
               result.test_is_true("consumed empty buffer", out_empty.empty());
1✔
241

242
               b.append(first_half_data);
1✔
243
               result.require("!ready_to_consume()", !b.ready_to_consume());
1✔
244
               const auto out_half = b.consume_partial();
1✔
245
               result.test_sz_eq("consumed half-data resets buffer", b.elements_in_buffer(), 0);
1✔
246
               result.test_bin_eq("half-in == half-out", out_half, first_half_data);
1✔
247

248
               b.append(data);
1✔
249
               result.require("ready_to_consume()", b.ready_to_consume());
1✔
250
               const auto out_full = b.consume_partial();
1✔
251
               result.test_sz_eq("consumed full-data resets buffer", b.elements_in_buffer(), 0);
1✔
252
               result.test_bin_eq("full-in == full-out", out_full, data);
1✔
253
            }),
1✔
254

255
      CHECK("Clear Alignment Buffer",
256
            [=](auto& result) {
1✔
257
               Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
258

259
               b.append(first_half_data);
1✔
260

261
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
1✔
262
               b.clear();
1✔
263

264
               result.test_sz_eq("size()", b.size(), 32);
1✔
265
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1✔
266
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
1✔
267
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
268
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
269
            }),
1✔
270

271
      CHECK("Add Zero-Padding to Alignment Buffer",
272
            [=](auto& result) {
1✔
273
               Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
274

275
               b.append(first_half_data);
1✔
276

277
               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
1✔
278
               b.fill_up_with_zeros();
1✔
279

280
               result.test_sz_eq("size()", b.size(), 32);
1✔
281
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
1✔
282
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1✔
283
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
284
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1✔
285

286
               const auto out = b.consume();
1✔
287

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

291
               // Regression test for GH #3734
292
               // fill_up_with_zeros() must work if called on a full (ready to consume) alignment buffer
293
               b.append(data);
1✔
294
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1✔
295
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
1✔
296

297
               b.fill_up_with_zeros();
1✔
298
               const auto out_without_padding = b.consume();
1✔
299

300
               result.test_bin_eq("no padding", out_without_padding, data);
1✔
301
            }),
1✔
302

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

307
               Botan::BufferSlicer first_half(first_half_data);
1✔
308
               Botan::BufferSlicer second_half(second_half_data);
1✔
309

310
               const auto r1 = b.handle_unaligned_data(first_half);
1✔
311
               result.test_is_true("half a block is not returned", !r1.has_value());
1✔
312
               result.test_is_true("first input is consumed", first_half.empty());
1✔
313

314
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1✔
315
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1✔
316
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
317
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
318

319
               const auto r2 = b.handle_unaligned_data(second_half);
1✔
320
               result.require("second half completes block", r2.has_value());
1✔
321
               result.test_is_true("second input is consumed", second_half.empty());
1✔
322

323
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1✔
324
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
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
               result.test_bin_eq("collected block is correct", r2.value(), data);
1✔
329
            }),
1✔
330

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

335
               Botan::BufferSlicer full_block_1(data);
1✔
336
               const auto r1 = b.handle_unaligned_data(full_block_1);
1✔
337
               result.test_is_true("aligned data is not buffered", !r1.has_value());
1✔
338
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
339
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
340
               result.test_sz_eq("aligned data is not consumed", full_block_1.remaining(), 32);
1✔
341

342
               Botan::BufferSlicer half_block(first_half_data);
1✔
343
               Botan::BufferSlicer full_block_2(data);
1✔
344
               const auto r2 = b.handle_unaligned_data(half_block);
1✔
345
               result.test_is_true("unaligned data is buffered", !r2.has_value());
1✔
346
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
347
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
348
               result.test_is_true("unaligned data is consumed", half_block.empty());
1✔
349

350
               const auto r3 = b.handle_unaligned_data(full_block_2);
1✔
351
               result.test_is_true("collected block is consumed", r3.has_value());
1✔
352
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
353
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
354
               result.test_sz_eq("input is consumed until alignment", full_block_2.remaining(), 16);
1✔
355
            }),
1✔
356

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

361
               Botan::BufferSlicer first_half(first_half_data);
1✔
362
               Botan::BufferSlicer second_half(second_half_data);
1✔
363
               Botan::BufferSlicer third_half(first_half_data);
1✔
364

365
               const auto r1 = b.handle_unaligned_data(first_half);
1✔
366
               result.test_is_true("half a block is not returned", !r1.has_value());
1✔
367
               result.test_is_true("first input is consumed", first_half.empty());
1✔
368

369
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
1✔
370
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
1✔
371
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
372
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
373

374
               const auto r2 = b.handle_unaligned_data(second_half);
1✔
375
               result.require("second half completes block but is not returned", !r2.has_value());
1✔
376
               result.test_is_true("second input is consumed", second_half.empty());
1✔
377

378
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
1✔
379
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
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 r3 = b.handle_unaligned_data(third_half);
1✔
384
               result.require("extra data pushes out block", r3.has_value());
1✔
385
               result.test_sz_eq("third input is not consumed", third_half.remaining(), 16);
1✔
386

387
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
1✔
388
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
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
               result.test_bin_eq("collected block is correct", r3.value(), data);
1✔
393
            }),
1✔
394

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

399
               Botan::BufferSlicer full_block_1(data);
1✔
400
               const auto r1 = b.handle_unaligned_data(full_block_1);
1✔
401
               result.test_is_true("exactly aligned data is buffered", !r1.has_value());
1✔
402
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
403
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1✔
404
               result.test_is_true("exactly aligned block is consumed", full_block_1.empty());
1✔
405

406
               Botan::BufferSlicer empty_input({});
1✔
407
               const auto r2 = b.handle_unaligned_data(empty_input);
1✔
408
               result.require("empty input does not push out buffer", !r2.has_value());
1✔
409
               result.test_is_true("!in_alignment()", !b.in_alignment());
1✔
410
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
1✔
411

412
               const uint8_t extra_byte = 1;
1✔
413
               Botan::BufferSlicer one_extra_byte({&extra_byte, 1});
1✔
414
               const auto r3 = b.handle_unaligned_data(one_extra_byte);
1✔
415
               result.require("more data pushes out buffer", r3.has_value());
1✔
416
               result.test_is_true("in_alignment()", b.in_alignment());
1✔
417
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
1✔
418
               result.test_sz_eq("no input data is consumed", one_extra_byte.remaining(), 1);
1✔
419

420
               result.test_bin_eq("collected block is correct", r3.value(), data);
1✔
421
            }),
1✔
422

423
      CHECK("Aligned data passthrough (no block-defer)",
424
            [=](auto& result) {
1✔
425
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
426
               result.require("buffer is in alignment", b.in_alignment());
1✔
427

428
               Botan::BufferSlicer half_block(first_half_data);
1✔
429
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1✔
430
               result.test_is_true("not enough data for alignment processing", s1.empty());
1✔
431
               result.test_sz_eq("not enough data for alignment processing", r1, 0);
1✔
432
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
433

434
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
435
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
436
               const auto [s2, r2] = b.aligned_data_to_process(one_and_a_half_block);
1✔
437
               result.test_sz_eq("data of one block for processing", s2.size(), 32);
1✔
438
               result.test_sz_eq("one block for processing", r2, 1);
1✔
439
               result.test_bin_eq("one block for processing", s2, data);
1✔
440
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
441

442
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
443
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
444
               const auto [s3, r3] = b.aligned_data_to_process(two_blocks);
1✔
445
               result.test_sz_eq("data of two block for processing", s3.size(), 64);
1✔
446
               result.test_sz_eq("two blocks for processing", r3, 2);
1✔
447
               result.test_bin_eq("two blocks for processing", s3, two_blocks_data);
1✔
448
               result.test_sz_eq("aligned data is fully consumed", two_blocks.remaining(), 0);
1✔
449
            }),
2✔
450

451
      CHECK("Aligned data blockwise (no block-defer)",
452
            [=](auto& result) {
1✔
453
               const Botan::AlignmentBuffer<uint8_t, 32> b;
1✔
454
               result.require("buffer is in alignment", b.in_alignment());
1✔
455

456
               Botan::BufferSlicer half_block(first_half_data);
1✔
457
               const auto s1 = b.next_aligned_block_to_process(half_block);
1✔
458
               result.test_is_true("not enough data for alignment processing", !s1.has_value());
1✔
459
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
460

461
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
462
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
463
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1✔
464
               result.require("one block for processing", s2.has_value());
1✔
465
               result.test_sz_eq("data of one block for processing", s2->size(), 32);
1✔
466
               result.test_bin_eq("sliced data", s2.value(), data);
1✔
467
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
468

469
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
470
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
471
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1✔
472
               result.require("first block for processing", s3_1.has_value());
1✔
473
               result.test_sz_eq("data of first block for processing", s3_1->size(), 32);
1✔
474
               result.test_bin_eq("sliced data", s3_1.value(), data);
1✔
475
               result.test_sz_eq("first block is consumed", two_blocks.remaining(), 32);
1✔
476

477
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1✔
478
               result.require("second block for processing", s3_2.has_value());
1✔
479
               result.test_sz_eq("data of second block for processing", s3_2->size(), 32);
1✔
480
               result.test_bin_eq("sliced data", s3_2.value(), data);
1✔
481
               result.test_sz_eq("second block is consumed", two_blocks.remaining(), 0);
1✔
482
            }),
2✔
483

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

489
               Botan::BufferSlicer half_block(first_half_data);
1✔
490
               const auto [s1, r1] = b.aligned_data_to_process(half_block);
1✔
491
               result.test_is_true("not enough data for alignment processing", s1.empty());
1✔
492
               result.test_sz_eq("not enough data for alignment processing", r1, 0);
1✔
493
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
494

495
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
496
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
497
               const auto [s2, r2] = b.aligned_data_to_process(one_and_a_half_block);
1✔
498
               result.test_sz_eq("data of one block for processing", s2.size(), 32);
1✔
499
               result.test_sz_eq("one block for processing", r2, 1);
1✔
500
               result.test_bin_eq("sliced data", s2, data);
1✔
501
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
502

503
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
504
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
505
               const auto [s3, r3] = b.aligned_data_to_process(two_blocks);
1✔
506
               result.test_sz_eq("data of first block for processing", s3.size(), 32);
1✔
507
               result.test_sz_eq("one block for processing", r3, 1);
1✔
508
               result.test_bin_eq("sliced data", s3, data);
1✔
509
               result.test_sz_eq("aligned data is partially consumed", two_blocks.remaining(), 32);
1✔
510
            }),
2✔
511

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

517
               Botan::BufferSlicer half_block(first_half_data);
1✔
518
               const auto s1 = b.next_aligned_block_to_process(half_block);
1✔
519
               result.test_is_true("not enough data for alignment processing", !s1.has_value());
1✔
520
               result.test_sz_eq("(short) unaligned data is not consumed", half_block.remaining(), 16);
1✔
521

522
               const auto more_than_one_block = Botan::concat<std::vector<uint8_t>>(data, first_half_data);
1✔
523
               Botan::BufferSlicer one_and_a_half_block(more_than_one_block);
1✔
524
               const auto s2 = b.next_aligned_block_to_process(one_and_a_half_block);
1✔
525
               result.require("one block for processing", s2.has_value());
1✔
526
               result.test_sz_eq("data of one block for processing", s2->size(), 32);
1✔
527
               result.test_bin_eq("sliced data", s2.value(), data);
1✔
528
               result.test_sz_eq("(middle) unaligned data is not consumed", one_and_a_half_block.remaining(), 16);
1✔
529

530
               const auto two_blocks_data = Botan::concat<std::vector<uint8_t>>(data, data);
1✔
531
               Botan::BufferSlicer two_blocks(two_blocks_data);
1✔
532
               const auto s3_1 = b.next_aligned_block_to_process(two_blocks);
1✔
533
               result.require("first block for processing", s3_1.has_value());
1✔
534
               result.test_sz_eq("data of first block for processing", s3_1->size(), 32);
1✔
535
               result.test_bin_eq("sliced data", s3_1.value(), data);
1✔
536
               result.test_sz_eq("first block is consumed", two_blocks.remaining(), 32);
1✔
537

538
               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
1✔
539
               result.test_is_true("second block is not passed through", !s3_2.has_value());
1✔
540
               result.test_sz_eq("second block is not consumed", two_blocks.remaining(), 32);
1✔
541
            }),
2✔
542
   };
15✔
543
}
1✔
544

545
std::vector<Test::Result> test_concat() {
1✔
546
   return {
1✔
547
      CHECK("empty concat",
548
            [](Test::Result& result) {
1✔
549
               // only define a dynamic output type, but no input to be concat'ed
550
               const auto empty1 = Botan::concat<std::vector<uint8_t>>();
1✔
551
               result.test_is_true("empty concat 1", empty1.empty());
1✔
552

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

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

561
               // pass multiple empty input buffers to be concat'ed without auto-detection of the output buffer
562
               const auto empty4 = Botan::concat<std::array<uint8_t, 0>>(
1✔
563
                  std::vector<uint8_t>(), std::array<uint8_t, 0>(), Botan::secure_vector<uint8_t>());
1✔
564
               result.test_is_true("empty concat 4", empty4.empty());
1✔
565
            }),
1✔
566

567
      CHECK("auto-detected output type",
568
            [](Test::Result& result) {
1✔
569
               // define a static output type without any input parameters
570
               const auto t0 = Botan::concat<std::array<uint8_t, 0>>();
1✔
571
               result.test_is_true("type 0", std::is_same_v<std::array<uint8_t, 0>, std::remove_cvref_t<decltype(t0)>>);
1✔
572

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

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

581
               // pass multiple dynamic input buffers and auto-detect result type
582
               const auto t3 = Botan::concat(Botan::secure_vector<uint8_t>(), std::vector<uint8_t>());
1✔
583
               result.test_is_true("type 3",
1✔
584
                                   std::is_same_v<Botan::secure_vector<uint8_t>, std::remove_cvref_t<decltype(t3)>>);
585

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

591
               // pass only static input buffers and auto-detect result type
592
               const std::array<uint8_t, 5> some_buffer{};
1✔
593
               const auto t5 =
1✔
594
                  Botan::concat(std::array<uint8_t, 1>(), std::array<uint8_t, 3>(), std::span{some_buffer});
3✔
595
               result.test_is_true("type 5", std::is_same_v<std::array<uint8_t, 9>, std::remove_cvref_t<decltype(t5)>>);
1✔
596
            }),
1✔
597

598
      CHECK("concatenate",
599
            [](Test::Result& result) {
1✔
600
               constexpr std::array<uint8_t, 5> a1 = {1, 2, 3, 4, 5};
1✔
601
               const std::vector<uint8_t> v1{6, 7, 8, 9, 10};
1✔
602

603
               // concatenate a single buffer
604
               const auto concat0 = Botan::concat<Botan::secure_vector<uint8_t>>(v1);
1✔
605
               result.test_bin_eq("concat 0", concat0, "060708090A");
1✔
606

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

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

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

621
               // concatenate into a statically sized output buffer, that is auto-detected, at compile time
622
               constexpr auto concat4 = Botan::concat(a1, a1);
1✔
623
               result.test_bin_eq("concat 4", concat4, {1, 2, 3, 4, 5, 1, 2, 3, 4, 5});
1✔
624
               result.test_is_true("correct type 4",
1✔
625
                                   std::is_same_v<std::array<uint8_t, 10>, std::remove_cvref_t<decltype(concat4)>>);
626
            }),
3✔
627

628
      CHECK("dynamic length-check",
629
            [](Test::Result& result) {
1✔
630
               std::vector<uint8_t> v1{1, 2, 3, 4, 5};
1✔
631
               std::vector<uint8_t> v2{6, 7, 8, 9, 10};
1✔
632

633
               result.test_throws("concatenate into a statically sized type with insufficient space",
1✔
634
                                  [&]() { Botan::concat<std::array<uint8_t, 4>>(v1, v2); });
2✔
635
               result.test_throws("concatenate into a statically sized type with too much space",
1✔
636
                                  [&]() { Botan::concat<std::array<uint8_t, 20>>(v1, v2); });
2✔
637
            }),
2✔
638

639
      CHECK("concatenate strong types",
640
            [](Test::Result& result) {
1✔
641
               using StrongV = Botan::Strong<std::vector<uint8_t>, struct StrongV_>;
1✔
642
               using StrongA = Botan::Strong<std::array<uint8_t, 4>, struct StrongA_>;
1✔
643

644
               StrongV v1(std::vector<uint8_t>{1, 2, 3, 4, 5});
1✔
645
               StrongA a2;
1✔
646
               a2[0] = 6;
1✔
647
               a2[1] = 7;
1✔
648
               a2[2] = 8;
1✔
649
               a2[3] = 9;
1✔
650

651
               // concat strong types into a verbatim type
652
               auto concat1 = Botan::concat<std::vector<uint8_t>>(v1, a2);
1✔
653
               result.test_bin_eq("concat 1", concat1, "010203040506070809");
1✔
654

655
               // concat strong types into a dynamically allocated strong type
656
               auto concat2 = Botan::concat<StrongV>(a2, v1);
1✔
657
               result.test_bin_eq("concat 2", concat2.get(), "060708090102030405");
1✔
658
            }),
3✔
659
   };
6✔
660
}
1✔
661

662
BOTAN_REGISTER_TEST_FN(
663
   "utils", "buffer_utilities", test_buffer_slicer, test_buffer_stuffer, test_alignment_buffer, test_concat);
664

665
}  // namespace
666

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