• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 16089210379

05 Jul 2025 02:47PM UTC coverage: 90.572% (+0.002%) from 90.57%
16089210379

Pull #4960

github

web-flow
Merge bc4a31e3c into c4f937d19
Pull Request #4960: Enable and fix clang-tidy warning bugprone-lambda-function-name

99063 of 109375 relevant lines covered (90.57%)

12408098.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.15
/src/lib/utils/loadstor.h
1
/*
2
* Load/Store Operators
3
* (C) 1999-2007,2015,2017 Jack Lloyd
4
*     2007 Yves Jerschow
5
*     2023-2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#ifndef BOTAN_LOAD_STORE_H_
11
#define BOTAN_LOAD_STORE_H_
12

13
#include <botan/concepts.h>
14
#include <botan/mem_ops.h>
15
#include <botan/strong_type.h>
16
#include <botan/types.h>
17
#include <botan/internal/bswap.h>
18
#include <bit>
19

20
/**
21
 * @file loadstor.h
22
 *
23
 * @brief This header contains various helper functions to load and store
24
 *        unsigned integers in big- or little-endian byte order.
25
 *
26
 * Storing integer values in various ways (same for BE and LE):
27
 * @code {.cpp}
28
 *
29
 *   std::array<uint8_t, 8> bytes = store_le(some_uint64);
30
 *   std::array<uint8_t, 12> bytes = store_le(some_uint32_1, some_uint32_2, some_uint32_3, ...);
31
 *   auto bytes = store_le<std::vector<uint8_t>>(some_uint64);
32
 *   auto bytes = store_le<MyContainerStrongType>(some_uint64);
33
 *   auto bytes = store_le<std::vector<uint8_t>>(vector_of_ints);
34
 *   auto bytes = store_le<secure_vector<uint8_t>>(some_uint32_1, some_uint32_2, some_uint32_3, ...);
35
 *   store_le(bytes, some_uint64);
36
 *   store_le(concatenated_bytes, some_uint64_1, some_uint64_2, some_uint64_3, ...);
37
 *   store_le(concatenated_bytes, vector_of_ints);
38
 *   copy_out_le(short_concated_bytes, vector_of_ints); // stores as many bytes as required in the output buffer
39
 *
40
 * @endcode
41
 *
42
 * Loading integer values in various ways (same for BE and LE):
43
 * @code {.cpp}
44
 *
45
 *   uint64_t some_uint64 = load_le(bytes_8);
46
 *   auto some_int32s = load_le<std::vector<uint32_t>>(concatenated_bytes);
47
 *   auto some_int32s = load_le<std::vector<MyIntStrongType>>(concatenated_bytes);
48
 *   auto some_int32s = load_le(some_strong_typed_bytes);
49
 *   auto strong_int  = load_le<MyStrongTypedInteger>(concatenated_bytes);
50
 *   load_le(concatenated_bytes, out_some_uint64);
51
 *   load_le(concatenated_bytes, out_some_uint64_1, out_some_uint64_2, out_some_uint64_3, ...);
52
 *   load_le(out_vector_of_ints, concatenated_bytes);
53
 *
54
 * @endcode
55
 */
56

57
namespace Botan {
58

59
static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little,
60
              "Mixed endian systems are not supported");
61

62
/**
63
* Byte extraction
64
* @param byte_num which byte to extract, 0 == highest byte
65
* @param input the value to extract from
66
* @return byte byte_num of input
67
*/
68
template <typename T>
69
inline constexpr uint8_t get_byte_var(size_t byte_num, T input) {
2,108,251✔
70
   return static_cast<uint8_t>(input >> (((~byte_num) & (sizeof(T) - 1)) << 3));
2,148,667✔
71
}
72

73
/**
74
* Byte extraction
75
* @param input the value to extract from
76
* @return byte byte number B of input
77
*/
78
template <size_t B, typename T>
79
inline constexpr uint8_t get_byte(T input)
2,147,483,647✔
80
   requires(B < sizeof(T))
81
{
82
   const size_t shift = ((~B) & (sizeof(T) - 1)) << 3;
2,147,483,647✔
83
   return static_cast<uint8_t>((input >> shift) & 0xFF);
2,147,483,647✔
84
}
85

86
/**
87
* Make a uint16_t from two bytes
88
* @param i0 the first byte
89
* @param i1 the second byte
90
* @return i0 || i1
91
*/
92
inline constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1) {
1,244,243✔
93
   return static_cast<uint16_t>((static_cast<uint16_t>(i0) << 8) | i1);
1,101,172✔
94
}
95

96
/**
97
* Make a uint32_t from four bytes
98
* @param i0 the first byte
99
* @param i1 the second byte
100
* @param i2 the third byte
101
* @param i3 the fourth byte
102
* @return i0 || i1 || i2 || i3
103
*/
104
inline constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3) {
84,038,805✔
105
   return ((static_cast<uint32_t>(i0) << 24) | (static_cast<uint32_t>(i1) << 16) | (static_cast<uint32_t>(i2) << 8) |
84,038,805✔
106
           (static_cast<uint32_t>(i3)));
83,771,784✔
107
}
108

109
/**
110
* Make a uint64_t from eight bytes
111
* @param i0 the first byte
112
* @param i1 the second byte
113
* @param i2 the third byte
114
* @param i3 the fourth byte
115
* @param i4 the fifth byte
116
* @param i5 the sixth byte
117
* @param i6 the seventh byte
118
* @param i7 the eighth byte
119
* @return i0 || i1 || i2 || i3 || i4 || i5 || i6 || i7
120
*/
121
inline constexpr uint64_t make_uint64(
50✔
122
   uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7) {
123
   return ((static_cast<uint64_t>(i0) << 56) | (static_cast<uint64_t>(i1) << 48) | (static_cast<uint64_t>(i2) << 40) |
50✔
124
           (static_cast<uint64_t>(i3) << 32) | (static_cast<uint64_t>(i4) << 24) | (static_cast<uint64_t>(i5) << 16) |
50✔
125
           (static_cast<uint64_t>(i6) << 8) | (static_cast<uint64_t>(i7)));
50✔
126
}
127

128
namespace detail {
129

130
/**
131
 * @returns the opposite endianness of the specified endianness
132
 *
133
 * Note this assumes that there are only two endian orderings; we
134
 * do not supported mixed endian systems
135
 */
136
consteval std::endian opposite(std::endian endianness) {
137
   if(endianness == std::endian::big) {
138
      return std::endian::little;
139
   } else {
140
      // We already verified via static assert earlier in this file that we are
141
      // running on either a big endian or little endian system
142
      return std::endian::big;
143
   }
144
}
145

146
/**
147
 * Models a custom type that provides factory methods to be loaded in big- or
148
 * little-endian byte order.
149
 */
150
template <typename T>
151
concept custom_loadable = requires(std::span<const uint8_t, sizeof(T)> data) {
152
   { T::load_be(data) } -> std::same_as<T>;
153
   { T::load_le(data) } -> std::same_as<T>;
154
};
155

156
/**
157
 * Models a custom type that provides store methods to be stored in big- or
158
 * little-endian byte order.
159
 */
160
template <typename T>
161
concept custom_storable = requires(std::span<uint8_t, sizeof(T)> data, const T value) {
162
   { value.store_be(data) };
163
   { value.store_le(data) };
164
};
165

166
/**
167
 * Models a type that can be loaded/stored from/to a byte range.
168
 */
169
template <typename T>
170
concept unsigned_integralish =
171
   std::unsigned_integral<strong_type_wrapped_type<T>> ||
172
   (std::is_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>) ||
173
   (custom_loadable<strong_type_wrapped_type<T>> || custom_storable<strong_type_wrapped_type<T>>);
174

175
template <typename T>
176
struct wrapped_type_helper_with_enum {
177
      using type = strong_type_wrapped_type<T>;
178
};
179

180
template <typename T>
181
   requires std::is_enum_v<T>
182
struct wrapped_type_helper_with_enum<T> {
183
      using type = std::underlying_type_t<T>;
184
};
185

186
template <unsigned_integralish T>
187
using wrapped_type = typename wrapped_type_helper_with_enum<T>::type;
188

189
template <unsigned_integralish InT>
190
constexpr auto unwrap_strong_type_or_enum(InT t) {
191
   if constexpr(std::is_enum_v<InT>) {
192
      // TODO: C++23: use std::to_underlying(in) instead
193
      return static_cast<std::underlying_type_t<InT>>(t);
194
   } else {
195
      return Botan::unwrap_strong_type(t);
196
   }
197
}
198

199
template <unsigned_integralish OutT, std::unsigned_integral T>
200
constexpr auto wrap_strong_type_or_enum(T t) {
2,229✔
201
   if constexpr(std::is_enum_v<OutT>) {
202
      return static_cast<OutT>(t);
203
   } else {
204
      return Botan::wrap_strong_type<OutT>(t);
2,229✔
205
   }
206
}
207

208
/**
209
 * Manually load a word from a range in either big or little endian byte order.
210
 *
211
 * This is only used at compile time.
212
 */
213
template <std::endian endianness, std::unsigned_integral OutT, ranges::contiguous_range<uint8_t> InR>
214
inline constexpr OutT fallback_load_any(const InR& in_range) {
6✔
215
   std::span in{in_range};
6✔
216
   // clang-format off
217
   if constexpr(endianness == std::endian::big) {
218
      return [&]<size_t... i>(std::index_sequence<i...>) {
2✔
219
         return static_cast<OutT>(((static_cast<OutT>(in[i]) << ((sizeof(OutT) - i - 1) * 8)) | ...));
1✔
220
      } (std::make_index_sequence<sizeof(OutT)>());
1✔
221
   } else {
222
      static_assert(endianness == std::endian::little);
223
      return [&]<size_t... i>(std::index_sequence<i...>) {
2✔
224
         return static_cast<OutT>(((static_cast<OutT>(in[i]) << (i * 8)) | ...));
1✔
225
      } (std::make_index_sequence<sizeof(OutT)>());
1✔
226
   }
227
   // clang-format on
228
}
229

230
/**
231
 * Manually store a word into a range in either big or little endian byte order.
232
 *
233
 * This will be used only at compile time.
234
 */
235
template <std::endian endianness, std::unsigned_integral InT, ranges::contiguous_output_range<uint8_t> OutR>
236
inline constexpr void fallback_store_any(InT in, OutR&& out_range /* NOLINT(*-std-forward) */) {
6✔
237
   std::span out{out_range};
6✔
238
   // clang-format off
239
   if constexpr(endianness == std::endian::big) {
240
      [&]<size_t... i>(std::index_sequence<i...>) {
4✔
241
         ((out[i] = get_byte<i>(in)), ...);
1✔
242
      } (std::make_index_sequence<sizeof(InT)>());
2✔
243
   } else {
244
      static_assert(endianness == std::endian::little);
245
      [&]<size_t... i>(std::index_sequence<i...>) {
4✔
246
         ((out[i] = get_byte<sizeof(InT) - i - 1>(in)), ...);
1✔
247
      } (std::make_index_sequence<sizeof(InT)>());
2✔
248
   }
249
   // clang-format on
250
}
251

252
/**
253
 * Load a word from a range in either big or little endian byte order
254
 *
255
 * This is the base implementation, all other overloads are just convenience
256
 * wrappers. It is assumed that the range has the correct size for the word.
257
 *
258
 * Template arguments of all overloads of load_any() share the same semantics:
259
 *
260
 *   1.  std::endian     Either `std::endian::big` or `std::endian::little`, that
261
 *                       will eventually select the byte order translation mode
262
 *                       implemented in this base function.
263
 *
264
 *   2.  Output type     Either `AutoDetect`, an unsigned integer or a container
265
 *                       holding an unsigned integer type. `AutoDetect` means
266
 *                       that the caller did not explicitly specify the type and
267
 *                       expects the type to be inferred from the input.
268
 *
269
 *   3+. Argument types  Typically, those are input and output ranges of bytes
270
 *                       or unsigned integers. Or one or more unsigned integers
271
 *                       acting as output parameters.
272
 *
273
 * @param in_range a fixed-length byte range
274
 * @return T loaded from @p in_range, as a big-endian value
275
 */
276
template <std::endian endianness, unsigned_integralish WrappedOutT, ranges::contiguous_range<uint8_t> InR>
277
   requires(!custom_loadable<strong_type_wrapped_type<WrappedOutT>>)
278
inline constexpr WrappedOutT load_any(InR&& in_range) {
2,147,483,647✔
279
   using OutT = detail::wrapped_type<WrappedOutT>;
280
   ranges::assert_exact_byte_length<sizeof(OutT)>(in_range);
2,603,376✔
281

282
   return detail::wrap_strong_type_or_enum<WrappedOutT>([&]() -> OutT {
2,147,483,647✔
283
      // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
284
      // internally to copy ranges on a byte-by-byte basis, which is not allowed
285
      // in a `constexpr` context.
286
      if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
287
         return fallback_load_any<endianness, OutT>(std::forward<InR>(in_range));
288
      } else {
289
         std::span in{in_range};
2,147,483,647✔
290
         if constexpr(sizeof(OutT) == 1) {
291
            return static_cast<OutT>(in[0]);
6,807,498✔
292
         } else if constexpr(endianness == std::endian::native) {
293
            return typecast_copy<OutT>(in);
2,147,483,647✔
294
         } else {
295
            static_assert(opposite(endianness) == std::endian::native);
296
            return reverse_bytes(typecast_copy<OutT>(in));
18,261,837✔
297
         }
298
      }
299
   }());
8✔
300
}
301

302
/**
303
 * Load a custom object from a range in either big or little endian byte order
304
 *
305
 * This is the base implementation for custom objects (e.g. SIMD type wrappres),
306
 * all other overloads are just convenience overloads.
307
 *
308
 * @param in_range a fixed-length byte range
309
 * @return T loaded from @p in_range, as a big-endian value
310
 */
311
template <std::endian endianness, unsigned_integralish WrappedOutT, ranges::contiguous_range<uint8_t> InR>
312
   requires(custom_loadable<strong_type_wrapped_type<WrappedOutT>>)
313
inline constexpr WrappedOutT load_any(const InR& in_range) {
8✔
314
   using OutT = detail::wrapped_type<WrappedOutT>;
315
   ranges::assert_exact_byte_length<sizeof(OutT)>(in_range);
4✔
316
   std::span<const uint8_t, sizeof(OutT)> ins{in_range};
2✔
317
   if constexpr(endianness == std::endian::big) {
318
      return wrap_strong_type<WrappedOutT>(OutT::load_be(ins));
3✔
319
   } else {
320
      return wrap_strong_type<WrappedOutT>(OutT::load_le(ins));
3✔
321
   }
322
}
323

324
/**
325
 * Load many unsigned integers
326
 * @param in   a fixed-length span to some bytes
327
 * @param outs a arbitrary-length parameter list of unsigned integers to be loaded
328
 */
329
template <std::endian endianness, typename OutT, ranges::contiguous_range<uint8_t> InR, unsigned_integralish... Ts>
330
   requires(sizeof...(Ts) > 0) && ((std::same_as<AutoDetect, OutT> && all_same_v<Ts...>) ||
331
                                   (unsigned_integralish<OutT> && all_same_v<OutT, Ts...>))
332
inline constexpr void load_any(const InR& in, Ts&... outs) {
1,106,953✔
333
   ranges::assert_exact_byte_length<(sizeof(Ts) + ...)>(in);
2✔
334
   auto load_one = [off = 0]<typename T>(auto i, T& o) mutable {
2,557,252✔
335
      o = load_any<endianness, T>(i.subspan(off).template first<sizeof(T)>());
1,106,961✔
336
      off += sizeof(T);
1,450,305✔
337
   };
338

339
   (load_one(std::span{in}, outs), ...);
1,106,953✔
340
}
1,106,953✔
341

342
/**
343
 * Load a variable number of words from @p in into @p out.
344
 * The byte length of the @p out and @p in ranges must match.
345
 *
346
 * @param out the output range of words
347
 * @param in the input range of bytes
348
 */
349
template <std::endian endianness,
350
          typename OutT,
351
          ranges::contiguous_output_range OutR,
352
          ranges::contiguous_range<uint8_t> InR>
353
   requires(unsigned_integralish<std::ranges::range_value_t<OutR>> &&
354
            (std::same_as<AutoDetect, OutT> || std::same_as<OutT, std::ranges::range_value_t<OutR>>))
355
inline constexpr void load_any(OutR&& out /* NOLINT(*-std-forward) */, const InR& in) {
121,874,722✔
356
   ranges::assert_equal_byte_lengths(out, in);
121,873,065✔
357
   using element_type = std::ranges::range_value_t<OutR>;
358

359
   auto load_elementwise = [&] {
121,996,356✔
360
      constexpr size_t bytes_per_element = sizeof(element_type);
60,819✔
361
      std::span<const uint8_t> in_s(in);
60,819✔
362
      for(auto& out_elem : out) {
478,449✔
363
         out_elem = load_any<endianness, element_type>(in_s.template first<bytes_per_element>());
417,630✔
364
         in_s = in_s.subspan(bytes_per_element);
417,630✔
365
      }
366
   };
367

368
   // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
369
   // internally to copy ranges on a byte-by-byte basis, which is not allowed
370
   // in a `constexpr` context.
371
   if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
372
      load_elementwise();
373
   } else {
374
      if constexpr(endianness == std::endian::native && !custom_loadable<element_type>) {
375
         typecast_copy(out, in);
121,813,903✔
376
      } else {
377
         load_elementwise();
60,819✔
378
      }
379
   }
380
}
218✔
381

382
//
383
// Type inference overloads
384
//
385

386
/**
387
 * Load one or more unsigned integers, auto-detect the output type if
388
 * possible. Otherwise, use the specified integer or integer container type.
389
 *
390
 * @param in_range a statically-sized range with some bytes
391
 * @return T loaded from in
392
 */
393
template <std::endian endianness, typename OutT, ranges::contiguous_range<uint8_t> InR>
394
   requires(std::same_as<AutoDetect, OutT> ||
395
            ((ranges::statically_spanable_range<OutT> || concepts::resizable_container<OutT>) &&
396
             unsigned_integralish<typename OutT::value_type>))
397
inline constexpr auto load_any(InR&& in_range) {
18,800,443✔
398
   auto out = []([[maybe_unused]] const auto& in) {
18,706,044✔
399
      if constexpr(std::same_as<AutoDetect, OutT>) {
400
         if constexpr(ranges::statically_spanable_range<InR>) {
401
            constexpr size_t extent = decltype(std::span{in})::extent;
8,945,717✔
402

403
            // clang-format off
404
            using type =
405
               std::conditional_t<extent == 1, uint8_t,
406
               std::conditional_t<extent == 2, uint16_t,
407
               std::conditional_t<extent == 4, uint32_t,
408
               std::conditional_t<extent == 8, uint64_t, void>>>>;
409
            // clang-format on
410

411
            static_assert(
412
               !std::is_void_v<type>,
413
               "Cannot determine the output type based on a statically sized bytearray with length other than those: 1, 2, 4, 8");
414

415
            return type{};
416
         } else {
417
            static_assert(
418
               !std::same_as<AutoDetect, OutT>,
419
               "cannot infer return type from a dynamic range at compile time, please specify it explicitly");
420
         }
421
      } else if constexpr(concepts::resizable_container<OutT>) {
422
         const size_t in_bytes = std::span{in}.size_bytes();
220✔
423
         constexpr size_t out_elem_bytes = sizeof(typename OutT::value_type);
220✔
424
         if(in_bytes % out_elem_bytes != 0) {
220✔
425
            Botan::throw_invalid_argument("Input range is not word-aligned with the requested output range",
×
426
                                          "Botan::load_any", __FILE__);
427
         }
428
         return OutT(in_bytes / out_elem_bytes);
220✔
429
      } else {
430
         return OutT{};
3,946✔
431
      }
432
   }(in_range);
18,800,223✔
433

434
   using out_type = decltype(out);
435
   if constexpr(unsigned_integralish<out_type>) {
436
      out = load_any<endianness, out_type>(std::forward<InR>(in_range));
8,945,717✔
437
   } else {
438
      static_assert(ranges::contiguous_range<out_type>);
439
      using out_range_type = std::ranges::range_value_t<out_type>;
440
      load_any<endianness, out_range_type>(out, std::forward<InR>(in_range));
9,854,726✔
441
   }
442
   return out;
9,850,786✔
443
}
×
444

445
//
446
// Legacy load functions that work on raw pointers and arrays
447
//
448

449
/**
450
 * Load a word from @p in at some offset @p off
451
 * @param in a pointer to some bytes
452
 * @param off an offset into the array
453
 * @return off'th T of in, as a big-endian value
454
 */
455
template <std::endian endianness, unsigned_integralish OutT>
456
inline constexpr OutT load_any(const uint8_t in[], size_t off) {
2,147,483,647✔
457
   // asserts that *in points to enough bytes to read at offset off
458
   constexpr size_t out_size = sizeof(OutT);
2,147,483,647✔
459
   return load_any<endianness, OutT>(std::span<const uint8_t, out_size>(in + off * out_size, out_size));
2,147,483,647✔
460
}
461

462
/**
463
 * Load many words from @p in
464
 * @param in   a pointer to some bytes
465
 * @param outs a arbitrary-length parameter list of unsigned integers to be loaded
466
 */
467
template <std::endian endianness, typename OutT, unsigned_integralish... Ts>
468
   requires(sizeof...(Ts) > 0 && all_same_v<Ts...> &&
469
            ((std::same_as<AutoDetect, OutT> && all_same_v<Ts...>) ||
470
             (unsigned_integralish<OutT> && all_same_v<OutT, Ts...>)))
471
inline constexpr void load_any(const uint8_t in[], Ts&... outs) {
1,106,949✔
472
   constexpr auto bytes = (sizeof(outs) + ...);
1,106,949✔
473
   // asserts that *in points to the correct amount of memory
474
   load_any<endianness, OutT>(std::span<const uint8_t, bytes>(in, bytes), outs...);
1,106,949✔
475
}
476

477
/**
478
 * Load a variable number of words from @p in into @p out.
479
 * @param out the output array of words
480
 * @param in the input array of bytes
481
 * @param count how many words are in in
482
 */
483
template <std::endian endianness, typename OutT, unsigned_integralish T>
484
   requires(std::same_as<AutoDetect, OutT> || std::same_as<T, OutT>)
485
inline constexpr void load_any(T out[], const uint8_t in[], size_t count) {
112,006,077✔
486
   // asserts that *in and *out point to the correct amount of memory
487
   load_any<endianness, OutT>(std::span<T>(out, count), std::span<const uint8_t>(in, count * sizeof(T)));
112,006,077✔
488
}
112,006,077✔
489

490
}  // namespace detail
491

492
/**
493
 * Load "something" in little endian byte order
494
 * See the documentation of this file for more details.
495
 */
496
template <typename OutT = detail::AutoDetect, typename... ParamTs>
497
inline constexpr auto load_le(ParamTs&&... params) {
2,147,483,647✔
498
   return detail::load_any<std::endian::little, OutT>(std::forward<ParamTs>(params)...);
2,147,483,647✔
499
}
500

501
/**
502
 * Load "something" in big endian byte order
503
 * See the documentation of this file for more details.
504
 */
505
template <typename OutT = detail::AutoDetect, typename... ParamTs>
506
inline constexpr auto load_be(ParamTs&&... params) {
23,533,243✔
507
   return detail::load_any<std::endian::big, OutT>(std::forward<ParamTs>(params)...);
24,619,669✔
508
}
509

510
namespace detail {
511

512
/**
513
 * Store a word in either big or little endian byte order into a range
514
 *
515
 * This is the base implementation, all other overloads are just convenience
516
 * wrappers. It is assumed that the range has the correct size for the word.
517
 *
518
 * Template arguments of all overloads of store_any() share the same semantics
519
 * as those of load_any(). See the documentation of this function for more
520
 * details.
521
 *
522
 * @param wrapped_in an unsigned integral to be stored
523
 * @param out_range  a byte range to store the word into
524
 */
525
template <std::endian endianness, unsigned_integralish WrappedInT, ranges::contiguous_output_range<uint8_t> OutR>
526
   requires(!custom_storable<strong_type_wrapped_type<WrappedInT>>)
527
inline constexpr void store_any(WrappedInT wrapped_in, OutR&& out_range) {
2,147,483,647✔
528
   const auto in = detail::unwrap_strong_type_or_enum(wrapped_in);
2,147,483,647✔
529
   using InT = decltype(in);
530
   ranges::assert_exact_byte_length<sizeof(in)>(out_range);
2✔
531
   std::span out{out_range};
2,147,483,647✔
532

533
   // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
534
   // internally to copy ranges on a byte-by-byte basis, which is not allowed
535
   // in a `constexpr` context.
536
   if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
537
      return fallback_store_any<endianness, InT>(in, std::forward<OutR>(out_range));
538
   } else {
539
      if constexpr(sizeof(InT) == 1) {
540
         out[0] = static_cast<uint8_t>(in);
24,661,192✔
541
      } else if constexpr(endianness == std::endian::native) {
542
         typecast_copy(out, in);
2,147,483,647✔
543
      } else {
544
         static_assert(opposite(endianness) == std::endian::native);
545
         typecast_copy(out, reverse_bytes(in));
2,147,483,647✔
546
      }
547
   }
548
}
549

550
/**
551
 * Store a custom word in either big or little endian byte order into a range
552
 *
553
 * This is the base implementation for storing custom objects, all other
554
 * overloads are just convenience overloads.
555
 *
556
 * @param wrapped_in a custom object to be stored
557
 * @param out_range  a byte range to store the word into
558
 */
559
template <std::endian endianness, unsigned_integralish WrappedInT, ranges::contiguous_output_range<uint8_t> OutR>
560
   requires(custom_storable<strong_type_wrapped_type<WrappedInT>>)
561
inline constexpr void store_any(WrappedInT wrapped_in, const OutR& out_range) {
7✔
562
   const auto in = detail::unwrap_strong_type_or_enum(wrapped_in);
563
   using InT = decltype(in);
564
   ranges::assert_exact_byte_length<sizeof(in)>(out_range);
565
   std::span<uint8_t, sizeof(InT)> outs{out_range};
7✔
566
   if constexpr(endianness == std::endian::big) {
567
      in.store_be(outs);
4✔
568
   } else {
569
      in.store_le(outs);
3✔
570
   }
571
}
572

573
/**
574
 * Store many unsigned integers words into a byte range
575
 * @param out a sized range of some bytes
576
 * @param ins a arbitrary-length parameter list of unsigned integers to be stored
577
 */
578
template <std::endian endianness,
579
          typename InT,
580
          ranges::contiguous_output_range<uint8_t> OutR,
581
          unsigned_integralish... Ts>
582
   requires(sizeof...(Ts) > 0) && ((std::same_as<AutoDetect, InT> && all_same_v<Ts...>) ||
583
                                   (unsigned_integralish<InT> && all_same_v<InT, Ts...>))
584
inline constexpr void store_any(OutR&& out /* NOLINT(*-std-forward) */, Ts... ins) {
2,147,483,647✔
585
   ranges::assert_exact_byte_length<(sizeof(Ts) + ...)>(out);
4✔
586
   auto store_one = [off = 0]<typename T>(auto o, T i) mutable {
2,147,483,647✔
587
      store_any<endianness, T>(i, o.subspan(off).template first<sizeof(T)>());
2,147,483,647✔
588
      off += sizeof(T);
13,914,694✔
589
   };
590

591
   (store_one(std::span{out}, ins), ...);
2,147,483,647✔
592
}
5,376,772✔
593

594
/**
595
 * Store a variable number of words given in @p in into @p out.
596
 * The byte lengths of @p in and @p out must be consistent.
597
 * @param out the output range of bytes
598
 * @param in the input range of words
599
 */
600
template <std::endian endianness,
601
          typename InT,
602
          ranges::contiguous_output_range<uint8_t> OutR,
603
          ranges::spanable_range InR>
604
   requires(std::same_as<AutoDetect, InT> || std::same_as<InT, std::ranges::range_value_t<InR>>)
605
inline constexpr void store_any(OutR&& out /* NOLINT(*-std-forward) */, const InR& in) {
387,456,416✔
606
   ranges::assert_equal_byte_lengths(out, in);
387,456,416✔
607
   using element_type = std::ranges::range_value_t<InR>;
608

609
   auto store_elementwise = [&] {
1,010,439,732✔
610
      constexpr size_t bytes_per_element = sizeof(element_type);
311,498,369✔
611
      std::span<uint8_t> out_s(out);
311,498,365✔
612
      for(auto in_elem : in) {
2,147,483,647✔
613
         store_any<endianness, element_type>(out_s.template first<bytes_per_element>(), in_elem);
2,147,483,647✔
614
         out_s = out_s.subspan(bytes_per_element);
2,147,483,647✔
615
      }
616
   };
617

618
   // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
619
   // internally to copy ranges on a byte-by-byte basis, which is not allowed
620
   // in a `constexpr` context.
621
   if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
622
      store_elementwise();
623
   } else {
624
      if constexpr(endianness == std::endian::native && !custom_storable<element_type>) {
625
         typecast_copy(out, in);
75,958,047✔
626
      } else {
627
         store_elementwise();
311,491,927✔
628
      }
629
   }
630
}
13✔
631

632
//
633
// Type inference overloads
634
//
635

636
/**
637
 * Infer InT from a single unsigned integer input parameter.
638
 *
639
 * TODO: we might consider dropping this overload (i.e. out-range as second
640
 *       parameter) and make this a "special case" of the overload below, that
641
 *       takes a variadic number of input parameters.
642
 *
643
 * @param in an unsigned integer to be stored
644
 * @param out_range a range of bytes to store the word into
645
 */
646
template <std::endian endianness, typename InT, unsigned_integralish T, ranges::contiguous_output_range<uint8_t> OutR>
647
   requires std::same_as<AutoDetect, InT>
648
inline constexpr void store_any(T in, OutR&& out_range) {
1,383,953,453✔
649
   store_any<endianness, T>(in, std::forward<OutR>(out_range));
2,147,483,647✔
650
}
651

652
/**
653
 * The caller provided some integer values in a collection but did not provide
654
 * the output container. Let's create one for them, fill it with one of the
655
 * overloads above and return it. This will default to a std::array if the
656
 * caller did not specify the desired output container type.
657
 *
658
 * @param in_range a range of words that should be stored
659
 * @return a container of bytes that contains the stored words
660
 */
661
template <std::endian endianness, typename OutR, ranges::spanable_range InR>
662
   requires(std::same_as<AutoDetect, OutR> ||
663
            (ranges::statically_spanable_range<OutR> && std::default_initializable<OutR>) ||
664
            concepts::resizable_byte_buffer<OutR>)
665
inline constexpr auto store_any(InR&& in_range) {
75,664,723✔
666
   auto out = []([[maybe_unused]] const auto& in) {
145,078,071✔
667
      if constexpr(std::same_as<AutoDetect, OutR>) {
668
         if constexpr(ranges::statically_spanable_range<InR>) {
669
            constexpr size_t bytes = decltype(std::span{in})::extent * sizeof(std::ranges::range_value_t<InR>);
75,614,501✔
670
            return std::array<uint8_t, bytes>();
282,920,881✔
671
         } else {
672
            static_assert(
673
               !std::same_as<AutoDetect, OutR>,
674
               "cannot infer a suitable result container type from the given parameters at compile time, please specify it explicitly");
675
         }
676
      } else if constexpr(concepts::resizable_byte_buffer<OutR>) {
677
         return OutR(std::span{in}.size_bytes());
43,780✔
678
      } else {
679
         return OutR{};
680
      }
681
   }(in_range);
75,614,501✔
682

683
   store_any<endianness, std::ranges::range_value_t<InR>>(out, std::forward<InR>(in_range));
75,664,723✔
684
   return out;
75,499,580✔
685
}
×
686

687
/**
688
 * The caller provided some integer values but did not provide the output
689
 * container. Let's create one for them, fill it with one of the overloads above
690
 * and return it. This will default to a std::array if the caller did not
691
 * specify the desired output container type.
692
 *
693
 * @param ins some words that should be stored
694
 * @return a container of bytes that contains the stored words
695
 */
696
template <std::endian endianness, typename OutR, unsigned_integralish... Ts>
697
   requires all_same_v<Ts...>
698
inline constexpr auto store_any(Ts... ins) {
69,596,511✔
699
   return store_any<endianness, OutR>(std::array{ins...});
139,192,687✔
700
}
701

702
//
703
// Legacy store functions that work on raw pointers and arrays
704
//
705

706
/**
707
 * Store a single unsigned integer into a raw pointer
708
 * @param in the input unsigned integer
709
 * @param out the byte array to write to
710
 */
711
template <std::endian endianness, typename InT, unsigned_integralish T>
712
   requires(std::same_as<AutoDetect, InT> || std::same_as<T, InT>)
713
inline constexpr void store_any(T in, uint8_t out[]) {
2,147,483,647✔
714
   // asserts that *out points to enough bytes to write into
715
   store_any<endianness, InT>(in, std::span<uint8_t, sizeof(T)>(out, sizeof(T)));
2,147,483,647✔
716
}
717

718
/**
719
 * Store many unsigned integers words into a raw pointer
720
 * @param ins a arbitrary-length parameter list of unsigned integers to be stored
721
 * @param out the byte array to write to
722
 */
723
template <std::endian endianness, typename InT, unsigned_integralish T0, unsigned_integralish... Ts>
724
   requires(std::same_as<AutoDetect, InT> || std::same_as<T0, InT>) && all_same_v<T0, Ts...>
725
inline constexpr void store_any(uint8_t out[], T0 in0, Ts... ins) {
5,376,768✔
726
   constexpr auto bytes = sizeof(in0) + (sizeof(ins) + ... + 0);
5,376,768✔
727
   // asserts that *out points to the correct amount of memory
728
   store_any<endianness, T0>(std::span<uint8_t, bytes>(out, bytes), in0, ins...);
5,383,095✔
729
}
730

731
}  // namespace detail
732

733
/**
734
 * Store "something" in little endian byte order
735
 * See the documentation of this file for more details.
736
 */
737
template <typename ModifierT = detail::AutoDetect, typename... ParamTs>
738
inline constexpr auto store_le(ParamTs&&... params) {
1,068,875,015✔
739
   return detail::store_any<std::endian::little, ModifierT>(std::forward<ParamTs>(params)...);
1,074,947,393✔
740
}
741

742
/**
743
 * Store "something" in big endian byte order
744
 * See the documentation of this file for more details.
745
 */
746
template <typename ModifierT = detail::AutoDetect, typename... ParamTs>
747
inline constexpr auto store_be(ParamTs&&... params) {
393,475,345✔
748
   return detail::store_any<std::endian::big, ModifierT>(std::forward<ParamTs>(params)...);
1,016,867,576✔
749
}
750

751
namespace detail {
752

753
template <std::endian endianness, unsigned_integralish T>
754
inline size_t copy_out_any_word_aligned_portion(std::span<uint8_t>& out, std::span<const T>& in) {
311,561,756✔
755
   const size_t full_words = out.size() / sizeof(T);
311,561,756✔
756
   const size_t full_word_bytes = full_words * sizeof(T);
311,561,756✔
757
   const size_t remaining_bytes = out.size() - full_word_bytes;
311,561,756✔
758
   BOTAN_ASSERT_NOMSG(in.size_bytes() >= full_word_bytes + remaining_bytes);
311,561,756✔
759

760
   // copy full words
761
   store_any<endianness, T>(out.first(full_word_bytes), in.first(full_words));
311,561,756✔
762
   out = out.subspan(full_word_bytes);
311,561,756✔
763
   in = in.subspan(full_words);
311,561,756✔
764

765
   return remaining_bytes;
311,561,756✔
766
}
767

768
}  // namespace detail
769

770
/**
771
 * Partially copy a subset of @p in into @p out using big-endian
772
 * byte order.
773
 */
774
template <ranges::spanable_range InR>
775
inline void copy_out_be(std::span<uint8_t> out, const InR& in) {
310,916,806✔
776
   using T = std::ranges::range_value_t<InR>;
777
   std::span<const T> in_s{in};
310,916,806✔
778
   const auto remaining_bytes = detail::copy_out_any_word_aligned_portion<std::endian::big>(out, in_s);
310,916,806✔
779

780
   // copy remaining bytes as a partial word
781
   for(size_t i = 0; i < remaining_bytes; ++i) {
310,916,814✔
782
      out[i] = get_byte_var(i, in_s.front());
8✔
783
   }
784
}
310,916,806✔
785

786
/**
787
 * Partially copy a subset of @p in into @p out using little-endian
788
 * byte order.
789
 */
790
template <ranges::spanable_range InR>
791
inline void copy_out_le(std::span<uint8_t> out, const InR& in) {
644,950✔
792
   using T = std::ranges::range_value_t<InR>;
793
   std::span<const T> in_s{in};
644,950✔
794
   const auto remaining_bytes = detail::copy_out_any_word_aligned_portion<std::endian::little>(out, in_s);
644,950✔
795

796
   // copy remaining bytes as a partial word
797
   for(size_t i = 0; i < remaining_bytes; ++i) {
656,706✔
798
      out[i] = get_byte_var(sizeof(T) - 1 - i, in_s.front());
11,757✔
799
   }
800
}
644,950✔
801

802
}  // namespace Botan
803

804
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc