323 lines
10 KiB
C++
323 lines
10 KiB
C++
//-----------------------------------------------------------------------------
|
|
// ___ __ _ _
|
|
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
|
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
|
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
|
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Author: Kurt Sassenrath
|
|
// Module: msgpack
|
|
//
|
|
// Core reading implementation, upon which readers are built.
|
|
//
|
|
// Copyright (c) 2023 Kurt Sassenrath.
|
|
//
|
|
// License TBD.
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef msgpack_core_reader_6c2f66f02585565
|
|
#define msgpack_core_reader_6c2f66f02585565
|
|
|
|
#include "../util/endianness.h"
|
|
#include "error.h"
|
|
#include "format.h"
|
|
|
|
#include <tl/expected.hpp>
|
|
|
|
namespace msgpack {
|
|
|
|
/**
|
|
* reader_policy dictates how flexible the reader can be when reading data
|
|
* from a binary blob containing MessagePack formats.
|
|
*
|
|
* More flexible policies will potentially include more code as a consequence.
|
|
* If structural integrity or code size is important, use the strict policy.
|
|
*/
|
|
namespace reader_policy {
|
|
struct relaxed {}; // Similar formats are acceptable.
|
|
|
|
struct strict {}; // Formats must exactly match the caller's request.
|
|
} // namespace reader_policy
|
|
|
|
namespace detail {
|
|
|
|
// Generic mechanism for reading a number of bytes out of an interator.
|
|
template <std::size_t N, typename Itr>
|
|
constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept {
|
|
std::array<std::byte, N> out;
|
|
auto const end = inp + N;
|
|
auto dst = out.begin();
|
|
while (inp != end) {
|
|
*dst = std::byte{*inp};
|
|
++dst;
|
|
++inp;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Read an integral type out of the iterator.
|
|
template <std::integral T, typename Itr>
|
|
constexpr inline decltype(auto) read_integral(Itr& inp) noexcept {
|
|
constexpr auto N = sizeof(T);
|
|
if constexpr (sizeof(T) == 1) {
|
|
return T(*inp++);
|
|
} else {
|
|
return be_to_host(::detail::value_cast<T>(read_bytes<N>(inp)));
|
|
}
|
|
}
|
|
|
|
// This helper structure provides a customization point for converting an
|
|
// iterator to some other type (e.g. a pointer) in the event that the
|
|
// final format does not have a suitable constructor. It's only currently
|
|
// used for string_view, but may be overridden on platforms that provide
|
|
// their own types.
|
|
template <typename T, typename Iter>
|
|
struct iterator_adapter {
|
|
static constexpr auto convert(Iter itr) { return itr; }
|
|
};
|
|
|
|
template <typename Iter>
|
|
struct iterator_adapter<std::string_view, Iter> {
|
|
static constexpr auto convert(Iter itr) {
|
|
return ::detail::value_cast<std::string_view::pointer>(&*itr);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
using expected = tl::expected<T, error>;
|
|
|
|
template <format_type F, typename Iter>
|
|
constexpr inline bool accept(Iter& itr) noexcept {
|
|
if constexpr (is_fixtype<F>) {
|
|
// Don't advance the iterator -- part of the format is locked away
|
|
// within the format byte.
|
|
return (std::byte{*itr} & ~F::mask) == F::marker;
|
|
} else {
|
|
// Advance the iterator past the format byte.
|
|
return std::byte{*itr++} == F::marker;
|
|
}
|
|
}
|
|
|
|
// Read a value from a MessagePack message. Assumes that itr != end to
|
|
// start with.
|
|
template <format_type F, typename Iter>
|
|
requires(!format::unimplemented<F>::value)
|
|
constexpr inline auto read(Iter& itr, Iter const end) noexcept
|
|
-> expected<typename F::value_type> {
|
|
// Copy off the iterator. Update it at the end _if_ everything goes
|
|
// smoothly.
|
|
auto cur = itr;
|
|
if (!accept<F>(cur)) return tl::make_unexpected(error::wrong_type);
|
|
|
|
// First thing's first. Read the format's "first_type", which is either
|
|
// the payload or the length of the payload. Ensure we have enough data
|
|
// from the span before we do so, though.
|
|
using first_type = typename F::first_type;
|
|
using value_type = typename F::value_type;
|
|
using diff_type = typename std::iterator_traits<Iter>::difference_type;
|
|
if (std::distance(cur, end) < diff_type{sizeof(first_type)}) {
|
|
return tl::make_unexpected(error::incomplete_message);
|
|
}
|
|
|
|
auto f = read_integral<first_type>(cur);
|
|
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
|
|
f &= decltype(f)(F::mask);
|
|
}
|
|
|
|
if constexpr (F::payload_type == format::payload::fixed) {
|
|
// We've read all we need to read. Update itr and return the value.
|
|
itr = cur;
|
|
return value_type(f);
|
|
} else {
|
|
// We're reading a variable length payload. `f` is the length of the
|
|
// payload. Ensure that the span has enough data and construct the
|
|
// value_type accordingly.
|
|
if (std::distance(cur, end) < diff_type{f}) {
|
|
return tl::make_unexpected(error::incomplete_message);
|
|
}
|
|
|
|
itr = cur + f;
|
|
using adapt = iterator_adapter<value_type, Iter>;
|
|
return value_type(adapt::convert(cur), f);
|
|
}
|
|
}
|
|
|
|
// "Relaxed" reader policies allow for certain conversions to take place
|
|
// at runtime. For instance, any smaller type of the same category is
|
|
// allowed.
|
|
//
|
|
// TODO: Possible improvement to readability; specify a single format list
|
|
// containing all formats, and filter them down for each instantiated
|
|
// format. Note that this would probably have compile-time performance
|
|
// implications.
|
|
|
|
template <format_type>
|
|
struct relaxed_conversions {};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::uint8> {
|
|
using type_list = format_list<format::uint8, format::positive_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::uint16> {
|
|
using type_list =
|
|
format_list<format::uint16, format::uint8, format::positive_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::uint32> {
|
|
using type_list = format_list<format::uint32, format::uint16, format::uint8,
|
|
format::positive_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::uint64> {
|
|
using type_list = format_list<format::uint64, format::uint32,
|
|
format::uint16, format::uint8, format::positive_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::int8> {
|
|
using type_list = format_list<format::int8, format::negative_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::int16> {
|
|
using type_list =
|
|
format_list<format::int16, format::int8, format::negative_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::int32> {
|
|
using type_list = format_list<format::int32, format::int16, format::int8,
|
|
format::negative_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::int64> {
|
|
using type_list = format_list<format::int64, format::int32, format::int16,
|
|
format::int8, format::negative_fixint>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::str8> {
|
|
using type_list = format_list<format::str8, format::fixstr>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::str16> {
|
|
using type_list = format_list<format::str16, format::str8, format::fixstr>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::map16> {
|
|
using type_list = format_list<format::map16, format::fixmap>;
|
|
};
|
|
|
|
template <>
|
|
struct relaxed_conversions<format::map32> {
|
|
using type_list = format_list<format::map32, format::map16, format::fixmap>;
|
|
};
|
|
|
|
template <typename F>
|
|
concept has_conversions = format_type<F> && requires {
|
|
typename relaxed_conversions<F>::type_list;
|
|
};
|
|
|
|
template <typename policy>
|
|
struct reader_policy_traits {};
|
|
|
|
template <>
|
|
struct reader_policy_traits<reader_policy::strict> {
|
|
template <format_type F, typename Iter>
|
|
constexpr static bool accept(Iter itr) noexcept {
|
|
return detail::accept<F>(itr);
|
|
}
|
|
|
|
template <format_type F, typename Iter>
|
|
constexpr static decltype(auto) read(Iter& itr, Iter const end) noexcept {
|
|
return detail::read<F>(itr, end);
|
|
};
|
|
};
|
|
|
|
template <format_type fmt, typename fmtlist, typename Iter, std::size_t I = 0>
|
|
constexpr static auto relaxed_read_impl(Iter& itr, Iter const end) noexcept
|
|
-> expected<typename fmt::value_type> {
|
|
if constexpr (I < fmtlist::size) {
|
|
using this_format = format_list_at<I, fmtlist>;
|
|
auto v = detail::read<this_format>(itr, end);
|
|
if (v.has_value()) {
|
|
return v.value();
|
|
} else if (v.error() == error::wrong_type) {
|
|
return relaxed_read_impl<fmt, fmtlist, Iter, I + 1>(itr, end);
|
|
} else {
|
|
return v;
|
|
}
|
|
} else {
|
|
return tl::make_unexpected(error::wrong_type);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
struct reader_policy_traits<reader_policy::relaxed> {
|
|
template <format_type F, typename Iter>
|
|
constexpr static decltype(auto) read(Iter& itr, Iter const& end) noexcept {
|
|
if constexpr (has_conversions<F>) {
|
|
using format_list = typename relaxed_conversions<F>::type_list;
|
|
return relaxed_read_impl<F, format_list, Iter, 0>(itr, end);
|
|
} else {
|
|
// Fall back on strict policy
|
|
return detail::read<F>(itr, end);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
template <typename policy = reader_policy::relaxed>
|
|
class reader {
|
|
public:
|
|
template <typename T>
|
|
using expected = detail::expected<T>;
|
|
|
|
constexpr reader(std::span<std::byte const> src)
|
|
: data(src)
|
|
, curr(std::begin(data))
|
|
, end(std::end(data)) {}
|
|
|
|
template <format_type F>
|
|
constexpr expected<typename F::value_type> read() noexcept {
|
|
if (curr == end) return tl::make_unexpected(error::end_of_message);
|
|
return detail::reader_policy_traits<policy>::template read<F>(
|
|
curr, end);
|
|
}
|
|
|
|
constexpr auto pos() const noexcept { return curr; }
|
|
|
|
constexpr auto subview() const noexcept {
|
|
return std::span<std::byte const>(curr, end);
|
|
}
|
|
|
|
constexpr auto tell() const noexcept {
|
|
return std::distance(data.begin(), curr);
|
|
}
|
|
|
|
constexpr void skip(std::size_t count) noexcept {
|
|
if (std::distance(curr, end) < count) {
|
|
curr = end;
|
|
} else {
|
|
curr += count;
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::span<std::byte const> data;
|
|
decltype(data)::iterator curr;
|
|
decltype(data)::iterator end;
|
|
};
|
|
|
|
} // namespace msgpack
|
|
|
|
#endif // msgpack_core_reader_6c2f66f02585565
|