|
13 | 13 |
|
14 | 14 | #include <boost/endian/arithmetic.hpp> |
15 | 15 |
|
16 | | -#include <scale/scale_error.hpp> |
17 | 16 | #include <scale/outcome/outcome_throw.hpp> |
| 17 | +#include <scale/scale_error.hpp> |
18 | 18 | #include <scale/unreachable.hpp> |
19 | 19 |
|
20 | 20 | namespace scale::detail { |
21 | | -/** |
22 | | - * encodeInteger encodes any integer type to little-endian representation |
23 | | - * @tparam T integer type |
24 | | - * @tparam S output stream type |
25 | | - * @param value integer value |
26 | | - * @return byte array representation of value |
27 | | - */ |
28 | | -template <class T, class S, typename I = std::decay_t<T>, |
29 | | - typename = std::enable_if_t<std::is_integral<I>::value>> |
30 | | -void encodeInteger(T value, S &out) { // no need to take integers by && |
31 | | - constexpr size_t size = sizeof(I); |
32 | | - constexpr size_t bits = size * 8; |
33 | | - boost::endian::endian_buffer<boost::endian::order::little, I, bits> buf{}; |
34 | | - buf = value; // cannot initialize, only assign |
35 | | - for (size_t i = 0; i < size; ++i) { |
36 | | - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) |
37 | | - out << buf.data()[i]; |
| 21 | + /** |
| 22 | + * encodeInteger encodes any integer type to little-endian representation |
| 23 | + * @tparam T integer type |
| 24 | + * @tparam S output stream type |
| 25 | + * @param value integer value |
| 26 | + * @return byte array representation of value |
| 27 | + */ |
| 28 | + template <class T, |
| 29 | + class S, |
| 30 | + typename I = std::decay_t<T>, |
| 31 | + typename = std::enable_if_t<std::is_integral<I>::value>> |
| 32 | + void encodeInteger(T value, S &out) { // no need to take integers by && |
| 33 | + constexpr size_t size = sizeof(I); |
| 34 | + constexpr size_t bits = size * 8; |
| 35 | + boost::endian::endian_buffer<boost::endian::order::little, I, bits> buf{}; |
| 36 | + buf = value; // cannot initialize, only assign |
| 37 | + for (size_t i = 0; i < size; ++i) { |
| 38 | + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) |
| 39 | + out << buf.data()[i]; |
| 40 | + } |
38 | 41 | } |
39 | | -} |
40 | 42 |
|
41 | | -/** |
42 | | - * @brief decodeInteger function decodes integer from stream |
43 | | - * @tparam T integer type |
44 | | - * @param stream source stream |
45 | | - * @return decoded value or error |
46 | | - */ |
47 | | -template <class T, class S, typename I = std::decay_t<T>, |
48 | | - typename = std::enable_if_t<std::is_integral_v<I>>> |
49 | | -I decodeInteger(S &stream) { |
50 | | - constexpr size_t size = sizeof(I); |
51 | | - static_assert(size <= 8); |
| 43 | + /** |
| 44 | + * @brief decodeInteger function decodes integer from stream |
| 45 | + * @tparam T integer type |
| 46 | + * @param stream source stream |
| 47 | + * @return decoded value or error |
| 48 | + */ |
| 49 | + template <class T, |
| 50 | + class S, |
| 51 | + typename I = std::decay_t<T>, |
| 52 | + typename = std::enable_if_t<std::is_integral_v<I>>> |
| 53 | + I decodeInteger(S &stream) { |
| 54 | + constexpr size_t size = sizeof(I); |
| 55 | + static_assert(size <= 8); |
52 | 56 |
|
53 | | - // clang-format off |
54 | 57 | // sign bit = 2^(num_bits - 1) |
55 | | - static constexpr std::array<uint64_t, 8> sign_bit = { |
56 | | - 0x80, // 1 byte |
57 | | - 0x8000, // 2 bytes |
58 | | - 0x800000, // 3 bytes |
59 | | - 0x80000000, // 4 bytes |
60 | | - 0x8000000000, // 5 bytes |
61 | | - 0x800000000000, // 6 bytes |
62 | | - 0x80000000000000, // 7 bytes |
63 | | - 0x8000000000000000 // 8 bytes |
| 58 | + static constexpr auto sign_bit = [](size_t num_bytes) -> uint64_t { |
| 59 | + return 0x80ul << (num_bytes * 8); |
64 | 60 | }; |
65 | 61 |
|
66 | | - static constexpr std::array<uint64_t, 8> multiplier = { |
67 | | - 0x1, // 2^0 |
68 | | - 0x100, // 2^8 |
69 | | - 0x10000, // 2^16 |
70 | | - 0x1000000, // 2^24 |
71 | | - 0x100000000, // 2^32 |
72 | | - 0x10000000000, // 2^40 |
73 | | - 0x1000000000000, // 2^48 |
74 | | - 0x100000000000000 // 2^56 |
| 62 | + static constexpr auto multiplier = [](size_t num_bytes) -> uint64_t { |
| 63 | + return 0x1ul << (num_bytes * 8); |
75 | 64 | }; |
76 | | - // clang-format on |
77 | | - |
78 | | - if (!stream.hasMore(size)) { |
79 | | - raise(DecodeError::NOT_ENOUGH_DATA); |
80 | | - UNREACHABLE |
81 | | - } |
82 | | - |
83 | | - // get integer as 4 bytes from little-endian stream |
84 | | - // and represent it as native-endian unsigned int eger |
85 | | - uint64_t v = 0u; |
86 | 65 |
|
87 | | - for (size_t i = 0; i < size; ++i) { |
88 | | - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) |
89 | | - v += multiplier[i] * static_cast<uint64_t>(stream.nextByte()); |
| 66 | + if (!stream.hasMore(size)) { |
| 67 | + raise(DecodeError::NOT_ENOUGH_DATA); |
| 68 | + UNREACHABLE |
| 69 | + } |
| 70 | + |
| 71 | + // get integer as 8 bytes from little-endian stream |
| 72 | + // and represent it as native-endian unsigned integer |
| 73 | + uint64_t v = 0u; |
| 74 | + |
| 75 | + for (size_t i = 0; i < size; ++i) { |
| 76 | + v += multiplier(i) * static_cast<uint64_t>(stream.nextByte()); |
| 77 | + } |
| 78 | + // now we have an uint64 native-endian value |
| 79 | + // which can store either a signed or an unsigned value |
| 80 | + |
| 81 | + // if the value is actually unsigned, we know that is not greater than |
| 82 | + // the max value for type T, so static_cast<T>(v) is safe |
| 83 | + |
| 84 | + // if it is signed and positive, it is also ok |
| 85 | + // we can be sure that it is less than max_value<T>/2. |
| 86 | + // To check if it is negative we check if the sign bit is present |
| 87 | + // in the unsigned representation. (which is true when the value is greater |
| 88 | + // than 2^(size_in_bits-1) |
| 89 | + bool is_positive_signed = v < sign_bit(size - 1); |
| 90 | + if (std::is_unsigned<I>() || is_positive_signed) { |
| 91 | + return static_cast<I>(v); |
| 92 | + } |
| 93 | + |
| 94 | + // T is a signed integer type and the value v is negative. |
| 95 | + // A value is negative, which means that (-x), |
| 96 | + // where (-x) is positive, is smaller than sign_bits[size-1]. |
| 97 | + // Find this x, safely cast to a positive signed and negate the result. |
| 98 | + // the bitwise negation operation affects higher bits as well, |
| 99 | + // but it doesn't spoil the result. |
| 100 | + // static_cast to a smaller type cuts these higher bits off. |
| 101 | + I sv = -static_cast<I>((~v) + 1); |
| 102 | + return sv; |
90 | 103 | } |
91 | | - // now we have uint64 native-endian value |
92 | | - // which can be signed or unsigned under the cover |
93 | | - |
94 | | - // if it is unsigned, we know that is not greater than max value for type T |
95 | | - // so static_cast<T>(v) is safe |
96 | | - |
97 | | - // if it is signed, but positive it is also ok |
98 | | - // we can be sure that it is less than max_value<T>/2 |
99 | | - // to check whether is is negative we check if the sign bit present |
100 | | - // in unsigned form it means that value is more than |
101 | | - // a value 2^(bits_number-1) |
102 | | - bool is_positive_signed = v < sign_bit[size - 1]; |
103 | | - if (std::is_unsigned<I>() || is_positive_signed) { |
104 | | - return static_cast<I>(v); |
105 | | - } |
106 | | - |
107 | | - // T is signed integer type and the value v is negative |
108 | | - // value is negative signed means ( - x ) |
109 | | - // where x is positive unsigned < sign_bits[size-1] |
110 | | - // find this x, safely cast to signed and negate result |
111 | | - // the bitwise negation operation affects higher bits as well |
112 | | - // but it doesn't spoil the result |
113 | | - // static_cast to smaller size cuts them off |
114 | | - T sv = -static_cast<I>((~v) + 1); |
115 | | - |
116 | | - return sv; |
117 | | -} |
118 | 104 | } // namespace scale::detail |
119 | 105 |
|
120 | 106 | #endif // SCALE_SCALE_UTIL_HPP |
0 commit comments