mirror of
				https://github.com/Icinga/icinga2.git
				synced 2025-10-28 09:44:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			247 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* 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);
 | |
| }
 |