Spicy
integer.h
1 // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #include <cinttypes>
6 #include <limits>
7 #include <string>
8 #include <tuple>
9 #include <utility>
10 
11 #include <hilti/rt/extension-points.h>
12 #include <hilti/rt/result.h>
13 #include <hilti/rt/safe-int.h>
14 #include <hilti/rt/types/bytes.h>
15 #include <hilti/rt/unpack.h>
16 #include <hilti/rt/util.h>
17 
18 namespace hilti::rt {
19 
20 namespace detail::adl {
21 inline std::string to_string(hilti::rt::integer::safe<uint64_t> x, adl::tag /*unused*/) {
22  return fmt("%" PRIu64, *x.Ptr());
23 }
24 
25 inline std::string to_string(hilti::rt::integer::safe<int64_t> x, adl::tag /*unused*/) {
26  return fmt("%" PRId64, *x.Ptr());
27 }
28 
29 inline std::string to_string(hilti::rt::integer::safe<uint32_t> x, adl::tag /*unused*/) {
30  return fmt("%" PRIu32, *x.Ptr());
31 }
32 
33 inline std::string to_string(hilti::rt::integer::safe<int32_t> x, adl::tag /*unused*/) {
34  return fmt("%" PRId32, *x.Ptr());
35 }
36 
37 inline std::string to_string(hilti::rt::integer::safe<uint16_t> x, adl::tag /*unused*/) {
38  return fmt("%" PRIu16, *x.Ptr());
39 }
40 
41 inline std::string to_string(hilti::rt::integer::safe<int16_t> x, adl::tag /*unused*/) {
42  return fmt("%" PRId16, *x.Ptr());
43 }
44 
45 inline std::string to_string(hilti::rt::integer::safe<uint8_t> x, adl::tag /*unused*/) {
46  return fmt("%" PRIu8, *x.Ptr());
47 }
48 
49 inline std::string to_string(hilti::rt::integer::safe<int8_t> x, adl::tag /*unused*/) {
50  return fmt("%" PRId8, *x.Ptr());
51 }
52 
53 inline std::string to_string(hilti::rt::integer::safe<char> x, adl::tag /*unused*/) { return fmt("%" PRId8, *x.Ptr()); }
54 
55 inline std::string to_string(uint64_t x, adl::tag /*unused*/) { return fmt("%" PRIu64, x); }
56 
57 inline std::string to_string(int64_t x, adl::tag /*unused*/) { return fmt("%" PRId64, x); }
58 
59 inline std::string to_string(uint32_t x, adl::tag /*unused*/) { return fmt("%" PRIu32, x); }
60 
61 inline std::string to_string(int32_t x, adl::tag /*unused*/) { return fmt("%" PRId32, x); }
62 
63 inline std::string to_string(uint16_t x, adl::tag /*unused*/) { return fmt("%" PRIu16, x); }
64 
65 inline std::string to_string(int16_t x, adl::tag /*unused*/) { return fmt("%" PRId16, x); }
66 
67 inline std::string to_string(uint8_t x, adl::tag /*unused*/) { return fmt("%" PRIu8, x); }
68 
69 inline std::string to_string(int8_t x, adl::tag /*unused*/) { return fmt("%" PRId8, x); }
70 
71 } // namespace detail::adl
72 
73 namespace integer {
74 namespace detail {
75 
76 template<typename T, typename D>
77 inline void pack(D x, uint8_t* dst, std::initializer_list<int> bytes) {
78  for ( auto i : bytes ) {
79  dst[sizeof(x) - i - 1] = (x & 0xff);
80  x >>= 8U;
81  }
82 }
83 
84 template<typename T, typename D>
85 inline Result<std::tuple<integer::safe<T>, D>> unpack(D b, const uint8_t* dst, std::initializer_list<int> bytes) {
86  T x = 0;
87  for ( auto i : bytes ) {
88  x <<= 8U;
89  x |= (static_cast<T>(dst[i]));
90  }
91 
92  return std::make_tuple(static_cast<integer::safe<T>>(x), std::move(b));
93 }
94 
95 } // namespace detail
96 
97 template<typename T>
98 inline Bytes pack(integer::safe<T> i, ByteOrder fmt) {
99  if ( fmt == ByteOrder::Host )
100  return pack<T>(i, systemByteOrder());
101 
102  uint8_t raw[sizeof(T)];
103 
104  switch ( fmt.value() ) {
105  case ByteOrder::Big:
106  case ByteOrder::Network:
107  if constexpr ( std::is_same_v<T, uint8_t> )
108  raw[0] = i;
109  else if constexpr ( std::is_same_v<T, int8_t> )
110  raw[0] = static_cast<uint8_t>(i);
111  else if constexpr ( std::is_same_v<T, uint16_t> || std::is_same_v<T, int16_t> )
112  detail::pack<T>(i, raw, {0, 1});
113  else if constexpr ( std::is_same_v<T, uint32_t> || std::is_same_v<T, int32_t> )
114  detail::pack<T>(i, raw, {0, 1, 2, 3});
115  else if constexpr ( std::is_same_v<T, uint64_t> || std::is_same_v<T, int64_t> )
116  detail::pack<T>(i, raw, {0, 1, 2, 3, 4, 5, 6, 7});
117  else
119 
120  break;
121 
122  case ByteOrder::Little:
123  if constexpr ( std::is_same_v<T, uint8_t> )
124  raw[0] = i;
125 
126  else if constexpr ( std::is_same_v<T, int8_t> )
127  raw[0] = static_cast<uint8_t>(i);
128 
129  else if constexpr ( std::is_same_v<T, uint16_t> || std::is_same_v<T, int16_t> )
130  detail::pack<T>(i, raw, {1, 0});
131 
132  else if constexpr ( std::is_same_v<T, uint32_t> || std::is_same_v<T, int32_t> )
133  detail::pack<T>(i, raw, {3, 2, 1, 0});
134 
135  else if constexpr ( std::is_same_v<T, uint64_t> || std::is_same_v<T, int64_t> )
136  detail::pack<T>(i, raw, {7, 6, 5, 4, 3, 2, 1, 0});
137  else
139 
140  break;
141 
142  case ByteOrder::Host:
143  // Cannot reach, we check this above.
145 
146  case ByteOrder::Undef: throw RuntimeError("attempt to pack value with undefined byte order");
147  }
148 
149  return Bytes(reinterpret_cast<Bytes::Base::value_type*>(raw), sizeof(raw));
150 }
151 
152 template<typename T, typename D>
153 inline Result<std::tuple<integer::safe<T>, D>> unpack(D b, ByteOrder fmt) {
154  if ( fmt == ByteOrder::Host )
155  return unpack<T>(b, systemByteOrder());
156 
157  if ( b.size() < static_cast<int64_t>(sizeof(T)) )
158  return result::Error("insufficient data to unpack integer");
159 
160  uint8_t raw[sizeof(T)];
161  b = b.extract(raw, sizeof(raw));
162 
163  switch ( fmt.value() ) {
164  case ByteOrder::Big:
165  case ByteOrder::Network:
166  if constexpr ( std::is_same<T, uint8_t>::value )
167  return std::make_tuple(static_cast<integer::safe<uint8_t>>(raw[0]), std::move(b));
168 
169  if constexpr ( std::is_same<T, int8_t>::value ) {
170  auto x = static_cast<int8_t>(raw[0]); // Forced cast to skip safe<T> range check.
171  return std::make_tuple(static_cast<integer::safe<int8_t>>(x), std::move(b));
172  }
173 
174  if constexpr ( std::is_same<T, uint16_t>::value || std::is_same<T, int16_t>::value )
175  return detail::unpack<T>(std::move(b), raw, {0, 1});
176 
177  if constexpr ( std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value )
178  return detail::unpack<T>(std::move(b), raw, {0, 1, 2, 3});
179 
180  if constexpr ( std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value )
181  return detail::unpack<T>(std::move(b), raw, {0, 1, 2, 3, 4, 5, 6, 7});
182 
184 
185  case ByteOrder::Little:
186  if constexpr ( std::is_same<T, uint8_t>::value )
187  return std::make_tuple(static_cast<integer::safe<uint8_t>>(raw[0]), std::move(b));
188 
189  if constexpr ( std::is_same<T, int8_t>::value ) {
190  auto x = static_cast<int8_t>(raw[0]); // Forced cast to skip safe<T> range check.
191  return std::make_tuple(static_cast<integer::safe<int8_t>>(x), std::move(b));
192  }
193 
194  if constexpr ( std::is_same<T, uint16_t>::value || std::is_same<T, int16_t>::value )
195  return detail::unpack<T>(std::move(b), raw, {1, 0});
196 
197  if constexpr ( std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value )
198  return detail::unpack<T>(std::move(b), raw, {3, 2, 1, 0});
199 
200  if constexpr ( std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value )
201  return detail::unpack<T>(std::move(b), raw, {7, 6, 5, 4, 3, 2, 1, 0});
202 
204 
205  case ByteOrder::Host:
206  // Cannot reach, we check this above.
208 
209  case ByteOrder::Undef: return result::Error("undefined byte order");
210  }
211 
213 }
214 
220 extern uint64_t hton64(uint64_t v);
221 
227 extern uint32_t hton32(uint32_t v);
228 
234 extern uint16_t hton16(uint16_t v);
235 
241 extern uint64_t ntoh64(uint64_t v);
242 
248 extern uint32_t ntoh32(uint32_t v);
249 
255 extern uint16_t ntoh16(uint16_t v);
256 
262 extern uint16_t flip16(uint16_t v);
263 
269 extern uint32_t flip32(uint32_t v);
270 
276 extern uint64_t flip64(uint64_t v);
277 
285 inline int64_t flip(int64_t v, uint64_t n) {
286  if ( n == 0 )
287  return v;
288  auto i = static_cast<uint64_t>(v);
289  i = flip64(i) >> (64 - n * 8);
290  return static_cast<int64_t>(i);
291 }
292 
300 inline uint64_t flip(uint64_t v, uint64_t n) {
301  if ( n == 0 )
302  return v;
303  return (flip64(v) >> (64 - n * 8));
304 }
305 
307 HILTI_RT_ENUM(BitOrder, LSB0, MSB0, Undef);
308 
310 template<typename UINT>
311 inline hilti::rt::integer::safe<UINT> bits(hilti::rt::integer::safe<UINT> v, uint64_t lower, uint64_t upper,
312  BitOrder bo) {
313  constexpr auto width = std::numeric_limits<UINT>::digits;
314 
315  if ( lower > upper )
316  throw InvalidArgument("lower limit needs to be less or equal the upper limit");
317 
318  if ( upper >= width )
319  throw InvalidArgument("upper limit needs to be less or equal the input width");
320 
321  switch ( bo.value() ) {
322  case BitOrder::LSB0: break;
323 
324  case BitOrder::MSB0:
325  lower = (width - lower - 1);
326  upper = (width - upper - 1);
327  std::swap(lower, upper);
328  break;
329 
330  case BitOrder::Undef: throw RuntimeError("undefined bit order");
331  }
332 
333  assert(lower <= upper);
334  const auto range = upper - lower + 1;
335 
336  // If the range to extract equals the width there is no work to do.
337  //
338  // NOTE: Not returning early here would lead to a shift beyond the width below.
339  if ( range == width )
340  return v;
341 
342  const auto mask = ((uint64_t(1) << range) - uint64_t(1U)) << lower;
343  return (v & mask) >> lower;
344 }
345 
346 } // namespace integer
347 
348 namespace detail::adl {
349 std::string to_string(const integer::BitOrder& x, tag /*unused*/);
350 }
351 
352 } // namespace hilti::rt
std::string to_string(T &&x)
Definition: extension-points.h:26
Definition: any.h:7
auto range(const T &t)
Definition: iterator.h:37
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:42
ByteOrder systemByteOrder()
Definition: util.cc:392
void abort_with_backtrace() __attribute__((noreturn))
Definition: util.cc:34
std::string fmt(const char *fmt, const Args &... args)
Definition: fmt.h:13