]> granicus.if.org Git - icinga2/blob - lib/base/object-packer.cpp
Merge pull request #7185 from Icinga/bugfix/gelfwriter-wrong-log-facility
[icinga2] / lib / base / object-packer.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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"
9 #include <algorithm>
10 #include <climits>
11 #include <cstdint>
12 #include <utility>
13 #include <vector>
14
15 using namespace icinga;
16
17 union EndiannessDetector
18 {
19         EndiannessDetector()
20         {
21                 i = 1;
22         }
23
24         int i;
25         char buf[sizeof(int)];
26 };
27
28 static const EndiannessDetector l_EndiannessDetector;
29
30 // Assumption: The compiler will optimize (away) if/else statements using this.
31 #define MACHINE_LITTLE_ENDIAN (l_EndiannessDetector.buf[0])
32
33 static void PackAny(const Value& value, StringBuilder& builder);
34
35 /**
36  * std::swap() seems not to work
37  */
38 static inline void SwapBytes(char& a, char& b)
39 {
40         char c = a;
41         a = b;
42         b = c;
43 }
44
45 #if CHAR_MIN != 0
46 union CharU2SConverter
47 {
48         CharU2SConverter()
49         {
50                 s = 0;
51         }
52
53         unsigned char u;
54         signed char s;
55 };
56 #endif
57
58 /**
59  * Avoid implementation-defined overflows during unsigned to signed casts
60  */
61 static inline char UIntToByte(unsigned i)
62 {
63 #if CHAR_MIN == 0
64         return i;
65 #else
66         CharU2SConverter converter;
67
68         converter.u = i;
69         return converter.s;
70 #endif
71 }
72
73 /**
74  * Append the given int as big-endian 64-bit unsigned int
75  */
76 static inline void PackUInt64BE(uint_least64_t i, StringBuilder& builder)
77 {
78         char buf[8] = {
79                 UIntToByte(i >> 56u),
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),
86                 UIntToByte(i & 255u)
87         };
88
89         builder.Append((char*)buf, (char*)buf + 8);
90 }
91
92 union Double2BytesConverter
93 {
94         Double2BytesConverter()
95         {
96                 buf[0] = 0;
97                 buf[1] = 0;
98                 buf[2] = 0;
99                 buf[3] = 0;
100                 buf[4] = 0;
101                 buf[5] = 0;
102                 buf[6] = 0;
103                 buf[7] = 0;
104         }
105
106         double f;
107         char buf[8];
108 };
109
110 /**
111  * Append the given double as big-endian IEEE 754 binary64
112  */
113 static inline void PackFloat64BE(double f, StringBuilder& builder)
114 {
115         Double2BytesConverter converter;
116
117         converter.f = f;
118
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]);
124         }
125
126         builder.Append((char*)converter.buf, (char*)converter.buf + 8);
127 }
128
129 /**
130  * Append the given string's length (BE uint64) and the string itself
131  */
132 static inline void PackString(const String& string, StringBuilder& builder)
133 {
134         PackUInt64BE(string.GetLength(), builder);
135         builder.Append(string);
136 }
137
138 /**
139  * Append the given array
140  */
141 static inline void PackArray(const Array::Ptr& arr, StringBuilder& builder)
142 {
143         ObjectLock olock(arr);
144
145         builder.Append('\5');
146         PackUInt64BE(arr->GetLength(), builder);
147
148         for (const Value& value : arr) {
149                 PackAny(value, builder);
150         }
151 }
152
153 /**
154  * Append the given dictionary
155  */
156 static inline void PackDictionary(const Dictionary::Ptr& dict, StringBuilder& builder)
157 {
158         ObjectLock olock(dict);
159
160         builder.Append('\6');
161         PackUInt64BE(dict->GetLength(), builder);
162
163         for (const Dictionary::Pair& kv : dict) {
164                 PackString(kv.first, builder);
165                 PackAny(kv.second, builder);
166         }
167 }
168
169 /**
170  * Append any JSON-encodable value
171  */
172 static void PackAny(const Value& value, StringBuilder& builder)
173 {
174         switch (value.GetType()) {
175                 case ValueString:
176                         builder.Append('\4');
177                         PackString(value.Get<String>(), builder);
178                         break;
179
180                 case ValueNumber:
181                         builder.Append('\3');
182                         PackFloat64BE(value.Get<double>(), builder);
183                         break;
184
185                 case ValueBoolean:
186                         builder.Append(value.ToBool() ? '\2' : '\1');
187                         break;
188
189                 case ValueEmpty:
190                         builder.Append('\0');
191                         break;
192
193                 case ValueObject:
194                         {
195                                 const Object::Ptr& obj = value.Get<Object::Ptr>();
196
197                                 Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj);
198                                 if (dict) {
199                                         PackDictionary(dict, builder);
200                                         break;
201                                 }
202
203                                 Array::Ptr arr = dynamic_pointer_cast<Array>(obj);
204                                 if (arr) {
205                                         PackArray(arr, builder);
206                                         break;
207                                 }
208                         }
209
210                         builder.Append('\0');
211                         break;
212
213                 default:
214                         VERIFY(!"Invalid variant type.");
215         }
216 }
217
218 /**
219  * Pack any JSON-encodable value to a BSON-similar structure suitable for consistent hashing
220  *
221  * Spec:
222  *   null: 0x00
223  *   false: 0x01
224  *   true: 0x02
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()
229  *
230  *   any: null|false|true|number|string|array|object
231  *   keyvalue: (uint64_bigendian)key.length (char[])key (any)value
232  *
233  * Assumptions:
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
238  *
239  * If not, this function will silently produce invalid results.
240  */
241 String icinga::PackObject(const Value& value)
242 {
243         StringBuilder builder;
244         PackAny(value, builder);
245
246         return builder.ToString();
247 }