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