Botan 3.8.1
Crypto and TLS for C&
stl_util.h
Go to the documentation of this file.
1/*
2* STL Utility Functions
3* (C) 1999-2007 Jack Lloyd
4* (C) 2015 Simon Warta (Kullo GmbH)
5* (C) 2023-2024 René Meusel - Rohde & Schwarz Cybersecurity
6*
7* Botan is released under the Simplified BSD License (see license.txt)
8*/
9
10#ifndef BOTAN_STL_UTIL_H_
11#define BOTAN_STL_UTIL_H_
12
13#include <botan/assert.h>
14#include <botan/concepts.h>
15#include <botan/secmem.h>
16#include <botan/strong_type.h>
17
18#include <algorithm>
19#include <functional>
20#include <optional>
21#include <span>
22#include <string>
23#include <tuple>
24#include <variant>
25#include <vector>
26
27namespace Botan {
28
29template <concepts::contiguous_container T = std::vector<uint8_t>>
30inline T to_byte_vector(std::string_view s) {
31 return T(s.cbegin(), s.cend());
32}
33
34inline std::string to_string(std::span<const uint8_t> bytes) {
35 return std::string(bytes.begin(), bytes.end());
36}
37
38/**
39 * Reduce the values of @p keys into an accumulator initialized with @p acc using
40 * the reducer function @p reducer.
41 *
42 * The @p reducer is a function taking the accumulator and a single key to return the
43 * new accumulator. Keys are consecutively reduced into the accumulator.
44 *
45 * @return the accumulator containing the reduction of @p keys
46 */
47template <typename RetT, typename KeyT, typename ReducerT>
48RetT reduce(const std::vector<KeyT>& keys, RetT acc, ReducerT reducer)
49 requires std::is_convertible_v<ReducerT, std::function<RetT(RetT, const KeyT&)>>
50{
51 for(const KeyT& key : keys) {
52 acc = reducer(std::move(acc), key);
53 }
54 return acc;
55}
56
57/**
58* Existence check for values
59*/
60template <typename T, typename OT>
61bool value_exists(const std::vector<T>& vec, const OT& val) {
62 for(size_t i = 0; i != vec.size(); ++i) {
63 if(vec[i] == val) {
64 return true;
65 }
66 }
67 return false;
68}
69
70template <typename T, typename Pred>
71void map_remove_if(Pred pred, T& assoc) {
72 auto i = assoc.begin();
73 while(i != assoc.end()) {
74 if(pred(i->first)) {
75 assoc.erase(i++);
76 } else {
77 i++;
78 }
79 }
80}
81
82/**
83 * Helper class to ease unmarshalling of concatenated fixed-length values
84 */
85class BufferSlicer final {
86 public:
87 BufferSlicer(std::span<const uint8_t> buffer) : m_remaining(buffer) {}
88
89 template <concepts::contiguous_container ContainerT>
90 auto copy(const size_t count) {
91 const auto result = take(count);
92 return ContainerT(result.begin(), result.end());
93 }
94
95 auto copy_as_vector(const size_t count) { return copy<std::vector<uint8_t>>(count); }
96
97 auto copy_as_secure_vector(const size_t count) { return copy<secure_vector<uint8_t>>(count); }
98
99 std::span<const uint8_t> take(const size_t count) {
100 BOTAN_STATE_CHECK(remaining() >= count);
101 auto result = m_remaining.first(count);
102 m_remaining = m_remaining.subspan(count);
103 return result;
104 }
105
106 template <size_t count>
107 std::span<const uint8_t, count> take() {
108 BOTAN_STATE_CHECK(remaining() >= count);
109 auto result = m_remaining.first<count>();
110 m_remaining = m_remaining.subspan(count);
111 return result;
112 }
113
114 template <concepts::contiguous_strong_type T>
115 StrongSpan<const T> take(const size_t count) {
116 return StrongSpan<const T>(take(count));
117 }
118
119 uint8_t take_byte() { return take(1)[0]; }
120
121 void copy_into(std::span<uint8_t> sink) {
122 const auto data = take(sink.size());
123 std::copy(data.begin(), data.end(), sink.begin());
124 }
125
126 void skip(const size_t count) { take(count); }
127
128 size_t remaining() const { return m_remaining.size(); }
129
130 bool empty() const { return m_remaining.empty(); }
131
132 private:
133 std::span<const uint8_t> m_remaining;
134};
135
136/**
137 * @brief Helper class to ease in-place marshalling of concatenated fixed-length
138 * values.
139 *
140 * The size of the final buffer must be known from the start, reallocations are
141 * not performed.
142 */
143class BufferStuffer final {
144 public:
145 constexpr BufferStuffer(std::span<uint8_t> buffer) : m_buffer(buffer) {}
146
147 /**
148 * @returns a span for the next @p bytes bytes in the concatenated buffer.
149 * Checks that the buffer is not exceded.
150 */
151 constexpr std::span<uint8_t> next(size_t bytes) {
152 BOTAN_STATE_CHECK(m_buffer.size() >= bytes);
153
154 auto result = m_buffer.first(bytes);
155 m_buffer = m_buffer.subspan(bytes);
156 return result;
157 }
158
159 template <size_t bytes>
160 constexpr std::span<uint8_t, bytes> next() {
161 BOTAN_STATE_CHECK(m_buffer.size() >= bytes);
162
163 auto result = m_buffer.first<bytes>();
164 m_buffer = m_buffer.subspan(bytes);
165 return result;
166 }
167
168 template <concepts::contiguous_strong_type StrongT>
169 StrongSpan<StrongT> next(size_t bytes) {
170 return StrongSpan<StrongT>(next(bytes));
171 }
172
173 /**
174 * @returns a reference to the next single byte in the buffer
175 */
176 constexpr uint8_t& next_byte() { return next(1)[0]; }
177
178 constexpr void append(std::span<const uint8_t> buffer) {
179 auto sink = next(buffer.size());
180 std::copy(buffer.begin(), buffer.end(), sink.begin());
181 }
182
183 constexpr void append(uint8_t b, size_t repeat = 1) {
184 auto sink = next(repeat);
185 std::fill(sink.begin(), sink.end(), b);
186 }
187
188 constexpr bool full() const { return m_buffer.empty(); }
189
190 constexpr size_t remaining_capacity() const { return m_buffer.size(); }
191
192 private:
193 std::span<uint8_t> m_buffer;
194};
195
196namespace detail {
197
198/**
199 * Helper function that performs range size-checks as required given the
200 * selected output and input range types. If all lengths are known at compile
201 * time, this check will be performed at compile time as well. It will then
202 * instantiate an output range and concatenate the input ranges' contents.
203 */
204template <ranges::spanable_range OutR, ranges::spanable_range... Rs>
205constexpr OutR concatenate(Rs&&... ranges)
207{
208 OutR result;
209
210 // Prepare and validate the output range and construct a lambda that does the
211 // actual filling of the result buffer.
212 // (if no input ranges are given, GCC claims that fill_fn is unused)
213 [[maybe_unused]] auto fill_fn = [&] {
215 // dynamically allocate the correct result byte length
216 const size_t total_size = (ranges.size() + ... + 0);
217 result.reserve(total_size);
218
219 // fill the result buffer using a back-inserter
220 return [&result](auto&& range) {
221 std::copy(
222 std::ranges::begin(range), std::ranges::end(range), std::back_inserter(unwrap_strong_type(result)));
223 };
224 } else {
225 if constexpr((ranges::statically_spanable_range<Rs> && ... && true)) {
226 // all input ranges have a static extent, so check the total size at compile time
227 // (work around an issue in MSVC that warns `total_size` is unused)
228 [[maybe_unused]] constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
229 static_assert(result.size() == total_size, "size of result buffer does not match the sum of input buffers");
230 } else {
231 // at least one input range has a dynamic extent, so check the total size at runtime
232 const size_t total_size = (ranges.size() + ... + 0);
233 BOTAN_ARG_CHECK(result.size() == total_size,
234 "result buffer has static extent that does not match the sum of input buffers");
235 }
236
237 // fill the result buffer and hold the current output-iterator position
238 return [itr = std::ranges::begin(result)](auto&& range) mutable {
239 std::copy(std::ranges::begin(range), std::ranges::end(range), itr);
240 std::advance(itr, std::ranges::size(range));
241 };
242 }
243 }();
244
245 // perform the actual concatenation
246 (fill_fn(std::forward<Rs>(ranges)), ...);
247
248 return result;
249}
250
251} // namespace detail
252
253/**
254 * Concatenate an arbitrary number of buffers. Performs range-checks as needed.
255 *
256 * The output type can be auto-detected based on the input ranges, or explicitly
257 * specified by the caller. If all input ranges have a static extent, the total
258 * size is calculated at compile time and a statically sized std::array<> is used.
259 * Otherwise this tries to use the type of the first input range as output type.
260 *
261 * Alternatively, the output container type can be specified explicitly.
262 */
263template <typename OutR = detail::AutoDetect, ranges::spanable_range... Rs>
264constexpr auto concat(Rs&&... ranges)
265 requires(all_same_v<std::ranges::range_value_t<Rs>...>)
266{
267 if constexpr(std::same_as<detail::AutoDetect, OutR>) {
268 // Try to auto-detect a reasonable output type given the input ranges
269 static_assert(sizeof...(Rs) > 0, "Cannot auto-detect the output type if not a single input range is provided.");
270 using candidate_result_t = std::remove_cvref_t<std::tuple_element_t<0, std::tuple<Rs...>>>;
271 using result_range_value_t = std::remove_cvref_t<std::ranges::range_value_t<candidate_result_t>>;
272
273 if constexpr((ranges::statically_spanable_range<Rs> && ...)) {
274 // If all input ranges have a static extent, we can calculate the total size at compile time
275 // and therefore can use a statically sized output container. This is constexpr.
276 constexpr size_t total_size = (decltype(std::span{ranges})::extent + ... + 0);
277 using out_array_t = std::array<result_range_value_t, total_size>;
278 return detail::concatenate<out_array_t>(std::forward<Rs>(ranges)...);
279 } else {
280 // If at least one input range has a dynamic extent, we must use a dynamically allocated output container.
281 // We assume that the user wants to use the first input range's container type as output type.
282 static_assert(
284 "First input range has static extent, but a dynamically allocated output range is required. Please explicitly specify a dynamically allocatable output type.");
285 return detail::concatenate<candidate_result_t>(std::forward<Rs>(ranges)...);
286 }
287 } else {
288 // The caller has explicitly specified the output type
289 return detail::concatenate<OutR>(std::forward<Rs>(ranges)...);
290 }
291}
292
293template <typename... Alts, typename... Ts>
294constexpr bool holds_any_of(const std::variant<Ts...>& v) noexcept {
295 return (std::holds_alternative<Alts>(v) || ...);
296}
297
298template <typename GeneralVariantT, typename SpecialT>
299constexpr bool is_generalizable_to(const SpecialT&) noexcept {
300 return std::is_constructible_v<GeneralVariantT, SpecialT>;
301}
302
303template <typename GeneralVariantT, typename... SpecialTs>
304constexpr bool is_generalizable_to(const std::variant<SpecialTs...>&) noexcept {
305 return (std::is_constructible_v<GeneralVariantT, SpecialTs> && ...);
306}
307
308/**
309 * @brief Converts a given variant into another variant-ish whose type states
310 * are a super set of the given variant.
311 *
312 * This is useful to convert restricted variant types into more general
313 * variants types.
314 */
315template <typename GeneralVariantT, typename SpecialT>
316constexpr GeneralVariantT generalize_to(SpecialT&& specific) noexcept
317 requires(std::is_constructible_v<GeneralVariantT, std::decay_t<SpecialT>>)
318{
319 return std::forward<SpecialT>(specific);
320}
321
322/**
323 * @brief Converts a given variant into another variant-ish whose type states
324 * are a super set of the given variant.
325 *
326 * This is useful to convert restricted variant types into more general
327 * variants types.
328 */
329template <typename GeneralVariantT, typename... SpecialTs>
330constexpr GeneralVariantT generalize_to(std::variant<SpecialTs...> specific) noexcept {
331 static_assert(
333 "Desired general type must be implicitly constructible by all types of the specialized std::variant<>");
334 return std::visit([](auto s) -> GeneralVariantT { return s; }, std::move(specific));
335}
336
337// This is a helper utility to emulate pattern matching with std::visit.
338// See https://en.cppreference.com/w/cpp/utility/variant/visit for more info.
339template <class... Ts>
340struct overloaded : Ts... {
341 using Ts::operator()...;
342};
343// explicit deduction guide (not needed as of C++20)
344template <class... Ts>
345overloaded(Ts...) -> overloaded<Ts...>;
346
347/**
348 * @brief Helper class to create a RAII-style cleanup callback
349 *
350 * Ensures that the cleanup callback given in the object's constructor is called
351 * when the object is destroyed. Use this to ensure some cleanup code runs when
352 * leaving the current scope.
353 */
354template <std::invocable FunT>
355class scoped_cleanup final {
356 public:
357 explicit scoped_cleanup(FunT cleanup) : m_cleanup(std::move(cleanup)) {}
358
361
362 scoped_cleanup(scoped_cleanup&& other) : m_cleanup(std::move(other.m_cleanup)) { other.disengage(); }
363
365 if(this != &other) {
366 m_cleanup = std::move(other.m_cleanup);
367 other.disengage();
368 }
369 return *this;
370 }
371
373 if(m_cleanup.has_value()) {
374 m_cleanup.value()();
375 }
376 }
377
378 /**
379 * Disengage the cleanup callback, i.e., prevent it from being called
380 */
381 void disengage() { m_cleanup.reset(); }
382
383 private:
384 std::optional<FunT> m_cleanup;
385};
386
387/**
388* Define BOTAN_ASSERT_IS_SOME
389*/
390template <typename T>
391T assert_is_some(std::optional<T> v, const char* expr, const char* func, const char* file, int line) {
392 if(v) {
393 return *v;
394 } else {
395 Botan::assertion_failure(expr, "optional had value", func, file, line);
396 }
397}
398
399#define BOTAN_ASSERT_IS_SOME(v) assert_is_some(v, #v, __func__, __FILE__, __LINE__)
400
401/*
402 * @brief Helper class to pass literal strings to C++ templates
403 */
404template <size_t N>
405class StringLiteral final {
406 public:
407 constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
408
409 char value[N];
410};
411
412// TODO: C++23: replace with std::to_underlying
413template <typename T>
414 requires std::is_enum_v<T>
415auto to_underlying(T e) noexcept {
416 return static_cast<std::underlying_type_t<T>>(e);
417}
418
419// TODO: C++23 - use std::out_ptr
420template <typename T>
421[[nodiscard]] constexpr auto out_ptr(T& outptr) noexcept {
422 class out_ptr_t {
423 public:
424 constexpr ~out_ptr_t() noexcept {
425 m_ptr.reset(m_rawptr);
426 m_rawptr = nullptr;
427 }
428
429 constexpr out_ptr_t(T& outptr) noexcept : m_ptr(outptr), m_rawptr(nullptr) {}
430
431 out_ptr_t(const out_ptr_t&) = delete;
432 out_ptr_t(out_ptr_t&&) = delete;
433 out_ptr_t& operator=(const out_ptr_t&) = delete;
434 out_ptr_t& operator=(out_ptr_t&&) = delete;
435
436 [[nodiscard]] constexpr operator typename T::element_type **() && noexcept { return &m_rawptr; }
437
438 private:
439 T& m_ptr;
440 typename T::element_type* m_rawptr;
441 };
442
443 return out_ptr_t{outptr};
444}
445
446template <typename T>
447 requires std::is_default_constructible_v<T>
448[[nodiscard]] constexpr auto out_opt(std::optional<T>& outopt) noexcept {
449 class out_opt_t {
450 public:
451 constexpr ~out_opt_t() noexcept { m_opt = m_raw; }
452
453 constexpr out_opt_t(std::optional<T>& outopt) noexcept : m_opt(outopt) {}
454
455 out_opt_t(const out_opt_t&) = delete;
456 out_opt_t(out_opt_t&&) = delete;
457 out_opt_t& operator=(const out_opt_t&) = delete;
458 out_opt_t& operator=(out_opt_t&&) = delete;
459
460 [[nodiscard]] constexpr operator T*() && noexcept { return &m_raw; }
461
462 private:
463 std::optional<T>& m_opt;
464 T m_raw;
465 };
466
467 return out_opt_t{outopt};
468}
469
470} // namespace Botan
471
472#endif
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:43
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:31
size_t remaining() const
Definition stl_util.h:128
void skip(const size_t count)
Definition stl_util.h:126
auto copy_as_secure_vector(const size_t count)
Definition stl_util.h:97
void copy_into(std::span< uint8_t > sink)
Definition stl_util.h:121
uint8_t take_byte()
Definition stl_util.h:119
BufferSlicer(std::span< const uint8_t > buffer)
Definition stl_util.h:87
std::span< const uint8_t, count > take()
Definition stl_util.h:107
auto copy(const size_t count)
Definition stl_util.h:90
bool empty() const
Definition stl_util.h:130
std::span< const uint8_t > take(const size_t count)
Definition stl_util.h:99
StrongSpan< const T > take(const size_t count)
Definition stl_util.h:115
auto copy_as_vector(const size_t count)
Definition stl_util.h:95
constexpr void append(std::span< const uint8_t > buffer)
Definition stl_util.h:178
constexpr void append(uint8_t b, size_t repeat=1)
Definition stl_util.h:183
constexpr size_t remaining_capacity() const
Definition stl_util.h:190
StrongSpan< StrongT > next(size_t bytes)
Definition stl_util.h:169
constexpr std::span< uint8_t > next(size_t bytes)
Definition stl_util.h:151
constexpr std::span< uint8_t, bytes > next()
Definition stl_util.h:160
constexpr uint8_t & next_byte()
Definition stl_util.h:176
constexpr BufferStuffer(std::span< uint8_t > buffer)
Definition stl_util.h:145
constexpr bool full() const
Definition stl_util.h:188
constexpr StringLiteral(const char(&str)[N])
Definition stl_util.h:407
scoped_cleanup(const scoped_cleanup &)=delete
scoped_cleanup & operator=(const scoped_cleanup &)=delete
scoped_cleanup(FunT cleanup)
Definition stl_util.h:357
scoped_cleanup & operator=(scoped_cleanup &&other)
Definition stl_util.h:364
scoped_cleanup(scoped_cleanup &&other)
Definition stl_util.h:362
constexpr OutR concatenate(Rs &&... ranges)
Definition stl_util.h:205
constexpr GeneralVariantT generalize_to(SpecialT &&specific) noexcept
Converts a given variant into another variant-ish whose type states are a super set of the given vari...
Definition stl_util.h:316
void map_remove_if(Pred pred, T &assoc)
Definition stl_util.h:71
T to_byte_vector(std::string_view s)
Definition stl_util.h:30
constexpr auto out_ptr(T &outptr) noexcept
Definition stl_util.h:421
constexpr bool holds_any_of(const std::variant< Ts... > &v) noexcept
Definition stl_util.h:294
RetT reduce(const std::vector< KeyT > &keys, RetT acc, ReducerT reducer)
Definition stl_util.h:48
constexpr decltype(auto) unwrap_strong_type(T &&t)
Generically unwraps a strong type to its underlying type.
constexpr auto out_opt(std::optional< T > &outopt) noexcept
Definition stl_util.h:448
T assert_is_some(std::optional< T > v, const char *expr, const char *func, const char *file, int line)
Definition stl_util.h:391
constexpr auto concat(Rs &&... ranges)
Definition stl_util.h:264
bool value_exists(const std::vector< T > &vec, const OT &val)
Definition stl_util.h:61
constexpr bool is_generalizable_to(const SpecialT &) noexcept
Definition stl_util.h:299
std::string to_string(ErrorType type)
Convert an ErrorType to string.
Definition exceptn.cpp:13
auto to_underlying(T e) noexcept
Definition stl_util.h:415
void assertion_failure(const char *expr_str, const char *assertion_made, const char *func, const char *file, int line)
Definition assert.cpp:30
overloaded(Ts...) -> overloaded< Ts... >