1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/object-packer.hpp"
4 #include "base/debug.hpp"
5 #include "base/dictionary.hpp"
6 #include "base/array.hpp"
7 #include "base/objectlock.hpp"
8 #include "base/stringbuilder.hpp"
15 using namespace icinga;
17 union EndiannessDetector
25 char buf[sizeof(int)];
28 static const EndiannessDetector l_EndiannessDetector;
30 // Assumption: The compiler will optimize (away) if/else statements using this.
31 #define MACHINE_LITTLE_ENDIAN (l_EndiannessDetector.buf[0])
33 static void PackAny(const Value& value, StringBuilder& builder);
36 * std::swap() seems not to work
38 static inline void SwapBytes(char& a, char& b)
46 union CharU2SConverter
59 * Avoid implementation-defined overflows during unsigned to signed casts
61 static inline char UIntToByte(unsigned i)
66 CharU2SConverter converter;
74 * Append the given int as big-endian 64-bit unsigned int
76 static inline void PackUInt64BE(uint_least64_t i, StringBuilder& builder)
80 UIntToByte((i >> 48u) & 255u),
81 UIntToByte((i >> 40u) & 255u),
82 UIntToByte((i >> 32u) & 255u),
83 UIntToByte((i >> 24u) & 255u),
84 UIntToByte((i >> 16u) & 255u),
85 UIntToByte((i >> 8u) & 255u),
89 builder.Append((char*)buf, (char*)buf + 8);
92 union Double2BytesConverter
94 Double2BytesConverter()
111 * Append the given double as big-endian IEEE 754 binary64
113 static inline void PackFloat64BE(double f, StringBuilder& builder)
115 Double2BytesConverter converter;
119 if (MACHINE_LITTLE_ENDIAN) {
120 SwapBytes(converter.buf[0], converter.buf[7]);
121 SwapBytes(converter.buf[1], converter.buf[6]);
122 SwapBytes(converter.buf[2], converter.buf[5]);
123 SwapBytes(converter.buf[3], converter.buf[4]);
126 builder.Append((char*)converter.buf, (char*)converter.buf + 8);
130 * Append the given string's length (BE uint64) and the string itself
132 static inline void PackString(const String& string, StringBuilder& builder)
134 PackUInt64BE(string.GetLength(), builder);
135 builder.Append(string);
139 * Append the given array
141 static inline void PackArray(const Array::Ptr& arr, StringBuilder& builder)
143 ObjectLock olock(arr);
145 builder.Append('\5');
146 PackUInt64BE(arr->GetLength(), builder);
148 for (const Value& value : arr) {
149 PackAny(value, builder);
154 * Append the given dictionary
156 static inline void PackDictionary(const Dictionary::Ptr& dict, StringBuilder& builder)
158 ObjectLock olock(dict);
160 builder.Append('\6');
161 PackUInt64BE(dict->GetLength(), builder);
163 for (const Dictionary::Pair& kv : dict) {
164 PackString(kv.first, builder);
165 PackAny(kv.second, builder);
170 * Append any JSON-encodable value
172 static void PackAny(const Value& value, StringBuilder& builder)
174 switch (value.GetType()) {
176 builder.Append('\4');
177 PackString(value.Get<String>(), builder);
181 builder.Append('\3');
182 PackFloat64BE(value.Get<double>(), builder);
186 builder.Append(value.ToBool() ? '\2' : '\1');
190 builder.Append('\0');
195 const Object::Ptr& obj = value.Get<Object::Ptr>();
197 Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj);
199 PackDictionary(dict, builder);
203 Array::Ptr arr = dynamic_pointer_cast<Array>(obj);
205 PackArray(arr, builder);
210 builder.Append('\0');
214 VERIFY(!"Invalid variant type.");
219 * Pack any JSON-encodable value to a BSON-similar structure suitable for consistent hashing
225 * number: 0x03 (ieee754_binary64_bigendian)payload
226 * string: 0x04 (uint64_bigendian)payload.length (char[])payload
227 * array: 0x05 (uint64_bigendian)payload.length (any[])payload
228 * object: 0x06 (uint64_bigendian)payload.length (keyvalue[])payload.sort()
230 * any: null|false|true|number|string|array|object
231 * keyvalue: (uint64_bigendian)key.length (char[])key (any)value
234 * - double is IEEE 754 binary64
235 * - all int types (signed and unsigned) and all float types share the same endianness
236 * - char is exactly 8 bits wide and one char is exactly one byte affected by the machine endianness
237 * - all input strings, arrays and dictionaries are at most 2^64-1 long
239 * If not, this function will silently produce invalid results.
241 String icinga::PackObject(const Value& value)
243 StringBuilder builder;
244 PackAny(value, builder);
246 return builder.ToString();