438 lines
12 KiB
C++
438 lines
12 KiB
C++
//-----------------------------------------------------------------------------
|
|
// ___ __ _ _
|
|
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
|
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
|
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
|
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Author: Kurt Sassenrath
|
|
// Module: msgpack
|
|
//
|
|
// An implementation of the MessagePack binary format specification.
|
|
//
|
|
// Copyright (c) 2023 Kurt Sassenrath.
|
|
//
|
|
// License TBD.
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef msgpack_format_849b5c5238d8212
|
|
#define msgpack_format_849b5c5238d8212
|
|
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
|
|
namespace msgpack {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Supporting types/tags for formats
|
|
//-----------------------------------------------------------------------------
|
|
struct nil {
|
|
nil() = default;
|
|
// This constructor is used by the reader implementation.
|
|
nil(auto){};
|
|
};
|
|
|
|
struct invalid {};
|
|
|
|
using boolean = bool;
|
|
|
|
struct array_desc {
|
|
constexpr array_desc() noexcept = default;
|
|
constexpr array_desc(auto sz) noexcept
|
|
: count(std::size_t(sz)){};
|
|
// This constructor is implemented for use by reader
|
|
constexpr array_desc(auto, auto sz) noexcept
|
|
: count(std::size_t(sz)){};
|
|
|
|
constexpr bool operator==(array_desc const& other) const noexcept {
|
|
return count == other.count;
|
|
}
|
|
|
|
std::size_t count = 0;
|
|
};
|
|
|
|
struct map_desc {
|
|
constexpr map_desc() noexcept = default;
|
|
constexpr map_desc(auto sz) noexcept
|
|
: count(std::size_t(sz)){};
|
|
// This constructor is implemented for use by reader
|
|
constexpr map_desc(auto, auto sz) noexcept
|
|
: count(std::size_t(sz)){};
|
|
std::size_t count = 0;
|
|
};
|
|
|
|
namespace format {
|
|
|
|
// Classification of the format type. Used by the token API to distinguish
|
|
// different formats.
|
|
enum class type : std::uint8_t {
|
|
invalid,
|
|
unsigned_int,
|
|
signed_int,
|
|
string,
|
|
binary,
|
|
nil,
|
|
boolean,
|
|
array,
|
|
map,
|
|
array_view,
|
|
map_view,
|
|
};
|
|
|
|
// Flags that may control the behavior of readers/writers.
|
|
enum flag : std::uint8_t {
|
|
apply_mask = 1,
|
|
fixed_size = 2,
|
|
};
|
|
|
|
// MessagePack formats break down into one of the following schemes:
|
|
// Marker byte + Fixed-length value
|
|
// Marker byte + Fixed-length payload length + payload
|
|
//
|
|
// In addition, there are "fix" types that embed the literal value or the
|
|
// payload length within the marker byte.
|
|
enum payload : std::uint8_t { fixed, variable };
|
|
|
|
// Base structure for all MessagePack formats to inherit from.
|
|
struct base_format {
|
|
constexpr static flag flags = {};
|
|
};
|
|
|
|
// Traits describing a particular format.
|
|
struct traits {
|
|
std::uint8_t flags{};
|
|
std::uint8_t size{};
|
|
std::byte mask{};
|
|
type token_type{};
|
|
constexpr auto operator<=>(traits const&) const noexcept = default;
|
|
};
|
|
|
|
// "Sentinel" traits instance indicating no trait found
|
|
constexpr static traits no_traits{};
|
|
|
|
struct fixtype_format : base_format {};
|
|
|
|
template <class>
|
|
struct resolve_token_type {
|
|
constexpr static type value = type::invalid;
|
|
};
|
|
|
|
template <std::integral T>
|
|
struct resolve_token_type<T> {
|
|
constexpr static type value =
|
|
std::is_signed_v<T> ? type::signed_int : type::unsigned_int;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<std::string_view> {
|
|
constexpr static type value = type::string;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<std::span<std::byte const>> {
|
|
constexpr static type value = type::binary;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<bool> {
|
|
constexpr static type value = type::boolean;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<nil> {
|
|
constexpr static type value = type::nil;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<invalid> {
|
|
constexpr static type value = type::invalid;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<array_desc> {
|
|
constexpr static type value = type::array;
|
|
};
|
|
|
|
template <>
|
|
struct resolve_token_type<map_desc> {
|
|
constexpr static type value = type::map;
|
|
};
|
|
|
|
template <typename T>
|
|
constexpr static auto resolve_type_v = resolve_token_type<T>::value;
|
|
|
|
// The library's representation of certain data types does not always
|
|
// match the underlying data serialized in the message. For example,
|
|
// arrays and maps are encoded as a marker byte + fixed length value, but
|
|
// msgpack will present them wrapped in a structure for stronger type
|
|
// semantics.
|
|
|
|
template <std::uint8_t Marker, typename First, typename Value = First>
|
|
struct format : base_format {
|
|
using first_type = First;
|
|
using value_type = Value; // Can be overridden
|
|
constexpr static std::byte marker{Marker};
|
|
constexpr static payload payload_type{payload::fixed};
|
|
constexpr static std::uint8_t flags{};
|
|
};
|
|
|
|
template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t>
|
|
struct fixtype : fixtype_format {
|
|
using first_type = First;
|
|
using value_type = first_type; // Can be overridden
|
|
constexpr static std::byte marker{Marker};
|
|
constexpr static std::byte mask{Mask};
|
|
constexpr static payload payload_type{payload::fixed};
|
|
constexpr static auto flags{flag::apply_mask};
|
|
};
|
|
|
|
/*
|
|
* Integral types
|
|
*/
|
|
|
|
// Positive/negative fixint represent the literal value specified, do not
|
|
// need to mask it off.
|
|
struct positive_fixint : fixtype<0x00, 0x7f> {
|
|
constexpr static flag flags{};
|
|
};
|
|
|
|
struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
|
|
constexpr static flag flags{};
|
|
};
|
|
|
|
struct uint8 : format<0xcc, std::uint8_t> {};
|
|
|
|
struct uint16 : format<0xcd, std::uint16_t> {};
|
|
|
|
struct uint32 : format<0xce, std::uint32_t> {};
|
|
|
|
struct uint64 : format<0xcf, std::uint64_t> {};
|
|
|
|
struct int8 : format<0xd0, std::int8_t> {};
|
|
|
|
struct int16 : format<0xd1, std::int16_t> {};
|
|
|
|
struct int32 : format<0xd2, std::int32_t> {};
|
|
|
|
struct int64 : format<0xd3, std::int64_t> {};
|
|
|
|
/*
|
|
* Other primitive types
|
|
*/
|
|
struct nil : fixtype<0xc0, 0x00> {
|
|
using value_type = msgpack::nil;
|
|
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
|
|
};
|
|
|
|
struct invalid : fixtype<0xc1, 0x00> {
|
|
using value_type = msgpack::invalid;
|
|
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
|
|
};
|
|
|
|
struct boolean : fixtype<0xc2, 0x01> {
|
|
using value_type = bool;
|
|
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
|
|
};
|
|
|
|
/*
|
|
* Maps
|
|
*/
|
|
|
|
template <typename Fmt>
|
|
struct map_format : Fmt {
|
|
using value_type = map_desc;
|
|
};
|
|
|
|
struct fixmap : map_format<fixtype<0x80, 0x0f>> {};
|
|
|
|
struct map16 : map_format<format<0xde, std::uint16_t>> {};
|
|
|
|
struct map32 : map_format<format<0xdf, std::uint32_t>> {};
|
|
|
|
/*
|
|
* Arrays
|
|
*/
|
|
|
|
template <typename Fmt>
|
|
struct array_format : Fmt {
|
|
using value_type = array_desc;
|
|
};
|
|
|
|
struct fixarray : array_format<fixtype<0x90, 0x0f>> {};
|
|
|
|
struct array16 : array_format<format<0xdc, std::uint16_t>> {};
|
|
|
|
struct array32 : array_format<format<0xdd, std::uint32_t>> {};
|
|
|
|
/*
|
|
* Strings
|
|
*/
|
|
|
|
template <typename Fmt>
|
|
struct string_format : Fmt {
|
|
using value_type = std::string_view;
|
|
constexpr static auto payload_type = payload::variable;
|
|
};
|
|
|
|
struct fixstr : string_format<fixtype<0xa0, 0x1f>> {};
|
|
|
|
struct str8 : string_format<format<0xd9, std::uint8_t>> {};
|
|
|
|
struct str16 : string_format<format<0xda, std::uint16_t>> {};
|
|
|
|
struct str32 : string_format<format<0xdb, std::uint32_t>> {};
|
|
|
|
/*
|
|
* Binary arrays
|
|
*/
|
|
|
|
template <typename Fmt>
|
|
struct bin_format : Fmt {
|
|
using value_type = std::span<std::byte const>;
|
|
constexpr static auto payload_type = payload::variable;
|
|
};
|
|
|
|
struct bin8 : bin_format<format<0xc4, std::uint8_t>> {};
|
|
|
|
struct bin16 : bin_format<format<0xc5, std::uint16_t>> {};
|
|
|
|
struct bin32 : bin_format<format<0xc6, std::uint32_t>> {};
|
|
|
|
/*
|
|
* Extension types, not yet supported.
|
|
*/
|
|
|
|
template <typename Fmt>
|
|
struct unimplemented_format : Fmt {};
|
|
|
|
struct fixext1 : unimplemented_format<fixtype<0xd4, 0x00>> {};
|
|
|
|
struct fixext2 : unimplemented_format<fixtype<0xd5, 0x00>> {};
|
|
|
|
struct fixext4 : unimplemented_format<fixtype<0xd6, 0x00>> {};
|
|
|
|
struct fixext8 : unimplemented_format<fixtype<0xd7, 0x00>> {};
|
|
|
|
struct fixext16 : unimplemented_format<fixtype<0xd8, 0x00>> {};
|
|
|
|
struct ext8 : unimplemented_format<format<0xc7, std::uint8_t>> {};
|
|
|
|
struct ext16 : unimplemented_format<format<0xc8, std::uint16_t>> {};
|
|
|
|
struct ext32 : unimplemented_format<format<0xc9, std::uint32_t>> {};
|
|
|
|
template <typename T>
|
|
struct unimplemented : std::false_type {};
|
|
|
|
template <typename Fmt>
|
|
struct unimplemented<unimplemented_format<Fmt>> : std::true_type {};
|
|
|
|
namespace detail {
|
|
// Simple typelist for looking up compatible types.
|
|
// TODO: Use traits to generate the compatibility list automatically?
|
|
template <std::size_t I, typename T>
|
|
struct type_list_entry {
|
|
using type = T;
|
|
};
|
|
|
|
template <typename...>
|
|
struct type_list_impl;
|
|
|
|
template <std::size_t... Is, typename... Ts>
|
|
struct type_list_impl<std::index_sequence<Is...>, Ts...>
|
|
: type_list_entry<Is, Ts>... {
|
|
static constexpr auto size = sizeof...(Ts);
|
|
};
|
|
|
|
template <typename T>
|
|
struct typelist_lookup_tag {
|
|
using type = T;
|
|
};
|
|
|
|
template <std::size_t I, typename T>
|
|
typelist_lookup_tag<T> typelist_lookup(type_list_entry<I, T> const&);
|
|
|
|
template <std::size_t I, typename List>
|
|
using type_list_at =
|
|
typename decltype(typelist_lookup<I>(std::declval<List>()))::type;
|
|
|
|
template <typename... Ts>
|
|
using type_list =
|
|
detail::type_list_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;
|
|
|
|
template <typename, typename, template <typename> typename>
|
|
struct filter_type_list;
|
|
|
|
template <typename... Ts, template <typename> typename Predicate>
|
|
struct filter_type_list<type_list<>, type_list<Ts...>, Predicate> {
|
|
using type = type_list<Ts...>;
|
|
};
|
|
|
|
template <typename Head, typename... Tail, typename... Ts,
|
|
template <typename> typename Predicate>
|
|
struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> {
|
|
using type = typename std::conditional_t<Predicate<Head>::value,
|
|
filter_type_list<type_list<Tail...>, type_list<Ts..., Head>,
|
|
Predicate>,
|
|
filter_type_list<type_list<Tail...>, type_list<Ts...>,
|
|
Predicate>>::type;
|
|
};
|
|
|
|
template <typename List, template <typename> typename Predicate>
|
|
using filter = filter_type_list<List, type_list<>, Predicate>;
|
|
|
|
template <typename List, template <typename> typename Predicate>
|
|
using filter_t = typename filter<List, Predicate>::type;
|
|
} // namespace detail
|
|
} // namespace format
|
|
|
|
template <typename T>
|
|
concept format_type = std::is_base_of_v<format::base_format, T>;
|
|
|
|
template <typename T>
|
|
concept is_fixtype = std::is_base_of_v<format::fixtype_format, T>;
|
|
|
|
namespace format {
|
|
// This template instantiates a format::traits object for a given format.
|
|
// This should only occur once per format type, and should happen at
|
|
// compile time.
|
|
//
|
|
// This isn't the prettiest way to go about it.
|
|
template <format_type Format>
|
|
inline traits const& get_traits() noexcept {
|
|
constexpr static auto inst = [] {
|
|
// Fixtypes define the size within the identifier byte, so the
|
|
// trait size must be zero.
|
|
auto size =
|
|
is_fixtype<Format> ? 0 : sizeof(typename Format::first_type);
|
|
|
|
traits inst{.flags = Format::flags,
|
|
.size = std::uint8_t(size),
|
|
.token_type = resolve_type_v<typename Format::value_type>};
|
|
// Add in the fixed_size flag for integral types.
|
|
if constexpr (std::is_integral_v<typename Format::value_type>) {
|
|
inst.flags |= flag::fixed_size;
|
|
}
|
|
if constexpr (is_fixtype<Format>) {
|
|
inst.mask = Format::mask;
|
|
}
|
|
return inst;
|
|
}();
|
|
return inst;
|
|
};
|
|
} // namespace format
|
|
|
|
template <format_type... Formats>
|
|
using format_list = format::detail::type_list<Formats...>;
|
|
|
|
template <std::size_t I, typename List>
|
|
using format_list_at = format::detail::type_list_at<I, List>;
|
|
|
|
} // namespace msgpack
|
|
|
|
#endif // oh_msgpack_format_849b5c5238d8212
|