summaryrefslogtreecommitdiffstats
path: root/lib/base/object-packer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/base/object-packer.cpp246
1 files changed, 246 insertions, 0 deletions
diff --git a/lib/base/object-packer.cpp b/lib/base/object-packer.cpp
new file mode 100644
index 0000000..123ddad
--- /dev/null
+++ b/lib/base/object-packer.cpp
@@ -0,0 +1,246 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/object-packer.hpp"
+#include "base/debug.hpp"
+#include "base/dictionary.hpp"
+#include "base/array.hpp"
+#include "base/objectlock.hpp"
+#include <algorithm>
+#include <climits>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+using namespace icinga;
+
+union EndiannessDetector
+{
+ EndiannessDetector()
+ {
+ i = 1;
+ }
+
+ int i;
+ char buf[sizeof(int)];
+};
+
+static const EndiannessDetector l_EndiannessDetector;
+
+// Assumption: The compiler will optimize (away) if/else statements using this.
+#define MACHINE_LITTLE_ENDIAN (l_EndiannessDetector.buf[0])
+
+static void PackAny(const Value& value, std::string& builder);
+
+/**
+ * std::swap() seems not to work
+ */
+static inline void SwapBytes(char& a, char& b)
+{
+ char c = a;
+ a = b;
+ b = c;
+}
+
+#if CHAR_MIN != 0
+union CharU2SConverter
+{
+ CharU2SConverter()
+ {
+ s = 0;
+ }
+
+ unsigned char u;
+ signed char s;
+};
+#endif
+
+/**
+ * Avoid implementation-defined overflows during unsigned to signed casts
+ */
+static inline char UIntToByte(unsigned i)
+{
+#if CHAR_MIN == 0
+ return i;
+#else
+ CharU2SConverter converter;
+
+ converter.u = i;
+ return converter.s;
+#endif
+}
+
+/**
+ * Append the given int as big-endian 64-bit unsigned int
+ */
+static inline void PackUInt64BE(uint_least64_t i, std::string& builder)
+{
+ char buf[8] = {
+ UIntToByte(i >> 56u),
+ UIntToByte((i >> 48u) & 255u),
+ UIntToByte((i >> 40u) & 255u),
+ UIntToByte((i >> 32u) & 255u),
+ UIntToByte((i >> 24u) & 255u),
+ UIntToByte((i >> 16u) & 255u),
+ UIntToByte((i >> 8u) & 255u),
+ UIntToByte(i & 255u)
+ };
+
+ builder.append((char*)buf, 8);
+}
+
+union Double2BytesConverter
+{
+ Double2BytesConverter()
+ {
+ buf[0] = 0;
+ buf[1] = 0;
+ buf[2] = 0;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+ }
+
+ double f;
+ char buf[8];
+};
+
+/**
+ * Append the given double as big-endian IEEE 754 binary64
+ */
+static inline void PackFloat64BE(double f, std::string& builder)
+{
+ Double2BytesConverter converter;
+
+ converter.f = f;
+
+ if (MACHINE_LITTLE_ENDIAN) {
+ SwapBytes(converter.buf[0], converter.buf[7]);
+ SwapBytes(converter.buf[1], converter.buf[6]);
+ SwapBytes(converter.buf[2], converter.buf[5]);
+ SwapBytes(converter.buf[3], converter.buf[4]);
+ }
+
+ builder.append((char*)converter.buf, 8);
+}
+
+/**
+ * Append the given string's length (BE uint64) and the string itself
+ */
+static inline void PackString(const String& string, std::string& builder)
+{
+ PackUInt64BE(string.GetLength(), builder);
+ builder += string.GetData();
+}
+
+/**
+ * Append the given array
+ */
+static inline void PackArray(const Array::Ptr& arr, std::string& builder)
+{
+ ObjectLock olock(arr);
+
+ builder += '\5';
+ PackUInt64BE(arr->GetLength(), builder);
+
+ for (const Value& value : arr) {
+ PackAny(value, builder);
+ }
+}
+
+/**
+ * Append the given dictionary
+ */
+static inline void PackDictionary(const Dictionary::Ptr& dict, std::string& builder)
+{
+ ObjectLock olock(dict);
+
+ builder += '\6';
+ PackUInt64BE(dict->GetLength(), builder);
+
+ for (const Dictionary::Pair& kv : dict) {
+ PackString(kv.first, builder);
+ PackAny(kv.second, builder);
+ }
+}
+
+/**
+ * Append any JSON-encodable value
+ */
+static void PackAny(const Value& value, std::string& builder)
+{
+ switch (value.GetType()) {
+ case ValueString:
+ builder += '\4';
+ PackString(value.Get<String>(), builder);
+ break;
+
+ case ValueNumber:
+ builder += '\3';
+ PackFloat64BE(value.Get<double>(), builder);
+ break;
+
+ case ValueBoolean:
+ builder += (value.ToBool() ? '\2' : '\1');
+ break;
+
+ case ValueEmpty:
+ builder += '\0';
+ break;
+
+ case ValueObject:
+ {
+ const Object::Ptr& obj = value.Get<Object::Ptr>();
+
+ Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj);
+ if (dict) {
+ PackDictionary(dict, builder);
+ break;
+ }
+
+ Array::Ptr arr = dynamic_pointer_cast<Array>(obj);
+ if (arr) {
+ PackArray(arr, builder);
+ break;
+ }
+ }
+
+ builder += '\0';
+ break;
+
+ default:
+ VERIFY(!"Invalid variant type.");
+ }
+}
+
+/**
+ * Pack any JSON-encodable value to a BSON-similar structure suitable for consistent hashing
+ *
+ * Spec:
+ * null: 0x00
+ * false: 0x01
+ * true: 0x02
+ * number: 0x03 (ieee754_binary64_bigendian)payload
+ * string: 0x04 (uint64_bigendian)payload.length (char[])payload
+ * array: 0x05 (uint64_bigendian)payload.length (any[])payload
+ * object: 0x06 (uint64_bigendian)payload.length (keyvalue[])payload.sort()
+ *
+ * any: null|false|true|number|string|array|object
+ * keyvalue: (uint64_bigendian)key.length (char[])key (any)value
+ *
+ * Assumptions:
+ * - double is IEEE 754 binary64
+ * - all int types (signed and unsigned) and all float types share the same endianness
+ * - char is exactly 8 bits wide and one char is exactly one byte affected by the machine endianness
+ * - all input strings, arrays and dictionaries are at most 2^64-1 long
+ *
+ * If not, this function will silently produce invalid results.
+ */
+String icinga::PackObject(const Value& value)
+{
+ std::string builder;
+ PackAny(value, builder);
+
+ return std::move(builder);
+}