parselink-old/include/parselink/msgpack/core/reader.h

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