Skip to content

file platform/flake/FlakeTypes.h

Namespaces

Name
flake
flake::ReservedProperties
System-reserved property tags.
flake::MetaProperties
Meta-property tags (attached as auxiliary data, not user properties).
flake::ParameterProperties
Standard parameter property tags used in protocol messages.
flake::MessageType
Protocol message type constants.

Namespaces

Name
flake
flake::ReservedProperties
System-reserved property tags.
flake::MetaProperties
Meta-property tags (attached as auxiliary data, not user properties).
flake::ParameterProperties
Standard parameter property tags used in protocol messages.
flake::MessageType
Protocol message type constants.

Types

Name
structflake::Serializable
Interface for types that support binary serialization.
classflake::UniqueId
128-bit (16 byte) universally unique identifier.
structbinary_t
structarray_t
classflake::Array
Type-safe accessor for a raw typed array (array_t).
structflake::tt_of< int8_t >
structflake::tt_of< int16_t >
structflake::tt_of< int32_t >
structflake::tt_of< int64_t >
structflake::tt_of< uint8_t >
structflake::tt_of< uint16_t >
structflake::tt_of< uint32_t >
structflake::tt_of< uint64_t >
structflake::tt_of< float >
structflake::tt_of< bool >
structflake::tt_of< char * >
structflake::tt_of< tt_str_t >
structflake::tt_of< tt_bin_t >
structflake::tt_of< tt_uuid_t >
structflake::tt_of< tt_array_t >
structflake::tt_allowed
structflake::tt_allowed< uint32_t >
structflake::tt_allowed< tt_bin_t >
structflake::tt_allowed< tt_str_t >
structflake::tt_allowed< tt_array_t >
structflake::unwrap_optional
structflake::unwrap_optional< std::optional< U > >
structflake::has_tt_of
structflake::has_tt_of< T, std::void_t< decltype(tt_of< std::decay_t< T > >::value)> >
classflake::TagArray
Ordered set of property tags (uint32_t).
structflake::PropTypeFromTag<(0x1U) >
structflake::PropTypeFromTag<(0x2U) >
structflake::PropTypeFromTag<(0x3U) >
structflake::PropTypeFromTag<(0x4U) >
structflake::PropTypeFromTag<(0xFU) >
structflake::PropTypeFromTag<(0xBU) >
structflake::PropTypeFromTag<(0x5U) >
structflake::PropTypeFromTag<(0x6U) >
structflake::PropTypeFromTag<(0x7U) >
structflake::PropTypeFromTag<(0xCU) >
structflake::PropTypeFromTag<(0x9U) >
structflake::PropTypeFromTag<(0xEU) >
structflake::PropTypeFromTag<(0xAU) >
structflake::PropTypeFromTag<(0x8U) >
structflake::PropTypeFromTag<(0x81U) >
structflake::PropTypeFromTag<(0x82U) >
structflake::is_std_optional
structflake::is_std_optional< std::optional< T > >
classflake::Property
A single typed, tagged property value.
classflake::PropArray
Ordered, serializable collection of Property values.

Defines

Name
UINT8_FMT
UINT16_FMT
UINT32_FMT
INT8_FMT
INT16_FMT
INT32_FMT
UINT32_HEX_FMT
DEALLOC_SELF_FLAGInternal flag: the Property owns its heap-allocated payload and will free it on destruction.
DONT_SERIALIZE_FLAGInternal flag: skip this Property during serialization.
DONT_COPY_FLAGInternal flag: do not deep-copy binary payloads (shallow reference only).
DONT_CACHE_FLAGInternal flag: this Property should not be stored in the local cache.
TAG_TYPE(x)Extract the primitive type code from a property tag (lower 11 bits).
TAG_ID(x)Extract the property identifier from a tag (upper 16 bits).
TAG_READONLYAttribute flag: property is read-only.
TAG_ACTIONABLEAttribute flag: setting this property triggers an action.
TAG_VOLATILEAttribute flag: property value may change without notification.
TAG_NULLAttribute flag: property currently holds no value (null).
TAG_ERRORAttribute flag: property carries an error code instead of a value.
TAG_ARRAYAttribute flag: property holds a typed array rather than a scalar.
TAG_METAAttribute flag: property is metadata (e.g. timestamps), not user data.
IS_ACTIONABLE(x)Test whether a tag has the TAG_ACTIONABLE flag set.
IS_READONLY(x)Test whether a tag has the TAG_READONLY flag set.
IS_META(x)Test whether a tag has the TAG_META flag set.
IS_RESERVED(x)Test whether a tag ID falls into the reserved range (0xFFF0..0xFFFF).
IS_ERROR(x)Test whether a tag has the TAG_ERROR flag set.
IS_VOLATILE(x)Test whether a tag has the TAG_VOLATILE flag set.
IS_ARRAY(x)Test whether a tag has the TAG_ARRAY flag set.
IS_STREAM(x)Test whether a tag type indicates a stream property (string or binary stream).
TT_INT32Type code: signed 32-bit integer.
TT_INT16Type code: signed 16-bit integer.
TT_INT8Type code: signed 8-bit integer.
TT_UINT32Type code: unsigned 32-bit integer.
TT_UINT16Type code: unsigned 16-bit integer.
TT_UINT8Type code: unsigned 8-bit integer.
TT_BOOLType code: boolean.
TT_UUIDType code: 128-bit UUID.
TT_FLOATType code: single-precision floating point.
TT_DATETIMEType code: date/time as Unix timestamp (uint32_t).
TT_UINT64Type code: unsigned 64-bit integer.
TT_BINType code: binary blob (length + pointer).
TT_STRINGType code: UTF-8 string (std::string).
TT_INT64Type code: signed 64-bit integer.
TT_OBJECTType code: embedded object reference.
TT_STRING_STREAMType code: UTF-8 string (std::string).
TT_BIN_STREAMType code: binary blob (length + pointer).
TT_TABLEType code: embedded table.
ERROR_VAL(x)Test whether a Property instance holds an error value.

Macros Documentation

define UINT8_FMT

cpp
#define UINT8_FMT "u"

define UINT16_FMT

cpp
#define UINT16_FMT "u"

define UINT32_FMT

cpp
#define UINT32_FMT "u"

define INT8_FMT

cpp
#define INT8_FMT "d"

define INT16_FMT

cpp
#define INT16_FMT "d"

define INT32_FMT

cpp
#define INT32_FMT "d"

define UINT32_HEX_FMT

cpp
#define UINT32_HEX_FMT "lX"

define DEALLOC_SELF_FLAG

cpp
#define DEALLOC_SELF_FLAG (1UL)

Internal flag: the Property owns its heap-allocated payload and will free it on destruction.

define DONT_SERIALIZE_FLAG

cpp
#define DONT_SERIALIZE_FLAG (2UL)

Internal flag: skip this Property during serialization.

define DONT_COPY_FLAG

cpp
#define DONT_COPY_FLAG (4UL)

Internal flag: do not deep-copy binary payloads (shallow reference only).

define DONT_CACHE_FLAG

cpp
#define DONT_CACHE_FLAG (8UL)

Internal flag: this Property should not be stored in the local cache.

define TAG_TYPE

cpp
#define TAG_TYPE(
    x
)
((x) & 0x7FFU)

Extract the primitive type code from a property tag (lower 11 bits).

define TAG_ID

cpp
#define TAG_ID(
    x
)
((x) >> 16)

Extract the property identifier from a tag (upper 16 bits).

define TAG_READONLY

cpp
#define TAG_READONLY (0x1000UL)

Attribute flag: property is read-only.

define TAG_ACTIONABLE

cpp
#define TAG_ACTIONABLE (0x2000UL)

Attribute flag: setting this property triggers an action.

define TAG_VOLATILE

cpp
#define TAG_VOLATILE (0x4000UL)

Attribute flag: property value may change without notification.

define TAG_NULL

cpp
#define TAG_NULL (0x8000UL)

Attribute flag: property currently holds no value (null).

define TAG_ERROR

cpp
#define TAG_ERROR (0x100UL)

Attribute flag: property carries an error code instead of a value.

define TAG_ARRAY

cpp
#define TAG_ARRAY (0x040UL)

Attribute flag: property holds a typed array rather than a scalar.

define TAG_META

cpp
#define TAG_META (0x800UL)

Attribute flag: property is metadata (e.g. timestamps), not user data.

define IS_ACTIONABLE

cpp
#define IS_ACTIONABLE(
    x
)
(((x) & TAG_ACTIONABLE) == TAG_ACTIONABLE)

Test whether a tag has the TAG_ACTIONABLE flag set.

define IS_READONLY

cpp
#define IS_READONLY(
    x
)
(((x) & TAG_READONLY) == TAG_READONLY)

Test whether a tag has the TAG_READONLY flag set.

define IS_META

cpp
#define IS_META(
    x
)
(((x) & TAG_META) == TAG_META)

Test whether a tag has the TAG_META flag set.

define IS_RESERVED

cpp
#define IS_RESERVED(
    x
)
((((x) >> 16U) & 0xfff0U) == 0xfff0U)

Test whether a tag ID falls into the reserved range (0xFFF0..0xFFFF).

define IS_ERROR

cpp
#define IS_ERROR(
    x
)
(((x) & TAG_ERROR) == TAG_ERROR)

Test whether a tag has the TAG_ERROR flag set.

define IS_VOLATILE

cpp
#define IS_VOLATILE(
    x
)
(((x) & TAG_VOLATILE) == TAG_VOLATILE)

Test whether a tag has the TAG_VOLATILE flag set.

define IS_ARRAY

cpp
#define IS_ARRAY(
    x
)
(((x) & TAG_ARRAY) == TAG_ARRAY)

Test whether a tag has the TAG_ARRAY flag set.

define IS_STREAM

cpp
#define IS_STREAM(
    x
)
((((x) & 0x81U) == 0x81U) || (((x) & 0x82U) == 0x82U))

Test whether a tag type indicates a stream property (string or binary stream).

define TT_INT32

cpp
#define TT_INT32 (0x1U)

Type code: signed 32-bit integer.

define TT_INT16

cpp
#define TT_INT16 (0x2U)

Type code: signed 16-bit integer.

define TT_INT8

cpp
#define TT_INT8 (0x3U)

Type code: signed 8-bit integer.

define TT_UINT32

cpp
#define TT_UINT32 (0x4U)

Type code: unsigned 32-bit integer.

define TT_UINT16

cpp
#define TT_UINT16 (0x5U)

Type code: unsigned 16-bit integer.

define TT_UINT8

cpp
#define TT_UINT8 (0x6U)

Type code: unsigned 8-bit integer.

define TT_BOOL

cpp
#define TT_BOOL (0x7U)

Type code: boolean.

define TT_UUID

cpp
#define TT_UUID (0x8U)

Type code: 128-bit UUID.

define TT_FLOAT

cpp
#define TT_FLOAT (0x9U)

Type code: single-precision floating point.

define TT_DATETIME

cpp
#define TT_DATETIME (0xAU)

Type code: date/time as Unix timestamp (uint32_t).

define TT_UINT64

cpp
#define TT_UINT64 (0xBU)

Type code: unsigned 64-bit integer.

define TT_BIN

cpp
#define TT_BIN (0xCU)

Type code: binary blob (length + pointer).

define TT_STRING

cpp
#define TT_STRING (0xEU)

Type code: UTF-8 string (std::string).

define TT_INT64

cpp
#define TT_INT64 (0xFU)

Type code: signed 64-bit integer.

define TT_OBJECT

cpp
#define TT_OBJECT (0x80U)

Type code: embedded object reference.

define TT_STRING_STREAM

cpp
#define TT_STRING_STREAM (0x81U)

Type code: UTF-8 string (std::string).

define TT_BIN_STREAM

cpp
#define TT_BIN_STREAM (0x82U)

Type code: binary blob (length + pointer).

define TT_TABLE

cpp
#define TT_TABLE (0x83U)

Type code: embedded table.

define ERROR_VAL

cpp
#define ERROR_VAL(
    x
)
(((x).tag() & TAG_ERROR) == TAG_ERROR)

Test whether a Property instance holds an error value.

Source code

cpp
/*******************************************************************************
 * @file      FlakeTypes.h
 * @brief     Core data types for the Flake property system.
 * @details   This header defines the full type hierarchy used to represent
 *            and transport typed property values across the Flake bus:
 *
 *            - **Tag macros** – encode property ID, primitive type, and
 *              attribute flags (read-only, volatile, array, meta, …) into a
 *              single @c uint32_t.
 *            - **Primitive type codes** (@c TT_INT32, @c TT_STRING, …) and
 *              their corresponding C++ typedefs (@c tt_int32_t, @c tt_str_t, …).
 *            - **UniqueId** – 128-bit UUID value type.
 *            - **binary_t / array_t** – lightweight wrappers for raw byte
 *              blobs and typed arrays.
 *            - **Property** – a single tagged value with serialization support.
 *            - **PropArray** – an ordered, serializable collection of
 *              Property objects (the universal parameter / payload type in
 *              the Flake protocol).
 *            - **PropMake\*** convenience factories for quick Property
 *              construction.
 *            - **MessageType** constants used by the protocol layer.
 *
 * @license   This file is part of the ImagineOn Flake software package
 *            licensed under the ImagineOn software-licensing terms available
 *            under https://www.imagineon.de/de/info/licensing-terms
 * @copyright Copyright (c) 2025 ImagineOn GmbH. www.imagineon.de.
 ******************************************************************************/

#ifndef FLAKE_TYPES_H_
#define FLAKE_TYPES_H_

#include <string>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <optional>
#include <variant>
#include <float.h>
#include <math.h>
#include <util.h>


#ifdef _WIN32
#include <stdint.h>
#endif

#if __WORDSIZE == 64
#define UINT8_FMT "u"
#define UINT16_FMT "u"
#define UINT32_FMT "u"
#define INT8_FMT "d"
#define INT16_FMT "d"
#define INT32_FMT "d"
#define UINT32_HEX_FMT "X"
#else
#define UINT8_FMT "u"
#define UINT16_FMT "u"
#define UINT32_FMT "u"
#define INT8_FMT "d"
#define INT16_FMT "d"
#define INT32_FMT "d"
#define UINT32_HEX_FMT "lX"
#endif

#include "FlakeConst.h"

#define DEALLOC_SELF_FLAG   (1UL)
#define DONT_SERIALIZE_FLAG (2UL)
#define DONT_COPY_FLAG      (4UL)
#define DONT_CACHE_FLAG     (8UL)

#define TAG_TYPE(x)         ((x) & 0x7FFU)
#define TAG_ID(x)           ((x) >> 16)
#define TAG_READONLY        (0x1000UL)
#define TAG_ACTIONABLE      (0x2000UL)
#define TAG_VOLATILE        (0x4000UL)
#define TAG_NULL            (0x8000UL)
#define TAG_ERROR           (0x100UL)
#define TAG_ARRAY           (0x040UL)
#define TAG_META            (0x800UL)
#define IS_ACTIONABLE(x)    (((x) & TAG_ACTIONABLE) == TAG_ACTIONABLE)
#define IS_READONLY(x)      (((x) & TAG_READONLY) == TAG_READONLY)
#define IS_META(x)          (((x) & TAG_META) == TAG_META)
#define IS_RESERVED(x)      ((((x) >> 16U) & 0xfff0U) == 0xfff0U)
#define IS_ERROR(x)         (((x) & TAG_ERROR) == TAG_ERROR)
#define IS_VOLATILE(x)      (((x) & TAG_VOLATILE) == TAG_VOLATILE)
#define IS_ARRAY(x)         (((x) & TAG_ARRAY) == TAG_ARRAY)
#define IS_STREAM(x)        ((((x) & 0x81U) == 0x81U) || (((x) & 0x82U) == 0x82U))
#define TT_INT32            (0x1U)
#define TT_INT16            (0x2U)
#define TT_INT8             (0x3U)
#define TT_UINT32           (0x4U)
#define TT_UINT16           (0x5U)
#define TT_UINT8            (0x6U)
#define TT_BOOL             (0x7U)
#define TT_UUID             (0x8U)
#define TT_FLOAT            (0x9U)
#define TT_DATETIME         (0xAU)
#define TT_UINT64           (0xBU)
#define TT_BIN              (0xCU)
#define TT_STRING           (0xEU)
#define TT_INT64            (0xFU)
#define TT_OBJECT           (0x80U)
#define TT_STRING_STREAM    (0x81U)
#define TT_BIN_STREAM       (0x82U)
#define TT_TABLE            (0x83U)
#define ERROR_VAL(x)        (((x).tag() & TAG_ERROR) == TAG_ERROR)

#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif

namespace flake
{
    template <typename T>
    constexpr unsigned int UNSIGNED(T v) noexcept
    {
        return static_cast<unsigned int>(v);
    }

    template <typename T>
    constexpr int SIGNED(T v) noexcept
    {
        return static_cast<int>(v);
    }

    template <typename T>
    constexpr std::uint32_t U32(T v) noexcept
    {
        return static_cast<std::uint32_t>(v);
    }

    template <typename T>
    constexpr std::int32_t I32(T v) noexcept
    {
        return static_cast<std::int32_t>(v);
    }

    template <typename T>
    constexpr std::uint16_t U16(T v) noexcept
    {
        return static_cast<std::uint16_t>(v);
    }

    template <typename T>
    constexpr std::int16_t I16(T v) noexcept
    {
        return static_cast<std::int16_t>(v);
    }

    template <typename T>
    constexpr std::uint8_t U8(T v) noexcept
    {
        return static_cast<std::uint8_t>(v);
    }

    template <typename T>
    constexpr std::int8_t I8(T v) noexcept
    {
        return static_cast<std::int8_t>(v);
    }

    inline bool tokens_match(unsigned a, unsigned b)
    {
        return a == b;
    }

    inline bool addresses_match(unsigned a, unsigned b)
    {
        return a == b;
    }

    inline bool safeWrite(uint8_t* buf,
                          uint16_t bufSize,
                          uint16_t& offset,
                          const uint8_t* src,
                          uint16_t len,
                          uint16_t* outConsumed = nullptr)
    {
        bool result = false;

        if (nullptr != src && U32(offset) + U32(len) <= bufSize)
        {
            memcpy(&buf[offset], src, len);
            offset += U32(len);
            if (nullptr != outConsumed)
            {
                *outConsumed = len;
            }
            result = true;
        }
        return result;
    }

    inline bool safeRead(const uint8_t* buf,
                         uint16_t bufSize,
                         uint16_t& offset,
                         uint8_t* dst,
                         uint16_t len,
                         uint16_t* outConsumed = nullptr)
    {
        bool result = false;
        if (U32(offset) + U32(len) <= bufSize)
        {
            memcpy(dst, &buf[offset], len);
            offset += U32(len);
            if (nullptr != outConsumed)
            {
                *outConsumed = len;
            }
            result = true;
        }
        return result;
    }

    struct Serializable
    {
        Serializable(const Serializable&) = delete;
        Serializable& operator=(const Serializable&) = delete;


    protected:
        Serializable() = default;
        ~Serializable() = default;
        Serializable(Serializable&&) = default;
        Serializable& operator=(Serializable&&) = default;

    public:
        virtual bool
        serialize(uint16_t* outLen, uint8_t** outBuf) = 0;
        virtual bool
        deserialize(uint16_t len, uint8_t* buf) = 0;
    };

    class UniqueId
    {
    public:
        static constexpr std::size_t SIZE = 16U;

        ~UniqueId() = default;

        constexpr UniqueId& operator=(UniqueId&&) = default;
        constexpr UniqueId(UniqueId&&) = default;

        constexpr UniqueId() : data() {};
        constexpr UniqueId(const UniqueId& other) : data()
        {
            *this = other;
        }

        void
        set_nil()
        {
            for (unsigned i = 0U; i < 16U; i++)
            {
                data[i] = U8(0U);
            }
        }
#if FLAKE_DEBUG_LOGGING
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
        char*
        toString() const
        {
            char* out = new char[37];

            (void)snprintf(
                out, 37,
                "%02x%02x%02x%02x-"
                "%02x%02x-"
                "%02x%02x-"
                "%02x%02x-"
                "%02x%02x%02x%02x%02x%02x",
                data[0], data[1], data[2], data[3], data[4], data[5], data[6],
                data[7], data[8], data[9], data[10], data[11], data[12], data[13],
                data[14], data[15]);

            return out;
        }
#pragma GCC diagnostic pop
#endif

        void initFromString(const char* in)
        {
            if ((utils::strlen(in, 37U) == 36U) && ((in[8] == '-') ||
                (in[13] == '-') || (in[18] == '-') || (in[23] == '-')))
            {
                for (unsigned i = 0U; i < UniqueId::SIZE; i++)
                {
                    data[i] = U8(0U);
                }

                unsigned index = 0U;
                unsigned i = 0U;
                while (index < 36U)
                {
                    char c = in[index];
                    int value = 0;
                    if (c >= '0' && c <= '9')
                    {
                        value = (c - '0');
                    }
                    else if (c >= 'A' && c <= 'F')
                    {
                        value = (10 + (c - 'A'));
                    }
                    else if (c >= 'a' && c <= 'f')
                    {
                        value = (10 + (c - 'a'));
                    }
                    else
                    {
                        index++;
                        continue;
                    }
                    unsigned j = i / 2U;
                    data[j] += value << (((i + 1U) % 2U) * 4U);
                    ++i;
                    ++index;
                }
            }
        }

        bool
        is_nil() const
        {
            bool result = true;

            for (unsigned char i : data)
            {
                if (i != 0U)
                {
                    result = false;
                }
            }
            return result;
        }

        constexpr UniqueId&
        operator=(UniqueId const& rhs)
        {
            if (this != &rhs)
            {
                memcpy(&data[0], &rhs.data[0], UniqueId::SIZE * sizeof(uint8_t));
            }
            return *this;
        }

        void setData(uint8_t data_[UniqueId::SIZE])
        {
            memcpy(this->data, data_, UniqueId::SIZE * sizeof(uint8_t));
        }

        uint8_t* getData()
        {
            return data;
        }

    private:
        uint8_t data[SIZE];
    };

    inline bool operator==(UniqueId& lhs, UniqueId& rhs)
    {
        bool equal = true;
        std::size_t i = 0U;

        while ((i < UniqueId::SIZE) && equal)
        {
            unsigned a = U32(lhs.getData()[i]);
            unsigned b = U32(rhs.getData()[i]);
            if (a != b)
            {
                equal = false;
            }
            else
            {
                ++i;
            }
        }

        return equal;
    }

    inline bool operator!=(UniqueId& lhs, UniqueId& rhs)
    {
        return !(lhs == rhs);
    }

    inline bool operator<(UniqueId& lhs, UniqueId& rhs)
    {
        bool less = false;
        std::size_t i = 0U;

        while ((i < UniqueId::SIZE) && (!less))
        {
            unsigned a = U32(lhs.getData()[i]);
            unsigned b = U32(rhs.getData()[i]);
            if (a < b)
            {
                less = true;
            }
            else if (a > b)
            {
                break;
            }
            else
            {
                ++i;
            }
        }

        return less;
    }

    typedef UniqueId uniqueId_t;

    typedef uint16_t addr_t;

    typedef struct binary_t
    {
        uint16_t cb;
        uint8_t* lpb;

        ~binary_t() = default;

        binary_t() { cb = 0; lpb = nullptr; }

        binary_t(const binary_t& other)
        {
            cb = other.cb;
            lpb = other.lpb;
        }

        constexpr binary_t(uint16_t cb, uint8_t* lpb) : cb(cb), lpb(lpb) {}

        binary_t(binary_t&& other)
        {
            cb = other.cb;
            lpb = other.lpb;
        };

        binary_t& operator=(binary_t&& other) {
            cb = other.cb;
            lpb = other.lpb;
            return *this;
        }

        binary_t& operator=(const binary_t& other) {
            if (this == &other) return *this;
            cb = other.cb;
            lpb = other.lpb;
            return *this;
        }
    } binary_t;

    typedef int32_t tt_int32_t;
    typedef int64_t tt_int64_t;
    typedef int16_t tt_int16_t;
    typedef int8_t tt_int8_t;
    typedef uint32_t tt_uint32_t;
    typedef uint64_t tt_uint64_t;
    typedef uint16_t tt_uint16_t;
    typedef uint8_t tt_uint8_t;
    typedef bool tt_bool_t;
    typedef binary_t tt_bin_t;
    typedef uniqueId_t tt_uuid_t;
    typedef std::string tt_str_t;
    typedef float tt_float_t;

    typedef struct array_t
    {
        uint16_t totalSize;
        uint8_t* lpValues;
        uint16_t type;


        ~array_t() = default;

        array_t() { totalSize = 0; lpValues = nullptr; type = 0; }

        array_t(const array_t& other)
        {
            totalSize = other.totalSize;
            type = other.type;
            lpValues = other.lpValues;
        }

        constexpr array_t(uint16_t totalSize_, uint8_t* lpValues, uint16_t type) : totalSize(totalSize_), lpValues(lpValues), type(type) {}

        array_t(array_t&& other)
        {
            totalSize = other.totalSize;
            lpValues = other.lpValues;
            type = other.type;
        };

        array_t& operator=(array_t&& other) {
            totalSize = other.totalSize;
            lpValues = other.lpValues;
            type = other.type;
            return *this;
        }

        array_t& operator=(const array_t& other) {
            if (this == &other) return *this;
            totalSize = other.totalSize;
            lpValues = other.lpValues;
            type = other.type;
            return *this;
        }
    } array_t;

    typedef array_t tt_array_t;

    class Array {
        Array() = delete;
        Array(const Array&) = delete;
        Array& operator=(const Array&) = delete;
        Array(Array&&) = delete;
        Array& operator=(Array&&) = delete;

    public:
        explicit Array(array_t* arr) : arr_(arr) {}
        virtual ~Array() = default;

        static uint8_t sizeForType(uint8_t t)
        {
            switch (t) {
            case TT_INT8:
            case TT_UINT8:
            case TT_BOOL:    return 1;
            case TT_INT16:
            case TT_UINT16:  return 2;
            case TT_INT32:
            case TT_UINT32:
            case TT_FLOAT:   return 4;
            case TT_UUID:    return 16;
            default:         return 0;
            }
        }
        uint8_t elementSize() const {
            return sizeForType(arr_->type);
        }

        uint16_t count() const {
            uint8_t es = elementSize();
            return es ? arr_->totalSize / es : 0;
        }

        template<typename T>
        bool set(uint16_t index, const T& value) {
            if (!arr_ || sizeof(T) != elementSize() || index >= count()) return false;
            memcpy(&arr_->lpValues[index * sizeof(T)], &value, sizeof(T));
            return true;
        }

        template<typename T>
        bool get(uint16_t index, T& out) const {
            if (!arr_ || sizeof(T) != elementSize() || index >= count()) return false;
            memcpy(&out, &arr_->lpValues[index * sizeof(T)], sizeof(T));
            return true;
        }

        bool resize(uint16_t newCount) {
            uint8_t es = elementSize();
            if (!es) return false;

            size_t old_size = arr_->totalSize;
            size_t new_size = static_cast<size_t>(newCount) * es;
            uint8_t* new_buf = new uint8_t[new_size];
            if (!new_buf) return false;

            std::size_t copy_size = (old_size < new_size) ? old_size : new_size;
            std::memcpy(new_buf, arr_->lpValues, copy_size);

            delete[] arr_->lpValues;

            arr_->lpValues = new_buf;
            arr_->totalSize = newCount * es;
            return true;
        }

        void init(uint16_t type, uint16_t count, uint8_t* buf) {
            arr_->type = type;
            arr_->lpValues = buf;
            arr_->totalSize = count * elementSize();
        }

        uint8_t* raw() const { return arr_ ? arr_->lpValues : nullptr; }

    private:
        array_t* arr_;
    };




    template<typename T> struct tt_of;

    template<> struct tt_of<int8_t>   { static constexpr uint32_t value = TT_INT8;  };
    template<> struct tt_of<int16_t>  { static constexpr uint32_t value = TT_INT16; };
    template<> struct tt_of<int32_t>  { static constexpr uint32_t value = TT_INT32; };
    template<> struct tt_of<int64_t>  { static constexpr uint32_t value = TT_INT64; };
    template<> struct tt_of<uint8_t>  { static constexpr uint32_t value = TT_UINT8; };
    template<> struct tt_of<uint16_t> { static constexpr uint32_t value = TT_UINT16;};
    template<> struct tt_of<uint32_t> { static constexpr uint32_t value = TT_UINT32;};
    template<> struct tt_of<uint64_t> { static constexpr uint32_t value = TT_UINT64;};
    template<> struct tt_of<float>    { static constexpr uint32_t value = TT_FLOAT; };
    template<> struct tt_of<bool>     { static constexpr uint32_t value = TT_BOOL;  };
    template<> struct tt_of<char*>    { static constexpr uint32_t value = TT_STRING;};
    template<> struct tt_of<tt_str_t> { static constexpr uint32_t value = TT_STRING;};
    template<> struct tt_of<tt_bin_t> { static constexpr uint32_t value = TT_BIN;   };
    template<> struct tt_of<tt_uuid_t> { static constexpr uint32_t value = TT_UUID; };
    template<> struct tt_of<tt_array_t> { static constexpr uint32_t value = TAG_ARRAY; };

    template<typename T> struct tt_of; // absichtlich keine Default-Definition

    template<typename T>
    struct tt_allowed {
        static constexpr bool contains(uint32_t tt) {
            return tt == tt_of<std::decay_t<T>>::value;
        }
    };

    template<>
    struct tt_allowed<uint32_t> {
        static constexpr bool contains(uint32_t tt) {
            return tt == TT_UINT32 || tt == TT_DATETIME;
        }
    };

    template<>
    struct tt_allowed<tt_bin_t> {
        static constexpr bool contains(uint32_t tt) {
            return tt == TT_BIN || tt == TT_BIN_STREAM;
        }
    };

    template<>
   struct tt_allowed<tt_str_t> {
        static constexpr bool contains(uint32_t tt) {
            return tt == TT_STRING || tt == TT_STRING_STREAM;
        }
    };

    template<>
  struct tt_allowed<tt_array_t> {
        static constexpr bool contains(uint32_t tt) {
            return (tt & TAG_ARRAY) == TAG_ARRAY;
        }
    };

    template<class T>
    struct unwrap_optional { using type = T; };

    template<class U>
    struct unwrap_optional<std::optional<U>> { using type = U; };

    template<class T>
    using unwrap_optional_t = typename unwrap_optional<std::decay_t<T>>::type;

    template<typename T, typename = void>
    struct has_tt_of : std::false_type {};

    template<typename T>
    struct has_tt_of<T, std::void_t<decltype(tt_of<std::decay_t<T>>::value)>> : std::true_type {};

    template<typename T>
    constexpr bool type_matches_tag(uint32_t tag)
    {
        using U = unwrap_optional_t<T>;

        if constexpr (!has_tt_of<U>::value)
            return false;
        else
            return tt_allowed<std::decay_t<U>>::contains(TAG_TYPE(tag));
    }

    namespace ReservedProperties
    {
        constexpr uint32_t PROP_MAPPINGS = U32(0xFFFFUL << 16U | (U8(TT_BIN) | TAG_ARRAY | TAG_READONLY));
        constexpr uint32_t PARENT_OBJECT = U32(0xFFFEUL << 16U | (U8(TT_UINT16) | TAG_READONLY));
        constexpr uint32_t OBJECT_UUID = U32(0xFFFDUL << 16U | (U8(TT_UUID) | TAG_READONLY));
    }

    namespace MetaProperties
    {
        constexpr uint32_t TIMESTAMP = U32(0x9UL << 16U | U8(TT_DATETIME) | TAG_META);
    }

    namespace ParameterProperties {
        constexpr uint32_t OBJECT_TYPE = U32(0x1UL << 16U | U8(TT_UUID) | TAG_READONLY );
        constexpr uint32_t OBJECT_ADDR = U32(0x2UL << 16U | U8(TT_UINT16) | TAG_READONLY );
        constexpr uint32_t BROADCAST_ADDR = U32(0x3UL << 16U | U8(TT_UINT16) | TAG_READONLY );
        constexpr uint32_t MESSAGE_NAME = U32(0x4UL << 16U | U8(TT_STRING) );
        constexpr uint32_t CHILDREN_TABLE = U32(0x5UL << 16U | U8(TT_BIN) );
        constexpr uint32_t COLUMN_SET = U32(0x6UL << 16U | U8(TT_UINT16) | TAG_ARRAY );
        constexpr uint32_t LAST_MODIFICATION_TIME = U32(0x7UL << 16U | U8(TT_DATETIME) );
        constexpr uint32_t CREATION_TIME = U32(0x8UL << 16U | U8(TT_DATETIME) );
        constexpr uint32_t OBJECT_TABLE = U32(0xDUL << 16U | U8(TT_BIN) );
        constexpr uint32_t PROP_TAG = U32(0xFUL << 16U | U8(TT_UINT32) );
        constexpr uint32_t PROP_NAME = U32(0x10UL << 16U | U8(TT_STRING) );
        constexpr uint32_t PROP_TYPE = U32(0x11UL << 16U | U8(TT_UINT16) );
        constexpr uint32_t REQUIRES_AUTH = U32(0x18UL << 16U | U8(U8(TT_BOOL)) );
        constexpr uint32_t STREAM_DATA_STR = U32(0x20UL << 16U | U8(TT_STRING) );
        constexpr uint32_t STREAM_DATA_BIN = U32(0x21UL << 16U | U8(TT_BIN) );
        constexpr uint32_t STREAM_LEN = U32(0x22UL << 16U | U8(TT_UINT16) );
        constexpr uint32_t STREAM_POS = U32(0x23UL << 16U | U8(TT_UINT32) );
        constexpr uint32_t STREAM_FLAGS = U32(0x24UL << 16U | U8(TT_UINT8) );
        constexpr uint32_t AUTH_TYPE = U32(0x31UL << 16U | U8(TT_UINT8) | TAG_READONLY );
        constexpr uint32_t AUTH_USER = U32(0x32UL << 16U | U8(TT_STRING) | TAG_READONLY );
        constexpr uint32_t AUTH_PASS = U32(0x33UL << 16U | U8(TT_STRING) | TAG_READONLY );
        constexpr uint32_t SIGN_ALGO = U32(0x34UL << 16U | U8(TT_STRING) | TAG_READONLY );
        constexpr uint32_t SIGN_HASH = U32(0x35UL << 16U | U8(TT_BIN) | TAG_READONLY );
        constexpr uint32_t SIGNATURE = U32(0x36UL << 16U | U8(TT_BIN) | TAG_READONLY );
        constexpr uint32_t OBJECT_TYPE_NAME = U32(0x101UL << 16U | U8(TT_STRING) | TAG_READONLY );
    }

    enum class flakeAuthType : uint_least8_t
    {
        atNone = U8(0),
        atSignature = U8(1),
        atInteractive = U8(2)
    };

    class TagArray
    {
        TagArray(TagArray&&) = delete;
        TagArray& operator=(TagArray&&) = delete;

        TagArray(const TagArray& ta)
        {
            numTags = ta.numTags;
            if (ta.numTags > 0U)
            {
                tags = new uint32_t[numTags];
                for (uint32_t i = 0U; i < ta.numTags; i++)
                {
                    tags[i] = ta.tags[i];
                }
            }
        }

        TagArray()
        {
            tags = nullptr;
            numTags = 0U;
        }

        ~TagArray()
        {
            if (numTags > 0U)
            {
                delete[] tags;
            }
        }

        TagArray&
        operator=(const TagArray& other)
        {
            if (this != &other)
            {
                numTags = other.numTags;
                if (other.numTags > 0U)
                {
                    tags = new uint32_t[numTags];
                    for (uint32_t i = 0U; i < other.numTags; i++)
                    {
                        tags[i] = other.tags[i];
                    }
                }
            }
            return *this;
        }

    private:
        mutable uint32_t numTags;
        mutable uint32_t* tags;
    };

    typedef std
    ::optional<std::variant<tt_int8_t,
                            tt_int16_t,
                            tt_int32_t,
                            tt_int64_t,
                            tt_uint8_t,
                            tt_uint16_t,
                            tt_uint32_t,
                            tt_uint64_t,
                            tt_bool_t,
                            tt_float_t,
                            tt_uuid_t,
                            tt_bin_t,
                            tt_str_t,
                            tt_array_t>> PropValue;

    class Property;
    class PropArray;

    template <uint32_t PropType>
    struct PropTypeFromTag;

    template <uint32_t PropTag>
    using PropType = typename PropTypeFromTag<TAG_TYPE(PropTag)>::type;


    template <uint32_t PropTag>
    using PropType = typename PropTypeFromTag<TAG_TYPE(PropTag)>::type;

    template <>
    struct PropTypeFromTag<TT_INT32>
    {
        using type = tt_int32_t;
        static constexpr tt_int32_t default_value = 0;
    };

    template <>
    struct PropTypeFromTag<TT_INT16>
    {
        using type = tt_int16_t;
        static constexpr tt_int16_t default_value = 0;
    };

    template <>
    struct PropTypeFromTag<TT_INT8>
    {
        using type = tt_int8_t;
        static constexpr tt_int8_t default_value = I8(0);
    };

    template <>
    struct PropTypeFromTag<TT_UINT32>
    {
        using type = tt_uint32_t;
        static constexpr tt_uint32_t default_value = 0U;
    };

    template <>
    struct PropTypeFromTag<TT_INT64>
    {
        using type = tt_int64_t;
        static constexpr tt_uint64_t default_value = 0U;
    };

    template <>
    struct PropTypeFromTag<TT_UINT64>
    {
        using type = tt_uint64_t;
        static constexpr tt_uint64_t default_value = 0U;
    };

    template <>
    struct PropTypeFromTag<TT_UINT16>
    {
        using type = tt_uint16_t;
        static constexpr tt_uint16_t default_value = 0U;
    };

    template <>
    struct PropTypeFromTag<TT_UINT8>
    {
        using type = tt_uint8_t;
        static constexpr tt_uint8_t default_value = U8(0U);
    };

    template <>
    struct PropTypeFromTag<TT_BOOL>
    {
        using type = tt_bool_t;
        static constexpr tt_bool_t default_value = false;
    };

    template <>
    struct PropTypeFromTag<TT_BIN>
    {
        using type = tt_bin_t;
        static constexpr tt_bin_t default_value = {0U, nullptr};
    };

    template <>
    struct PropTypeFromTag<TT_FLOAT>
    {
        using type = tt_float_t;
        static constexpr tt_float_t default_value = 0.0F;
    };

    template <>
    struct PropTypeFromTag<TT_STRING>
    {
        using type = tt_str_t;
        static constexpr std::string_view default_value = std::string_view();
    };

    template <>
    struct PropTypeFromTag<TT_DATETIME>
    {
        using type = tt_uint32_t;
        static constexpr tt_uint32_t default_value = 0U;
    };

    template <>
    struct PropTypeFromTag<TT_UUID>
    {
        using type = tt_uuid_t;
        static constexpr tt_uuid_t default_value = tt_uuid_t();
    };

    template <>
    struct PropTypeFromTag<TT_STRING_STREAM>
    {
        using type = tt_str_t;
        static constexpr std::string_view default_value = std::string_view();
    };

    template <>
    struct PropTypeFromTag<TT_BIN_STREAM>
    {
        using type = tt_bin_t;
        static constexpr tt_bin_t default_value = {0U, nullptr};
    };

    template <typename T>
    struct is_std_optional : std::false_type
    {
    };

    template <typename T>
    struct is_std_optional<std::optional<T>> : std::true_type
    {
    };

    template <typename T>
    constexpr bool is_std_optional_v = is_std_optional<T>::value;


    class Property
    {

        mutable uint32_t tag_;
        uint8_t flags;
        PropValue data_;

    public:
        Property(Property&&) = default;
        Property& operator=(Property&&) = default;


        uint32_t tag() const
        {
            return tag_;
        }

        bool isMeta() const
        {
            return (tag_ & TAG_META) == TAG_META;
        }

        void setNull()
        {
            tag_ |= TAG_NULL;
            data_ = std::nullopt;
        }

        void setError(tt_int8_t err)
        {
            tag_ &= ~TAG_NULL;
            tag_ |= TAG_ERROR;
            data_ = err;
        }

        PropValue data() const
        {
            return data_;
        }

        template <typename T>
        void setData(T data)
        {
            if constexpr (
                std::is_same_v<T, std::nullptr_t> ||
                (std::is_pointer_v<T>) ||
                (is_std_optional_v<T>)
            )
            {
                if (!data)
                {
                    data_ = std::nullopt;
                    return;
                }
            }

            if (!type_matches_tag<T>(tag_) && !is_std_optional_v<T>)
            {
                data_ = std::nullopt;
            } else
            {
                tag_ &= ~TAG_NULL;
                data_ = data;
            }
        }

        template <typename T>
        std::optional<T> value() const
        {
            std::optional<T> result = std::nullopt;
            if (data_ != std::nullopt && std::holds_alternative<T>(*data_))
            {
                result = std::get<T>(*data_);
            }
            return result;
        }

        explicit Property(const uint32_t _tag)
            : tag_(_tag)
        {
            flags = U8(0U);
            tag_ |= TAG_NULL;
        }

        Property()
            : tag_(0U)
        {
            flags = U8(0U);
            tag_ |= TAG_NULL;
        }

        Property(const Property& v)
        {
            tag_ = 0U;
            flags = U8(0U);
            copyFrom(v);
        }

        void
        makeReadOnly() const
        {
            tag_ |= TAG_READONLY;
        }

        ~Property()
        {
            if ((flags & DEALLOC_SELF_FLAG) == DEALLOC_SELF_FLAG)
            {
                if ((tag_ & TAG_ARRAY) == TAG_ARRAY)
                {
                    if (auto arr = value<tt_array_t>())
                    {
                        if ((arr->totalSize) > 0U)
                        {
                            delete [] (arr->lpValues);
                        }
                    }
                }
                else
                {
                    switch (TAG_TYPE(tag_))
                    {
                    case TT_BIN:
                        if (auto bin = value<tt_bin_t>())
                        {
                            if ((bin->lpb != nullptr) && (bin->cb > 0U))
                            {
                                delete [] (bin->lpb);
                            }
                        }
                        break;
                    default: break;
                    }
                }
            }
        }

        void
        setDeallocSelf()
        {
            flags |= DEALLOC_SELF_FLAG;
        }

        Property&
        operator=(const Property& other)
        {
            if (&other != this)
            {
                copyFrom(other);
            }
            return *this;
        }

        void
        copyFrom(const Property& other)
        {
            tag_ = other.tag_;
            flags = other.flags;


            if ((other.tag_ & TAG_ARRAY) == TAG_ARRAY)
            {
                if (std::nullopt == value<tt_array_t>())
                {
                    data_ = tt_array_t();
                }

                auto a1 = value<tt_array_t>();
                auto a2 = other.value<tt_array_t>();

                if (a1 && a2)
                {
                    if (a1->lpValues != nullptr && ((flags & DEALLOC_SELF_FLAG) == DEALLOC_SELF_FLAG))
                    {
                        delete [] (a1->lpValues);
                    }


                    a1->totalSize = a2->totalSize;
                    a1->type = a2->type;

                    if (a1->totalSize != 0U)
                    {
                        setDeallocSelf();
                        a1->lpValues = new uint8_t[U32(a1->totalSize)];
                        memcpy(a1->lpValues, a2->lpValues, U32(a1->totalSize));
                    }

                    data_ = *a1;
                }
                else
                {
                    flags = U8(0U);
                }
            }
            else
            {
                switch (TAG_TYPE(other.tag_))
                {
                case TT_BIN:
                    {
                        if (std::nullopt == value<tt_bin_t>())
                        {
                            data_ = tt_bin_t();
                        }

                        auto bin1 = value<tt_bin_t>();
                        auto bin2 = other.value<tt_bin_t>();

                        if (bin1 && bin2)
                        {
                            if (bin1->lpb != nullptr && ((flags & DEALLOC_SELF_FLAG) == DEALLOC_SELF_FLAG))
                            {
                                delete [] (bin1->lpb);
                                bin1->lpb = nullptr;
                            }

                            bin1->cb = bin2->cb;

                            if (bin1->cb != 0U)
                            {
                                if ((other.flags & DONT_COPY_FLAG) == DONT_COPY_FLAG)
                                {
                                    bin1->lpb = bin2->lpb;
                                }
                                else
                                {
                                    setDeallocSelf();
                                    bin1->lpb = new uint8_t[bin1->cb];
                                    memcpy(bin1->lpb, bin2->lpb, bin1->cb);

                                }
                            }

                            data_ = *bin1;
                        }
                        else
                        {
                            flags = U8(0U);
                        }
                    }
                    break;
                default:
                    {
                        flags = U8(0U);
                        data_ = other.data_;
                    }
                    break;
                }
            }
        }

        bool serializeTo(uint8_t* buf,
                         uint16_t& offset,
                         uint16_t bufSize,
                         uint16_t* consumedBytes = nullptr) const
        {
            const uint16_t startOff = offset;
            uint16_t tmp{};
            uint16_t startOffset = offset;
            // 1) Header: Tag
            if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&tag_),
                           U16(sizeof(tag_)), &tmp))
            {
                offset = startOffset;
                return false;
            }

            // 2) Null oder Error
            if ((tag_ & TAG_NULL) == TAG_NULL)
            {
                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                return true;
            }
            if (ERROR_VAL(*this))
            {
                tt_int8_t e{};
                if (data_ != std::nullopt && std::holds_alternative<tt_int8_t>(*data_))
                {
                    e = std::get<tt_int8_t>(*data_);
                }
                if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&e),
                               U16(sizeof(e)), &tmp))
                {
                    offset = startOffset;
                    return false;
                }
                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                return true;
            }

            // 3) Array-Typen
            if (((tag_ & TAG_ARRAY) == TAG_ARRAY) && data_)
            {
                auto arr = std::get_if<tt_array_t>(&*data_);
                if (arr)
                {
                    const uint16_t headerSize = 2U;
                    uint16_t payloadLen =  arr->totalSize;
                    if (payloadLen > U32(UINT16_MAX) - headerSize)
                    {
                        offset = startOffset;
                        return false;
                    }

                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&payloadLen),
                                   U16(sizeof(payloadLen)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }

                    if (!safeWrite(buf, bufSize, offset, arr->lpValues, payloadLen, &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                return true;
            }

            // 4) Primitive & Co.
            switch (TAG_TYPE(tag_))
            {
            case TT_INT8:
                {
                    tt_int8_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_int8_t>(*data_))
                    {
                        v = std::get<tt_int8_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_INT16:
                {
                    tt_int16_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_int16_t>(*data_))
                    {
                        v = std::get<tt_int16_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_INT32:
                {
                    tt_int32_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_int32_t>(*data_))
                    {
                        v = std::get<tt_int32_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_UINT8:
                {
                    tt_uint8_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_uint8_t>(*data_))
                    {
                        v = std::get<tt_uint8_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_UINT16:
                {
                    tt_uint16_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_uint16_t>(*data_))
                    {
                        v = std::get<tt_uint16_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_UINT32:
            case TT_DATETIME: //NOLINT MISRA 5-4-4 fallthrough on purpose
                {
                    tt_uint32_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_uint32_t>(*data_))
                    {
                        v = std::get<tt_uint32_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_BOOL:
                {
                    tt_bool_t v = false;
                    if (data_ != std::nullopt && std::holds_alternative<tt_bool_t>(*data_))
                    {
                        v = std::get<tt_bool_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_FLOAT:
                {
                    tt_float_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_float_t>(*data_))
                    {
                        v = std::get<tt_float_t>(*data_);
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&v),
                                   U16(sizeof(v)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_UUID:
                {
                    tt_uuid_t v{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_uuid_t>(*data_))
                    {
                        v = std::get<tt_uuid_t>(*data_);
                    }

                    if (!safeWrite(buf, bufSize, offset, v.getData(),
                                   U16(sizeof(tt_uuid_t)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_STRING:
                {
                    tt_str_t s{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_str_t>(*data_))
                    {
                        s = std::get<tt_str_t>(*data_);
                    }
                    uint16_t len = U16(s.size());
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&len),
                                   U16(sizeof(len)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(s.data()),
                                   len, &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_BIN:
            case TT_TABLE: //NOLINT MISRA 5-4-4 fallthrough on purpose
                {
                    tt_bin_t b{};
                    if (data_ != std::nullopt && std::holds_alternative<tt_bin_t>(*data_))
                    {
                        b = std::get<tt_bin_t>(*data_);
                    }
                    uint16_t cb = b.cb;
                    if (!safeWrite(buf, bufSize, offset, reinterpret_cast<const uint8_t*>(&cb),
                                   U16(sizeof(cb)), &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                    if (cb > 0U && !safeWrite(buf, bufSize, offset, b.lpb, cb, &tmp))
                    {
                        offset = startOffset;
                        return false;
                    }
                }
                break;
            case TT_STRING_STREAM:
            case TT_BIN_STREAM: //NOLINT MISRA 5-4-4 fallthrough on purpose
            case TT_OBJECT: //NOLINT MISRA 5-4-4 fallthrough on purpose
            default: //NOLINT MISRA 5-4-4 fallthrough on purpose
                return false;
                break;
            }

            if (nullptr != consumedBytes)
            {
                *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
            }
            return true;
        }

        bool deserializeFrom(const uint8_t* buf,
                             uint16_t& offset,
                             uint16_t bufSize,
                             uint16_t* consumedBytes = nullptr)
        {
            const uint16_t startOff = offset;
            uint16_t tmp{};
            // 1. Read Tag
            uint16_t tagSize = U16(sizeof(tag_));
            if (!safeRead(buf, bufSize, offset,
                          reinterpret_cast<uint8_t*>(&tag_), tagSize, &tmp))
            {
                offset = startOff;
                return false;
            }

            // 2. Null-Tag?
            if ((tag_ & TAG_NULL) == TAG_NULL)
            {
                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                return true;
            }

            // 3. Error-Tag?
            if (ERROR_VAL(*this))
            {
                tt_int8_t e{};
                if (!safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&e),
                              U16(sizeof(e)), &tmp))
                {
                    offset = startOff;
                    return false;
                }
                data_ = e;
                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                return true;
            }

            // 4. Array-Typ
            if ((tag_ & TAG_ARRAY) == TAG_ARRAY)
            {
                tt_array_t arr{};
                uint16_t payloadLen{};

                if (!safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&payloadLen),
                              U16(sizeof(payloadLen)), &tmp))
                {
                    offset = startOff;
                    return false;
                }

                if (payloadLen > 0U && UNSIGNED(offset) + payloadLen <= bufSize)
                {
                    uint8_t* arr_buf = new uint8_t[U32(payloadLen)];

                    Array array(&arr);

                    array.init(tag_ & 0x1F, payloadLen / array.sizeForType(tag_ & 0x1F), arr_buf);

                    if (!safeRead(buf, bufSize, offset, arr.lpValues, payloadLen, &tmp))
                    {
                        delete [] (arr.lpValues);
                        offset = startOff;
                        return false;
                    }

                }

                data_ = arr;

                if (nullptr != consumedBytes)
                {
                    *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
                }
                setDeallocSelf();
                return true;
            }

            // 5. Primitive & Co.
            switch (TAG_TYPE(tag_))
            {
            case TT_INT8:
                {
                    tt_int8_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_INT16:
                {
                    tt_int16_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_INT32:
                {
                    tt_int32_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_UINT8:
                {
                    tt_uint8_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_UINT16:
                {
                    tt_uint16_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_UINT32:
            case TT_DATETIME: //NOLINT MISRA 5-4-4 fallthrough on purpose
                {
                    tt_uint32_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_BOOL:
                {
                    tt_bool_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_FLOAT:
                {
                    tt_float_t v{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&v),
                                 U16(sizeof(v)), &tmp))
                    {
                        data_ = v;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_UUID:
                {
                    tt_uuid_t u{};
                    if (safeRead(buf, bufSize, offset, u.getData(),
                                 U16(sizeof(u)), &tmp))
                    {
                        data_ = u;
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                }
                break;
            case TT_STRING:
                {
                    uint16_t len{};
                    if (!safeRead(buf, bufSize, offset,
                                  reinterpret_cast<uint8_t*>(&len),
                                  U16(sizeof(len)), &tmp))
                    {
                        offset = startOff;
                        return false;
                    }
                    if (len > 0U)
                    {
                        if (U32(offset) + len <= bufSize)
                        {
                            char* tmpBuf = new char[len + 1U];
                            if (!safeRead(buf, bufSize, offset,
                                          reinterpret_cast<uint8_t*>(tmpBuf), len, &tmp))
                            {
                                delete [] (tmpBuf);
                                offset = startOff;
                                return false;
                            }
                            tmpBuf[len] = '\0';
                            data_ = tt_str_t(tmpBuf, len);
                            delete [] (tmpBuf);
                        }
                        else
                        {
                            offset = startOff;
                            return false;
                        }
                    }
                    else
                    {
                        data_ = tt_str_t();
                    }
                }
                break;
            case TT_BIN:
            case TT_TABLE: //NOLINT MISRA 5-4-4 fallthrough on purpose
                {
                    tt_bin_t b{};
                    if (safeRead(buf, bufSize, offset, reinterpret_cast<uint8_t*>(&b.cb),
                                 U16(sizeof(b.cb))))
                    {
                        if (b.cb > 0U)
                        {
                            if (U32(offset) + b.cb <= bufSize)
                            {
                                b.lpb = new uint8_t[b.cb];
                                if (!safeRead(buf, bufSize, offset, b.lpb, b.cb))
                                {
                                    offset = startOff;
                                    return false;
                                }
                            }
                            else
                            {
                                offset = startOff;
                                return false;
                            }
                        }
                        else
                        {
                            data_ = tt_bin_t();
                        }
                    }
                    else
                    {
                        offset = startOff;
                        return false;
                    }
                    setDeallocSelf();
                    data_ = b;
                }
                break;
            case TT_STRING_STREAM:
            case TT_BIN_STREAM: //NOLINT MISRA 5-4-4 fallthrough on purpose
            case TT_OBJECT: //NOLINT MISRA 5-4-4 fallthrough on purpose
            default: //NOLINT MISRA 5-4-4 fallthrough on purpose
                offset = startOff;
                return false;
            }

            if (nullptr != consumedBytes)
            {
                *consumedBytes = utils::safe_sub<uint16_t>(offset, startOff);
            }
            return true;
        }

        bool
        isErrorValue() const
        {
            return (tag_ & TAG_ERROR) == TAG_ERROR;
        }

        uint16_t getSerializedSize() const
        {
            size_t result = sizeof(uint32_t);

            if ((tag_ & TAG_NULL) != TAG_NULL)
            {
                if (ERROR_VAL(*this))
                {
                    result = sizeof(uint32_t) + sizeof(tt_int8_t);
                }
                else if ((tag_ & TAG_ARRAY) == TAG_ARRAY)
                {
                    auto optArr = value<tt_array_t>();
                    if (optArr.has_value())
                    {
                        const tt_array_t& arr = *optArr;
                        result = sizeof(uint32_t) + sizeof(uint16_t) + (arr.totalSize);
                    }
                    else
                    {
                        // MISRA 6-4-2: switch preferred over else-if for known cases
                    }
                }
                else
                {
                    switch (TAG_TYPE(tag_))
                    {
                    case TT_FLOAT:
                        if (value<tt_float_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_float_t);
                        }
                        break;
                    case TT_UINT8:
                        if (value<tt_uint8_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uint8_t);
                        }
                        break;
                    case TT_UINT16:
                        if (value<tt_uint16_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uint16_t);
                        }
                        break;
                    case TT_UINT32:
                    case TT_DATETIME: //NOLINT MISRA 5-4-4 fallthrough on purpose
                        if (value<tt_uint32_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uint32_t);
                        }
                        break;
                    case TT_INT8:
                        if (value<tt_int8_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_int8_t);
                        }
                        break;
                    case TT_INT16:
                        if (value<tt_int16_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_int16_t);
                        }
                        break;
                    case TT_INT32:
                        if (value<tt_int32_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_int32_t);
                        }
                        break;
                    case TT_BOOL:
                        if (value<tt_bool_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_bool_t);
                        }
                        break;
                    case TT_UUID:
                        if (value<tt_uuid_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uuid_t);
                        }
                        break;
                    case TT_STRING:
                        if (value<tt_str_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uint16_t) + (sizeof(char) * value<tt_str_t>()->
                                length());
                        }
                        break;
                    case TT_STRING_STREAM:
                        break;
                    case TT_BIN:
                        if (value<tt_bin_t>())
                        {
                            result = sizeof(uint32_t) + sizeof(tt_uint16_t) + (sizeof(uint8_t) * value<tt_bin_t>()->cb);
                        }
                        break;
                    case TT_BIN_STREAM:
                        break;
                    default:
                        break;
                    }
                }
            }
            return U16(result);
        }

        friend bool operator==(const Property& lhs, const Property& rhs);
        friend bool operator!=(const Property& lhs, const Property& rhs);

#if FLAKE_DEBUG_LOGGING
        char*
        toString() const
        {
            char* _str = nullptr;
            char* tagStr = tagToString(tag_);

            if (IS_ERROR(tag_))
            {
                _str = new char[120];

                if (auto err = value<tt_int8_t>())
                {
                    (void)snprintf(_str, 120, "%30s: %s", tagStr, errorToString(
                                 (long)*err));
                }
                else
                {
                    (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                }
                delete[] tagStr;
                return _str;
            }

            if ((tag_ & TAG_NULL) == TAG_NULL)
            {
                _str = new char[60];
                (void)snprintf(_str, 60, "%30s: NULL", tagStr);
                delete[] tagStr;
                return _str;
            }

            if ((tag_ & TAG_ARRAY) == TAG_ARRAY)
            {
                _str = new char[120];
                if (auto arr = value<tt_array_t>())
                {
                    Array array(&*arr);
                    (void)snprintf(_str, 120, "%30s: Array (%d elements of size %d) ",
                             tagStr, array.count(), array.elementSize());
                }
                else
                {
                    (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                }
                delete[] tagStr;
                return _str;
            }

            switch (TAG_TYPE(tag_))
            {
            case TT_BOOL:
                {
                    auto val = value<tt_bool_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %s", tagStr, *val ? "true" : "false");
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_UINT8:
                {
                    auto val = value<tt_uint8_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" UINT8_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_UINT16:
                {
                    auto val = value<tt_uint16_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" UINT16_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_UINT32:
                {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
                    auto val = value<tt_uint32_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" UINT32_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
#pragma GCC diagnostic pop
                break;
            case TT_INT8:
                {
                    auto val = value<tt_int8_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" INT8_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_INT16:
                {
                    auto val = value<tt_int16_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" INT16_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_INT32:
                {
                    auto val = value<tt_int32_t>();
                    _str = new char[120];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" INT32_FMT, tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_DATETIME:
                {
                    auto val = value<tt_uint32_t>();
                    _str = new char[120];
                    if (val)
                    {
                        time_t t = (time_t)*val;
                        struct tm *tm = gmtime(&t);
                        (void)snprintf(_str, 120, "%30s: %04d-%02d-%02d %02d:%02d:%02d", tagStr,  tm->tm_year + 1900,
                                tm->tm_mon + 1,
                                tm->tm_mday,
                                tm->tm_hour,
                                tm->tm_min,
                                tm->tm_sec);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
#pragma GCC diagnostic pop
                }
                break;
            case TT_BIN:
                {
                    auto val = value<tt_bin_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %" UINT16_FMT " bytes", tagStr, val->cb);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;

            case TT_UUID:
                {
                    auto val = value<tt_uuid_t>();
                    _str = new char[120];
                    if (val)
                    {
                        char* s = val->toString();
                        (void)snprintf(_str, 120, "%30s: %s", tagStr, s);
                        delete[] s;
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_STRING:
                {
                    auto val = value<tt_str_t>();
                    if (val)
                    {
                        _str = new char[strlen(val->c_str()) + 40];
                        memset(_str, 0, strlen(val->c_str()) + 40);
                        (void)snprintf(_str, strlen(val->c_str()) + 40, "%30s: %s", tagStr, val->c_str());
                    }
                    else
                    {
                        _str = new char[40];
                        (void)snprintf(_str, 40, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_FLOAT:
                {
                    auto val = value<tt_float_t>();
                    _str = new char[120];
                    if (val)
                    {
                        (void)snprintf(_str, 120, "%30s: %f", tagStr, *val);
                    }
                    else
                    {
                        (void)snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            default: break;
            }
            delete[] tagStr;
            return _str;
        }
#endif
    };

    inline bool operator==(const Property& lhs, const Property& rhs)
    {
        bool result = false;

        if (TAG_TYPE(lhs.tag_) == TAG_TYPE(rhs.tag_) && TAG_ID(lhs.tag_) == TAG_ID(rhs.tag_) )
        {
            if ((lhs.tag_ & TAG_ARRAY) == TAG_ARRAY)
            {
                auto a1 = lhs.value<tt_array_t>();
                auto a2 = rhs.value<tt_array_t>();
                if (a1 && a2)
                {
                    if (U32(a1->totalSize) == a2->totalSize)
                    {
                        result = (0 == memcmp(a1->lpValues, a2->lpValues, a1->totalSize));
                    }
                }
                else if (!(a1 || a2))
                {
                    result = true;
                }
                else
                {
                    // MISRA 6-4-2: switch preferred over else-if for known cases
                }
            }

            switch (TAG_TYPE(lhs.tag_))
            {
            case TT_FLOAT:
                {
                    auto f1 = lhs.value<tt_float_t>();
                    auto f2 = rhs.value<tt_float_t>();
                    if (f1 && f2)
                    {
                        float diff = fabsf(*f1 - *f2);
                        float max_ab = fmaxf(fabsf(*f1), fabsf(*f2));
                        result = diff <= (FLT_EPSILON * max_ab);
                    }
                    else if (!(f1 || f2))
                    {
                        result = true;
                    }
                    else
                    {
                        // MISRA 6-4-2: switch preferred over else-if for known cases
                    }
                }
                break;
            case TT_UUID:
                {
                    auto u1 = lhs.value<tt_uuid_t>();
                    auto u2 = rhs.value<tt_uuid_t>();

                    if (u1 && u2)
                    {
                        result = u1.value() == u2.value();
                    }
                    else if (!(u1 || u2))
                    {
                        result = true;
                    }
                    else
                    {
                        // MISRA 6-4-2: switch preferred over else-if for known cases
                    }
                }
                break;
            case TT_STRING:
                {
                    auto s1 = lhs.value<tt_str_t>();
                    auto s2 = rhs.value<tt_str_t>();

                    if (s1 && s2)
                    {
                        if (s1->length() == s2->length())
                        {
                            result = *s1 == *s2;
                        }
                    }
                    else if (!(s1 || s2))
                    {
                        result = true;
                    }
                    else
                    {
                        // MISRA 6-4-2: switch preferred over else-if for known cases
                    }
                }
                break;
            case TT_BIN:
                {
                    auto bin1 = lhs.value<tt_bin_t>();
                    auto bin2 = rhs.value<tt_bin_t>();

                    if (bin1 && bin2 && (U32(bin1->cb) == U32(bin2->cb)))
                    {
                        result = (0 == memcmp(bin1->lpb, bin2->lpb, bin1->cb));
                    }
                    else if (!(bin1 || bin2))
                    {
                        result = true;
                    }
                    else
                    {
                        // MISRA 6-4-2: switch preferred over else-if for known cases
                    }
                }
                break;
            case TT_UINT32:
                result = lhs.value<tt_uint32_t>() == rhs.value<tt_uint32_t>();
                break;
            case TT_UINT16:
                result = lhs.value<tt_uint16_t>() == rhs.value<tt_uint16_t>();
                break;
            case TT_UINT8:
                result = lhs.value<tt_uint8_t>() == rhs.value<tt_uint8_t>();
                break;
            case TT_INT32:
                result = lhs.value<tt_int32_t>() == rhs.value<tt_int32_t>();
                break;
            case TT_INT16:
                result = lhs.value<tt_int16_t>() == rhs.value<tt_int16_t>();
                break;
            case TT_INT8:
                result = lhs.value<tt_int8_t>() == rhs.value<tt_int8_t>();
                break;
            case TT_DATETIME:
                result = lhs.value<tt_uint32_t>() == rhs.value<tt_uint32_t>();
                break;
            case TT_BOOL:
                result = lhs.value<tt_bool_t>() == rhs.value<tt_bool_t>();
                break;
            default:
                break;
            }
        }
        return result;
    }

    inline bool operator!=(const Property& lhs, const Property& rhs)
    {
        return !(lhs == rhs);
    }

    class PropArray : Serializable, std::vector<Property>
    {
        Property VAL_ERR_NOT_FOUND;

    public:
        using vector::clear;
        using vector::operator[];

        PropArray(PropArray&&) = default;
        PropArray& operator=(PropArray&&) = default;

        virtual ~PropArray() = default;

        PropArray() : VAL_ERR_NOT_FOUND(TAG_ERROR)
        {
            VAL_ERR_NOT_FOUND.setData(I8(E_NOT_FOUND));
        }

        PropArray(const PropArray& va)
            : PropArray()
        {
            copyFrom(va);
        }


        PropArray&
        operator=(const PropArray& other)
        {
            if (this != &other)
            {
                copyFrom(other);
            }
            return *this;
        }

        void
        copyFrom(const PropArray& other)
        {
            if (this != &other)
            {
                clear();

                for (const auto& i : other)
                {
                    push_back(i);
                }
            }
        }

        const Property&
        getAt(uint32_t index) const
        {
            return at(index);
        }

        const Property&
        get(uint32_t tag) const
        {
            const Property* result = &VAL_ERR_NOT_FOUND;
            uint32_t idx = 0U;
            const std::size_t max = this->count();

            while (idx < max)
            {
                const Property& p = this->getAt(idx);
                if (TAG_ID(p.tag()) == TAG_ID(tag))
                {
                    result = &p;
                    break;
                }
                ++idx;
            }

            return *result;
        }

        template <uint32_t PropTag>
        PropType<PropTag>
        get(PropType<PropTag> default_value = static_cast<PropType<PropTag>>(PropTypeFromTag<
            TAG_TYPE(PropTag)>::default_value))
        {
            PropType<PropTag> result = default_value;

            if (auto value = get(PropTag).value<typename PropTypeFromTag<TAG_TYPE(PropTag)>::type>())
            {
                result = *value;
            }

            return result;
        }

        bool
        has(uint32_t tag) const
        {
            bool result = false;
            for (const auto& i : *this)
            {
                if (TAG_ID(i.tag()) == TAG_ID(tag))
                {
                    result = true;
                }
            }

            return result;
        }

        void
        remove(const Property& val)
        {
            for (auto it = begin(); it != end(); ++it)
            {
                if (((*it).tag() & 0xffff0800) == (val.tag() & 0xffff0800))
                {
                    (void)erase(it);
                    break;
                }
            }
        }

        void
        set(const Property& val)
        {
            bool found = false;
            for (uint32_t i = 0U; i < this->size(); i++)
            {
                if ((at(i).tag() & 0xffff0800) == (val.tag() & 0xffff0800))
                {
                    at(i).copyFrom(val);
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                push_back(val);
            }
        }

        template <uint32_t PropTag, typename T>
        void set(T value)
        {
            if (std::is_convertible_v<T, typename PropTypeFromTag<TAG_TYPE(PropTag)>::type>)
            {
                Property p(PropTag);
                p.setData(static_cast<T>(value));
                set(p);
            }
        }

        size_type
        count() const
        {
            return size();
        }

        virtual bool serialize(uint16_t* outLen, uint8_t** outBuf) override
        {
            // compute total size
            size_t total = sizeof(uint16_t); // numProps
            for (auto& p : *this)
            {
                total += p.getSerializedSize();
            }

            uint8_t* buf = new uint8_t[total];
            uint16_t offset = 0U;

            // write number of properties
            uint16_t num = U16(this->size());

            *outLen = 0U;
            *outBuf = nullptr;

            if (safeWrite(buf, U16(total), offset, reinterpret_cast<const uint8_t*>(&num),
                          U16(sizeof(num))))
            {
                // write each property
                for (auto& p : *this)
                {
                    (void)p.serializeTo(buf, offset, U16(total));
                }

                *outLen = offset;
                *outBuf = buf;
            }
            return true;
        }

        virtual bool deserialize(uint16_t len, uint8_t* buf) override
        {
            uint16_t offset = 0U;
            uint16_t num = 0U;

            bool result = false;

            if (safeRead(buf, len, offset, reinterpret_cast<uint8_t*>(&num), U16(sizeof(num))))
            {
                result = true;
                for (unsigned i = 0U; i < num && U32(offset) < len; ++i)
                {
                    Property p;
                    if (!p.deserializeFrom(buf, offset, len))
                    {
                        result = false;
                        break;
                    }
                    this->push_back(p);
                }
            }
            return result;
        }

        uint16_t getSerializedSize() const
        {
            size_t total = sizeof(uint16_t);
            for (const auto& prop : *this)
            {
                total += prop.getSerializedSize();
            }
            return U16(total);
        }

#if FLAKE_DEBUG_LOGGING
        char*
        toString(size_t max_len) const
        {
            std::string s;

            for (uint32_t i = 0; i < size(); i++)
            {
                char* str = at(i).toString();
                if (str != nullptr)
                {
                    if (s.length() > (max_len - 37U))
                    {
                        char tmp[20]{};
                        (void)snprintf(tmp, 20, "<<%4ld more>>\n", (size() - i));
                        s.append("                      ");
                        s.append(tmp);
                        delete[] str;
                        break;
                    }
                    s.append(str);
                    if (i < size() - 1)
                        s.append("\n");
                    delete[] str;
                }
            }

            char* res = new char[s.length() + 1];

            memset(res, 0, s.length() + 1);
            if (!s.empty())
                memcpy(res, s.c_str(), s.length());

            return res;
        }
#endif
    };

    namespace MessageType
    {
        // Requests
        constexpr uint8_t connect = U8(0x01U); // no indi
        constexpr uint8_t disconnect = U8(0x02U); // no indi

        constexpr uint8_t createObject = U8(0x03U); // Parent
        constexpr uint8_t queryObjects = U8(0x04U); // no indi          // Parent          //    CONF: PL
        constexpr uint8_t destroyObject = U8(0x05U); // Obj Addr

        constexpr uint8_t openProperty = U8(0x06U); // Obj Addr         //    REQ: PL
        constexpr uint8_t setProperties = U8(0x07U); // Obj Addr         //    REQ/CONF: PL
        constexpr uint8_t getProperties = U8(0x08U); // Obj Addr         //    REQ/CONF: PL

        constexpr uint8_t joinGroup = U8(0x09U); // Group Addr (or Obj Addr)
        constexpr uint8_t leaveGroup = U8(0x0AU); // Group Addr (or Obj Addr)
        constexpr uint8_t custom = U8(0x0BU); // Src -> Dst        //    REQ/CONF: PL

        constexpr uint8_t streamSeek = U8(0x0CU);
        constexpr uint8_t streamRead = U8(0x0DU);
        constexpr uint8_t streamWrite = U8(0x0EU);

        // Indications

        // 0x11
        // 0x12
        constexpr uint8_t objectCreated = U8(0x13U); // [parent -> group]  //    INDI: PL
        // 0x14
        constexpr uint8_t objectDestroyed = U8(0x15U); // [object -> group]
        constexpr uint8_t openPropertyReq = U8(0x16U); // [object -> group]
        constexpr uint8_t setPropertiesReq = U8(0x17U); // Obj Addr          //    INDI/RESP: PL
        constexpr uint8_t getPropertiesReq = U8(0x18U); // Obj Addr          //    INDI/RESP: PL
        constexpr uint8_t joined = U8(0x19U); // [object -> group]
        constexpr uint8_t left = U8(0x1AU); // [object -> group]
        constexpr uint8_t customMsgReceived = U8(0x1BU); // Src & Dst needed  //    INDI/RESP: PL
        constexpr uint8_t streamSeekReq = U8(0x1CU); // Read or Write at Seek Pos
        constexpr uint8_t streamReadReq = U8(0x1DU); // Read or Write at Seek Pos
        constexpr uint8_t streamWriteReq = U8(0x1EU); // Read or Write at Seek Pos
        constexpr uint8_t changed = U8(0x1FU); // [object -> group]  //    INDI: PL

        constexpr uint8_t configure = U8(0x23U); // enable disable stuff, e.g. format of setProperties Confirmation

        // Control
        constexpr uint8_t ping = U8(0x30U); // 0x20 when client->srv or client->obj
        constexpr uint8_t auth = U8(0x31U);

        const char* toString(uint8_t mt);
    }

    inline
    uint32_t preprocessTag(uint32_t t)
    {
        uint32_t result = 0U;
        if (t > 0xFFFFU)
        {
            result = (TAG_ID(t) << 16 | (t & 0xf000U));
        }
        else
        {
            result = (t << 16) | (t & 0xf000U);
        }
        return result;
    }

    inline
    Property PropMakeBool(uint32_t t, tt_bool_t v)
    {
        Property val(preprocessTag(t) | TT_BOOL);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeU8(uint32_t t, tt_uint8_t v)
    {
        Property val(preprocessTag(t) | TT_UINT8);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeU16(uint32_t t, tt_uint16_t v)
    {
        Property val(preprocessTag(t) | TT_UINT16);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeU32(uint32_t t, tt_uint32_t v)
    {
        Property val(preprocessTag(t) | TT_UINT32);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeI8(uint32_t t, tt_int8_t v)
    {
        Property val(preprocessTag(t) | TT_INT8);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeI16(uint32_t t, tt_int16_t v)
    {
        Property val(preprocessTag(t) | TT_INT16);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeI32(uint32_t t, tt_int32_t v)
    {
        Property val(preprocessTag(t) | TT_INT32);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeDateTime(uint32_t t, tt_uint32_t v)
    {
        Property val(preprocessTag(t) | TT_DATETIME);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeFloat(uint32_t t, tt_float_t v)
    {
        Property val(preprocessTag(t) | TT_FLOAT);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeUUID(uint32_t t, const tt_uuid_t &v)
    {
        Property val(preprocessTag(t) | TT_UUID);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeString(uint32_t t, const tt_str_t& v)
    {
        Property val(preprocessTag(t) | TT_STRING);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeBin(uint32_t t, tt_bin_t v)
    {
        Property val(preprocessTag(t) | TT_BIN);
        val.setData(v);
        return val;
    }
}

#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

#endif /* FLAKE_TYPES_H_ */