Skip to content

file /Users/ios_developer/workspace/coldwave-os/build/_deps/flake-src/inc/FlakeTypes.h

Types

Name
structflake::Serializable
classflake::UniqueId
structflake::binary_t
structarray_t
classflake::Array
classflake::TagArray
structflake::PropTypeFromTag<(0x1U) >
structflake::PropTypeFromTag<(0x2U) >
structflake::PropTypeFromTag<(0x3U) >
structflake::PropTypeFromTag<(0x4U) >
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
classflake::PropArray

Defines

Name
UINT8_FMT
UINT16_FMT
UINT32_FMT
INT8_FMT
INT16_FMT
INT32_FMT
UINT32_HEX_FMT
DEALLOC_SELF_FLAG
DONT_SERIALIZE_FLAG
DONT_COPY_FLAG
DONT_CACHE_FLAG
TAG_TYPE(x)document me
TAG_ID(x)document me
TAG_READONLYdocument me
TAG_ACTIONABLEdocument me
TAG_VOLATILEdocument me
TAG_NULLdocument me
TAG_ERRORdocument me
TAG_ARRAYdocument me
TAG_METAdocument me
IS_ACTIONABLE(x)document me
IS_READONLY(x)document me
IS_META(x)document me
IS_RESERVED(x)document me
IS_ERROR(x)document me
IS_VOLATILE(x)document me
IS_ARRAY(x)document me
IS_STREAM(x)document me
TT_INT32document me
TT_INT16document me
TT_INT8document me
TT_UINT32document me
TT_UINT16document me
TT_UINT8document me
TT_BOOLdocument me
TT_UUIDdocument me
TT_FLOATdocument me
TT_DATETIMEdocument me
TT_BINdocument me
TT_STRINGdocument me
TT_TABLEdocument me
TT_OBJECTdocument me
TT_STRING_STREAMdocument me
TT_BIN_STREAMdocument me
ERROR_VAL(x)document me

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)

define DONT_SERIALIZE_FLAG

cpp
#define DONT_SERIALIZE_FLAG (2UL)

define DONT_COPY_FLAG

cpp
#define DONT_COPY_FLAG (4UL)

define DONT_CACHE_FLAG

cpp
#define DONT_CACHE_FLAG (8UL)

define TAG_TYPE

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

document me

define TAG_ID

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

document me

define TAG_READONLY

cpp
#define TAG_READONLY (0x1000UL)

document me

define TAG_ACTIONABLE

cpp
#define TAG_ACTIONABLE (0x2000UL)

document me

define TAG_VOLATILE

cpp
#define TAG_VOLATILE (0x4000UL)

document me

define TAG_NULL

cpp
#define TAG_NULL (0x8000UL)

document me

define TAG_ERROR

cpp
#define TAG_ERROR (0x100UL)

document me

define TAG_ARRAY

cpp
#define TAG_ARRAY (0x040UL)

document me

define TAG_META

cpp
#define TAG_META (0x800UL)

document me

define IS_ACTIONABLE

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

document me

define IS_READONLY

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

document me

define IS_META

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

document me

define IS_RESERVED

cpp
#define IS_RESERVED(
    x
)
((x & 0xfff0U) == 0xfff0U)

document me

define IS_ERROR

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

document me

define IS_VOLATILE

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

document me

define IS_ARRAY

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

document me

define IS_STREAM

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

document me

define TT_INT32

cpp
#define TT_INT32 (0x1U)

document me

define TT_INT16

cpp
#define TT_INT16 (0x2U)

document me

define TT_INT8

cpp
#define TT_INT8 (0x3U)

document me

define TT_UINT32

cpp
#define TT_UINT32 (0x4U)

document me

define TT_UINT16

cpp
#define TT_UINT16 (0x5U)

document me

define TT_UINT8

cpp
#define TT_UINT8 (0x6U)

document me

define TT_BOOL

cpp
#define TT_BOOL (0x7U)

document me

define TT_UUID

cpp
#define TT_UUID (0x8U)

document me

define TT_FLOAT

cpp
#define TT_FLOAT (0x9U)

document me

define TT_DATETIME

cpp
#define TT_DATETIME (0xAU)

document me

define TT_BIN

cpp
#define TT_BIN (0xCU)

document me

define TT_STRING

cpp
#define TT_STRING (0xEU)

document me

define TT_TABLE

cpp
#define TT_TABLE (0x1CU)

document me

define TT_OBJECT

cpp
#define TT_OBJECT (0x80U)

document me

define TT_STRING_STREAM

cpp
#define TT_STRING_STREAM (0x81U)

document me

define TT_BIN_STREAM

cpp
#define TT_BIN_STREAM (0x82U)

document me

define ERROR_VAL

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

document me

Source code

cpp
/*******************************************************************************
 * @file     FlakeTypes.h
 * @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 & 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_BIN              (0xCU)
#define TT_STRING           (0xEU)
#define TT_TABLE            (0x1CU)
#define TT_OBJECT           (0x80U)
#define TT_STRING_STREAM    (0x81U)
#define TT_BIN_STREAM         (0x82U)
#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 (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
    {
    protected:
        ~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:
        UniqueId() = default;
        static constexpr std::size_t SIZE = 16U;

        UniqueId(const UniqueId& other)
        {
            *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];

            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;
        }

        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
    {
        uint16_t cb;
        uint8_t* lpb;
    } binary_t;

    typedef int32_t tt_int32_t;
    typedef int16_t tt_int16_t;
    typedef int8_t tt_int8_t;
    typedef uint32_t tt_uint32_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;

    typedef array_t tt_array_t;

    class Array {
    public:
        explicit Array(array_t* arr) : arr_(arr) {}

        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;

            uint8_t* newBuf = static_cast<uint8_t*>(std::realloc(arr_->lpValues, newCount * es));
            if (!newBuf) return false;

            arr_->lpValues = newBuf;
            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_;
    };

    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 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
    {
        atSignature = U8(1),
        atInteractive = U8(2)
    };

    class TagArray
    {
        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_uint8_t,
                            tt_uint16_t,
                            tt_uint32_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_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:
        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_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;
                }
            }
            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)
                        {
                            free(arr->lpValues);
                        }
                    }
                }
                else
                {
                    switch (TAG_TYPE(tag_))
                    {
                    case TT_BIN:
                        if (auto bin = value<tt_bin_t>())
                        {
                            if ((bin->lpb != nullptr) && (bin->cb > 0U))
                            {
                                free(bin->lpb);
                            }
                        }
                        break;
                    default: break;
                    }
                }
            }
        }

        void
        setDeallocSelf()
        {
            flags |= DEALLOC_SELF_FLAG;
        }

        Property&
        operator=(const Property& other)
        {
            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>();

                if (auto a2 = other.value<tt_array_t>())
                {
                    if (a1->lpValues != nullptr && ((flags & DEALLOC_SELF_FLAG) == DEALLOC_SELF_FLAG))
                    {
                        free(a1->lpValues);
                    }


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

                    if (a1->totalSize != 0U)
                    {
                        setDeallocSelf();
                        a1->lpValues = (uint8_t*)(malloc(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>();

                        if (auto bin2 = other.value<tt_bin_t>())
                        {
                            if (bin1->lpb != nullptr && ((flags & DEALLOC_SELF_FLAG) == DEALLOC_SELF_FLAG))
                            {
                                free(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 = (uint8_t*)(malloc(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)
            {
                if (auto arr = std::get_if<tt_array_t>(&*data_))
                {
                    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 (!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 = (uint8_t*)(malloc(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))
                    {
                        free(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 = (char*)malloc(len + 1U);
                            if (!safeRead(buf, bufSize, offset,
                                          reinterpret_cast<uint8_t*>(tmpBuf), len, &tmp))
                            {
                                free(tmpBuf);
                                offset = startOff;
                                return false;
                            }
                            tmpBuf[len] = '\0';
                            data_ = tt_str_t(tmpBuf, len);
                            free(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 = (uint8_t*)(malloc(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>())
                {
                    snprintf(_str, 120, "%30s: %s", tagStr, errorToString(
                                 (long)*err));
                }
                else
                {
                    snprintf(_str, 120, "%30s: NULL", tagStr);
                }
                free(tagStr);
                return _str;
            }

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

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

            switch (TAG_TYPE(tag_))
            {
            case TT_BOOL:
                {
                    auto val = value<tt_bool_t>();
                    _str = new char[120];
                    if (val)
                    {
                        snprintf(_str, 120, "%30s: %s", tagStr, *val ? "true" : "false");
                    }
                    else
                    {
                        snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_UINT8:
                {
                    auto val = value<tt_uint8_t>();
                    _str = new char[120];
                    if (val)
                    {
                        snprintf(_str, 120, "%30s: %" UINT8_FMT, tagStr, *val);
                    }
                    else
                    {
                        snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_UINT16:
                {
                    auto val = value<tt_uint16_t>();
                    _str = new char[120];
                    if (val)
                    {
                        snprintf(_str, 120, "%30s: %" UINT16_FMT, tagStr, *val);
                    }
                    else
                    {
                        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)
                    {
                        snprintf(_str, 120, "%30s: %" UINT32_FMT, tagStr, *val);
                    }
                    else
                    {
                        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)
                    {
                        snprintf(_str, 120, "%30s: %" INT8_FMT, tagStr, *val);
                    }
                    else
                    {
                        snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_INT16:
                {
                    auto val = value<tt_int16_t>();
                    _str = new char[120];
                    if (val)
                    {
                        snprintf(_str, 120, "%30s: %" INT16_FMT, tagStr, *val);
                    }
                    else
                    {
                        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)
                    {
                        snprintf(_str, 120, "%30s: %" INT32_FMT, tagStr, *val);
                    }
                    else
                    {
                        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);
                        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
                    {
                        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)
                    {
                        snprintf(_str, 120, "%30s: %" UINT16_FMT " bytes", tagStr, val->cb);
                    }
                    else
                    {
                        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();
                        snprintf(_str, 120, "%30s: %s", tagStr, s);
                        free(s);
                    }
                    else
                    {
                        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);
                        snprintf(_str, strlen(val->c_str()) + 40, "%30s: %s", tagStr, val->c_str());
                    }
                    else
                    {
                        _str = new char[40];
                        snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            case TT_FLOAT:
                {
                    auto val = value<tt_float_t>();
                    _str = new char[120];
                    if (val)
                    {
                        snprintf(_str, 120, "%30s: %f", tagStr, *val);
                    }
                    else
                    {
                        snprintf(_str, 120, "%30s: NULL", tagStr);
                    }
                }
                break;
            default: break;
            }
            free(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[];

        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)
        {
            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);
            }
            else
            {
#if FLAKE_DEBUG_LOGGING
                logging::logf<lvlError>("the passed value cannot be safely cast to the property's type\n");
#endif
            }
        }

        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 = (uint8_t*)malloc(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, tt_uuid_t v)
    {
        Property val(preprocessTag(t) | TT_UUID);
        val.setData(v);
        return val;
    }

    inline
    Property PropMakeString(uint32_t t, 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_ */