CDT  v4.2.0
инструменты разработчика
key_utils.hpp
См. документацию.
1#pragma once
2
3#include <deque>
4#include <list>
5#include <map>
6#include <optional>
7#include <set>
8#include <tuple>
9#include <utility>
10#include <variant>
11#include <vector>
12#include <string>
13#include <cstring>
14
15#include "datastream.hpp"
16#include "reflect.hpp"
17
18namespace eosio {
19
20 namespace detail {
21 template<typename T>
22 constexpr bool has_bitwise_serialization() {
23 if constexpr (std::is_arithmetic_v<T>) {
24 return true;
25 } else if constexpr (std::is_enum_v<T>) {
26 static_assert(!std::is_convertible_v<T, std::underlying_type_t<T>>, "Serializing unscoped enum");
27 return true;
28 } else {
29 return false;
30 }
31 }
32
33 template <template <typename> class C, typename T>
34 constexpr bool is_ranged_type(C<T>) {
35 using type = std::decay_t<C<T>>;
36 return
37 std::is_same_v<std::vector<T>, type> ||
38 std::is_same_v<std::list<T>, type> ||
39 std::is_same_v<std::deque<T>, type> ||
40 std::is_same_v<std::set<T>, type>;
41 }
42
43 template <typename R, typename C>
44 auto member_pointer_type(R (C::*)) -> R;
45 template <typename R, typename C>
46 auto member_pointer_class(R (C::*)) -> C;
47
48 template <typename... Args>
49 constexpr inline std::size_t total_bytes_size() { return (sizeof(Args) + ...); }
50
51 // TODO rework the to_key and datastream logic to be constexpr/consteval friendly to get rid of this
52 template <std::size_t I, typename Arg, typename... Args>
53 inline void const_pack_helper(std::string& s, Arg&& arg, Args&&... args) {
54 std::memcpy(s.data()+I, &arg, sizeof(Arg));
55 if constexpr (std::is_integral_v<std::decay_t<Arg>> ||
56 std::is_same_v<std::decay_t<Arg>, eosio::name>) {
57 std::reverse(s.data()+I, s.data()+I+sizeof(Arg));
58 }
59 if constexpr (sizeof...(Args) > 0) {
60 return const_pack_helper<I+sizeof(Arg)>(s, std::forward<Args>(args)...);
61 }
62 }
63
64 // TODO rework the to_key and datastream logic to be constexpr/consteval friendly to get rid of this
65 template <typename... Args>
66 inline std::string const_pack(Args&&... args) {
67 std::string s;
68 s.resize(total_bytes_size<Args...>());
69 const_pack_helper<0>(s, std::forward<Args>(args)...);
70 return s;
71 }
72 } // namespace eosio::detail
73
74using key_type = std::string;
75
76// to_key задаёт преобразование типа в последовательность байт, лексикографический
77// порядок которой совпадает с порядком исходного типа.
78//
79// Для любых двух объектов типа T, a и b:
80//
81// - key(a) < key(b) тогда и только тогда, когда a < b
82// - key(a) не является префиксом key(b)
83//
84// Перегрузки to_key для пользовательских типов находятся поиском Кёнига (ADL).
85//
86// to_key специализирован для следующих типов
87// - std::string и std::string_view
88// - std::vector, std::list, std::deque
89// - std::tuple
90// - std::array
91// - std::optional
92// - std::variant
93// - арифметические типы
94// - типы областных перечислений (scoped enum)
95// - рефлектируемые структуры
96// - все типы, связанные со смарт-контрактами COOPOS, определённые в abieos
97template <typename T, typename S>
98void to_key(const T& obj, datastream<S>& stream);
99
100template <int I, typename T, typename S>
101void to_key_tuple(const T& obj, datastream<S>& stream) {
102 if constexpr (I < std::tuple_size_v<T>) {
103 to_key(std::get<I>(obj), stream);
104 to_key_tuple<I + 1>(obj, stream);
105 }
106}
107
108template <typename... Ts, typename S>
109void to_key(const std::tuple<Ts...>& obj, datastream<S>& stream) {
110 to_key_tuple<0>(obj, stream);
111}
112
113template <typename T, std::size_t N, typename S>
114void to_key(const std::array<T, N>& obj, datastream<S>& stream) {
115 for (const T& elem : obj) { to_key(elem, stream); }
116}
117
118template <typename T, typename S>
119void to_key_optional(const bool* obj, datastream<S>& stream) {
120 if (obj == nullptr)
121 stream.write('\0');
122 else if (!*obj)
123 stream.write('\1');
124 else {
125 stream.write('\2');
126 }
127}
128
129template <typename T, typename S>
130void to_key_optional(const T* obj, datastream<S>& stream) {
131 if constexpr (detail::has_bitwise_serialization<T>() && sizeof(T) == 1) {
132 if (obj == nullptr)
133 stream.write("\0", 2);
134 else {
135 char buf[1];
136 datastream<char*> tmp_stream(buf, 1);
137 to_key(*obj, tmp_stream);
138 stream.write(buf[0]);
139 if (buf[0] == '\0')
140 stream.write('\1');
141 }
142 } else {
143 if (obj) {
144 stream.write('\1');
145 to_key(*obj, stream);
146 } else {
147 stream.write('\0');
148 }
149 }
150}
151
152template <typename T, typename U, typename S>
153void to_key(const std::pair<T, U>& obj, datastream<S>& stream) {
154 to_key(obj.first, stream);
155 to_key(obj.second, stream);
156}
157
158template <typename T, typename S>
159void to_key_range(const T& obj, datastream<S>& stream) {
160 for (const auto& elem : obj) { to_key_optional(&elem, stream); }
161 to_key_optional((std::add_pointer_t<decltype(*std::begin(obj))>) nullptr, stream);
162}
163
164template <typename T, typename S>
165auto to_key(const T& obj, datastream<S>& stream) -> std::enable_if_t<is_ranged_type(std::declval<T>()), void> {
166 to_key_range(obj, stream);
167}
168
169template <typename T, typename U, typename S>
170void to_key(const std::map<T, U>& obj, datastream<S>& stream) {
171 to_key_range(obj, stream);
172}
173
174template <typename T, typename S>
175void to_key(const std::optional<T>& obj, datastream<S>& stream) {
176 to_key_optional(obj ? &*obj : nullptr, stream);
177}
178
179// Первый байт содержит:
180// 0–4 единицы (число дополнительных байт), 0 (бит-терминатор)
181//
182// Число представляется в порядке big-endian с использованием младших
183// разрядов первого байта и всех оставшихся байт.
184//
185// Примечания:
186// - значения должны кодироваться минимально возможным числом байт,
187// иначе неканонические представления нарушат порядок сортировки.
188template <typename S>
190 int num_bytes;
191 if (obj < 0x80u) {
192 num_bytes = 1;
193 } else if (obj < 0x4000u) {
194 num_bytes = 2;
195 } else if (obj < 0x200000u) {
196 num_bytes = 3;
197 } else if (obj < 0x10000000u) {
198 num_bytes = 4;
199 } else {
200 num_bytes = 5;
201 }
202
203 stream.write(
204 static_cast<char>(~(0xFFu >> (num_bytes - 1)) | (num_bytes == 5 ? 0 : (obj >> ((num_bytes - 1) * 8)))));
205 for (int i = num_bytes - 2; i >= 0; --i) { stream.write(static_cast<char>((obj >> i * 8) & 0xFFu)); }
206}
207
208// для неотрицательных значений
209// Первый байт содержит:
210// 1 (бит знака) 0–4 единицы (число дополнительных байт) 0 (бит-терминатор)
211// Значение представляется в порядке big-endian
212// для отрицательных значений
213// Первый байт содержит:
214// 0 (бит знака) 0–4 нуля (число дополнительных байт) 1 (бит-терминатор)
215// Значение приводится к положительному виду в допустимом диапазоне для
216// данного числа байт и затем кодируется как big-endian.
217//
218// Примечания:
219// - отрицательные значения должны сортироваться раньше положительных
220// - для отрицательных значений числа, требующие большего числа байт, меньше, поэтому
221// кодирование ширины должно быть противоположно используемому для
222// неотрицательных значений
223// - 5-байтовый varint может представлять значения в диапазоне $[-2^{34}, 2^{34})$; в этом случае
224// аргумент дополняется знаком (sign-extended)
225template <typename S>
226void to_key_varint32(std::int32_t obj, datastream<S>& stream) {
227 static_assert(std::is_same_v<S, void>, "to_key for varint32 has been temporarily disabled");
228 int num_bytes;
229 bool sign = (obj < 0);
230 if (obj < 0x40 && obj >= -0x40) {
231 num_bytes = 1;
232 } else if (obj < 0x2000 && obj >= -0x2000) {
233 num_bytes = 2;
234 } else if (obj < 0x100000 && obj >= -0x100000) {
235 num_bytes = 3;
236 } else if (obj < 0x08000000 && obj >= -0x08000000) {
237 num_bytes = 4;
238 } else {
239 num_bytes = 5;
240 }
241
242 unsigned char width_field;
243 if (sign) {
244 width_field = 0x80u >> num_bytes;
245 } else {
246 width_field = 0x80u | ~(0xFFu >> num_bytes);
247 }
248 auto uobj = static_cast<std::uint32_t>(obj);
249 unsigned char value_mask = (0xFFu >> (num_bytes + 1));
250 unsigned char high_byte = (num_bytes == 5 ? (sign ? 0xFF : 0) : (uobj >> ((num_bytes - 1) * 8)));
251 stream.write(width_field | (high_byte & value_mask));
252 for (int i = num_bytes - 2; i >= 0; --i) { stream.write(static_cast<char>((uobj >> i * 8) & 0xFFu)); }
253}
254
255template <typename... Ts, typename S>
256void to_key(const std::variant<Ts...>& obj, datastream<S>& stream) {
257 to_key_varuint32(static_cast<uint32_t>(obj.index()), stream);
258 std::visit([&](const auto& item) { to_key(item, stream); }, obj);
259}
260
261template <std::size_t N, typename S>
262void to_key(const char (&str)[N], datastream<S>& stream) {
263 to_key(std::string_view{str, N-1}, stream);
264}
265
266template <typename S>
267void to_key(std::string_view obj, datastream<S>& stream) {
268 for (char ch : obj) {
269 stream.write(ch);
270 if (ch == '\0') {
271 stream.write('\1');
272 }
273 }
274 stream.write("\0", 2);
275}
276
277template <typename S>
278void to_key(const std::string& obj, datastream<S>& stream) {
279 to_key(std::string_view(obj), stream);
280}
281
282template <typename S>
283void to_key(bool obj, datastream<S>& stream) {
284 stream.write(static_cast<char>(obj ? 1 : 0));
285}
286
287template <typename UInt, typename T>
288UInt float_to_key(T value) {
289 static_assert(sizeof(T) == sizeof(UInt), "Expected unsigned int of the same size");
290 UInt result;
291 std::memcpy(&result, &value, sizeof(T));
292 UInt signbit = (static_cast<UInt>(1) << (std::numeric_limits<UInt>::digits - 1));
293 UInt mask = 0;
294 if (result == signbit)
295 result = 0;
296 if (result & signbit)
297 mask = ~mask;
298 return result ^ (mask | signbit);
299}
300
301template <typename T, typename S>
302void to_key(const T& obj, datastream<S>& stream) {
303 if constexpr (std::is_floating_point_v<T>) {
304 if constexpr (sizeof(T) == 4) {
305 to_key(float_to_key<uint32_t>(obj), stream);
306 } else {
307 static_assert(sizeof(T) == 8, "Unknown floating point type");
308 to_key(float_to_key<uint64_t>(obj), stream);
309 }
310 } else if constexpr (std::is_integral_v<T>) {
311 auto v = static_cast<std::make_unsigned_t<T>>(obj);
312 v -= static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::min());
313 std::reverse(reinterpret_cast<char*>(&v), reinterpret_cast<char*>(&v + 1));
314 stream.write(&v, sizeof(v));
315 } else if constexpr (std::is_enum_v<T>) {
316 static_assert(!std::is_convertible_v<T, std::underlying_type_t<T>>, "Serializing unscoped enum");
317 to_key(static_cast<std::underlying_type_t<T>>(obj), stream);
318 } else {
319 using ts_meta = bluegrass::meta::meta_object<T>;
320 ts_meta::for_each_field(obj, [&](const auto& member) {
321 to_key(member, stream);
322 });
323 }
324}
325
326template <typename T>
327void convert_to_key(const T& t, key_type& bin) {
329 to_key(t, ss);
330 auto orig_size = bin.size();
331 bin.resize(orig_size + ss.tellp());
332 datastream<char*> fbs(bin.data() + orig_size, ss.tellp());
333 to_key(t, fbs);
334 check( fbs.valid(), "Stream overrun" );
335}
336
337template <typename T>
341 return result;
342}
343} // namespace eosio