//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: msgpack // // Generic (non-owning) wrapper for MessagePack data. // // Emphasis on the "non-owning" aspect of these objects. They're intended for // use in parsing contexts only where the lifetime of the backing data buffer // exceeds the lifetime of the object instances themselves, so for strings, // byte-strings, etc. an explicit copy must be made. // // TBD: How best to handle arrays and maps. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #ifndef msgpack_object_f43c22522692063f #define msgpack_object_f43c22522692063f #include "core/format.h" #include #include #include #include #include #include #include #include namespace msgpack { // This API is _currently_ optimizing on the fact that most desktop/server // platforms are running on 64-bit, but lengths are at most 32 bits. This means // that a generic object type can utilize the remaining 32-bit padding for type // information, resulting in an object that only occupies 16 bytes, rather than // 24. This could be further optimized to include type-specific flags, such as // whether the data referenced is heap-allocated/managed by the object, or if // it's merely a view of some other data. // // On the flip side, on 32-bit platforms, we manage with just 12 bytes. There // might be room for optimization on that front, but it's left unimplemented // for now. // // Of course, this means custom code! template requires (sizeof(SizeType) + sizeof(std::underlying_type_t) <= sizeof(uintptr_t)) class size_and_enum { public: using size_type = SizeType; using enum_type = E; using enum_int_type = std::underlying_type_t; constexpr static uintptr_t enum_mask = ((1 << (sizeof(enum_int_type) * 8)) - 1); constexpr static uintptr_t size_shift = (sizeof(SizeType) * 8); constexpr static uintptr_t size_mask = ~enum_mask; // Constructors constexpr size_and_enum() noexcept = default; constexpr size_and_enum(size_type size, enum_type enum_value) noexcept { set_both(size, enum_value); } // Accessors constexpr auto get_size() const noexcept { return static_cast((value & size_mask) >> size_shift); } constexpr auto get_enum() const noexcept { return static_cast((value & enum_mask)); } constexpr auto rep() const noexcept { return value; } // Mutators constexpr auto set_size(size_type size) noexcept { value = (static_cast(size) << size_shift) | (value & enum_mask); } constexpr auto set_enum(enum_type enum_value) noexcept { value = (static_cast(enum_value) & enum_mask) | (value & size_mask); } constexpr auto set_both(size_type size, enum_type enum_value) noexcept { value = (static_cast(size) << size_shift) | (static_cast(enum_value) & enum_mask); } // Explicit conversion constexpr explicit operator size_type() const noexcept { return get_size(); } constexpr explicit operator enum_type() const noexcept { return get_enum(); } private: uintptr_t value{}; }; enum class object_type : std::uint8_t { invalid, unsigned_int, signed_int, string, bytes, nil, boolean, array, map }; template class object_base; // TODO(ksassenrath): Move to own file. enum class object_error { wrong_type, will_truncate }; // Specialization for 64-bit systems. template <> class object_base<8> { public: object_base() noexcept = default; object_base(object_base const& other) noexcept : value_(other.value_) , size_and_type_(other.size_and_type_) {} template explicit object_base(T value) noexcept { if constexpr (std::is_same_v) { size_and_type_.set_enum(object_type::boolean); value_.b = value; } else if constexpr (std::is_signed_v) { size_and_type_.set_enum(object_type::signed_int); value_.i = value; } else { size_and_type_.set_enum(object_type::unsigned_int); value_.u = value; } } template T> explicit object_base(T const& value) noexcept { std::string_view sv(value); size_and_type_.set_enum(object_type::string); size_and_type_.set_size(sv.size()); value_.str = sv.data(); } template > T> explicit object_base(T const& value) noexcept { std::span bv(value); size_and_type_.set_enum(object_type::bytes); size_and_type_.set_size(bv.size()); value_.bp = bv.data(); } constexpr object_type type() const noexcept { return size_and_type_.get_enum(); } template constexpr tl::expected get() const noexcept; template constexpr tl::expected get() const noexcept { constexpr auto expected_type = std::is_same_v ? object_type::boolean : std::is_signed_v ? object_type::signed_int : object_type::unsigned_int; if (type() != expected_type) { return tl::make_unexpected(object_error::wrong_type); } if constexpr (expected_type == object_type::boolean) { return T(value_.b); } else if constexpr (expected_type == object_type::signed_int) { if (std::numeric_limits::max() < value_.i || std::numeric_limits::lowest() > value_.i) { return tl::make_unexpected(object_error::will_truncate); } return T(value_.i); } else { if (std::numeric_limits::max() < value_.u) { return tl::make_unexpected(object_error::will_truncate); } return T(value_.u); } } private: union { std::uint64_t u; std::int64_t i; char const* str; std::byte const* bp; bool b; object_base* obj; } value_; size_and_enum size_and_type_{}; }; template<> inline tl::expected object_base<8>::get() const noexcept { if (type() != object_type::string) { return tl::make_unexpected(object_error::wrong_type); } return std::string{value_.str, size_and_type_.get_size()}; } template<> constexpr tl::expected object_base<8>::get() const noexcept { if (type() != object_type::string) { return tl::make_unexpected(object_error::wrong_type); } return std::string_view{value_.str, size_and_type_.get_size()}; } template<> inline tl::expected, object_error> object_base<8>::get() const noexcept { tl::expected, object_error> result; if (type() != object_type::bytes) { result = tl::make_unexpected(object_error::wrong_type); } else { result = std::vector(value_.bp, value_.bp + size_and_type_.get_size()); } return result; } template<> constexpr tl::expected, object_error> object_base<8>::get() const noexcept { if (type() != object_type::bytes) { return tl::make_unexpected(object_error::wrong_type); } return std::span(value_.bp, size_and_type_.get_size()); } using object = object_base; } // namespace msgpack #endif // msgpack_object_f43c22522692063f