//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: msgpack // // Core writing implementation, upon which writers are built. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #ifndef msgpack_core_writer_ce48a51aa6ed0858 #define msgpack_core_writer_ce48a51aa6ed0858 #include #include "../util/endianness.h" #include "error.h" #include "format.h" #include #include namespace msgpack { enum class writer_error { none, unsupported, not_implemented, bad_value, out_of_space }; // Helper template for writing a non-integral datatype to an output. // template // struct write_adapter { // template // static constexpr tl::expected write(T const& t); //}; template constexpr inline decltype(auto) write_bytes( std::array&& data, Itr out) noexcept { for (auto b : data) { *out++ = b; } return out; } namespace detail { constexpr std::size_t bytes_needed_for_str(std::uint32_t v) { if (v <= std::uint32_t(format::fixstr::mask)) return 0; return std::bit_ceil(std::uint32_t((std::bit_width(v) + 7) >> 3)); } constexpr std::size_t bytes_needed(std::uint64_t v) { if (v <= std::uint64_t(format::positive_fixint::mask)) return 0; return std::bit_ceil(std::uint64_t((std::bit_width(v) + 7) >> 3)); } constexpr std::size_t bytes_needed(std::int64_t v) { if (v < 0 && v >= -32) return 0; auto width = 1 + std::numeric_limits::digits - (v < 0 ? std::countl_one(std::uint64_t(v)) : std::countl_zero(std::uint64_t(v))); return std::bit_ceil(std::uint64_t((width + 7) >> 3)); } constexpr std::byte get_format_signed(auto value) { switch (bytes_needed(std::int64_t{value})) { case 0: return std::byte(value); case 1: return format::int8::marker; case 2: return format::int16::marker; case 4: return format::int32::marker; default: return format::int64::marker; } } constexpr std::byte get_format(auto value) { switch (bytes_needed(std::uint64_t{value})) { case 0: return std::byte(value); case 1: return format::uint8::marker; case 2: return format::uint16::marker; case 4: return format::uint32::marker; default: return format::uint64::marker; } } } // namespace detail template struct write_adapter {}; template struct write_adapter { static constexpr auto size(T t) noexcept { return sizeof(T); } template static constexpr auto write(T t, Itr out) noexcept { return write_bytes(::detail::raw_cast(host_to_be(t)), out); } }; template struct deduced_write_adapter {}; template struct deduced_write_adapter { static constexpr auto size(T value) noexcept { return detail::bytes_needed(std::int64_t{value}); } template static constexpr auto write(T value, Itr out) noexcept { auto bytes = ::detail::as_bytes(host_to_be(value)); auto sz = size(value); for (std::size_t i = 0; i < sz; ++i) { *out++ = *(bytes.end() - sz + i); } return out; } static constexpr auto format_hint(T value) noexcept { return detail::get_format_signed(value); } }; template struct deduced_write_adapter { static constexpr auto size(T value) noexcept { return detail::bytes_needed(std::uint64_t{value}); } template static constexpr auto write(T value, Itr out) noexcept { auto bytes = ::detail::as_bytes(host_to_be(value)); auto sz = size(value); for (std::size_t i = 0; i < sz; ++i) { *out++ = *(bytes.end() - sz + i); } return out; } template static constexpr auto write(T value, Itr out, std::size_t sz) noexcept { auto bytes = ::detail::as_bytes(host_to_be(value)); for (std::size_t i = 0; i < sz; ++i) { *out++ = *(bytes.end() - sz + i); } return out; } static constexpr auto format_hint(T value) noexcept { return detail::get_format(value); } }; template <> struct deduced_write_adapter { static constexpr auto size(std::string_view value) noexcept { return value.size() + detail::bytes_needed_for_str(value.size()); } template static constexpr auto write(std::string_view value, Itr out) noexcept { auto bytes_needed = detail::bytes_needed_for_str(value.size()); if (bytes_needed) { out = deduced_write_adapter::write( value.size(), out, bytes_needed); } auto const* beg = reinterpret_cast(&*value.begin()); std::copy(beg, beg + value.size(), out); return out + value.size(); } static constexpr auto format_hint(std::string_view value) noexcept { switch (detail::bytes_needed_for_str(value.size())) { case 0: return format::fixstr::marker | std::byte(value.size()); case 1: return format::str8::marker; case 2: return format::str16::marker; case 4: default: return format::str32::marker; } } }; template T> struct deduced_write_adapter : deduced_write_adapter {}; template <> struct write_adapter { static constexpr auto size(std::string_view str) noexcept { return str.size(); } template static constexpr auto write(std::string_view str, Itr out) noexcept { std::byte const* beg = reinterpret_cast(&*str.begin()); std::copy(beg, beg + str.size(), out); return out + str.size(); } }; template <> struct write_adapter> { static constexpr auto size(std::span bytes) noexcept { return bytes.size(); } template static constexpr auto write( std::span bytes, Itr out) noexcept { std::copy(bytes.begin(), bytes.end(), out); return out += bytes.size(); } }; template <> struct write_adapter { static constexpr auto value(map_desc desc) noexcept { return desc.count; } }; template <> struct write_adapter { static constexpr auto value(array_desc desc) noexcept { return desc.count; } }; // TODO: These could be optimized away because invalid/nil never contain real // data. template <> struct write_adapter { static constexpr auto value(invalid) noexcept { return 0; } }; template <> struct write_adapter { static constexpr auto value(nil) noexcept { return 0; } }; namespace detail { template using expected = tl::expected; template constexpr inline std::size_t calculate_space( typename F::value_type val) noexcept { // At a minimum, one byte is needed to store the format. std::size_t size = sizeof(typename F::first_type); if (!is_fixtype) { ++size; // For format } if constexpr (F::payload_type == format::payload::variable) { size += write_adapter::size(val); } return size; } // The "first type" is either the size of the variable length payload or // a fixed-length value. Additionally, this "first type" may be inlined // into the marker byte if it's a fix type. template constexpr inline expected pack_first( typename F::value_type value) noexcept { using value_type = typename F::value_type; if constexpr (F::payload_type == format::payload::variable) { return typename F::first_type(write_adapter::size(value)); } else { if constexpr (requires { write_adapter::value; }) { return typename F::first_type( write_adapter::value(value)); } else { return typename F::first_type(value); } } } template concept v2_adapted = requires(T const& t, std::byte* b) { { deduced_write_adapter::size(t) } -> std::same_as; { deduced_write_adapter::write(t, b) } -> std::same_as; { deduced_write_adapter::format_hint(t) } -> std::same_as; }; template constexpr inline expected write( typename F::value_type&& value, Itr out, Itr const end) { using diff_type = typename std::iterator_traits::difference_type; if (diff_type(calculate_space(value)) > std::distance(out, end)) { return tl::make_unexpected(error::out_of_space); } auto marker = F::marker; auto result = pack_first(value); if (!result) { return tl::make_unexpected(result.error()); } if constexpr (is_fixtype) { if (*result > 0xff) { return tl::make_unexpected(error::bad_value); } if constexpr (F::flags & format::flag::apply_mask) { marker |= std::byte(*result); } else { marker = std::byte(*result); } if ((marker & ~F::mask) != F::marker) { return tl::make_unexpected(error::bad_value); } } *out++ = marker; if constexpr (!is_fixtype) { out = write_adapter::write( *result, out); } if constexpr (F::payload_type == format::payload::variable) { out = write_adapter::write(value, out); } return out; } template struct format_hint; template <> struct format_hint { using type = format::positive_fixint; }; template <> struct format_hint { using type = format::uint16; }; } // namespace detail class writer { public: template using expected = detail::expected; constexpr writer(std::span dest) : data(dest) , curr(std::begin(data)) , end(std::end(data)) {} template constexpr expected write( typename F::value_type&& v) noexcept { using value_type = typename F::value_type; auto result = detail::write(std::forward(v), curr, end); if (!result) { return tl::make_unexpected(result.error()); } curr = *result; return tl::monostate{}; } // Deduced-type write, automatically chooses smallest representative format template constexpr expected write2(T&& v) { using diff_type = std::iterator_traits::difference_type; auto const space_needed = diff_type(1 + deduced_write_adapter::size(v)); if (space_needed > std::distance(curr, end)) { return tl::make_unexpected(error::out_of_space); } *curr++ = deduced_write_adapter::format_hint(v); if (space_needed > 1) { curr = deduced_write_adapter::write(v, curr); } return tl::monostate{}; } constexpr auto pos() const noexcept { return curr; } constexpr auto tell() const noexcept { return std::distance(std::begin(data), curr); } constexpr auto subspan() const noexcept { return std::span(std::begin(data), tell()); } private: std::span data; decltype(data)::iterator curr; decltype(data)::iterator end; }; } // namespace msgpack #endif // msgpack_core_writer_ce48a51aa6ed0858