Repository: randombit/botan
Pull request #3564 (2023-05-30 14:06:08)

Utility Preparations for SPHINCS+
This adds the auxiliary stuff from #3549 as an independent pull request. Note that some of those additions could (and should be) used in existing code to simplify things. That's out of scope, though.

### Improvements to `BufferSlicer` interface

* Clearly state when methods copy data (`::copy()`, `::copy_as_...()`, `copy_into()`)
* Use `concepts::contiguous_container` where it makes sense
* Add tests

### Add `BufferStuffer`

That's basically the opposite concept of `BufferSlicer`. If a buffer's size is known up-front, it can be pre-allocated and then filled with this helper class. It keeps track of the buffer offsets and provides a convenient (span-based) API.

### Implement a `StrongSpan<>`

Just like `Strong<>` is a helper to assign strong type information to arbitrary base types (usually vectors) `StrongSpan<>` allows assigning strong type information to `std::span<>`. The template parameter of `StrongSpan<>` must be a `Strong<>` whose underlying type is a `concepts::contiguous_container`.

`StrongSpan<>` bind to values of their respective strong type, or `std::span<>` when explicitly casted. Otherwise it behaves like an ordinary `std::span<>`. It is meant to be used like an ordinary span (e.g. in-lieu of `const Strong<>&` as function parameter).

### Specialization `Strong<concepts::integral>`

Making it more convenient to assign strong type information to "magic" numbers. Note that this is _different_ from measurement units (like "seconds" or "miles"). It should typically define the entity that is quantified by the number, not the gauge. The mental model is something like that:

```C++
using Length = Strong<size_t, struct Length_>;
Length length(42 /* meters */);
```

As such, integral strong types can be compared to plain numbers (mostly for convenience). Unary operations (most notably increment/decrement) are allowed, and they support binary arithmetic operations closed under the same type. If you want to enable arithmetic with plain numbers, define the integral strong type like this:

```C++
using Height = Strong<size_t, struct Height_, EnableArithmeticWithPlainNumber>;
```


Patch:
diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
index eab13bd2f..c9fd81bf5 100644
--- a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
+++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
@@ -119,7 +119,7 @@ class Dilithium_PublicKeyInternal {
          BOTAN_ASSERT_NOMSG(raw_pk.size() == m_mode.public_key_bytes());
 
          BufferSlicer s(raw_pk);
-         m_rho = s.take_vector(DilithiumModeConstants::SEEDBYTES);
+         m_rho = s.copy_as_vector(DilithiumModeConstants::SEEDBYTES);
          m_t1 = Dilithium::PolynomialVector::unpack_t1(s.take(DilithiumModeConstants::POLYT1_PACKEDBYTES * m_mode.k()),
                                                        m_mode);
 
@@ -208,9 +208,9 @@ class Dilithium_PrivateKeyInternal {
          BOTAN_ASSERT_NOMSG(sk.size() == m_mode.private_key_bytes());
 
          BufferSlicer s(sk);
-         m_rho = s.take_vector(DilithiumModeConstants::SEEDBYTES);
-         m_key = s.take_secure_vector(DilithiumModeConstants::SEEDBYTES);
-         m_tr = s.take_secure_vector(DilithiumModeConstants::SEEDBYTES);
+         m_rho = s.copy_as_vector(DilithiumModeConstants::SEEDBYTES);
+         m_key = s.copy_as_secure_vector(DilithiumModeConstants::SEEDBYTES);
+         m_tr = s.copy_as_secure_vector(DilithiumModeConstants::SEEDBYTES);
          m_s1 = Dilithium::PolynomialVector::unpack_eta(
             s.take(m_mode.l() * m_mode.polyeta_packedbytes()), m_mode.l(), m_mode);
          m_s2 = Dilithium::PolynomialVector::unpack_eta(
diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.cpp b/src/lib/pubkey/kyber/kyber_common/kyber.cpp
index 9c65cd057..6fa6a7018 100644
--- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp
+++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp
@@ -1191,7 +1191,7 @@ std::shared_ptr<Kyber_PublicKeyInternal> Kyber_PublicKey::initialize_from_encodi
    BufferSlicer s(pub_key);
 
    auto poly_vec = s.take(mode.polynomial_vector_byte_length());
-   auto seed = s.take_vector(KyberConstants::kSeedLength);
+   auto seed = s.copy_as_vector(KyberConstants::kSeedLength);
    BOTAN_ASSERT_NOMSG(s.empty());
 
    return std::make_shared<Kyber_PublicKeyInternal>(std::move(mode), poly_vec, std::move(seed));
@@ -1227,7 +1227,7 @@ Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) {
    const auto middle = G->output_length() / 2;
 
    BufferSlicer s(seed);
-   auto seed1 = s.take_vector(middle);
+   auto seed1 = s.copy_as_vector(middle);
    auto seed2 = s.take(middle);
    BOTAN_ASSERT_NOMSG(s.empty());
 
@@ -1263,7 +1263,7 @@ Kyber_PrivateKey::Kyber_PrivateKey(std::span<const uint8_t> sk, KyberMode m) {
    auto skpv = PolynomialVector::from_bytes(s.take(mode.polynomial_vector_byte_length()), mode);
    auto pub_key = s.take(mode.public_key_byte_length());
    s.skip(KyberConstants::kPublicKeyHashLength);
-   auto z = s.take_secure_vector(KyberConstants::kZLength);
+   auto z = s.copy_as_secure_vector(KyberConstants::kZLength);
 
    BOTAN_ASSERT_NOMSG(s.empty());
 
diff --git a/src/lib/pubkey/xmss/xmss_privatekey.cpp b/src/lib/pubkey/xmss/xmss_privatekey.cpp
index fd6b6f523..5e993be03 100644
--- a/src/lib/pubkey/xmss/xmss_privatekey.cpp
+++ b/src/lib/pubkey/xmss/xmss_privatekey.cpp
@@ -116,8 +116,8 @@ class XMSS_PrivateKey_Internal {
             throw Decoding_Error("XMSS private key leaf index out of bounds");
          }
 
-         m_prf = s.take_secure_vector(m_xmss_params.element_size());
-         m_private_seed = s.take_secure_vector(m_xmss_params.element_size());
+         m_prf = s.copy_as_secure_vector(m_xmss_params.element_size());
+         m_private_seed = s.copy_as_secure_vector(m_xmss_params.element_size());
          set_unused_leaf_index(unused_leaf);
 
          // Legacy keys generated prior to Botan 3.x don't feature a
diff --git a/src/lib/pubkey/xmss/xmss_publickey.cpp b/src/lib/pubkey/xmss/xmss_publickey.cpp
index 56ae84dea..bd0c52a02 100644
--- a/src/lib/pubkey/xmss/xmss_publickey.cpp
+++ b/src/lib/pubkey/xmss/xmss_publickey.cpp
@@ -83,8 +83,8 @@ XMSS_PublicKey::XMSS_PublicKey(std::span<const uint8_t> key_bits) :
    BufferSlicer s(m_raw_key);
    s.skip(4 /* algorithm ID -- already consumed by `deserialize_xmss_oid()` */);
 
-   m_root = s.take_secure_vector(m_xmss_params.element_size());
-   m_public_seed = s.take_secure_vector(m_xmss_params.element_size());
+   m_root = s.copy_as_secure_vector(m_xmss_params.element_size());
+   m_public_seed = s.copy_as_secure_vector(m_xmss_params.element_size());
 }
 
 XMSS_PublicKey::XMSS_PublicKey(XMSS_Parameters::xmss_algorithm_t xmss_oid,
diff --git a/src/lib/utils/concepts.h b/src/lib/utils/concepts.h
index 79ff562a0..6e5c09ff3 100644
--- a/src/lib/utils/concepts.h
+++ b/src/lib/utils/concepts.h
@@ -15,7 +15,21 @@
 #include <ostream>
 #include <type_traits>
 
-namespace Botan::concepts {
+namespace Botan {
+
+template <typename T, typename Tag, typename... Capabilities>
+class Strong;
+
+template <typename... Ts>
+struct is_strong_type : std::false_type {};
+
+template <typename... Ts>
+struct is_strong_type<Strong<Ts...>> : std::true_type {};
+
+template <typename... Ts>
+constexpr bool is_strong_type_v = is_strong_type<std::remove_const_t<Ts>...>::value;
+
+namespace concepts {
 
 // TODO: C++20 use std::convertible_to<> that was not available in Android NDK
 //       as of this writing. Tested with API Level up to 33.
@@ -97,6 +111,20 @@ concept resizable_byte_buffer =
 template <typename T>
 concept streamable = requires(std::ostream& os, T a) { os << a; };
 
-}  // namespace Botan::concepts
+template <class T>
+concept strong_type = is_strong_type_v<T>;
+
+template <class T>
+concept contiguous_strong_type = strong_type<T> && contiguous_container<T>;
+
+// std::integral is a concept that is shipped with C++20 but Android NDK is not
+// yet there.
+// TODO: C++20 - replace with std::integral
+template <typename T>
+concept integral = std::is_integral_v<T>;
+
+}  // namespace concepts
+
+}  // namespace Botan
 
 #endif
diff --git a/src/lib/utils/mem_ops.h b/src/lib/utils/mem_ops.h
index 28f21d2a8..6161b94f1 100644
--- a/src/lib/utils/mem_ops.h
+++ b/src/lib/utils/mem_ops.h
@@ -10,6 +10,7 @@
 
 #include <botan/types.h>
 #include <cstring>
+#include <span>
 #include <type_traits>
 #include <vector>
 
@@ -277,8 +278,7 @@ inline void xor_buf(uint8_t out[], const uint8_t in[], const uint8_t in2[], size
    }
 }
 
-template <typename Alloc, typename Alloc2>
-void xor_buf(std::vector<uint8_t, Alloc>& out, const std::vector<uint8_t, Alloc2>& in, size_t n) {
+inline void xor_buf(std::span<uint8_t> out, std::span<const uint8_t> in, size_t n) {
    xor_buf(out.data(), in.data(), n);
 }
 
diff --git a/src/lib/utils/stl_util.h b/src/lib/utils/stl_util.h
index 6780bbd9f..1f7c98642 100644
--- a/src/lib/utils/stl_util.h
+++ b/src/lib/utils/stl_util.h
@@ -17,7 +17,9 @@
 #include <variant>
 #include <vector>
 
+#include <botan/concepts.h>
 #include <botan/secmem.h>
+#include <botan/strong_type.h>
 
 namespace Botan {
 
@@ -109,15 +111,15 @@ class BufferSlicer final {
    public:
       BufferSlicer(std::span<const uint8_t> buffer) : m_remaining(buffer) {}
 
-      template <typename ContainerT>
-      auto take_as(const size_t count) {
+      template <concepts::contiguous_container ContainerT>
+      auto copy(const size_t count) {
          const auto result = take(count);
          return ContainerT(result.begin(), result.end());
       }
 
-      auto take_vector(const size_t count) { return take_as<std::vector<uint8_t>>(count); }
+      auto copy_as_vector(const size_t count) { return copy<std::vector<uint8_t>>(count); }
 
-      auto take_secure_vector(const size_t count) { return take_as<secure_vector<uint8_t>>(count); }
+      auto copy_as_secure_vector(const size_t count) { return copy<secure_vector<uint8_t>>(count); }
 
       std::span<const uint8_t> take(const size_t count) {
          BOTAN_STATE_CHECK(remaining() >= count);
@@ -126,6 +128,11 @@ class BufferSlicer final {
          return result;
       }
 
+      template <concepts::contiguous_strong_type T>
+      StrongSpan<const T> take(const size_t count) {
+         return StrongSpan<const T>(take(count));
+      }
+
       void copy_into(std::span<uint8_t> sink) {
          const auto data = take(sink.size());
          std::copy(data.begin(), data.end(), sink.begin());
@@ -141,6 +148,47 @@ class BufferSlicer final {
       std::span<const uint8_t> m_remaining;
 };
 
+/**
+ * @brief Helper class to ease in-place marshalling of concatenated fixed-length
+ *        values.
+ *
+ * The size of the final buffer must be known from the start, reallocations are
+ * not performed.
+ */
+class BufferStuffer {
+   public:
+      BufferStuffer(std::span<uint8_t> buffer) : m_buffer(buffer) {}
+
+      /**
+       * @returns a span for the next @p bytes bytes in the concatenated buffer.
+       *          Checks that the buffer is not exceded.
+       */
+      std::span<uint8_t> next(size_t bytes) {
+         BOTAN_STATE_CHECK(m_buffer.size() >= bytes);
+
+         auto result = m_buffer.first(bytes);
+         m_buffer = m_buffer.subspan(bytes);
+         return result;
+      }
+
+      template <concepts::contiguous_strong_type StrongT>
+      StrongSpan<StrongT> next(size_t bytes) {
+         return StrongSpan<StrongT>(next(bytes));
+      }
+
+      void append(std::span<const uint8_t> buffer) {
+         auto sink = next(buffer.size());
+         std::copy(buffer.begin(), buffer.end(), sink.begin());
+      }
+
+      bool full() const { return m_buffer.empty(); }
+
+      size_t remaining_capacity() const { return m_buffer.size(); }
+
+   private:
+      std::span<uint8_t> m_buffer;
+};
+
 /**
  * Concatenate an arbitrary number of buffers.
  * @return the concatenation of \p buffers as the container type of the first buffer
diff --git a/src/lib/utils/strong_type.h b/src/lib/utils/strong_type.h
index fee7c61d2..99311ad91 100644
--- a/src/lib/utils/strong_type.h
+++ b/src/lib/utils/strong_type.h
@@ -16,13 +16,28 @@
 
 namespace Botan {
 
+/**
+ * Added as an additional "capability tag" to enable arithmetic operators with
+ * plain numbers for Strong<> types that wrap a number.
+ */
+struct EnableArithmeticWithPlainNumber {};
+
 namespace detail {
 
+/**
+ * Checks whether the @p CapabilityT is included in the @p Tags type pack.
+ */
+template <typename CapabilityT, typename... Tags>
+constexpr bool has_capability = (std::is_same_v<CapabilityT, Tags> || ...);
+
 template <typename T>
 class Strong_Base {
    private:
       T m_value;
 
+   public:
+      using wrapped_type = T;
+
    public:
       Strong_Base() = default;
       Strong_Base(const Strong_Base&) = default;
@@ -30,7 +45,7 @@ class Strong_Base {
       Strong_Base& operator=(const Strong_Base&) = default;
       Strong_Base& operator=(Strong_Base&&) = default;
 
-      explicit Strong_Base(T v) : m_value(std::move(v)) {}
+      constexpr explicit Strong_Base(T v) : m_value(std::move(v)) {}
 
       T& get() { return m_value; }
 
@@ -43,6 +58,12 @@ class Strong_Adapter : public Strong_Base<T> {
       using Strong_Base<T>::Strong_Base;
 };
 
+template <concepts::integral T>
+class Strong_Adapter<T> : public Strong_Base<T> {
+   public:
+      using Strong_Base<T>::Strong_Base;
+};
+
 template <concepts::container T>
 class Strong_Adapter<T> : public Strong_Base<T> {
    public:
@@ -64,6 +85,9 @@ class Strong_Adapter<T> : public Strong_Base<T> {
          requires(concepts::resizable_container<T>)
             : Strong_Adapter(T(size)) {}
 
+      template <typename InputIt>
+      Strong_Adapter(InputIt begin, InputIt end) : Strong_Adapter(T(begin, end)) {}
+
       // Disambiguates the usage of string literals, otherwise:
       // Strong_Adapter(std::span<>) and Strong_Adapter(const char*)
       // would be ambiguous.
@@ -128,7 +152,7 @@ class Strong_Adapter<T> : public Strong_Base<T> {
  * This implementation was inspired by:
  *   https://stackoverflow.com/a/69030899
  */
-template <typename T, typename TagTypeT>
+template <typename T, typename TagTypeT, typename... Capabilities>
 class Strong : public detail::Strong_Adapter<T> {
    public:
       using detail::Strong_Adapter<T>::Strong_Adapter;
@@ -155,6 +179,390 @@ auto operator<=>(const Strong<T, Tags...>& lhs, const Strong<T, Tags...>& rhs) {
    return lhs.get() <=> rhs.get();
 }
 
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+auto operator<=>(T1 a, Strong<T2, Tags...> b) {
+   return a <=> b.get();
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+auto operator<=>(Strong<T1, Tags...> a, T2 b) {
+   return a.get() <=> b;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+auto operator==(T1 a, Strong<T2, Tags...> b) {
+   return a == b.get();
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+auto operator==(Strong<T1, Tags...> a, T2 b) {
+   return a.get() == b;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator+(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a + b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator+(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() + b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator+(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() + b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator-(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a - b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator-(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() - b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator-(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() - b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator*(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a * b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator*(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() * b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator*(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() * b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator/(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a / b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator/(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() / b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator/(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() / b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator^(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a ^ b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator^(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() ^ b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator^(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() ^ b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator&(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a & b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator&(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() & b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator&(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() & b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator|(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a | b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator|(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() | b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator|(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() | b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator>>(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a >> b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator>>(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() >> b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator>>(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() >> b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator<<(T1 a, Strong<T2, Tags...> b) {
+   return Strong<T2, Tags...>(a << b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr decltype(auto) operator<<(Strong<T1, Tags...> a, T2 b) {
+   return Strong<T1, Tags...>(a.get() << b);
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr decltype(auto) operator<<(Strong<T, Tags...> a, Strong<T, Tags...> b) {
+   return Strong<T, Tags...>(a.get() << b.get());
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator+=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() += b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator+=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() += b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator-=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() -= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator-=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() -= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator*=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() *= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator*=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() *= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator/=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() /= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator/=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() /= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator^=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() ^= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator^=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() ^= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator&=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() &= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator&=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() &= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator|=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() |= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator|=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() |= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator>>=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() >>= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator>>=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() >>= b.get();
+   return a;
+}
+
+template <concepts::integral T1, concepts::integral T2, typename... Tags>
+   requires(detail::has_capability<EnableArithmeticWithPlainNumber, Tags...>)
+constexpr auto operator<<=(Strong<T1, Tags...>& a, T2 b) {
+   a.get() <<= b;
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator<<=(Strong<T, Tags...>& a, Strong<T, Tags...> b) {
+   a.get() <<= b.get();
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator++(Strong<T, Tags...>& a, int) {
+   auto tmp = a;
+   ++a.get();
+   return tmp;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator++(Strong<T, Tags...>& a) {
+   ++a.get();
+   return a;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator--(Strong<T, Tags...>& a, int) {
+   auto tmp = a;
+   --a.get();
+   return tmp;
+}
+
+template <concepts::integral T, typename... Tags>
+constexpr auto operator--(Strong<T, Tags...>& a) {
+   --a.get();
+   return a;
+}
+
+/**
+ * This mimmicks a std::span but keeps track of the strong-type information. Use
+ * this when you would want to use `const Strong<...>&` as a parameter
+ * declaration. In particular this allows assigning strong-type information to
+ * slices of a bigger buffer without copying the bytes. E.g:
+ *
+ *    using Foo = Strong<std::vector<uint8_t>, Foo_>;
+ *
+ *    void bar(StrongSpan<Foo> foo) { ... }
+ *
+ *    std::vector<uint8_t> buffer;
+ *    BufferSlicer slicer(buffer);
+ *    bar(slicer.take<Foo>());  // This does not copy the data from buffer but
+ *                              // just annotates the 'Foo' strong-type info.
+ */
+template <concepts::contiguous_strong_type T>
+class StrongSpan {
+      using underlying_span = std::
+         conditional_t<std::is_const_v<T>, std::span<const typename T::value_type>, std::span<typename T::value_type>>;
+
+   public:
+      using value_type = typename underlying_span::value_type;
+      using size_type = typename underlying_span::size_type;
+      using iterator = typename underlying_span::iterator;
+      using pointer = typename underlying_span::pointer;
+      using const_pointer = typename underlying_span::const_pointer;
+
+      StrongSpan() = default;
+
+      explicit StrongSpan(underlying_span span) : m_span(span) {}
+
+      StrongSpan(T& strong) : m_span(strong) {}
+
+      // allows implicit conversion from `StrongSpan<T>` to `StrongSpan<const T>`
+      // -> if T is a const type, the compiler will generate a copy-constructor
+      //    allowing copies from `StrongSpan<T>` and `StrongSpan<const T>`
+      // -> if T is a non-const type, no additional copy-constructor will be
+      //    generated. Instead this explicitly defines it. As a result, implicit
+      //    cast from a `StrongSpan<const T>` to `StrongSpan<T>` is prohibited.
+      StrongSpan(const StrongSpan<std::remove_const_t<T>>& other) : m_span(other.get()) {}
+
+      ~StrongSpan() = default;
+
+      /**
+       * @returns the underlying std::span without any type constraints
+       */
+      underlying_span get() const { return m_span; }
+
+      decltype(auto) data() noexcept(noexcept(this->m_span.data())) { return this->m_span.data(); }
+
+      decltype(auto) data() const noexcept(noexcept(this->m_span.data())) { return this->m_span.data(); }
+
+      decltype(auto) size() const noexcept(noexcept(this->m_span.size())) { return this->m_span.size(); }
+
+      bool empty() const noexcept(noexcept(this->m_span.empty())) { return this->m_span.empty(); }
+
+      decltype(auto) begin() noexcept(noexcept(this->m_span.begin())) { return this->m_span.begin(); }
+
+      decltype(auto) begin() const noexcept(noexcept(this->m_span.begin())) { return this->m_span.begin(); }
+
+      decltype(auto) end() noexcept(noexcept(this->m_span.end())) { return this->m_span.end(); }
+
+      decltype(auto) end() const noexcept(noexcept(this->m_span.end())) { return this->m_span.end(); }
+
+   private:
+      underlying_span m_span;
+};
+
 }  // namespace Botan
 
 #endif


Test patch:
diff --git a/src/tests/test_strong_type.cpp b/src/tests/test_strong_type.cpp
index e9dfd87a6..229144ee6 100644
--- a/src/tests/test_strong_type.cpp
+++ b/src/tests/test_strong_type.cpp
@@ -71,6 +71,17 @@ std::vector<Test::Result> test_strong_type() {
                             result.test_eq("overloading size", foo(Test_Size(42)), "some size");
                             result.test_eq("overloading size", foo(Test_Length(42)), "some length");
                          }),
+
+      Botan_Tests::CHECK("is_strong_type",
+                         [](auto& result) {
+                            result.confirm("strong type (int)", Botan::is_strong_type_v<Test_Size>);
+                            result.confirm("no strong type (int)", !Botan::is_strong_type_v<size_t>);
+                            result.confirm("strong type (vector)", Botan::is_strong_type_v<Test_Nonce>);
+                            result.confirm("no strong type (vector)", !Botan::is_strong_type_v<std::vector<uint8_t>>);
+                            result.confirm("strong type (const vector)", Botan::is_strong_type_v<const Test_Nonce>);
+                            result.confirm("no strong type (const vector)",
+                                           !Botan::is_strong_type_v<const std::vector<uint8_t>>);
+                         }),
    };
 }
 
@@ -180,8 +191,203 @@ std::vector<Test::Result> test_container_strong_type() {
    };
 }
 
+std::vector<Test::Result> test_integer_strong_type() {
+   using StrongInt = Botan::Strong<int, struct StrongInt_>;
+   using StrongIntWithPodArithmetics = Botan::Strong<int, struct StrongInt_, Botan::EnableArithmeticWithPlainNumber>;
+
+   return {
+      Botan_Tests::CHECK("comparison operators with POD are always allowed",
+                         [](auto& result) {
+                            StrongInt i(42);
+
+                            result.confirm("i ==", i == 42);
+                            result.confirm("i !=", i != 0);
+                            result.confirm("i >", i > 41);
+                            result.confirm("i >= 1", i >= 41);
+                            result.confirm("i >= 2", i >= 42);
+                            result.confirm("i <", i < 43);
+                            result.confirm("i <= 1", i <= 43);
+                            result.confirm("i <= 2", i <= 42);
+
+                            result.confirm("== i", 42 == i);
+                            result.confirm("!= i", 0 != i);
+                            result.confirm("> i", 43 > i);
+                            result.confirm(">= 1 i", 43 >= i);
+                            result.confirm(">= 2 i", 42 >= i);
+                            result.confirm("< i", 41 < i);
+                            result.confirm("<= 1 i", 41 <= i);
+                            result.confirm("<= 2 i", 42 <= i);
+                         }),
+
+      Botan_Tests::CHECK("increment/decrement are always allowed",
+                         [](auto& result) {
+                            StrongInt i(42);
+
+                            result.confirm("i++", i++ == 42);
+                            result.confirm("i post-incremented", i == 43);
+                            result.confirm("++i", ++i == 44);
+                            result.confirm("i pre-incremented", i == 44);
+
+                            result.confirm("i--", i-- == 44);
+                            result.confirm("i post-decremented", i == 43);
+                            result.confirm("--i", --i == 42);
+                            result.confirm("i pre-decremented", i == 42);
+                         }),
+
+      Botan_Tests::CHECK("comparison operators with Strong<>",
+                         [](auto& result) {
+                            StrongInt i(42);
+                            StrongInt i42(42);
+                            StrongInt i41(41);
+                            StrongInt i43(43);
+                            StrongInt i0(0);
+
+                            result.confirm("==", i == i42);
+                            result.confirm("!=", i != i0);
+                            result.confirm(">", i > i41);
+                            result.confirm(">= 1", i >= i41);
+                            result.confirm(">= 2", i >= i42);
+                            result.confirm("<", i < i43);
+                            result.confirm("<= 1", i <= i43);
+                            result.confirm("<= 2", i <= i42);
+                         }),
+
+      Botan_Tests::CHECK("arithmetics with Strong<>",
+                         [](auto& result) {
+                            StrongInt i(42);
+                            StrongInt i2(2);
+                            StrongInt i4(4);
+                            StrongInt i12(12);
+
+                            result.confirm("+", i + i == 84);
+                            result.confirm("-", i - i == 0);
+                            result.confirm("*", i * i == 1764);
+                            result.confirm("/", i / i == 1);
+                            result.confirm("^", (i ^ i) == 0);
+                            result.confirm("&", (i & i) == 42);
+                            result.confirm("|", (i | i) == 42);
+                            result.confirm(">>", (i >> i2) == 10);
+                            result.confirm("<<", (i << i2) == 168);
+
+                            result.confirm("+=", (i += i2) == 44);
+                            result.confirm("-=", (i -= i2) == 42);
+                            result.confirm("*=", (i *= i2) == 84);
+                            result.confirm("/=", (i /= i2) == 42);
+                            result.confirm("^=", (i ^= i2) == 40);
+                            result.confirm("&=", (i &= i12) == 8);
+                            result.confirm("|=", (i |= i2) == 10);
+                            result.confirm("<<=", (i <<= i2) == 40);
+                            result.confirm(">>=", (i >>= i4) == 2);
+                         }),
+
+      Botan_Tests::CHECK("arithmetics with POD",
+                         [](auto& result) {
+                            StrongIntWithPodArithmetics i(42);
+                            StrongIntWithPodArithmetics i2(2);
+
+                            result.confirm("i +", i + 1 == 43);
+                            result.confirm("i -", i - 1 == 41);
+                            result.confirm("i *", i * 2 == 84);
+                            result.confirm("i /", i / 2 == 21);
+                            result.confirm("i ^", (i ^ 10) == 32);
+                            result.confirm("i &", (i & 15) == 10);
+                            result.confirm("i |", (i | 4) == 46);
+                            result.confirm("i >>", (i >> 2) == 10);
+                            result.confirm("i <<", (i << 2) == 168);
+
+                            result.confirm("+ i", 1 + i == 43);
+                            result.confirm("- i", 1 - i == -41);
+                            result.confirm("* i", 2 * i == 84);
+                            result.confirm("/ i", 84 / i == 2);
+                            result.confirm("^ i", (10 ^ i) == 32);
+                            result.confirm("& i", (15 & i) == 10);
+                            result.confirm("| i", (4 | i) == 46);
+                            result.confirm(">> i", (4 >> i2) == 1);
+                            result.confirm("<< i", (2 << i2) == 8);
+
+                            result.confirm("i +=", (i += 2) == 44);
+                            result.confirm("i -=", (i -= 2) == 42);
+                            result.confirm("i *=", (i *= 2) == 84);
+                            result.confirm("i /=", (i /= 2) == 42);
+                            result.confirm("i ^=", (i ^= 2) == 40);
+                            result.confirm("i &=", (i &= 12) == 8);
+                            result.confirm("i |=", (i |= 2) == 10);
+                            result.confirm("i <<=", (i <<= 2) == 40);
+                            result.confirm("i >>=", (i >>= 4) == 2);
+                         }),
+
+      Botan_Tests::CHECK("arithmetics with POD is still Strong<>",
+                         [](auto& result) {
+                            StrongIntWithPodArithmetics i(42);
+                            StrongIntWithPodArithmetics i2(2);
+
+                            result.confirm("i +", Botan::is_strong_type_v<decltype(i + 1)>);
+                            result.confirm("i -", Botan::is_strong_type_v<decltype(i - 1)>);
+                            result.confirm("i *", Botan::is_strong_type_v<decltype(i * 2)>);
+                            result.confirm("i /", Botan::is_strong_type_v<decltype(i / 2)>);
+                            result.confirm("i ^", Botan::is_strong_type_v<decltype((i ^ 10))>);
+                            result.confirm("i &", Botan::is_strong_type_v<decltype((i & 15))>);
+                            result.confirm("i |", Botan::is_strong_type_v<decltype((i | 4))>);
+                            result.confirm("i >>", Botan::is_strong_type_v<decltype((i >> 2))>);
+                            result.confirm("i <<", Botan::is_strong_type_v<decltype((i << 2))>);
+
+                            result.confirm("+ i", Botan::is_strong_type_v<decltype(1 + i)>);
+                            result.confirm("- i", Botan::is_strong_type_v<decltype(1 - i)>);
+                            result.confirm("* i", Botan::is_strong_type_v<decltype(2 * i)>);
+                            result.confirm("/ i", Botan::is_strong_type_v<decltype(84 / i)>);
+                            result.confirm("^ i", Botan::is_strong_type_v<decltype((10 ^ i))>);
+                            result.confirm("& i", Botan::is_strong_type_v<decltype((15 & i))>);
+                            result.confirm("| i", Botan::is_strong_type_v<decltype((4 | i))>);
+                            result.confirm(">> i", Botan::is_strong_type_v<decltype((4 >> i2))>);
+                            result.confirm("<< i", Botan::is_strong_type_v<decltype((2 << i2))>);
+
+                            result.confirm("i +=", Botan::is_strong_type_v<decltype(i += 2)>);
+                            result.confirm("i -=", Botan::is_strong_type_v<decltype(i -= 2)>);
+                            result.confirm("i *=", Botan::is_strong_type_v<decltype(i *= 2)>);
+                            result.confirm("i /=", Botan::is_strong_type_v<decltype(i /= 2)>);
+                            result.confirm("i ^=", Botan::is_strong_type_v<decltype(i ^= 2)>);
+                            result.confirm("i &=", Botan::is_strong_type_v<decltype(i &= 12)>);
+                            result.confirm("i |=", Botan::is_strong_type_v<decltype(i |= 2)>);
+                            result.confirm("i <<=", Botan::is_strong_type_v<decltype(i <<= 2)>);
+                            result.confirm("i >>=", Botan::is_strong_type_v<decltype(i >>= 4)>);
+                         }),
+   };
+}
+
+using Test_Foo = Botan::Strong<std::vector<uint8_t>, struct Test_Foo_>;
+using Test_Bar = Botan::Strong<std::vector<uint8_t>, struct Test_Bar_>;
+
+[[maybe_unused]] int test_strong_helper(Botan::StrongSpan<Test_Foo>) { return 0; }
+
+[[maybe_unused]] int test_strong_helper(Botan::StrongSpan<const Test_Foo>) { return 1; }
+
+[[maybe_unused]] int test_strong_helper(Botan::StrongSpan<Test_Bar>) { return 2; }
+
+Test::Result test_strong_span() {
+   Test::Result result("StrongSpan<>");
+
+   const Test_Foo foo(Botan::hex_decode("DEADBEEF"));
+   result.test_is_eq("binds to StrongSpan<const Test_Foo>", test_strong_helper(foo), 1);
+
+   Test_Bar bar(Botan::hex_decode("CAFECAFE"));
+   result.test_is_eq("binds to StrongSpan<Test_Bar>", test_strong_helper(bar), 2);
+
+   Botan::StrongSpan<const Test_Foo> span(foo);
+
+   result.confirm("underlying type is uint8_t", std::is_same_v<decltype(span)::value_type, uint8_t>);
+   result.confirm("strong type is a contiguous buffer", Botan::concepts::contiguous_container<decltype(foo)>);
+   result.confirm("strong type is a contiguous strong type buffer",
+                  Botan::concepts::contiguous_strong_type<decltype(foo)>);
+   result.confirm("strong span is not a contiguous buffer", !Botan::concepts::contiguous_container<decltype(span)>);
+   result.confirm("strong span is not a contiguous strong type buffer",
+                  !Botan::concepts::contiguous_strong_type<decltype(span)>);
+
+   return result;
+}
+
 }  // namespace
 
-BOTAN_REGISTER_TEST_FN("utils", "strong_type", test_strong_type, test_container_strong_type);
+BOTAN_REGISTER_TEST_FN(
+   "utils", "strong_type", test_strong_type, test_container_strong_type, test_integer_strong_type, test_strong_span);
 
 }  // namespace Botan_Tests
diff --git a/src/tests/test_utils_buffer.cpp b/src/tests/test_utils_buffer.cpp
new file mode 100644
index 000000000..ebbda67c9
--- /dev/null
+++ b/src/tests/test_utils_buffer.cpp
@@ -0,0 +1,155 @@
+/*
+ * (C) 2023 Jack Lloyd
+ * (C) 2023 René Meusel, Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#include "tests.h"
+
+#include <botan/internal/stl_util.h>
+
+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.confirm("empty slicer has no remaining bytes", s.remaining() == 0);
+               result.confirm("empty slicer is empty()", s.empty());
+               result.confirm("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_eq("non-empty slicer has remaining bytes", s.remaining(), buffer.size());
+               result.confirm("non-empty slicer is not empty()", !s.empty());
+
+               const auto hello = s.take(5);
+               result.require("has 5 bytes", hello.size() == 5);
+               result.test_is_eq("took hello", hello[0], uint8_t('h'));
+               result.test_is_eq("took hello", hello[1], uint8_t('e'));
+               result.test_is_eq("took hello", hello[2], uint8_t('l'));
+               result.test_is_eq("took hello", hello[3], uint8_t('l'));
+               result.test_is_eq("took hello", hello[4], uint8_t('o'));
+
+               result.test_eq("remaining bytes", s.remaining(), 6);
+
+               s.skip(1);
+               result.test_eq("remaining bytes", s.remaining(), 5);
+
+               const auto wor = s.copy_as_vector(3);
+               result.require("has 3 bytes", wor.size() == 3);
+               result.test_is_eq("took wor...", wor[0], uint8_t('w'));
+               result.test_is_eq("took wor...", wor[1], uint8_t('o'));
+               result.test_is_eq("took wor...", wor[2], uint8_t('r'));
+               result.test_eq("remaining bytes", s.remaining(), 2);
+
+               std::vector<uint8_t> ld(2);
+               s.copy_into(ld);
+               result.test_is_eq("took ...ld", ld[0], uint8_t('l'));
+               result.test_is_eq("took ...ld", ld[1], uint8_t('d'));
+
+               result.confirm("empty", s.empty());
+               result.test_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_as<std::vector<uint8_t>>(span1, span2, vec1, vec2, vec3, vec4);
+               result.test_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_eq("has no capacity", s.remaining_capacity(), 0);
+               result.confirm("is immediately full", s.full());
+               result.confirm("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(11);
+               Botan::BufferStuffer s(sink);
+
+               result.test_eq("has some capacity", s.remaining_capacity(), sink.size());
+               result.confirm("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_eq("has 3 bytes remaining", s.remaining_capacity(), 3);
+
+               std::vector<uint8_t> rld{'r', 'l', 'd'};
+               s.append(rld);
+
+               result.test_eq("has 0 bytes remaining", s.remaining_capacity(), 0);
+               result.confirm("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);
+               });
+            }),
+   };
+}
+
+BOTAN_REGISTER_TEST_FN("utils", "buffer_utilities", test_buffer_slicer, test_buffer_stuffer);
+
+}  // namespace
+
+}  // namespace Botan_Tests