Repository: randombit/botan
Pull request #3165 (2023-01-09 10:53:45)

Introduce a strong-type template
Usage:

```C++
// declare a strong type (`struct SessionID_` is needed to make this
// a unique type among all the other byte vector based types)
using SessionID = Strong<std::vector<uint8_t>, struct SessionID_>

SessionID sid{unlock(rng.random_vec(32))};
// I'm hoping this will eventually be:
// auto sid = rng.random_range<SessionID>(32);

// Strong Types try hard to act like their underlying type without
// doing implicit conversion between each other
std::cout << hex_encode(sid); // assumg `hex_encode` has a `std::span<>` overload
```

With that, constructors that take two byte vectors are much more ergonomic. No more mixing up parameters. Overloading constructors (despite both overloads being byte vectors):

```C++
using SessionID = Strong<std::vector<uint8_t>, struct SessionID_>
using SessionTicket = Strong<std::vector<uint8_t>, struct SessionTicket_>

// A Session_Handle must be constructed with an ID, a Ticket or both
// but it cannot be constructed without either of them.
struct Session_Handle {
  Session_Handle(SessionID id);
  Session_Handle(SessionTicket ticket);
  Session_Handle(SessionID id, SessionTicket ticket);
};
```

codecov-commenter: # [Codecov](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) Report
Base: **88.03**% // Head: **88.05**% // Increases project coverage by **`+0.02%`** :tada:
> Coverage data is based on head [(`a99defc`)](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) compared to base [(`8fa6793`)](https://codecov.io/gh/randombit/botan/commit/8fa679331b708cad199ba274453cbdbf01416492?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3165      +/-   ##
==========================================
+ Coverage   88.03%   88.05%   +0.02%     
==========================================
  Files         604      605       +1     
  Lines       67228    67294      +66     
  Branches     6736     6737       +1     
==========================================
+ Hits        59181    59254      +73     
+ Misses       5221     5212       -9     
- Partials     2826     2828       +2     
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | Coverage Δ | |
|---|---|---|
| [src/tests/test\_strong\_type.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3Rfc3Ryb25nX3R5cGUuY3Bw) | `100.00% <100.00%> (ø)` | |
| [src/lib/pk\_pad/emsa\_raw/emsa\_raw.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wa19wYWQvZW1zYV9yYXcvZW1zYV9yYXcuY3Bw) | `54.54% <0.00%> (-6.07%)` | :arrow_down: |
| [src/fuzzer/mode\_padding.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2Z1enplci9tb2RlX3BhZGRpbmcuY3Bw) | `91.07% <0.00%> (-3.58%)` | :arrow_down: |
| [src/cli/tls\_proxy.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2NsaS90bHNfcHJveHkuY3Bw) | `81.81% <0.00%> (-0.54%)` | :arrow_down: |
| [src/tests/test\_bigint.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfYmlnaW50LmNwcA==) | `90.83% <0.00%> (+0.04%)` | :arrow_up: |
| [src/lib/math/numbertheory/numthry.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9tYXRoL251bWJlcnRoZW9yeS9udW10aHJ5LmNwcA==) | `83.90% <0.00%> (+0.09%)` | :arrow_up: |
| [src/lib/asn1/asn1\_time.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9hc24xL2FzbjFfdGltZS5jcHA=) | `86.95% <0.00%> (+0.72%)` | :arrow_up: |
| [src/lib/asn1/der\_enc.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9hc24xL2Rlcl9lbmMuY3Bw) | `78.94% <0.00%> (+1.31%)` | :arrow_up: |
| [src/lib/pubkey/elgamal/elgamal.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZWxnYW1hbC9lbGdhbWFsLmNwcA==) | `86.30% <0.00%> (+1.36%)` | :arrow_up: |
| [src/fuzzer/barrett.cpp](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2Z1enplci9iYXJyZXR0LmNwcA==) | `72.22% <0.00%> (+5.55%)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/randombit/botan/pull/3165?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).

reneme: Ugh... I already tried to work around that. Apparently without much luck. I'll look into it once more. So far 2019 did keep better pace than other compilers. But I agree, it's likely that it'll become painful.

Patch:
diff --git a/src/lib/utils/concepts.h b/src/lib/utils/concepts.h
new file mode 100644
index 000000000..16540b028
--- /dev/null
+++ b/src/lib/utils/concepts.h
@@ -0,0 +1,100 @@
+/**
+ * Useful concepts that are available throughout the library
+ * (C) 2023 Jack Lloyd
+ *     2023 René Meusel - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#ifndef BOTAN_CONCEPTS_H_
+#define BOTAN_CONCEPTS_H_
+
+#include <compare>
+#include <concepts>
+#include <type_traits>
+
+namespace Botan::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.
+template <class FromT, class ToT>
+concept convertible_to =
+  std::is_convertible_v<FromT, ToT> &&
+  requires {
+    static_cast<ToT>(std::declval<FromT>());
+  };
+
+// TODO: C++20 provides concepts like std::equality_comparable or
+//       std::three_way_comparable, but at the time of this writing, some
+//       target platforms did not ship with those (Xcode 14, Android NDK r25,
+//       emscripten)
+
+template<typename T>
+concept equality_comparable = requires(const std::remove_reference_t<T>& a, const std::remove_reference_t<T> b)
+   {
+   { a == b } -> convertible_to<bool>;
+   };
+
+template<typename T>
+concept three_way_comparison_result =
+   convertible_to<T, std::weak_ordering> ||
+   convertible_to<T, std::partial_ordering> ||
+   convertible_to<T, std::strong_ordering>;
+
+template<typename T>
+concept three_way_comparable = requires(const std::remove_reference_t<T>& a, const std::remove_reference_t<T> b)
+   {
+   { a <=> b } -> three_way_comparison_result;
+   };
+
+// TODO: C++20 provides concepts like std::ranges::range or ::sized_range
+//       but at the time of this writing clang had not caught up on all
+//       platforms. E.g. clang 14 on Xcode does not support ranges properly.
+
+template<typename IterT, typename ContainerT>
+concept container_iterator =
+   std::same_as<IterT, typename ContainerT::iterator> ||
+   std::same_as<IterT, typename ContainerT::const_iterator>;
+
+template<typename PtrT, typename ContainerT>
+concept container_pointer =
+   std::same_as<PtrT, typename ContainerT::pointer> ||
+   std::same_as<PtrT, typename ContainerT::const_pointer>;
+
+
+template<typename T>
+concept container = requires(T a)
+   {
+   { a.begin() } -> container_iterator<T>;
+   { a.end() } -> container_iterator<T>;
+   { a.cbegin() } -> container_iterator<T>;
+   { a.cend() } -> container_iterator<T>;
+   { a.size() } -> std::same_as<typename T::size_type>;
+   };
+
+template<typename T>
+concept contiguous_container =
+   container<T> &&
+   requires(T a)
+   {
+   { a.data() } -> container_pointer<T>;
+   };
+
+template<typename T>
+concept has_empty = requires(T a)
+   {
+   { a.empty() } -> std::same_as<bool>;
+   };
+
+template <typename T>
+concept resizable_container =
+    container<T> &&
+    requires(T& c, typename T::size_type s) { c.resize(s); };
+
+template<typename T>
+concept streamable = requires(std::ostream& os, T a)
+   { os << a; };
+
+}
+
+#endif
diff --git a/src/lib/utils/info.txt b/src/lib/utils/info.txt
index 2f1d0da0c..7557d7710 100644
--- a/src/lib/utils/info.txt
+++ b/src/lib/utils/info.txt
@@ -12,12 +12,14 @@ load_on always
 <header:public>
 assert.h
 compiler.h
+concepts.h
 data_src.h
 database.h
 exceptn.h
 mem_ops.h
 mutex.h
 types.h
+strong_type.h
 version.h
 </header:public>
 
diff --git a/src/lib/utils/strong_type.h b/src/lib/utils/strong_type.h
new file mode 100644
index 000000000..9b6c36181
--- /dev/null
+++ b/src/lib/utils/strong_type.h
@@ -0,0 +1,142 @@
+/**
+ * A wrapper class to implement strong types
+ * (C) 2022 Jack Lloyd
+ *     2022 René Meusel - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#ifndef BOTAN_STRONG_TYPE_H_
+#define BOTAN_STRONG_TYPE_H_
+
+#include <ostream>
+#include <span>
+
+#include <botan/concepts.h>
+
+namespace Botan
+{
+
+namespace detail
+{
+
+template <typename T>
+class Strong_Base
+   {
+   private:
+      T m_value;
+
+   public:
+      Strong_Base() = default;
+      Strong_Base(const Strong_Base&) = default;
+      Strong_Base(Strong_Base&&) = default;
+      Strong_Base& operator=(const Strong_Base&) = default;
+      Strong_Base& operator=(Strong_Base&&) = default;
+
+      explicit Strong_Base(T v) : m_value(std::move(v)) {}
+
+      T& get() { return m_value; }
+      const T& get() const { return m_value; }
+   };
+
+template <typename T>
+class Strong_Adapter : public Strong_Base<T>
+   {
+   public:
+      using Strong_Base<T>::Strong_Base;
+   };
+
+template <concepts::container T>
+class Strong_Adapter<T> : public Strong_Base<T>
+   {
+   public:
+      using value_type = typename T::value_type;
+
+   public:
+      using Strong_Base<T>::Strong_Base;
+
+      explicit Strong_Adapter(std::span<const value_type> span)
+      requires(concepts::contiguous_container<T>)
+         : Strong_Adapter(T(span.begin(), span.end())) {}
+
+      // Disambiguates the usage of string literals, otherwise:
+      // Strong_Adapter(std::span<>) and Strong_Adapter(const char*)
+      // would be ambiguous.
+      explicit Strong_Adapter(const char* str)
+      requires(std::same_as<T, std::string>)
+         : Strong_Adapter(std::string(str)) {}
+
+   public:
+      decltype(auto) begin() noexcept(noexcept(this->get().begin()))
+         { return this->get().begin(); }
+
+      decltype(auto) begin() const noexcept(noexcept(this->get().begin()))
+         { return this->get().begin(); }
+
+      decltype(auto) end() noexcept(noexcept(this->get().end()))
+         { return this->get().end(); }
+
+      decltype(auto) end() const noexcept(noexcept(this->get().end()))
+         { return this->get().end(); }
+
+      auto size() const noexcept(noexcept(this->get().size()))
+         { return this->get().size(); }
+
+      decltype(auto) data() noexcept(noexcept(this->get().data()))
+      requires(concepts::contiguous_container<T>)
+         { return this->get().data(); }
+
+      decltype(auto) data() const noexcept(noexcept(this->get().data()))
+      requires(concepts::contiguous_container<T>)
+         { return this->get().data(); }
+
+      bool empty() const noexcept(noexcept(this->get().empty()))
+      requires(concepts::has_empty<T>)
+         { return this->get().empty(); }
+
+      void resize(size_t size) noexcept(noexcept(this->get().resize(size)))
+      requires(concepts::resizable_container<T>)
+         { this->get().resize(size); }
+   };
+
+}
+
+/**
+ * Strong types can be used as wrappers around common types to provide
+ * compile time semantics. They usually contribute to more maintainable and
+ * less error-prone code especially when dealing with function parameters.
+ *
+ * Internally, this provides adapters so that the wrapping strong type behaves
+ * as much as the underlying type as possible and desirable.
+ *
+ * This implementation was inspired by:
+ *   https://stackoverflow.com/a/69030899
+ */
+template<typename T, typename TagTypeT>
+class Strong : public detail::Strong_Adapter<T>
+   {
+   public:
+      using detail::Strong_Adapter<T>::Strong_Adapter;
+
+   private:
+      using Tag = TagTypeT;
+   };
+
+template<typename T, typename... Tags>
+requires(concepts::streamable<T>)
+decltype(auto) operator<<(std::ostream& os, const Strong<T, Tags...>& v)
+   { return os << v.get(); }
+
+template<typename T, typename... Tags>
+requires(concepts::equality_comparable<T>)
+auto operator==(const Strong<T, Tags...>& lhs, const Strong<T, Tags...>& rhs)
+   { return lhs.get() == rhs.get(); }
+
+template<typename T, typename... Tags>
+requires(concepts::three_way_comparable<T>)
+auto operator<=>(const Strong<T, Tags...>& lhs, const Strong<T, Tags...>& rhs)
+   { return lhs.get() <=> rhs.get(); }
+
+}
+
+#endif


Test patch:
diff --git a/src/tests/test_strong_type.cpp b/src/tests/test_strong_type.cpp
new file mode 100644
index 000000000..dc6740750
--- /dev/null
+++ b/src/tests/test_strong_type.cpp
@@ -0,0 +1,144 @@
+/*
+ * (C) 2023 Jack Lloyd
+ *     2023 René Meusel - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#include "tests.h"
+
+#include <botan/strong_type.h>
+#include <botan/hex.h>
+
+#include <sstream>
+#include <algorithm>
+
+namespace Botan_Tests {
+
+namespace {
+
+using Test_Size = Botan::Strong<size_t, struct Test_Size_>;
+using Test_Length = Botan::Strong<size_t, struct Test_Length_>;
+
+std::string foo(Test_Size)
+   { return "some size"; }
+std::string foo(Test_Length)
+   { return "some length"; }
+
+using Test_Nonce = Botan::Strong<std::vector<uint8_t>, struct Test_Nonce_>;
+using Test_Hash_Name = Botan::Strong<std::string, struct Test_Hash_Name_>;
+
+std::vector<Test::Result> test_strong_type()
+   {
+   return
+      {
+      Botan_Tests::CHECK("strong type initialization", [](auto&)
+         {
+         // default constructor
+         Test_Size size1;
+
+         // value initialization
+         [[maybe_unused]] Test_Size size2(42);
+
+         // assignment operator
+         size1 = Test_Size(42);
+         }),
+
+      Botan_Tests::CHECK("value retrieval", [](auto& result)
+         {
+         Test_Size a(42);
+         const Test_Size b(42);
+
+         result.test_is_eq("get()", a.get(), size_t(42));
+         result.test_is_eq("const get()", b.get(), size_t(42));
+         }),
+
+      Botan_Tests::CHECK("comparisons", [](auto& result)
+         {
+         const Test_Size a(42);
+         const Test_Size b(42);
+
+         result.confirm("equal", a == b);
+         result.confirm("lower than", a < Test_Size(1337));
+         result.confirm("greater than", Test_Size(1337) > b);
+         }),
+
+      Botan_Tests::CHECK("function overloading", [](auto& result)
+         {
+         result.test_eq("overloading size", foo(Test_Size(42)), "some size");
+         result.test_eq("overloading size", foo(Test_Length(42)), "some length");
+         }),
+      };
+   }
+
+std::vector<Test::Result> test_container_strong_type()
+   {
+   return
+      {
+      Botan_Tests::CHECK("initialization", [](auto&)
+         {
+         [[maybe_unused]] Test_Nonce empty_nonce;
+         [[maybe_unused]] Test_Nonce short_nonce(Botan::hex_decode("DEADBEEF"));
+         }),
+
+      Botan_Tests::CHECK("behaves like a standard container", [](auto& result)
+         {
+         auto base_nonce = Botan::hex_decode("DEADBEEF");
+         auto dataptr = base_nonce.data();
+         auto nonce = Test_Nonce(std::move(base_nonce));
+
+         result.test_is_eq("size()", nonce.size(), size_t(4));
+         result.confirm("empty()", !nonce.empty());
+         result.test_is_eq("data()", nonce.data(), dataptr);
+
+         for(auto& c : nonce)
+            { result.confirm("iteration", c > 0); }
+         }),
+
+      Botan_Tests::CHECK("binds to a std::span<>", [](auto& result)
+         {
+         auto get_size = [](std::span<const uint8_t> data)
+            { return data.size(); };
+
+         const auto nonce = Test_Nonce(Botan::hex_decode("DEADBEEF"));
+
+         result.test_is_eq("can bind to std::span<>", get_size(nonce), nonce.size());
+         }),
+
+      Botan_Tests::CHECK("std::string container", [](auto& result)
+         {
+         Test_Hash_Name thn("SHA-1");
+
+         std::stringstream stream;
+         stream << thn;
+         result.test_eq("strong types are streamable", stream.str(), std::string("SHA-1"));
+         }),
+
+      Botan_Tests::CHECK("strong types are sortable", [](auto& result)
+         {
+         using Test_Length_List = Botan::Strong<std::vector<Test_Length>, struct Test_Length_List_>;
+
+         Test_Length_List hashes(
+            {
+            Test_Length(3),
+            Test_Length(1),
+            Test_Length(4),
+            Test_Length(2)
+            });
+
+         // TODO: C++20 - std::ranges::sort
+         std::sort(hashes.begin(), hashes.end());
+
+         result.test_eq("1", hashes.get().at(0).get(), size_t(1));
+         result.test_eq("2", hashes.get().at(1).get(), size_t(2));
+         result.test_eq("3", hashes.get().at(2).get(), size_t(3));
+         result.test_eq("4", hashes.get().at(3).get(), size_t(4));
+         }),
+      };
+   }
+
+}
+
+BOTAN_REGISTER_TEST_FN("stream", "strong_type", test_strong_type, test_container_strong_type);
+
+}