//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: msgpack // // Default packer implementation, which aims to deduce the best format to use // for a given value. For example, if a 32-bit unsigned integer type only // contains the value 5, a uint32 format would serialize into: // // 0xce, 0x00, 0x00, 0x00, 0x05 // // Instead, the packer will note that this value could be stored in a positive // fixint, which is simply: // // 0x05 // // The same optimization will be applied to variable-length types, like strings, // bytes, arrays, and maps. // // This flexibility comes at the cost of CPU instructions. For embedded targets, // writer (to be renamed verbatim_packer in the future) may be a better choice. // // Future goals for this particular packer: // 1. Support containers/ranges seamlessly. // 2. Support packing of trivial POD structures without an explicit // pack_adapter. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #ifndef msgpack_core_packer_1d5939e9c1498568 #define msgpack_core_packer_1d5939e9c1498568 #include "parselink/msgpack/core/error.h" #include "parselink/msgpack/core/format.h" #include "parselink/msgpack/util/endianness.h" #include #include #include namespace msgpack { namespace detail { // This is a generic helper function for writing integral bytes. template constexpr auto write_integral(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; } // Depending on the format, a number of bytes will be necessary to represent // either the value (integer formats) or the length (variable length formats). template struct pack_helper {}; template <> struct pack_helper { static constexpr std::size_t num_bytes(std::uint64_t value) noexcept { if (value <= std::uint64_t(format::positive_fixint::mask)) return 0; return std::bit_ceil(std::uint64_t((std::bit_width(value) + 7) >> 3)); } static constexpr std::byte marker(std::uint64_t value) noexcept { switch (num_bytes(value)) { case 0: return static_cast(value); case 1: return format::uint8::marker; case 2: return format::uint16::marker; case 4: return format::uint32::marker; default: return format::uint64::marker; } } }; template <> struct pack_helper { static constexpr std::size_t num_bytes(std::int64_t value) noexcept { // Probably a better way to do this. if (value < 0 && value >= -32) return 0; auto underlying = static_cast(value); // save a branch; these should be cheap on modern hardware. std::uint64_t counts[2] = { static_cast(std::countl_zero(underlying)), static_cast(std::countl_one(underlying))}; std::uint64_t width = 1 + std::numeric_limits::digits - counts[underlying >> 63]; return std::bit_ceil((width + 7) >> 3); } static constexpr std::byte marker(std::int64_t value) noexcept { switch (num_bytes(value)) { case 0: return static_cast(value); case 1: return format::int8::marker; case 2: return format::int16::marker; case 4: return format::int32::marker; default: return format::int64::marker; } } }; template <> struct pack_helper { static constexpr std::size_t num_bytes(std::uint64_t value) noexcept { if (value <= std::uint32_t(format::fixstr::mask)) return 0; return std::bit_ceil(std::uint32_t((std::bit_width(value) + 7) >> 3)); } static constexpr auto marker(std::string_view value) noexcept { switch (num_bytes(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; } } }; } // namespace detail // Pack adapter is the basis for packing native values into MessagePack format. template struct pack_adapter {}; template concept builtin_packable_type = requires(T const& t, std::byte* b) { { pack_adapter::size(t) } -> std::same_as; { pack_adapter::write(t, b) } -> std::same_as; { pack_adapter::marker(t) } -> std::same_as; }; template concept packable_type = builtin_packable_type; template struct builtin_pack_adapter { static constexpr auto format_type = F; static constexpr auto size(auto value) noexcept { return detail::pack_helper::num_bytes(value); } static constexpr auto marker(auto value) noexcept { return detail::pack_helper::marker(value); } }; template struct pack_adapter : builtin_pack_adapter { template static constexpr Itr write(T value, Itr out) noexcept { return detail::write_integral(value, out, size(value)); } }; template struct pack_adapter : builtin_pack_adapter { template static constexpr Itr write(T value, Itr out) noexcept { return detail::write_integral(value, out, size(value)); } }; } // namespace msgpack #endif // msgpack_core_packer_1d5939e9c1498568