/*
 * (C) 2023 Jack Lloyd
 * (C) 2023-2024 René Meusel, Rohde & Schwarz Cybersecurity
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */

#include "tests.h"

#include <botan/mem_ops.h>
#include <botan/internal/alignment_buffer.h>
#include <botan/internal/buffer_stuffer.h>
#include <botan/internal/concat_util.h>

#include <array>

namespace Botan_Tests {

namespace {

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

std::vector<Test::Result> test_buffer_slicer() {
   return {
      CHECK("Empty BufferSlicer",
            [](auto& result) {
               const std::vector<uint8_t> buffer(0);
               Botan::BufferSlicer s(buffer);
               result.test_is_true("empty slicer has no remaining bytes", s.remaining() == 0);
               result.test_is_true("empty slicer is empty()", s.empty());
               result.test_is_true("empty slicer can take() 0 bytes", s.take(0).empty());

               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
            }),

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

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

               const auto hello = s.take(5);
               result.require("has 5 bytes", hello.size() == 5);
               result.test_u8_eq("took hello", hello[0], uint8_t('h'));
               result.test_u8_eq("took hello", hello[1], uint8_t('e'));
               result.test_u8_eq("took hello", hello[2], uint8_t('l'));
               result.test_u8_eq("took hello", hello[3], uint8_t('l'));
               result.test_u8_eq("took hello", hello[4], uint8_t('o'));

               result.test_sz_eq("remaining bytes", s.remaining(), 8);

               s.skip(1);
               result.test_sz_eq("remaining bytes", s.remaining(), 7);

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

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

               s.skip(1);
               result.test_sz_eq("remaining bytes", s.remaining(), 1);

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

               result.test_is_true("empty", s.empty());
               result.test_sz_eq("nothing remaining", s.remaining(), 0);

               result.test_throws("empty slicer cannot emit bytes", [&]() { s.take(1); });
               result.test_throws("empty slicer cannot skip bytes", [&]() { s.skip(1); });
               result.test_throws("empty slicer cannot copy bytes", [&]() { s.copy_as_vector(1); });
            }),

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

               auto span1 = s.take(1);
               auto span2 = s.take<StrongBuffer>(2);
               auto vec1 = s.copy<StrongBuffer>(2);
               auto vec2 = s.copy_as_vector(2);
               auto vec3 = s.copy_as_secure_vector(2);
               StrongBuffer vec4(s.remaining());
               s.copy_into(vec4);

               const auto reproduce = Botan::concat<std::vector<uint8_t>>(span1, span2, vec1, vec2, vec3, vec4);
               result.test_bin_eq("sliced into various types", reproduce, secure_buffer);
            }),
   };
}

std::vector<Test::Result> test_buffer_stuffer() {
   return {
      CHECK("Empty BufferStuffer",
            [](auto& result) {
               std::vector<uint8_t> empty_buffer;
               Botan::BufferStuffer s(empty_buffer);

               result.test_sz_eq("has no capacity", s.remaining_capacity(), 0);
               result.test_is_true("is immediately full", s.full());
               result.test_is_true("can next() 0 bytes", s.next(0).empty());

               result.test_throws("cannot next() anything", [&]() { s.next(1); });
               result.test_throws("cannot append bytes", [&]() {
                  std::vector<uint8_t> some_bytes(42);
                  s.append(some_bytes);
               });
            }),

      CHECK("Fill BufferStuffer",
            [](auto& result) {
               std::vector<uint8_t> sink(13);
               Botan::BufferStuffer s(sink);

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

               auto n1 = s.next(5);
               result.require("got requested bytes", n1.size() == 5);
               n1[0] = 'h';
               n1[1] = 'e';
               n1[2] = 'l';
               n1[3] = 'l';
               n1[4] = 'o';

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

               n2.get()[0] = ' ';
               n2.get()[1] = 'w';
               n2.get()[2] = 'o';

               result.test_sz_eq("has 5 bytes remaining", s.remaining_capacity(), 5);

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

               result.test_sz_eq("has 2 bytes remaining", s.remaining_capacity(), 2);

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

               n3[0] = ' ';
               n3[1] = '!';

               result.test_is_true("is full", s.full());

               result.test_throws("cannot next() anything", [&]() { s.next(1); });
               result.test_throws("cannot append bytes", [&]() {
                  std::vector<uint8_t> some_bytes(42);
                  s.append(some_bytes);
               });

               const std::string final_string(Botan::cast_uint8_ptr_to_char(sink.data()), sink.size());
               result.test_str_eq("final buffer is as expected", final_string, "hello world !");
            }),
   };
}

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

   return {
      CHECK("Fresh Alignment Buffer",
            [](auto& result) {
               const Botan::AlignmentBuffer<uint8_t, 32> b;
               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
            }),

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

               b.append(first_half_data);

               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

               b.append(second_half_data);

               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
            }),

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

               b.append(data);

               result.require("ready_to_consume()", b.ready_to_consume());
               const auto out = b.consume();

               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

               result.test_bin_eq("in == out", data, out);
            }),

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

               result.require("!ready_to_consume()", !b.ready_to_consume());
               const auto out_empty = b.consume_partial();
               result.test_sz_eq("consuming nothing resets buffer", b.elements_in_buffer(), 0);
               result.test_is_true("consumed empty buffer", out_empty.empty());

               b.append(first_half_data);
               result.require("!ready_to_consume()", !b.ready_to_consume());
               const auto out_half = b.consume_partial();
               result.test_sz_eq("consumed half-data resets buffer", b.elements_in_buffer(), 0);
               result.test_bin_eq("half-in == half-out", out_half, first_half_data);

               b.append(data);
               result.require("ready_to_consume()", b.ready_to_consume());
               const auto out_full = b.consume_partial();
               result.test_sz_eq("consumed full-data resets buffer", b.elements_in_buffer(), 0);
               result.test_bin_eq("full-in == full-out", out_full, data);
            }),

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

               b.append(first_half_data);

               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
               b.clear();

               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
            }),

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

               b.append(first_half_data);

               result.require("elements_in_buffer()", b.elements_in_buffer() == 16);
               b.fill_up_with_zeros();

               result.test_sz_eq("size()", b.size(), 32);
               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("ready_to_consume()", b.ready_to_consume());

               const auto out = b.consume();

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

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

               b.fill_up_with_zeros();
               const auto out_without_padding = b.consume();

               result.test_bin_eq("no padding", out_without_padding, data);
            }),

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

               Botan::BufferSlicer first_half(first_half_data);
               Botan::BufferSlicer second_half(second_half_data);

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

               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

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

               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

               result.test_bin_eq("collected block is correct", r2.value(), data);
            }),

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

               Botan::BufferSlicer full_block_1(data);
               const auto r1 = b.handle_unaligned_data(full_block_1);
               result.test_is_true("aligned data is not buffered", !r1.has_value());
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
               result.test_sz_eq("aligned data is not consumed", full_block_1.remaining(), 32);

               Botan::BufferSlicer half_block(first_half_data);
               Botan::BufferSlicer full_block_2(data);
               const auto r2 = b.handle_unaligned_data(half_block);
               result.test_is_true("unaligned data is buffered", !r2.has_value());
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
               result.test_is_true("unaligned data is consumed", half_block.empty());

               const auto r3 = b.handle_unaligned_data(full_block_2);
               result.test_is_true("collected block is consumed", r3.has_value());
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());
               result.test_sz_eq("input is consumed until alignment", full_block_2.remaining(), 16);
            }),

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

               Botan::BufferSlicer first_half(first_half_data);
               Botan::BufferSlicer second_half(second_half_data);
               Botan::BufferSlicer third_half(first_half_data);

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

               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 16);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 16);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

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

               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 32);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 0);
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("ready_to_consume()", b.ready_to_consume());

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

               result.test_sz_eq("elements_in_buffer()", b.elements_in_buffer(), 0);
               result.test_sz_eq("elements_until_alignment()", b.elements_until_alignment(), 32);
               result.test_is_true("in_alignment()", b.in_alignment());
               result.test_is_true("!ready_to_consume()", !b.ready_to_consume());

               result.test_bin_eq("collected block is correct", r3.value(), data);
            }),

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

               Botan::BufferSlicer full_block_1(data);
               const auto r1 = b.handle_unaligned_data(full_block_1);
               result.test_is_true("exactly aligned data is buffered", !r1.has_value());
               result.test_is_true("!in_alignment()", !b.in_alignment());
               result.test_is_true("ready_to_consume()", b.ready_to_consume());
               result.test_is_true("exactly aligned block is consumed", full_block_1.empty());

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

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

               result.test_bin_eq("collected block is correct", r3.value(), data);
            }),

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

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

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

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

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

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

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

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

               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
               result.require("second block for processing", s3_2.has_value());
               result.test_sz_eq("data of second block for processing", s3_2->size(), 32);
               result.test_bin_eq("sliced data", s3_2.value(), data);
               result.test_sz_eq("second block is consumed", two_blocks.remaining(), 0);
            }),

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

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

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

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

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

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

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

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

               const auto s3_2 = b.next_aligned_block_to_process(two_blocks);
               result.test_is_true("second block is not passed through", !s3_2.has_value());
               result.test_sz_eq("second block is not consumed", two_blocks.remaining(), 32);
            }),
   };
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

               StrongV v1(std::vector<uint8_t>{1, 2, 3, 4, 5});
               StrongA a2;
               a2[0] = 6;
               a2[1] = 7;
               a2[2] = 8;
               a2[3] = 9;

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

               // concat strong types into a dynamically allocated strong type
               auto concat2 = Botan::concat<StrongV>(a2, v1);
               result.test_bin_eq("concat 2", concat2.get(), "060708090102030405");
            }),
   };
}

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

}  // namespace

}  // namespace Botan_Tests
