Skip to content

Commit c55e98f

Browse files
authored
Merge pull request #2 from soramitsu/feature/enum-codecs
Enum codecs
2 parents c9f6355 + 5d9161a commit c55e98f

23 files changed

Lines changed: 479 additions & 243 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
matrix:
3737
options:
3838
- name: "Linux: gcc-9"
39-
run: ./scripts/build.sh -DCMAKE_CXX_COMPIILER=g++-9
39+
run: ./scripts/build.sh -DCMAKE_CXX_COMPILER=g++-9
4040
- name: "Linux: clang-10"
4141
run: ./scripts/build.sh -DCMAKE_CXX_COMPILER=clang++-10
4242
name: "${{ matrix.options.name }}"

CMakeLists.txt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ cmake_minimum_required(VERSION 3.12)
88
include(${CMAKE_CURRENT_LIST_DIR}/cmake/HunterGate.cmake)
99
set(HUNTER_STATUS_DEBUG ON)
1010
HunterGate(
11-
URL https://github.com/soramitsu/soramitsu-hunter/archive/9ca72322e8d9de70d360dc7f371b223d32999123.zip
12-
SHA1 37cc1150526fb9c5dcf197f918e5d9aab611823f
11+
URL https://github.com/soramitsu/soramitsu-hunter/archive/tags/v0.23.257-soramitsu17.tar.gz
12+
SHA1 c7ccd337314b27485b75d0f0f5d5b42e7e3c2629
1313
)
1414

15-
project(Scale CXX)
15+
project(Scale LANGUAGES CXX VERSION 1.0.0)
1616

1717
set(CMAKE_CXX_STANDARD 17)
1818
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -33,7 +33,7 @@ add_subdirectory(src)
3333
if (BUILD_TESTS)
3434
enable_testing()
3535
add_subdirectory(test ${CMAKE_BINARY_DIR}/tests_bin)
36-
endif()
36+
endif ()
3737

3838
###############################################################################
3939
# INSTALLATION
@@ -55,6 +55,18 @@ install(
5555
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
5656
)
5757

58+
include(CMakePackageConfigHelpers)
59+
60+
write_basic_package_version_file(
61+
${CMAKE_CURRENT_BINARY_DIR}/scaleConfigVersion.cmake
62+
COMPATIBILITY SameMajorVersion
63+
)
64+
65+
install(
66+
FILES ${CMAKE_CURRENT_BINARY_DIR}/scaleConfigVersion.cmake
67+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/scale
68+
)
69+
5870
install(
5971
EXPORT scaleConfig
6072
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/scale

include/scale/detail/fixed_width_integer.hpp

Lines changed: 75 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -13,108 +13,94 @@
1313

1414
#include <boost/endian/arithmetic.hpp>
1515

16-
#include <scale/scale_error.hpp>
1716
#include <scale/outcome/outcome_throw.hpp>
17+
#include <scale/scale_error.hpp>
1818
#include <scale/unreachable.hpp>
1919

2020
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+
}
3841
}
39-
}
4042

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);
5256

53-
// clang-format off
5457
// 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);
6460
};
6561

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);
7564
};
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;
8665

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;
90103
}
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-
}
118104
} // namespace scale::detail
119105

120106
#endif // SCALE_SCALE_UTIL_HPP

include/scale/enum_traits.hpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef SCALE_ENUM_TRAITS_HPP
7+
#define SCALE_ENUM_TRAITS_HPP
8+
9+
#include <type_traits>
10+
11+
#include <scale/outcome/outcome_throw.hpp>
12+
#include <scale/scale_error.hpp>
13+
14+
namespace scale {
15+
16+
/**
17+
* Description of an enum type
18+
* Two specialization choices:
19+
* - min_value and max_value convertible to std::underlying_type_t<E>
20+
* - a container of std::underlying_type_t<E> named valid_values, listing
21+
* valid values
22+
* @note check macros below for specialization convenience
23+
* @tparam E the enum type
24+
*/
25+
template <typename E>
26+
struct [[deprecated(
27+
"Check the doc comment to see the specialization options")]] enum_traits
28+
final {
29+
static_assert(std::is_enum_v<E>);
30+
31+
// to easily detect an unspecialized enum_traits
32+
static constexpr bool is_default = true;
33+
};
34+
35+
#define SCALE_DEFINE_ENUM_VALUE_RANGE(enum_namespace, enum_name, min, max) \
36+
template <> \
37+
struct scale::enum_traits<enum_namespace::enum_name> final { \
38+
using underlying = std::underlying_type_t<enum_namespace::enum_name>; \
39+
static constexpr underlying min_value = static_cast<underlying>((min)); \
40+
static constexpr underlying max_value = static_cast<underlying>((max)); \
41+
};
42+
43+
// Mind that values should be enum constants, not numbers
44+
#define SCALE_DEFINE_ENUM_VALUE_LIST(enum_namespace, enum_name, ...) \
45+
template <> \
46+
struct scale::enum_traits<enum_namespace::enum_name> final { \
47+
static constexpr enum_namespace::enum_name valid_values[] = {__VA_ARGS__}; \
48+
};
49+
50+
template <typename T,
51+
typename E = std::decay_t<T>,
52+
typename E_traits = enum_traits<E>,
53+
std::underlying_type_t<E> Min = E_traits::min_value,
54+
std::underlying_type_t<E> Max = E_traits::max_value>
55+
constexpr bool is_valid_enum_value(std::underlying_type_t<E> value) noexcept {
56+
return value >= Min && value <= Max;
57+
}
58+
59+
template <typename T,
60+
typename E = std::decay_t<T>,
61+
typename E_traits = enum_traits<E>,
62+
typename = decltype(E_traits::valid_values)>
63+
constexpr bool is_valid_enum_value(std::underlying_type_t<E> value) noexcept {
64+
const auto &valid_values = E_traits::valid_values;
65+
return std::find(std::begin(valid_values),
66+
std::end(valid_values),
67+
static_cast<E>(value))
68+
!= std::end(valid_values);
69+
}
70+
71+
template <typename T,
72+
typename E = std::decay_t<T>,
73+
typename = std::enable_if_t<enum_traits<E>::is_default>>
74+
[[deprecated(
75+
"Please specialize scale::enum_traits for your enum so it can be "
76+
"validated during decoding")]] constexpr bool
77+
is_valid_enum_value(std::underlying_type_t<E> value) noexcept {
78+
return true;
79+
}
80+
81+
/**
82+
* @brief scale-decodes any enum type as underlying type
83+
* @tparam T enum type
84+
* @param v value of enum type
85+
* @return reference to stream
86+
*/
87+
template <typename T,
88+
typename S,
89+
typename E = std::decay_t<T>,
90+
typename = std::enable_if_t<S::is_decoder_stream>,
91+
typename = std::enable_if_t<std::is_enum_v<E>>>
92+
S &operator>>(S &s, T &v) {
93+
std::underlying_type_t<E> value;
94+
s >> value;
95+
if (is_valid_enum_value<E>(value)) {
96+
v = static_cast<E>(value);
97+
return s;
98+
}
99+
raise(DecodeError::INVALID_ENUM_VALUE);
100+
}
101+
102+
} // namespace scale
103+
104+
#endif // SCALE_ENUM_TRAITS_HPP

include/scale/scale.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <scale/outcome/outcome.hpp>
1616
#include <scale/scale_decoder_stream.hpp>
1717
#include <scale/scale_encoder_stream.hpp>
18+
#include <scale/enum_traits.hpp>
1819

1920
#define SCALE_EMPTY_DECODER(TargetType) \
2021
template <typename Stream, \
@@ -49,7 +50,7 @@ namespace scale {
4950
} catch (std::system_error &e) {
5051
return outcome::failure(e.code());
5152
}
52-
return s.data();
53+
return s.to_vector();
5354
}
5455

5556
/**

include/scale/scale_encoder_stream.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace scale {
3636
/**
3737
* @return vector of bytes containing encoded data
3838
*/
39-
std::vector<uint8_t> data() const;
39+
std::vector<uint8_t> to_vector() const;
4040

4141
/**
4242
* Get amount of encoded data written to the stream
@@ -285,6 +285,23 @@ namespace scale {
285285
size_t bytes_written_;
286286
};
287287

288+
/**
289+
* @brief scale-encodes any enum type as its underlying type
290+
* Defined outside ScaleEncoderStream to allow custom overloads for
291+
* specific enum types.
292+
* @tparam T enum type
293+
* @param v value of the enum type
294+
* @return reference to stream
295+
*/
296+
template <typename S,
297+
typename T,
298+
typename E = std::decay_t<T>,
299+
typename = std::enable_if_t<S::is_encoder_stream>,
300+
typename = std::enable_if_t<std::is_enum_v<E>>>
301+
S &operator<<(S &s, const T &v) {
302+
return s << static_cast<std::underlying_type_t<E>>(v);
303+
}
304+
288305
} // namespace scale
289306

290307
#endif // SCALE_CORE_SCALE_SCALE_ENCODER_STREAM_HPP

include/scale/scale_error.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace scale {
2929
UNEXPECTED_VALUE, ///< unexpected value
3030
TOO_MANY_ITEMS, ///< too many items, cannot address them in memory
3131
WRONG_TYPE_INDEX, ///< wrong type index, cannot decode variant
32+
INVALID_ENUM_VALUE ///< enum value which doesn't belong to the enum
3233
};
3334

3435
} // namespace scale

0 commit comments

Comments
 (0)