Repository: randombit/botan
Pull request #3294 (2023-02-16 13:55:16)

Chore: Buffered_Computation can deal with generic containers
This should be a transparent change for the most part but enables modern support for all sorts of containers without resorting to the C-style pointer APIs.

Allows passing all sorts of contiguous ranges to `::update()` and `::process()` as well as storing results of `Buffered_Computation` in user-defined contiguous containers.

The latter is most-useful for the strong types:

```C++
using MessageHash = Strong<std::vector<uint8_t>, struct MessageHash_>;

auto sha = HashFunction::create("SHA-256");
sha->update("some message");
auto message_hash = sha->final<MessageHash>();
```


Patch:
diff --git a/src/lib/base/buf_comp.h b/src/lib/base/buf_comp.h
index 31bf48529..c01cea6e2 100644
--- a/src/lib/base/buf_comp.h
+++ b/src/lib/base/buf_comp.h
@@ -8,8 +8,10 @@
 #ifndef BOTAN_BUFFERED_COMPUTATION_H_
 #define BOTAN_BUFFERED_COMPUTATION_H_
 
+#include <botan/concepts.h>
 #include <botan/secmem.h>
-#include <string>
+#include <string_view>
+#include <span>
 
 namespace Botan {
 
@@ -34,18 +36,9 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
 
       /**
       * Add new input to process.
-      * @param in the input to process as a secure_vector
+      * @param in the input to process as a contiguous data range
       */
-      void update(const secure_vector<uint8_t>& in)
-         {
-         add_data(in.data(), in.size());
-         }
-
-      /**
-      * Add new input to process.
-      * @param in the input to process as a std::vector
-      */
-      void update(const std::vector<uint8_t>& in)
+      void update(std::span<const uint8_t> in)
          {
          add_data(in.data(), in.size());
          }
@@ -60,10 +53,10 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
 
       /**
       * Add new input to process.
-      * @param str the input to process as a std::string. Will be interpreted
+      * @param str the input to process as a std::string_view. Will be interpreted
       * as a byte array based on the strings encoding.
       */
-      void update(const std::string& str)
+      void update(std::string_view str)
          {
          add_data(cast_char_ptr_to_uint8(str.data()), str.size());
          }
@@ -82,6 +75,21 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
       */
       void final(uint8_t out[]) { final_result(out); }
 
+      /**
+      * Complete the computation and retrieve the
+      * final result as a container of your choice.
+      * @return a contiguous container holding the result
+      */
+      template<typename T>
+         requires(concepts::contiguous_container<T> &&
+                  concepts::resizable_container<T>)
+         T final()
+         {
+         T output(output_length());
+         final_result(output.data());
+         return output;
+         }
+
       /**
       * Complete the computation and retrieve the
       * final result.
@@ -89,16 +97,18 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
       */
       secure_vector<uint8_t> final()
          {
-         secure_vector<uint8_t> output(output_length());
-         final_result(output.data());
-         return output;
+         return final<secure_vector<uint8_t>>();
          }
 
       std::vector<uint8_t> final_stdvec()
          {
-         std::vector<uint8_t> output(output_length());
-         final_result(output.data());
-         return output;
+         return final<std::vector<uint8_t>>();
+         }
+
+      void final(std::span<uint8_t> out)
+         {
+         BOTAN_ASSERT_NOMSG(out.size() >= output_length());
+         final_result(out.data());
          }
 
       template<typename Alloc>
@@ -127,7 +137,7 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
       * @param in the input to process
       * @result the result of the call to final()
       */
-      secure_vector<uint8_t> process(const secure_vector<uint8_t>& in)
+      secure_vector<uint8_t> process(std::span<const uint8_t> in)
          {
          add_data(in.data(), in.size());
          return final();
@@ -136,25 +146,28 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
       /**
       * Update and finalize computation. Does the same as calling update()
       * and final() consecutively.
-      * @param in the input to process
+      * @param in the input to process as a string
       * @result the result of the call to final()
       */
-      secure_vector<uint8_t> process(const std::vector<uint8_t>& in)
+      secure_vector<uint8_t> process(std::string_view in)
          {
-         add_data(in.data(), in.size());
+         update(in);
          return final();
          }
 
       /**
       * Update and finalize computation. Does the same as calling update()
       * and final() consecutively.
-      * @param in the input to process as a string
+      * @param in the input to process as a contiguous container or string-like
       * @result the result of the call to final()
       */
-      secure_vector<uint8_t> process(const std::string& in)
+      template<typename OutT, typename T>
+         requires(concepts::convertible_to<T, std::string_view> ||
+                  concepts::convertible_to<T, std::span<const uint8_t>>)
+      OutT process(T in)
          {
          update(in);
-         return final();
+         return final<OutT>();
          }
 
       virtual ~Buffered_Computation() = default;
diff --git a/src/lib/utils/concepts.h b/src/lib/utils/concepts.h
index efda68047..dd172fa0e 100644
--- a/src/lib/utils/concepts.h
+++ b/src/lib/utils/concepts.h
@@ -103,8 +103,12 @@ concept has_empty = requires(T a)
 
 template <typename T>
 concept resizable_container =
-    container<T> &&
-    requires(T& c, typename T::size_type s) { c.resize(s); };
+   container<T> &&
+   requires(T& c, typename T::size_type s)
+      {
+      T(s);
+      c.resize(s);
+      };
 
 template<typename T>
 concept streamable = requires(std::ostream& os, T a)
diff --git a/src/lib/utils/strong_type.h b/src/lib/utils/strong_type.h
index 02d0020c9..7306a3170 100644
--- a/src/lib/utils/strong_type.h
+++ b/src/lib/utils/strong_type.h
@@ -64,6 +64,10 @@ class Strong_Adapter<T> : public Strong_Base<T>
       requires(concepts::contiguous_container<T>)
          : Strong_Adapter(T(span.begin(), span.end())) {}
 
+      explicit Strong_Adapter(size_t size)
+      requires(concepts::resizable_container<T>)
+         : Strong_Adapter(T(size)) {}
+
       // Disambiguates the usage of string literals, otherwise:
       // Strong_Adapter(std::span<>) and Strong_Adapter(const char*)
       // would be ambiguous.


Test patch:
diff --git a/src/tests/test_bufcomp.cpp b/src/tests/test_bufcomp.cpp
new file mode 100644
index 000000000..00776030f
--- /dev/null
+++ b/src/tests/test_bufcomp.cpp
@@ -0,0 +1,144 @@
+/**
+ * (C) 2023 Jack Lloyd
+ *     2023 Philippe Lieser - Rohde & Schwarz Cybersecurity
+ *     2023 René Meusel - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#include "tests.h"
+
+#include <botan/buf_comp.h>
+#include <botan/secmem.h>
+#include <botan/mem_ops.h>
+#include <botan/strong_type.h>
+
+#include <array>
+#include <string>
+#include <vector>
+
+namespace Botan_Tests {
+
+namespace {
+
+class Test_Buf_Comp final : public Botan::Buffered_Computation
+   {
+   public:
+      Test_Buf_Comp(Test::Result& res)
+         : m_result(res)
+         , m_counter(0) {}
+
+      size_t output_length() const override { return sizeof(m_counter); }
+
+      void add_data(const uint8_t input[], size_t length) override
+         {
+         if(m_result.test_eq("input length as expected", length, size_t(5)))
+            {
+            m_result.confirm("input[0] == 'A'", input[0] == 'A');
+            m_result.confirm("input[0] == 'B'", input[1] == 'B');
+            m_result.confirm("input[0] == 'C'", input[2] == 'C');
+            m_result.confirm("input[0] == 'D'", input[3] == 'D');
+            m_result.confirm("input[0] == 'E'", input[4] == 'E');
+            }
+
+         ++m_counter;
+         }
+
+      void final_result(uint8_t out[]) override
+         {
+         const uint8_t* counter = reinterpret_cast<const uint8_t*>(&m_counter);
+         std::copy(counter, counter + sizeof(m_counter), out);
+         }
+
+      size_t counter() const { return m_counter; }
+
+   private:
+      Test::Result& m_result;
+      size_t m_counter;
+   };
+
+void check(Test::Result& result, std::span<const uint8_t> produced, size_t expected)
+   {
+   const uint8_t* eptr = reinterpret_cast<const uint8_t*>(&expected);
+   result.confirm("result is correct", Botan::same_mem(produced.data(), eptr, sizeof(size_t)));
+   }
+
+using TestStdVector = Botan::Strong<std::vector<uint8_t>, struct TestStdVector_>;
+using TestSecureVector = Botan::Strong<Botan::secure_vector<uint8_t>, struct TestSecureVector_>;
+
+Test::Result test_buffered_computation_convenience_api()
+   {
+   // This is mainly to test compilability of the various container
+   // types as in and out parameters. Hence, we refrain from checking
+   // the 'final' output everywhere.
+   Test::Result result("Convenience API of Buffered_Computation");
+
+   Test_Buf_Comp t(result);
+
+   constexpr auto test_string = "ABCDE";
+   const std::vector<uint8_t> test_vector = {'A', 'B', 'C', 'D', 'E'};
+   const std::array<uint8_t, 5> test_array = {'A', 'B', 'C', 'D', 'E'};
+   const TestStdVector test_strong_type(test_vector);
+
+   Botan::secure_vector<uint8_t> out_sv;
+   std::vector<uint8_t> out_vec;
+   std::array<uint8_t, sizeof(std::size_t)> out_arr;
+   TestSecureVector out_strong_type;
+
+   // update with basic string-ish types
+   t.update("ABCDE");
+   t.update(test_string);
+   t.update(std::string(test_string));
+
+   // update with container types
+   t.update(test_vector);
+   t.update(test_array);
+   t.update(test_strong_type);
+
+   // final returning result
+   out_sv = t.final();
+   out_vec  = t.final_stdvec();
+   out_strong_type = t.final<TestSecureVector>();
+
+   // final using out param
+   t.final(out_sv);
+   t.final(out_arr);
+   t.final(out_strong_type);
+
+   check(result, out_strong_type, 6);
+
+   // test resizing of final out param
+   out_vec.resize(0);
+   t.final(out_vec);
+   out_vec.resize(t.output_length()*2);
+   t.final(out_vec);
+   result.test_int_eq("out vector is resized", out_vec.size(), t.output_length());
+
+   check(result, out_vec, 6);
+
+   // process with basic string-ish types as input
+   out_sv = t.process(test_string);
+   out_sv = t.process(std::string(test_string));
+
+   check(result, out_sv, 8);
+
+   // process with container types as input
+   out_sv = t.process(test_vector);
+   out_sv = t.process(test_array);
+
+   check(result, out_sv, 10);
+
+   // process with specific in and out type
+   out_vec = t.process<std::vector<uint8_t>>(test_vector);
+   const auto out_strong_sec_vec = t.process<TestSecureVector>(test_vector);
+
+   check(result, out_strong_sec_vec, 12);
+
+   return result;
+   }
+
+BOTAN_REGISTER_TEST_FN("base", "bufcomp_base_api", test_buffered_computation_convenience_api);
+
+}
+
+}