Botan 3.9.0
Crypto and TLS for C&
loadstor.h
Go to the documentation of this file.
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
57namespace Botan {
58
59static_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*/
68template <typename T>
69inline constexpr uint8_t get_byte_var(size_t byte_num, T input) {
70 return static_cast<uint8_t>(input >> (((~byte_num) & (sizeof(T) - 1)) << 3));
71}
72
73/**
74* Byte extraction
75* @param input the value to extract from
76* @return byte byte number B of input
77*/
78template <size_t B, typename T>
79inline constexpr uint8_t get_byte(T input)
80 requires(B < sizeof(T))
81{
82 const size_t shift = ((~B) & (sizeof(T) - 1)) << 3;
83 return static_cast<uint8_t>((input >> shift) & 0xFF);
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*/
92inline constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1) {
93 return static_cast<uint16_t>((static_cast<uint16_t>(i0) << 8) | i1);
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*/
104inline constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3) {
105 return ((static_cast<uint32_t>(i0) << 24) | (static_cast<uint32_t>(i1) << 16) | (static_cast<uint32_t>(i2) << 8) |
106 (static_cast<uint32_t>(i3)));
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*/
121inline constexpr uint64_t make_uint64(
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) |
124 (static_cast<uint64_t>(i3) << 32) | (static_cast<uint64_t>(i4) << 24) | (static_cast<uint64_t>(i5) << 16) |
125 (static_cast<uint64_t>(i6) << 8) | (static_cast<uint64_t>(i7)));
126}
127
128namespace 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 */
136consteval 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 */
150template <typename T>
151concept 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 */
160template <typename T>
161concept 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 */
169template <typename T>
171 std::unsigned_integral<strong_type_wrapped_type<T>> ||
172 (std::is_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>) ||
174
175template <typename T>
179
180template <typename T>
181 requires std::is_enum_v<T>
183 using type = std::underlying_type_t<T>;
184};
185
186template <unsigned_integralish T>
188
189template <unsigned_integralish InT>
190constexpr 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 {
196 }
197}
198
199template <unsigned_integralish OutT, std::unsigned_integral T>
200constexpr auto wrap_strong_type_or_enum(T t) {
201 if constexpr(std::is_enum_v<OutT>) {
202 return static_cast<OutT>(t);
203 } else {
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 */
213template <std::endian endianness, std::unsigned_integral OutT, ranges::contiguous_range<uint8_t> InR>
214inline constexpr OutT fallback_load_any(const InR& in_range) {
215 std::span in{in_range};
216 // clang-format off
217 if constexpr(endianness == std::endian::big) {
218 return [&]<size_t... i>(std::index_sequence<i...>) {
219 return static_cast<OutT>(((static_cast<OutT>(in[i]) << ((sizeof(OutT) - i - 1) * 8)) | ...));
220 } (std::make_index_sequence<sizeof(OutT)>());
221 } else {
222 static_assert(endianness == std::endian::little);
223 return [&]<size_t... i>(std::index_sequence<i...>) {
224 return static_cast<OutT>(((static_cast<OutT>(in[i]) << (i * 8)) | ...));
225 } (std::make_index_sequence<sizeof(OutT)>());
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 */
235template <std::endian endianness, std::unsigned_integral InT, ranges::contiguous_output_range<uint8_t> OutR>
236inline constexpr void fallback_store_any(InT in, OutR&& out_range /* NOLINT(*-std-forward) */) {
237 std::span out{out_range};
238 // clang-format off
239 if constexpr(endianness == std::endian::big) {
240 [&]<size_t... i>(std::index_sequence<i...>) {
241 ((out[i] = get_byte<i>(in)), ...);
242 } (std::make_index_sequence<sizeof(InT)>());
243 } else {
244 static_assert(endianness == std::endian::little);
245 [&]<size_t... i>(std::index_sequence<i...>) {
246 ((out[i] = get_byte<sizeof(InT) - i - 1>(in)), ...);
247 } (std::make_index_sequence<sizeof(InT)>());
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 */
276template <std::endian endianness, unsigned_integralish WrappedOutT, ranges::contiguous_range<uint8_t> InR>
277 requires(!custom_loadable<strong_type_wrapped_type<WrappedOutT>>)
278inline constexpr WrappedOutT load_any(InR&& in_range) {
281
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};
290 if constexpr(sizeof(OutT) == 1) {
291 return static_cast<OutT>(in[0]);
292 } else if constexpr(endianness == std::endian::native) {
293 return typecast_copy<OutT>(in);
294 } else {
295 static_assert(opposite(endianness) == std::endian::native);
297 }
298 }
299 }());
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 */
311template <std::endian endianness, unsigned_integralish WrappedOutT, ranges::contiguous_range<uint8_t> InR>
312 requires(custom_loadable<strong_type_wrapped_type<WrappedOutT>>)
313inline constexpr WrappedOutT load_any(const InR& in_range) {
316 std::span<const uint8_t, sizeof(OutT)> ins{in_range};
317 if constexpr(endianness == std::endian::big) {
318 return wrap_strong_type<WrappedOutT>(OutT::load_be(ins));
319 } else {
320 return wrap_strong_type<WrappedOutT>(OutT::load_le(ins));
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 */
329template <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...>))
332inline constexpr void load_any(const InR& in, Ts&... outs) {
333 ranges::assert_exact_byte_length<(sizeof(Ts) + ...)>(in);
334 auto load_one = [off = 0]<typename T>(auto i, T& o) mutable {
335 o = load_any<endianness, T>(i.subspan(off).template first<sizeof(T)>());
336 off += sizeof(T);
337 };
338
339 (load_one(std::span{in}, outs), ...);
340}
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 */
349template <std::endian endianness,
350 typename OutT,
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>>))
355inline constexpr void load_any(OutR&& out /* NOLINT(*-std-forward) */, const InR& in) {
357 using element_type = std::ranges::range_value_t<OutR>;
358
359 auto load_elementwise = [&] {
360 constexpr size_t bytes_per_element = sizeof(element_type);
361 std::span<const uint8_t> in_s(in);
362 for(auto& out_elem : out) {
363 out_elem = load_any<endianness, element_type>(in_s.template first<bytes_per_element>());
364 in_s = in_s.subspan(bytes_per_element);
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);
376 } else {
377 load_elementwise();
378 }
379 }
380}
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 */
393template <std::endian endianness, typename OutT, ranges::contiguous_range<uint8_t> InR>
394 requires(std::same_as<AutoDetect, OutT> ||
396 unsigned_integralish<typename OutT::value_type>))
397inline constexpr auto load_any(InR&& in_range) {
398 auto out = []([[maybe_unused]] const auto& in) {
399 if constexpr(std::same_as<AutoDetect, OutT>) {
401 constexpr size_t extent = decltype(std::span{in})::extent;
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();
423 constexpr size_t out_elem_bytes = sizeof(typename OutT::value_type);
424 BOTAN_ARG_CHECK(in_bytes % out_elem_bytes == 0,
425 "Input range is not word-aligned with the requested output range");
426 return OutT(in_bytes / out_elem_bytes);
427 } else {
428 return OutT{};
429 }
430 }(in_range);
431
432 using out_type = decltype(out);
433 if constexpr(unsigned_integralish<out_type>) {
434 out = load_any<endianness, out_type>(std::forward<InR>(in_range));
435 } else {
437 using out_range_type = std::ranges::range_value_t<out_type>;
438 load_any<endianness, out_range_type>(out, std::forward<InR>(in_range));
439 }
440 return out;
441}
442
443//
444// Legacy load functions that work on raw pointers and arrays
445//
446
447/**
448 * Load a word from @p in at some offset @p off
449 * @param in a pointer to some bytes
450 * @param off an offset into the array
451 * @return off'th T of in, as a big-endian value
452 */
453template <std::endian endianness, unsigned_integralish OutT>
454inline constexpr OutT load_any(const uint8_t in[], size_t off) {
455 // asserts that *in points to enough bytes to read at offset off
456 constexpr size_t out_size = sizeof(OutT);
457 return load_any<endianness, OutT>(std::span<const uint8_t, out_size>(in + off * out_size, out_size));
458}
459
460/**
461 * Load many words from @p in
462 * @param in a pointer to some bytes
463 * @param outs a arbitrary-length parameter list of unsigned integers to be loaded
464 */
465template <std::endian endianness, typename OutT, unsigned_integralish... Ts>
466 requires(sizeof...(Ts) > 0 && all_same_v<Ts...> &&
467 ((std::same_as<AutoDetect, OutT> && all_same_v<Ts...>) ||
468 (unsigned_integralish<OutT> && all_same_v<OutT, Ts...>)))
469inline constexpr void load_any(const uint8_t in[], Ts&... outs) {
470 constexpr auto bytes = (sizeof(outs) + ...);
471 // asserts that *in points to the correct amount of memory
472 load_any<endianness, OutT>(std::span<const uint8_t, bytes>(in, bytes), outs...);
473}
474
475/**
476 * Load a variable number of words from @p in into @p out.
477 * @param out the output array of words
478 * @param in the input array of bytes
479 * @param count how many words are in in
480 */
481template <std::endian endianness, typename OutT, unsigned_integralish T>
482 requires(std::same_as<AutoDetect, OutT> || std::same_as<T, OutT>)
483inline constexpr void load_any(T out[], const uint8_t in[], size_t count) {
484 // asserts that *in and *out point to the correct amount of memory
485 load_any<endianness, OutT>(std::span<T>(out, count), std::span<const uint8_t>(in, count * sizeof(T)));
486}
487
488} // namespace detail
489
490/**
491 * Load "something" in little endian byte order
492 * See the documentation of this file for more details.
493 */
494template <typename OutT = detail::AutoDetect, typename... ParamTs>
495inline constexpr auto load_le(ParamTs&&... params) {
496 return detail::load_any<std::endian::little, OutT>(std::forward<ParamTs>(params)...);
497}
498
499/**
500 * Load "something" in big endian byte order
501 * See the documentation of this file for more details.
502 */
503template <typename OutT = detail::AutoDetect, typename... ParamTs>
504inline constexpr auto load_be(ParamTs&&... params) {
505 return detail::load_any<std::endian::big, OutT>(std::forward<ParamTs>(params)...);
506}
507
508namespace detail {
509
510/**
511 * Store a word in either big or little endian byte order into a range
512 *
513 * This is the base implementation, all other overloads are just convenience
514 * wrappers. It is assumed that the range has the correct size for the word.
515 *
516 * Template arguments of all overloads of store_any() share the same semantics
517 * as those of load_any(). See the documentation of this function for more
518 * details.
519 *
520 * @param wrapped_in an unsigned integral to be stored
521 * @param out_range a byte range to store the word into
522 */
523template <std::endian endianness, unsigned_integralish WrappedInT, ranges::contiguous_output_range<uint8_t> OutR>
524 requires(!custom_storable<strong_type_wrapped_type<WrappedInT>>)
525inline constexpr void store_any(WrappedInT wrapped_in, OutR&& out_range) {
526 const auto in = detail::unwrap_strong_type_or_enum(wrapped_in);
527 using InT = decltype(in);
529 std::span out{out_range};
530
531 // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
532 // internally to copy ranges on a byte-by-byte basis, which is not allowed
533 // in a `constexpr` context.
534 if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
535 return fallback_store_any<endianness, InT>(in, std::forward<OutR>(out_range));
536 } else {
537 if constexpr(sizeof(InT) == 1) {
538 out[0] = static_cast<uint8_t>(in);
539 } else if constexpr(endianness == std::endian::native) {
540 typecast_copy(out, in);
541 } else {
542 static_assert(opposite(endianness) == std::endian::native);
544 }
545 }
546}
547
548/**
549 * Store a custom word in either big or little endian byte order into a range
550 *
551 * This is the base implementation for storing custom objects, all other
552 * overloads are just convenience overloads.
553 *
554 * @param wrapped_in a custom object to be stored
555 * @param out_range a byte range to store the word into
556 */
557template <std::endian endianness, unsigned_integralish WrappedInT, ranges::contiguous_output_range<uint8_t> OutR>
558 requires(custom_storable<strong_type_wrapped_type<WrappedInT>>)
559inline constexpr void store_any(WrappedInT wrapped_in, const OutR& out_range) {
560 const auto in = detail::unwrap_strong_type_or_enum(wrapped_in);
561 using InT = decltype(in);
563 std::span<uint8_t, sizeof(InT)> outs{out_range};
564 if constexpr(endianness == std::endian::big) {
565 in.store_be(outs);
566 } else {
567 in.store_le(outs);
568 }
569}
570
571/**
572 * Store many unsigned integers words into a byte range
573 * @param out a sized range of some bytes
574 * @param ins a arbitrary-length parameter list of unsigned integers to be stored
575 */
576template <std::endian endianness,
577 typename InT,
579 unsigned_integralish... Ts>
580 requires(sizeof...(Ts) > 0) && ((std::same_as<AutoDetect, InT> && all_same_v<Ts...>) ||
581 (unsigned_integralish<InT> && all_same_v<InT, Ts...>))
582inline constexpr void store_any(OutR&& out /* NOLINT(*-std-forward) */, Ts... ins) {
583 ranges::assert_exact_byte_length<(sizeof(Ts) + ...)>(out);
584 auto store_one = [off = 0]<typename T>(auto o, T i) mutable {
585 store_any<endianness, T>(i, o.subspan(off).template first<sizeof(T)>());
586 off += sizeof(T);
587 };
588
589 (store_one(std::span{out}, ins), ...);
590}
591
592/**
593 * Store a variable number of words given in @p in into @p out.
594 * The byte lengths of @p in and @p out must be consistent.
595 * @param out the output range of bytes
596 * @param in the input range of words
597 */
598template <std::endian endianness,
599 typename InT,
602 requires(std::same_as<AutoDetect, InT> || std::same_as<InT, std::ranges::range_value_t<InR>>)
603inline constexpr void store_any(OutR&& out /* NOLINT(*-std-forward) */, const InR& in) {
605 using element_type = std::ranges::range_value_t<InR>;
606
607 auto store_elementwise = [&] {
608 constexpr size_t bytes_per_element = sizeof(element_type);
609 std::span<uint8_t> out_s(out);
610 for(auto in_elem : in) {
611 store_any<endianness, element_type>(out_s.template first<bytes_per_element>(), in_elem);
612 out_s = out_s.subspan(bytes_per_element);
613 }
614 };
615
616 // At compile time we cannot use `typecast_copy` as it uses `std::memcpy`
617 // internally to copy ranges on a byte-by-byte basis, which is not allowed
618 // in a `constexpr` context.
619 if(std::is_constant_evaluated()) /* TODO: C++23: if consteval {} */ {
620 store_elementwise();
621 } else {
622 if constexpr(endianness == std::endian::native && !custom_storable<element_type>) {
623 typecast_copy(out, in);
624 } else {
625 store_elementwise();
626 }
627 }
628}
629
630//
631// Type inference overloads
632//
633
634/**
635 * Infer InT from a single unsigned integer input parameter.
636 *
637 * TODO: we might consider dropping this overload (i.e. out-range as second
638 * parameter) and make this a "special case" of the overload below, that
639 * takes a variadic number of input parameters.
640 *
641 * @param in an unsigned integer to be stored
642 * @param out_range a range of bytes to store the word into
643 */
644template <std::endian endianness, typename InT, unsigned_integralish T, ranges::contiguous_output_range<uint8_t> OutR>
645 requires std::same_as<AutoDetect, InT>
646inline constexpr void store_any(T in, OutR&& out_range) {
647 store_any<endianness, T>(in, std::forward<OutR>(out_range));
648}
649
650/**
651 * The caller provided some integer values in a collection but did not provide
652 * the output container. Let's create one for them, fill it with one of the
653 * overloads above and return it. This will default to a std::array if the
654 * caller did not specify the desired output container type.
655 *
656 * @param in_range a range of words that should be stored
657 * @return a container of bytes that contains the stored words
658 */
659template <std::endian endianness, typename OutR, ranges::spanable_range InR>
660 requires(std::same_as<AutoDetect, OutR> ||
661 (ranges::statically_spanable_range<OutR> && std::default_initializable<OutR>) ||
663inline constexpr auto store_any(InR&& in_range) {
664 auto out = []([[maybe_unused]] const auto& in) {
665 if constexpr(std::same_as<AutoDetect, OutR>) {
667 constexpr size_t bytes = decltype(std::span{in})::extent * sizeof(std::ranges::range_value_t<InR>);
668 return std::array<uint8_t, bytes>();
669 } else {
670 static_assert(
671 !std::same_as<AutoDetect, OutR>,
672 "cannot infer a suitable result container type from the given parameters at compile time, please specify it explicitly");
673 }
674 } else if constexpr(concepts::resizable_byte_buffer<OutR>) {
675 return OutR(std::span{in}.size_bytes());
676 } else {
677 return OutR{};
678 }
679 }(in_range);
680
681 store_any<endianness, std::ranges::range_value_t<InR>>(out, std::forward<InR>(in_range));
682 return out;
683}
684
685/**
686 * The caller provided some integer values but did not provide the output
687 * container. Let's create one for them, fill it with one of the overloads above
688 * and return it. This will default to a std::array if the caller did not
689 * specify the desired output container type.
690 *
691 * @param ins some words that should be stored
692 * @return a container of bytes that contains the stored words
693 */
694template <std::endian endianness, typename OutR, unsigned_integralish... Ts>
695 requires all_same_v<Ts...>
696inline constexpr auto store_any(Ts... ins) {
697 return store_any<endianness, OutR>(std::array{ins...});
698}
699
700//
701// Legacy store functions that work on raw pointers and arrays
702//
703
704/**
705 * Store a single unsigned integer into a raw pointer
706 * @param in the input unsigned integer
707 * @param out the byte array to write to
708 */
709template <std::endian endianness, typename InT, unsigned_integralish T>
710 requires(std::same_as<AutoDetect, InT> || std::same_as<T, InT>)
711inline constexpr void store_any(T in, uint8_t out[]) {
712 // asserts that *out points to enough bytes to write into
713 store_any<endianness, InT>(in, std::span<uint8_t, sizeof(T)>(out, sizeof(T)));
714}
715
716/**
717 * Store many unsigned integers words into a raw pointer
718 * @param ins a arbitrary-length parameter list of unsigned integers to be stored
719 * @param out the byte array to write to
720 */
721template <std::endian endianness, typename InT, unsigned_integralish T0, unsigned_integralish... Ts>
722 requires(std::same_as<AutoDetect, InT> || std::same_as<T0, InT>) && all_same_v<T0, Ts...>
723inline constexpr void store_any(uint8_t out[], T0 in0, Ts... ins) {
724 constexpr auto bytes = sizeof(in0) + (sizeof(ins) + ... + 0);
725 // asserts that *out points to the correct amount of memory
726 store_any<endianness, T0>(std::span<uint8_t, bytes>(out, bytes), in0, ins...);
727}
728
729} // namespace detail
730
731/**
732 * Store "something" in little endian byte order
733 * See the documentation of this file for more details.
734 */
735template <typename ModifierT = detail::AutoDetect, typename... ParamTs>
736inline constexpr auto store_le(ParamTs&&... params) {
737 return detail::store_any<std::endian::little, ModifierT>(std::forward<ParamTs>(params)...);
738}
739
740/**
741 * Store "something" in big endian byte order
742 * See the documentation of this file for more details.
743 */
744template <typename ModifierT = detail::AutoDetect, typename... ParamTs>
745inline constexpr auto store_be(ParamTs&&... params) {
746 return detail::store_any<std::endian::big, ModifierT>(std::forward<ParamTs>(params)...);
747}
748
749namespace detail {
750
751template <std::endian endianness, unsigned_integralish T>
752inline size_t copy_out_any_word_aligned_portion(std::span<uint8_t>& out, std::span<const T>& in) {
753 const size_t full_words = out.size() / sizeof(T);
754 const size_t full_word_bytes = full_words * sizeof(T);
755 const size_t remaining_bytes = out.size() - full_word_bytes;
756 BOTAN_ASSERT_NOMSG(in.size_bytes() >= full_word_bytes + remaining_bytes);
757
758 // copy full words
759 store_any<endianness, T>(out.first(full_word_bytes), in.first(full_words));
760 out = out.subspan(full_word_bytes);
761 in = in.subspan(full_words);
762
763 return remaining_bytes;
764}
765
766} // namespace detail
767
768/**
769 * Partially copy a subset of @p in into @p out using big-endian
770 * byte order.
771 */
772template <ranges::spanable_range InR>
773inline void copy_out_be(std::span<uint8_t> out, const InR& in) {
774 using T = std::ranges::range_value_t<InR>;
775 std::span<const T> in_s{in};
776 const auto remaining_bytes = detail::copy_out_any_word_aligned_portion<std::endian::big>(out, in_s);
777
778 // copy remaining bytes as a partial word
779 for(size_t i = 0; i < remaining_bytes; ++i) {
780 out[i] = get_byte_var(i, in_s.front());
781 }
782}
783
784/**
785 * Partially copy a subset of @p in into @p out using little-endian
786 * byte order.
787 */
788template <ranges::spanable_range InR>
789inline void copy_out_le(std::span<uint8_t> out, const InR& in) {
790 using T = std::ranges::range_value_t<InR>;
791 std::span<const T> in_s{in};
792 const auto remaining_bytes = detail::copy_out_any_word_aligned_portion<std::endian::little>(out, in_s);
793
794 // copy remaining bytes as a partial word
795 for(size_t i = 0; i < remaining_bytes; ++i) {
796 out[i] = get_byte_var(sizeof(T) - 1 - i, in_s.front());
797 }
798}
799
800} // namespace Botan
801
802#endif
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
consteval std::endian opposite(std::endian endianness)
Definition loadstor.h:136
constexpr void fallback_store_any(InT in, OutR &&out_range)
Definition loadstor.h:236
size_t copy_out_any_word_aligned_portion(std::span< uint8_t > &out, std::span< const T > &in)
Definition loadstor.h:752
constexpr OutT fallback_load_any(const InR &in_range)
Definition loadstor.h:214
typename wrapped_type_helper_with_enum< T >::type wrapped_type
Definition loadstor.h:187
constexpr WrappedOutT load_any(InR &&in_range)
Definition loadstor.h:278
constexpr auto unwrap_strong_type_or_enum(InT t)
Definition loadstor.h:190
constexpr auto wrap_strong_type_or_enum(T t)
Definition loadstor.h:200
constexpr void store_any(WrappedInT wrapped_in, OutR &&out_range)
Definition loadstor.h:525
constexpr void assert_equal_byte_lengths(const R0 &r0, const Rs &... rs)
Definition concepts.h:128
constexpr void assert_exact_byte_length(const R &r)
Definition concepts.h:107
constexpr uint8_t get_byte(T input)
Definition loadstor.h:79
constexpr uint64_t make_uint64(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7)
Definition loadstor.h:121
void copy_out_le(std::span< uint8_t > out, const InR &in)
Definition loadstor.h:789
void copy_out_be(std::span< uint8_t > out, const InR &in)
Definition loadstor.h:773
constexpr uint32_t make_uint32(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3)
Definition loadstor.h:104
constexpr decltype(auto) unwrap_strong_type(T &&t)
Generically unwraps a strong type to its underlying type.
constexpr T reverse_bytes(T x)
Definition bswap.h:27
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
typename detail::wrapped_type_helper< std::remove_cvref_t< T > >::type strong_type_wrapped_type
Extracts the wrapped type from a strong type.
constexpr decltype(auto) wrap_strong_type(ParamT &&t)
Wraps a value into a caller-defined (strong) type.
constexpr auto load_le(ParamTs &&... params)
Definition loadstor.h:495
constexpr uint8_t get_byte_var(size_t byte_num, T input)
Definition loadstor.h:69
constexpr void typecast_copy(ToR &&out, const FromR &in)
Definition mem_ops.h:177
constexpr auto store_be(ParamTs &&... params)
Definition loadstor.h:745
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504
constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1)
Definition loadstor.h:92
strong_type_wrapped_type< T > type
Definition loadstor.h:177