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