import msgpack, begin work on object API
This commit is contained in:
parent
ec3b953384
commit
d3c59248a2
4
.bazelrc
4
.bazelrc
@ -1,2 +1,2 @@
|
||||
build --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O2"
|
||||
run --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O2"
|
||||
build --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O3"
|
||||
run --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O3"
|
||||
|
||||
22
WORKSPACE
22
WORKSPACE
@ -54,6 +54,28 @@ http_archive(
|
||||
strip_prefix = "fmt-" + fmt_version,
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# tl/expected: An implementation of std::expected with monadic extensions.
|
||||
#-------------------------------------------------------------------------------
|
||||
tl_expected_version = "1.1.0"
|
||||
tl_expected_base_url = \
|
||||
"https://github.com/TartanLlama/expected/archive/refs/tags/v"
|
||||
|
||||
http_archive(
|
||||
name = "expected",
|
||||
url = tl_expected_base_url + tl_expected_version + ".zip",
|
||||
strip_prefix = "expected-" + tl_expected_version,
|
||||
build_file_content =
|
||||
"""
|
||||
cc_library(
|
||||
name = "expected",
|
||||
includes = ["include"],
|
||||
hdrs = ["include/tl/expected.hpp"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# ut: Unit test framework.
|
||||
# TODO(kss): Only if tests are needed?
|
||||
|
||||
14
source/BUILD
14
source/BUILD
@ -5,8 +5,22 @@ cc_library(
|
||||
"include/parselink/server.h",
|
||||
],
|
||||
strip_include_prefix = "include/parselink",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "msgpack",
|
||||
hdrs = glob([
|
||||
"include/parselink/msgpack/**/*.h"
|
||||
]),
|
||||
deps = [
|
||||
"@expected",
|
||||
],
|
||||
strip_include_prefix = "include/parselink",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
cc_binary(
|
||||
name = "parselinkd",
|
||||
srcs = [
|
||||
|
||||
6
source/include/parselink/msgpack.h
Normal file
6
source/include/parselink/msgpack.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef msgpack_9865a64955e64703
|
||||
#define msgpack_9865a64955e64703
|
||||
|
||||
#include "msgpack/core.h"
|
||||
|
||||
#endif // msgpack_9865a64955e64703
|
||||
8
source/include/parselink/msgpack/core.h
Normal file
8
source/include/parselink/msgpack/core.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef msgpack_core_2e1a9d55129a666e
|
||||
#define msgpack_core_2e1a9d55129a666e
|
||||
|
||||
#include "core/format.h"
|
||||
#include "core/reader.h"
|
||||
#include "core/writer.h"
|
||||
|
||||
#endif // msgpack_core_2e1a9d55129a666e
|
||||
276
source/include/parselink/msgpack/core/format.h
Normal file
276
source/include/parselink/msgpack/core/format.h
Normal file
@ -0,0 +1,276 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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_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 {
|
||||
|
||||
// Flags that may control the behavior of readers/writers.
|
||||
enum flag {
|
||||
apply_mask = 1
|
||||
};
|
||||
|
||||
// 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 {
|
||||
fixed,
|
||||
variable
|
||||
};
|
||||
|
||||
// Base structure for all MessagePack formats to inherit from.
|
||||
struct base_format {
|
||||
constexpr static flag flags = {};
|
||||
};
|
||||
|
||||
struct fixtype_format : base_format {};
|
||||
|
||||
// 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>
|
||||
struct format : base_format {
|
||||
using first_type = First;
|
||||
using value_type = first_type; // Can be overridden
|
||||
constexpr static std::byte marker{Marker};
|
||||
constexpr static payload payload_type{payload::fixed};
|
||||
};
|
||||
|
||||
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; };
|
||||
struct invalid : fixtype<0xc1, 0x00> { using value_type = msgpack::invalid; };
|
||||
struct boolean : fixtype<0xc2, 0x01> { using value_type = bool; };
|
||||
|
||||
/*
|
||||
* 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>;
|
||||
|
||||
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
|
||||
334
source/include/parselink/msgpack/core/reader.h
Normal file
334
source/include/parselink/msgpack/core/reader.h
Normal file
@ -0,0 +1,334 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 "format.h"
|
||||
#include "../util/endianness.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
|
||||
|
||||
enum class reader_error {
|
||||
unsupported,
|
||||
not_implemented,
|
||||
wrong_type,
|
||||
end_of_message,
|
||||
premature_end_of_message,
|
||||
};
|
||||
|
||||
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, reader_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(reader_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(reader_error::premature_end_of_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(reader_error::premature_end_of_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() == reader_error::wrong_type) {
|
||||
return relaxed_read_impl<fmt, fmtlist, Iter, I + 1>(itr, end);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
} else {
|
||||
return tl::make_unexpected(reader_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:
|
||||
|
||||
using error_code = reader_error;
|
||||
|
||||
template <typename T>
|
||||
using expected = detail::expected<T>;
|
||||
|
||||
constexpr reader(std::span<std::byte const> 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_code::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
|
||||
274
source/include/parselink/msgpack/core/writer.h
Normal file
274
source/include/parselink/msgpack/core/writer.h
Normal file
@ -0,0 +1,274 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 "format.h"
|
||||
#include "../util/endianness.h"
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
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 <typename T>
|
||||
//struct write_adapter {
|
||||
//template <typename Itr>
|
||||
//static constexpr tl::expected<Itr, writer_error> write(T const& t);
|
||||
//};
|
||||
|
||||
template <std::size_t N, typename Itr>
|
||||
constexpr inline decltype(auto) write_bytes(std::array<std::byte, N>&& data,
|
||||
Itr out) noexcept {
|
||||
for (auto b : data) {
|
||||
*out++ = b;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct write_adapter {};
|
||||
|
||||
template <std::integral T>
|
||||
struct write_adapter<T> {
|
||||
static constexpr auto size(T) noexcept {
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
template <typename Itr>
|
||||
static constexpr auto write(T t, Itr out) noexcept {
|
||||
return write_bytes(detail::raw_cast(host_to_be(t)), out);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct write_adapter<std::string_view> {
|
||||
static constexpr auto size(std::string_view str) noexcept {
|
||||
return str.size();
|
||||
}
|
||||
|
||||
template <typename Itr>
|
||||
static constexpr auto write(std::string_view str, Itr out) noexcept {
|
||||
std::byte const* beg = reinterpret_cast<std::byte const*>(&*str.begin());
|
||||
std::copy(beg, beg + str.size(), out);
|
||||
return out += str.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct write_adapter<std::span<std::byte const>> {
|
||||
static constexpr auto size(std::span<std::byte const> bytes) noexcept {
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
template <typename Itr>
|
||||
static constexpr auto write(std::span<std::byte const> bytes,
|
||||
Itr out) noexcept {
|
||||
std::copy(bytes.begin(), bytes.end(), out);
|
||||
return out += bytes.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct write_adapter<map_desc> {
|
||||
static constexpr auto value(map_desc desc) noexcept {
|
||||
return desc.count;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct write_adapter<array_desc> {
|
||||
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<invalid> {
|
||||
static constexpr auto value(invalid) noexcept {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct write_adapter<nil> {
|
||||
static constexpr auto value(nil) noexcept {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using expected = tl::expected<T, writer_error>;
|
||||
|
||||
template <format_type F>
|
||||
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<F>) {
|
||||
++size; // For format
|
||||
}
|
||||
|
||||
if constexpr (F::payload_type == format::payload::variable) {
|
||||
size += write_adapter<typename F::value_type>::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 <format_type F>
|
||||
constexpr inline expected<typename F::first_type> 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<value_type>::size(value));
|
||||
} else {
|
||||
if constexpr (requires { write_adapter<value_type>::value; }) {
|
||||
return typename F::first_type(write_adapter<value_type>::value(value));
|
||||
} else {
|
||||
return typename F::first_type(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <format_type F, typename Itr>
|
||||
constexpr inline expected<Itr> write(
|
||||
typename F::value_type&& value, Itr out, Itr const end) {
|
||||
using diff_type = typename std::iterator_traits<Itr>::difference_type;
|
||||
if (diff_type(calculate_space<F>(value)) > std::distance(out, end)) {
|
||||
return tl::make_unexpected(writer_error::out_of_space);
|
||||
}
|
||||
|
||||
auto marker = F::marker;
|
||||
|
||||
auto result = pack_first<F>(value);
|
||||
if (!result) {
|
||||
return tl::make_unexpected(result.error());
|
||||
}
|
||||
|
||||
if constexpr (is_fixtype<F>) {
|
||||
if (*result > 0xff) {
|
||||
return tl::make_unexpected(writer_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(writer_error::bad_value);
|
||||
}
|
||||
}
|
||||
|
||||
*out++ = marker;
|
||||
|
||||
if constexpr (!is_fixtype<F>) {
|
||||
out = write_adapter<typename decltype(result)::value_type>::write(*result, out);
|
||||
}
|
||||
|
||||
if constexpr (F::payload_type == format::payload::variable) {
|
||||
out = write_adapter<typename F::value_type>::write(value, out);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct format_hint;
|
||||
|
||||
template <>
|
||||
struct format_hint<std::uint8_t> {
|
||||
using type = format::positive_fixint;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct format_hint<std::uint16_t> {
|
||||
using type = format::uint16;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
class writer {
|
||||
public:
|
||||
|
||||
using error_code = writer_error;
|
||||
|
||||
template <typename T>
|
||||
using expected = detail::expected<T>;
|
||||
|
||||
constexpr writer(std::span<std::byte> dest) :
|
||||
data(dest), curr(std::begin(data)), end(std::end(data)) {}
|
||||
|
||||
template <format_type F>
|
||||
constexpr expected<tl::monostate> write(typename F::value_type&& v)
|
||||
noexcept {
|
||||
using value_type = typename F::value_type;
|
||||
if (curr == end) return tl::make_unexpected(error_code::out_of_space);
|
||||
auto result = detail::write<F>(std::forward<value_type>(v), curr, end);
|
||||
if (!result) {
|
||||
return tl::make_unexpected(result.error());
|
||||
}
|
||||
|
||||
curr = *result;
|
||||
return tl::monostate{};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires requires { typename detail::format_hint<T>::type; }
|
||||
constexpr expected<tl::monostate> write(T&& v) {
|
||||
return write<typename detail::format_hint<T>::type>(std::forward<T>(v));
|
||||
}
|
||||
|
||||
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::byte>(std::begin(data), tell());
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<std::byte> data;
|
||||
decltype(data)::iterator curr;
|
||||
decltype(data)::iterator end;
|
||||
};
|
||||
|
||||
} // namespace msgpack
|
||||
|
||||
#endif // msgpack_core_writer_ce48a51aa6ed0858
|
||||
259
source/include/parselink/msgpack/object.h
Normal file
259
source/include/parselink/msgpack/object.h
Normal file
@ -0,0 +1,259 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: msgpack
|
||||
//
|
||||
// Generic (non-owning) wrapper for MessagePack data.
|
||||
//
|
||||
// Emphasis on the "non-owning" aspect of these objects. They're intended for
|
||||
// use in parsing contexts only where the lifetime of the backing data buffer
|
||||
// exceeds the lifetime of the object instances themselves, so for strings,
|
||||
// byte-strings, etc. an explicit copy must be made.
|
||||
//
|
||||
// TBD: How best to handle arrays and maps.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef msgpack_object_f43c22522692063f
|
||||
#define msgpack_object_f43c22522692063f
|
||||
|
||||
#include "core/format.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace msgpack {
|
||||
|
||||
// This API is _currently_ optimizing on the fact that most desktop/server
|
||||
// platforms are running on 64-bit, but lengths are at most 32 bits. This means
|
||||
// that a generic object type can utilize the remaining 32-bit padding for type
|
||||
// information, resulting in an object that only occupies 16 bytes, rather than
|
||||
// 24. This could be further optimized to include type-specific flags, such as
|
||||
// whether the data referenced is heap-allocated/managed by the object, or if
|
||||
// it's merely a view of some other data.
|
||||
//
|
||||
// On the flip side, on 32-bit platforms, we manage with just 12 bytes. There
|
||||
// might be room for optimization on that front, but it's left unimplemented
|
||||
// for now.
|
||||
//
|
||||
// Of course, this means custom code!
|
||||
|
||||
template <std::integral SizeType, typename E>
|
||||
requires (sizeof(SizeType) + sizeof(std::underlying_type_t<E>) <= sizeof(uintptr_t))
|
||||
class size_and_enum {
|
||||
public:
|
||||
using size_type = SizeType;
|
||||
using enum_type = E;
|
||||
using enum_int_type = std::underlying_type_t<E>;
|
||||
constexpr static uintptr_t enum_mask = ((1 << (sizeof(enum_int_type) * 8)) - 1);
|
||||
constexpr static uintptr_t size_shift = (sizeof(SizeType) * 8);
|
||||
constexpr static uintptr_t size_mask = ~enum_mask;
|
||||
|
||||
// Constructors
|
||||
constexpr size_and_enum() noexcept = default;
|
||||
constexpr size_and_enum(size_type size, enum_type enum_value) noexcept {
|
||||
set_both(size, enum_value);
|
||||
}
|
||||
|
||||
// Accessors
|
||||
constexpr auto get_size() const noexcept {
|
||||
return static_cast<size_type>((value & size_mask) >> size_shift);
|
||||
}
|
||||
constexpr auto get_enum() const noexcept {
|
||||
return static_cast<enum_type>((value & enum_mask));
|
||||
}
|
||||
constexpr auto rep() const noexcept { return value; }
|
||||
|
||||
// Mutators
|
||||
constexpr auto set_size(size_type size) noexcept {
|
||||
value = (static_cast<uintptr_t>(size) << size_shift) | (value & enum_mask);
|
||||
}
|
||||
constexpr auto set_enum(enum_type enum_value) noexcept {
|
||||
value = (static_cast<uintptr_t>(enum_value) & enum_mask) | (value & size_mask);
|
||||
}
|
||||
|
||||
constexpr auto set_both(size_type size, enum_type enum_value) noexcept {
|
||||
value = (static_cast<uintptr_t>(size) << size_shift) |
|
||||
(static_cast<uintptr_t>(enum_value) & enum_mask);
|
||||
}
|
||||
|
||||
// Explicit conversion
|
||||
constexpr explicit operator size_type() const noexcept {
|
||||
return get_size();
|
||||
}
|
||||
constexpr explicit operator enum_type() const noexcept {
|
||||
return get_enum();
|
||||
}
|
||||
|
||||
private:
|
||||
uintptr_t value{};
|
||||
};
|
||||
|
||||
enum class object_type : std::uint8_t {
|
||||
invalid,
|
||||
unsigned_int,
|
||||
signed_int,
|
||||
string,
|
||||
bytes,
|
||||
nil,
|
||||
boolean,
|
||||
array,
|
||||
map
|
||||
};
|
||||
|
||||
template <size_t WordSize>
|
||||
class object_base;
|
||||
|
||||
// TODO(ksassenrath): Move to own file.
|
||||
enum class object_error {
|
||||
wrong_type,
|
||||
will_truncate
|
||||
};
|
||||
|
||||
// Specialization for 64-bit systems.
|
||||
template <>
|
||||
class object_base<8> {
|
||||
public:
|
||||
object_base() noexcept = default;
|
||||
object_base(object_base const& other) noexcept
|
||||
: value_(other.value_)
|
||||
, size_and_type_(other.size_and_type_) {}
|
||||
|
||||
template <std::integral T>
|
||||
explicit object_base(T value) noexcept {
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
size_and_type_.set_enum(object_type::boolean);
|
||||
value_.b = value;
|
||||
} else if constexpr (std::is_signed_v<T>) {
|
||||
size_and_type_.set_enum(object_type::signed_int);
|
||||
value_.i = value;
|
||||
} else {
|
||||
size_and_type_.set_enum(object_type::unsigned_int);
|
||||
value_.u = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
explicit object_base(T const& value) noexcept {
|
||||
std::string_view sv(value);
|
||||
size_and_type_.set_enum(object_type::string);
|
||||
size_and_type_.set_size(sv.size());
|
||||
value_.str = sv.data();
|
||||
}
|
||||
|
||||
template <std::convertible_to<std::span<std::byte const>> T>
|
||||
explicit object_base(T const& value) noexcept {
|
||||
std::span<std::byte const> bv(value);
|
||||
size_and_type_.set_enum(object_type::bytes);
|
||||
size_and_type_.set_size(bv.size());
|
||||
value_.bp = bv.data();
|
||||
}
|
||||
|
||||
constexpr object_type type() const noexcept {
|
||||
return size_and_type_.get_enum();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr tl::expected<T, object_error> get() const noexcept;
|
||||
|
||||
template<std::integral T>
|
||||
constexpr tl::expected<T, object_error> get() const noexcept {
|
||||
constexpr auto expected_type = std::is_same_v<T, bool> ?
|
||||
object_type::boolean : std::is_signed_v<T> ?
|
||||
object_type::signed_int : object_type::unsigned_int;
|
||||
|
||||
if (type() != expected_type) {
|
||||
return tl::make_unexpected(object_error::wrong_type);
|
||||
}
|
||||
if constexpr (expected_type == object_type::boolean) {
|
||||
return T(value_.b);
|
||||
} else if constexpr (expected_type == object_type::signed_int) {
|
||||
if (std::numeric_limits<T>::max() < value_.i ||
|
||||
std::numeric_limits<T>::lowest() > value_.i) {
|
||||
return tl::make_unexpected(object_error::will_truncate);
|
||||
}
|
||||
return T(value_.i);
|
||||
} else {
|
||||
if (std::numeric_limits<T>::max() < value_.u) {
|
||||
return tl::make_unexpected(object_error::will_truncate);
|
||||
}
|
||||
return T(value_.u);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
std::uint64_t u;
|
||||
std::int64_t i;
|
||||
char const* str;
|
||||
std::byte const* bp;
|
||||
bool b;
|
||||
object_base* obj;
|
||||
} value_;
|
||||
size_and_enum<std::uint32_t, object_type> size_and_type_{};
|
||||
};
|
||||
|
||||
template<>
|
||||
inline tl::expected<std::string, object_error> object_base<8>::get()
|
||||
const noexcept
|
||||
{
|
||||
if (type() != object_type::string) {
|
||||
return tl::make_unexpected(object_error::wrong_type);
|
||||
}
|
||||
return std::string{value_.str, size_and_type_.get_size()};
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr tl::expected<std::string_view, object_error> object_base<8>::get()
|
||||
const noexcept
|
||||
{
|
||||
if (type() != object_type::string) {
|
||||
return tl::make_unexpected(object_error::wrong_type);
|
||||
}
|
||||
return std::string_view{value_.str, size_and_type_.get_size()};
|
||||
}
|
||||
|
||||
template<>
|
||||
inline tl::expected<std::vector<std::byte>, object_error> object_base<8>::get()
|
||||
const noexcept
|
||||
{
|
||||
tl::expected<std::vector<std::byte>, object_error> result;
|
||||
if (type() != object_type::bytes) {
|
||||
result = tl::make_unexpected(object_error::wrong_type);
|
||||
} else {
|
||||
result = std::vector<std::byte>(value_.bp,
|
||||
value_.bp + size_and_type_.get_size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr tl::expected<std::span<std::byte const>, object_error> object_base<8>::get() const noexcept
|
||||
{
|
||||
if (type() != object_type::bytes) {
|
||||
return tl::make_unexpected(object_error::wrong_type);
|
||||
}
|
||||
return std::span<std::byte const>(value_.bp, size_and_type_.get_size());
|
||||
}
|
||||
|
||||
using object = object_base<sizeof(void*)>;
|
||||
|
||||
} // namespace msgpack
|
||||
|
||||
|
||||
#endif // msgpack_object_f43c22522692063f
|
||||
155
source/include/parselink/msgpack/util/endianness.h
Normal file
155
source/include/parselink/msgpack/util/endianness.h
Normal file
@ -0,0 +1,155 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: msgpack
|
||||
//
|
||||
// Endianness helper file. Might be replaced with <bit>'s endian in the near
|
||||
// future.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef msgpack_endianness_d8ae54f45851ed13
|
||||
#define msgpack_endianness_d8ae54f45851ed13
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
enum class endianness {
|
||||
big,
|
||||
little,
|
||||
other
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Structure which determines the target's endianness at compile time.
|
||||
*/
|
||||
struct host_endianness_check {
|
||||
constexpr static inline std::uint32_t impl = 0x01020304;
|
||||
constexpr static inline auto test = static_cast<const unsigned char&>(impl);
|
||||
constexpr static inline auto value = []{
|
||||
switch (test) {
|
||||
case 4: return endianness::little;
|
||||
case 1: return endianness::big;
|
||||
default: return endianness::other;
|
||||
}
|
||||
}();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function that swaps bytes of arbitrary lengths by copying input to
|
||||
* output in reverse order.
|
||||
*/
|
||||
template <typename Iter>
|
||||
constexpr void byte_swap(Iter begin, Iter end, Iter dest) {
|
||||
while (begin != end) {
|
||||
--end;
|
||||
*dest = *end;
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally swap the byte order of an array based on endianness. If the
|
||||
* endianness is identical, this should optimize away. Otherwise, a call to
|
||||
* byte_swap above should optimize to bswap or some equivalent assembly.
|
||||
*/
|
||||
template <endianness From, endianness To, std::size_t N>
|
||||
requires (From != endianness::other && To != endianness::other)
|
||||
constexpr auto maybe_swap(std::array<std::byte, N> val) noexcept {
|
||||
if constexpr (From == To) {
|
||||
return val;
|
||||
} else {
|
||||
decltype(val) dest;
|
||||
byte_swap(val.begin(), val.end(), dest.begin());
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for converting a data type into an std::array for use
|
||||
* with byte swap operations. May also come in use for reinterpreting data
|
||||
* at a byte level.
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr auto raw_cast(T val) noexcept {
|
||||
union trick_to_array {
|
||||
T val;
|
||||
std::array<std::byte, sizeof(T)> data;
|
||||
};
|
||||
trick_to_array u{val};
|
||||
return u.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for converting a std::array into some arbitrary type.
|
||||
* Beware, this must be used on trivial data types.
|
||||
*/
|
||||
template <typename T, typename Array>
|
||||
constexpr auto value_cast(Array&& data) noexcept {
|
||||
union trick_to_value {
|
||||
Array data;
|
||||
T val;
|
||||
};
|
||||
trick_to_value u{std::forward<Array>(data)};
|
||||
return u.val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte swap implementation for arbitrary endiannesses.
|
||||
*/
|
||||
template <endianness From, endianness To, typename T>
|
||||
requires std::is_trivial_v<std::remove_reference_t<T>>
|
||||
constexpr T swap(T val) noexcept {
|
||||
return value_cast<T>(
|
||||
maybe_swap<From, To>(raw_cast(val)));
|
||||
}
|
||||
|
||||
} // namespace detail;
|
||||
|
||||
/**
|
||||
* Exposes endianness information about a target.
|
||||
*/
|
||||
struct endian {
|
||||
constexpr static inline auto big = endianness::big;
|
||||
constexpr static inline auto little = endianness::little;
|
||||
constexpr static inline auto network = endianness::big;
|
||||
constexpr static inline auto host = detail::host_endianness_check::value;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* The functions below implement byte-swapping on a type.
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
template <typename T>
|
||||
constexpr T be_to_host(T val) noexcept {
|
||||
return detail::swap<endian::big, endian::host>(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T host_to_be(T val) noexcept {
|
||||
return detail::swap<endian::host, endian::big>(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T le_to_host(T val) noexcept {
|
||||
return detail::swap<endian::little, endian::host>(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T host_to_le(T val) noexcept {
|
||||
return detail::swap<endian::host, endian::little>(val);
|
||||
}
|
||||
|
||||
#endif // msgpack_endianness_d8ae54f45851ed13
|
||||
40
tests/msgpack/BUILD
Normal file
40
tests/msgpack/BUILD
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
cc_library(
|
||||
name = "test_deps",
|
||||
srcs = [
|
||||
"test_main.cpp",
|
||||
"rng.h"
|
||||
],
|
||||
deps = [
|
||||
"//source:msgpack",
|
||||
"@expected",
|
||||
"@ut",
|
||||
"@fmt",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "reader",
|
||||
srcs = [
|
||||
"test_reader_relaxed.cpp",
|
||||
"test_reader_strict.cpp",
|
||||
],
|
||||
deps = ["test_deps"],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "writer",
|
||||
srcs = [
|
||||
"test_writer.cpp",
|
||||
],
|
||||
deps = ["test_deps"],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "object",
|
||||
srcs = [
|
||||
"test_object.cpp",
|
||||
],
|
||||
deps = ["test_deps"],
|
||||
)
|
||||
|
||||
39
tests/msgpack/rng.h
Normal file
39
tests/msgpack/rng.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef tests_rng_483f8f25f09ae06
|
||||
#define tests_rng_483f8f25f09ae06
|
||||
|
||||
#include <array>
|
||||
#include <random>
|
||||
|
||||
struct rng {
|
||||
rng(auto s) : seed(s), generator(seed) {}
|
||||
rng() : rng([]{ return std::random_device{}(); }()) {}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T>
|
||||
T get() noexcept {
|
||||
union {
|
||||
std::array<std::mt19937::result_type, 1 + sizeof(T) / sizeof(std::mt19937::result_type)> data;
|
||||
T v;
|
||||
} u;
|
||||
for (auto& d : u.data) {
|
||||
d = generator();
|
||||
}
|
||||
|
||||
return u.v;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
requires std::is_integral_v<T>
|
||||
auto get() noexcept {
|
||||
std::array<T, N> values;
|
||||
for (auto& value : values) {
|
||||
value = get<T>();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::random_device::result_type seed;
|
||||
std::mt19937 generator;
|
||||
};
|
||||
|
||||
#endif // tests_rng_483f8f25f09ae06
|
||||
2
tests/msgpack/test_main.cpp
Normal file
2
tests/msgpack/test_main.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
int main(int, char**) {}
|
||||
155
tests/msgpack/test_object.cpp
Normal file
155
tests/msgpack/test_object.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#include "source/include/parselink/msgpack/object.h"
|
||||
#include <string>
|
||||
#include <tl/expected.hpp>
|
||||
#include <msgpack/object.h>
|
||||
|
||||
#include <boost/ut.hpp>
|
||||
|
||||
using namespace boost::ut;
|
||||
|
||||
namespace {
|
||||
template <typename... Bytes>
|
||||
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
|
||||
return {std::byte(std::forward<Bytes>(bytes))...};
|
||||
}
|
||||
|
||||
template <typename First, typename... Others>
|
||||
constexpr bool wrong_types(auto const& obj) {
|
||||
auto err = tl::make_unexpected(msgpack::object_error::wrong_type);
|
||||
if (obj.template get<First>() != err) return false;
|
||||
if constexpr (sizeof...(Others)) {
|
||||
return wrong_types<Others...>(obj);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
std::ostream &operator<<(std::ostream &os, tl::expected<T, E> const &exp) {
|
||||
if (exp.has_value()) {
|
||||
os << "Value: '" << *exp << "'";
|
||||
} else {
|
||||
os << "Error";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
||||
enum class foo : std::uint8_t {
|
||||
a, b, c, d, e
|
||||
};
|
||||
|
||||
suite size_and_enum = [] {
|
||||
"expected use case"_test = [] {
|
||||
msgpack::size_and_enum<std::uint32_t, foo> value;
|
||||
expect(sizeof(value) == 8);
|
||||
expect(value.rep() == uintptr_t{});
|
||||
expect(value.get_size() == std::uint32_t{});
|
||||
expect(value.get_enum() == foo::a);
|
||||
value.set_size(std::uint32_t(-1));
|
||||
expect(value.get_size() == std::uint32_t(-1));
|
||||
expect(value.get_enum() == foo::a);
|
||||
expect(value.rep() == 0xffffffff00000000);
|
||||
value.set_enum(foo::c);
|
||||
expect(value.get_size() == std::uint32_t(-1));
|
||||
expect(value.get_enum() == foo::c);
|
||||
expect(value.rep() == 0xffffffff00000002);
|
||||
value.set_both(0xaa, foo::e);
|
||||
expect(value.get_size() == 0xaa);
|
||||
expect(value.get_enum() == foo::e);
|
||||
expect(value.rep() == 0x000000aa00000004);
|
||||
};
|
||||
};
|
||||
|
||||
suite assignment_and_access = [] {
|
||||
"object::object()"_test = [] {
|
||||
msgpack::object obj;
|
||||
expect(obj.type() == msgpack::object_type::invalid);
|
||||
};
|
||||
"object::object(bool)"_test = [] {
|
||||
msgpack::object obj(true);
|
||||
expect(obj.type() == msgpack::object_type::boolean);
|
||||
auto retrieved = obj.get<bool>();
|
||||
expect(retrieved && *retrieved);
|
||||
expect(wrong_types<int, unsigned, char, std::string_view>(obj));
|
||||
};
|
||||
"object::object(std::int8_t)"_test = [] {
|
||||
std::int8_t val = 0x32;
|
||||
msgpack::object obj(val);
|
||||
expect(obj.type() == msgpack::object_type::signed_int);
|
||||
auto retrieved = obj.get<std::int8_t>();
|
||||
expect(retrieved && *retrieved == val);
|
||||
expect(wrong_types<bool, unsigned, std::string_view>(obj));
|
||||
};
|
||||
"object::object(std::uint8_t)"_test = [] {
|
||||
std::uint8_t val = 0xaa;
|
||||
msgpack::object obj(val);
|
||||
expect(obj.type() == msgpack::object_type::unsigned_int);
|
||||
auto retrieved = obj.get<std::uint8_t>();
|
||||
expect(retrieved && *retrieved == val);
|
||||
expect(wrong_types<bool, int, std::string_view>(obj));
|
||||
};
|
||||
"object::object(char const*)"_test = [] {
|
||||
std::string extracted_val;
|
||||
{
|
||||
char const* val = "hello world";
|
||||
msgpack::object obj(val);
|
||||
expect(obj.type() == msgpack::object_type::string);
|
||||
auto retrieved = obj.get<std::string_view>();
|
||||
expect(bool(retrieved));
|
||||
if (*retrieved != std::string_view(val)) {
|
||||
expect(false);
|
||||
}
|
||||
expect(wrong_types<bool, int, unsigned>(obj));
|
||||
auto string_retrieved = obj.get<std::string>();
|
||||
expect(bool(string_retrieved));
|
||||
extracted_val = std::move(*string_retrieved);
|
||||
}
|
||||
expect(extracted_val == "hello world");
|
||||
};
|
||||
"object::object(std::string)"_test = [] {
|
||||
std::string val = "std::string";
|
||||
msgpack::object obj(val);
|
||||
expect(obj.type() == msgpack::object_type::string);
|
||||
auto retrieved = obj.get<std::string_view>();
|
||||
expect(bool(retrieved));
|
||||
expect(*retrieved == val);
|
||||
expect(wrong_types<bool, int, unsigned>(obj));
|
||||
};
|
||||
"object::object(std::span<byte const>)"_test = [] {
|
||||
auto expected_val = make_bytes(0x32, 0xff, 0xaa, 0xce);
|
||||
std::vector<std::byte> extracted_val;
|
||||
{
|
||||
auto val = make_bytes(0x32, 0xff, 0xaa, 0xce);
|
||||
msgpack::object obj(val);
|
||||
expect(obj.type() == msgpack::object_type::bytes);
|
||||
auto retrieved = obj.get<std::span<std::byte const>>();
|
||||
expect(bool(retrieved));
|
||||
expect(std::equal(retrieved->begin(), retrieved->end(),
|
||||
val.begin(), val.end()));
|
||||
expect(wrong_types<bool, int, unsigned, std::string_view>(obj));
|
||||
auto bytes_retrieved = obj.get<std::vector<std::byte>>();
|
||||
expect(bool(bytes_retrieved));
|
||||
extracted_val = std::move(*bytes_retrieved);
|
||||
}
|
||||
expect(std::equal(expected_val.begin(), expected_val.end(),
|
||||
extracted_val.begin(), extracted_val.end()));
|
||||
};
|
||||
};
|
||||
|
||||
suite int_truncation = [] {
|
||||
"unsigned truncation"_test = [] {
|
||||
msgpack::object obj(0xffffffffu);
|
||||
expect(obj.type() == msgpack::object_type::unsigned_int);
|
||||
auto retrieved = obj.get<std::uint8_t>();
|
||||
auto err = tl::make_unexpected(msgpack::object_error::will_truncate);
|
||||
expect(retrieved == err);
|
||||
};
|
||||
"signed truncation"_test = [] {
|
||||
msgpack::object obj(-0xffff);
|
||||
expect(obj.type() == msgpack::object_type::signed_int);
|
||||
auto retrieved = obj.get<std::int8_t>();
|
||||
auto err = tl::make_unexpected(msgpack::object_error::will_truncate);
|
||||
expect(retrieved == err);
|
||||
};
|
||||
};
|
||||
130
tests/msgpack/test_reader_relaxed.cpp
Normal file
130
tests/msgpack/test_reader_relaxed.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include <msgpack/core/reader.h>
|
||||
|
||||
#include <boost/ut.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace boost::ut;
|
||||
namespace format = msgpack::format;
|
||||
|
||||
template <typename... Bytes>
|
||||
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
|
||||
return {std::byte(std::forward<Bytes>(bytes))...};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t C = 1024> struct oversized_array {
|
||||
std::array<T, C> data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
constexpr auto to_bytes_array_oversized(auto const &container) {
|
||||
oversized_array<std::byte> arr;
|
||||
std::copy(std::begin(container), std::end(container), std::begin(arr.data));
|
||||
arr.size = std::distance(std::begin(container), std::end(container));
|
||||
return arr;
|
||||
}
|
||||
|
||||
consteval auto generate_bytes(auto callable) {
|
||||
constexpr auto oversized = to_bytes_array_oversized(callable());
|
||||
std::array<std::byte, oversized.size> out;
|
||||
std::copy(std::begin(oversized.data),
|
||||
std::next(std::begin(oversized.data), oversized.size),
|
||||
std::begin(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <std::size_t A, std::size_t B>
|
||||
consteval auto cat(std::array<std::byte, A> const &a,
|
||||
std::array<std::byte, B> const &b) {
|
||||
std::array<std::byte, A + B> out;
|
||||
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
|
||||
std::begin(out));
|
||||
std::copy(std::begin(b), std::next(std::begin(b), std::size(b)),
|
||||
std::next(std::begin(out), std::size(a)));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T, typename Itr>
|
||||
constexpr auto zip(T val, Itr begin, Itr end) {
|
||||
std::vector<T> output;
|
||||
for (auto itr = begin; itr != end; ++itr) {
|
||||
output.emplace_back(val);
|
||||
output.emplace_back(*itr);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename T, typename Iterable>
|
||||
constexpr auto zip(T val, Iterable const &itr) {
|
||||
return zip(val, std::begin(itr), std::end(itr));
|
||||
}
|
||||
|
||||
using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>;
|
||||
|
||||
} // namespace
|
||||
|
||||
suite relaxed = [] {
|
||||
"empty span"_test = [] {
|
||||
using error = msgpack::reader_error;
|
||||
std::span<std::byte> bytes;
|
||||
relaxed_reader reader(bytes);
|
||||
auto v = reader.read<msgpack::format::uint8>();
|
||||
expect(v.error() == error::end_of_message);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Unsigned integers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
"reader<reader_policy::relaxed>::read<format::uint8>"_test = [] {
|
||||
using fmt = format::uint8;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* All bytes 0x00->0x79 are effectively literal uint8s.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84);
|
||||
relaxed_reader reader(payload);
|
||||
{
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint8_t(0x52));
|
||||
}
|
||||
{
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint8_t(0x84));
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
};
|
||||
|
||||
"reader<reader_policy::relaxed>::read<formats::uint16>"_test = [] {
|
||||
using fmt = msgpack::format::uint16;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* All bytes 0x00->0x79 are effectively literal uint8s.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84, 0xcd, 0xaa, 0xcc);
|
||||
relaxed_reader reader(payload);
|
||||
{
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint16_t(0x52));
|
||||
}
|
||||
{
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint16_t(0x84));
|
||||
}
|
||||
{
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint16_t(0xaacc));
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
707
tests/msgpack/test_reader_strict.cpp
Normal file
707
tests/msgpack/test_reader_strict.cpp
Normal file
@ -0,0 +1,707 @@
|
||||
#include <msgpack/core/reader.h>
|
||||
#include "rng.h"
|
||||
|
||||
#include <boost/ut.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
constexpr static std::size_t rng_samples_count = 10;
|
||||
|
||||
template <typename E> constexpr auto enum_name() noexcept {
|
||||
return __PRETTY_FUNCTION__;
|
||||
}
|
||||
|
||||
using namespace boost::ut;
|
||||
namespace format = msgpack::format;
|
||||
|
||||
template <typename... Bytes>
|
||||
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
|
||||
return {std::byte(std::forward<Bytes>(bytes))...};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t C = 1024> struct oversized_array {
|
||||
std::array<T, C> data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
constexpr auto to_bytes_array_oversized(auto const &container) {
|
||||
oversized_array<std::byte> arr;
|
||||
std::copy(std::begin(container), std::end(container), std::begin(arr.data));
|
||||
arr.size = std::distance(std::begin(container), std::end(container));
|
||||
return arr;
|
||||
}
|
||||
|
||||
consteval auto generate_bytes(auto callable) {
|
||||
constexpr auto oversized = to_bytes_array_oversized(callable());
|
||||
std::array<std::byte, oversized.size> out;
|
||||
std::copy(std::begin(oversized.data),
|
||||
std::next(std::begin(oversized.data), oversized.size),
|
||||
std::begin(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <std::size_t A, std::size_t B>
|
||||
consteval auto cat(std::array<std::byte, A> const &a,
|
||||
std::array<std::byte, B> const &b) {
|
||||
std::array<std::byte, A + B> out;
|
||||
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
|
||||
std::begin(out));
|
||||
std::copy(std::begin(b), std::next(std::begin(b), std::size(b)),
|
||||
std::next(std::begin(out), std::size(a)));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T, typename Itr>
|
||||
constexpr auto zip(T val, Itr begin, Itr end) {
|
||||
std::vector<T> output;
|
||||
for (auto itr = begin; itr != end; ++itr) {
|
||||
output.emplace_back(val);
|
||||
output.emplace_back(*itr);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename T, typename Iterable>
|
||||
constexpr auto zip(T val, Iterable const &itr) {
|
||||
return zip(val, std::begin(itr), std::end(itr));
|
||||
}
|
||||
|
||||
constexpr auto from_string_view(std::string_view sv) {
|
||||
std::vector<std::byte> range;
|
||||
range.resize(sv.size());
|
||||
auto itr = range.begin();
|
||||
for (auto c : sv) {
|
||||
*itr = std::byte(c);
|
||||
++itr;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
constexpr auto make_contiguous_range(std::uint8_t start, std::uint8_t end) {
|
||||
auto count = std::size_t(end) - std::size_t(start) + 1;
|
||||
std::vector<std::byte> range;
|
||||
range.resize(count);
|
||||
for (auto i = std::size_t(start); i <= std::size_t(end); ++i) {
|
||||
range[i - std::size_t(start)] = std::byte(i);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
suite reader = [] {
|
||||
"empty span"_test = [] {
|
||||
std::span<std::byte> bytes;
|
||||
msgpack::reader reader(bytes);
|
||||
auto v = reader.read<msgpack::format::uint8>();
|
||||
expect(v.error() == msgpack::reader_error::end_of_message);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Unsigned integers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
"reader::read<format::positive_fixint>"_test = [] {
|
||||
using fmt = format::positive_fixint;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* All bytes 0x00->0x79 are effectively literal uint8s.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload =
|
||||
generate_bytes([] { return make_contiguous_range(0, 0x79); });
|
||||
msgpack::reader reader(payload);
|
||||
static_assert(
|
||||
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::uint8_t>);
|
||||
for (auto byte : payload) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint8_t(byte));
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
{
|
||||
constexpr auto payload =
|
||||
generate_bytes([] { return make_contiguous_range(0x80, 0xff); });
|
||||
// Continually narrow the span over the range, as read will not
|
||||
// advance the internal span on failure.
|
||||
for (auto itr = payload.begin(); itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader(std::span(itr, std::end(payload)));
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::wrong_type));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::uint8>"_test = [] {
|
||||
using fmt = format::uint8;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* The uint8 format is 0xcc followed by one byte of data.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload = generate_bytes([] {
|
||||
return zip(std::byte{0xcc}, make_contiguous_range(0x00, 0xff));
|
||||
});
|
||||
msgpack::reader reader(payload);
|
||||
static_assert(
|
||||
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::uint8_t>);
|
||||
for (auto i = 0; i < 0x100; ++i) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::uint8_t(i));
|
||||
}
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
{
|
||||
using error = msgpack::reader_error;
|
||||
// Test that partial read fails.
|
||||
constexpr auto payload = make_bytes(0xcc);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
// Retry, ensure the error remains the same.
|
||||
result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::uint16>"_test = [] {
|
||||
using fmt = format::uint16;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
{
|
||||
auto samples = rng.get<std::uint16_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xcd});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sample);
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xee);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::wrong_type));
|
||||
}
|
||||
|
||||
// Test that partial read fails.
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xcd);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
constexpr auto payload2 = make_bytes(0xcd, 0x01);
|
||||
reader = msgpack::reader(payload2);
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::uint32>"_test = [] {
|
||||
using fmt = format::uint32;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
auto samples = rng.get<std::uint32_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xce});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(bool(result));
|
||||
expect(result == sample);
|
||||
}
|
||||
// Test that partial read fails.
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xce, 0x01, 0x02, 0x03, 0x09);
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == 0x01020309);
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::uint64>"_test = [] {
|
||||
using fmt = format::uint64;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
auto samples = rng.get<std::uint64_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xcf});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(bool(result));
|
||||
expect(result == sample);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto payload =
|
||||
make_bytes(0xcf, 0x01, 0x02, 0x03, 0x09, 0x10, 0x20, 0x30, 0x90);
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == 0x0102030910203090);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Signed integers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
"reader::read<format::negative_fixint>"_test = [] {
|
||||
using fmt = format::negative_fixint;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* All bytes 0x00->0x79 are effectively literal uint8s.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload =
|
||||
generate_bytes([] { return make_contiguous_range(0xe0, 0xff); });
|
||||
msgpack::reader reader(payload);
|
||||
static_assert(
|
||||
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::int8_t>);
|
||||
for (auto byte : payload) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == std::int8_t(byte));
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
{
|
||||
constexpr auto payload =
|
||||
generate_bytes([] { return make_contiguous_range(0x00, 0xdf); });
|
||||
// Continually narrow the span over the range, as read will not
|
||||
// advance the internal span on failure.
|
||||
for (auto itr = payload.begin(); itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader(std::span(itr, std::end(payload)));
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::wrong_type));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::int8>"_test = [] {
|
||||
using fmt = format::int8;
|
||||
using error = msgpack::reader_error;
|
||||
/**
|
||||
* The uint8 format is 0xcc followed by one byte of data.
|
||||
*/
|
||||
{
|
||||
constexpr auto payload = generate_bytes([] {
|
||||
return zip(std::byte{0xd0}, make_contiguous_range(0x00, 0xff));
|
||||
});
|
||||
msgpack::reader reader(payload);
|
||||
static_assert(
|
||||
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::int8_t>);
|
||||
for (auto i = 0; i < 0x100; ++i) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(*result == std::int8_t(i));
|
||||
}
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
{
|
||||
using error = msgpack::reader_error;
|
||||
// Test that partial read fails.
|
||||
constexpr auto payload = make_bytes(0xd0);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
// Retry, ensure the error remains the same.
|
||||
result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::int16>"_test = [] {
|
||||
using fmt = format::int16;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
{
|
||||
auto samples = rng.get<std::int16_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xd1});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sample);
|
||||
}
|
||||
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::end_of_message));
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xd2);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::wrong_type));
|
||||
}
|
||||
|
||||
// Test that partial read fails.
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xd1);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
constexpr auto payload2 = make_bytes(0xd1, 0x01);
|
||||
reader = msgpack::reader(payload2);
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::int32>"_test = [] {
|
||||
using fmt = format::int32;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
auto samples = rng.get<std::int32_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xd2});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(bool(result));
|
||||
expect(result == sample);
|
||||
}
|
||||
// Test that partial read fails.
|
||||
{
|
||||
constexpr auto payload = make_bytes(0xd2, 0xff, 0xff, 0xff, 0xfe);
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == -2);
|
||||
}
|
||||
};
|
||||
|
||||
"reader::read<format::int64>"_test = [] {
|
||||
using fmt = format::int64;
|
||||
using error = msgpack::reader_error;
|
||||
rng rng;
|
||||
auto samples = rng.get<std::int64_t, rng_samples_count>();
|
||||
std::vector<std::byte> payload;
|
||||
for (auto sample : samples) {
|
||||
auto bytes = ::detail::raw_cast(host_to_be(sample));
|
||||
payload.push_back(std::byte{0xd3});
|
||||
for (auto byte : bytes) {
|
||||
payload.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
for (auto sample : samples) {
|
||||
auto result = reader.read<fmt>();
|
||||
expect(bool(result));
|
||||
expect(result == sample);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto payload =
|
||||
make_bytes(0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0);
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == -16);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Strings
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
"reader::read<fmt::fixstr>"_test = [] {
|
||||
using fmt = msgpack::format::fixstr;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr std::string_view sv = "hello";
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xa0 + sv.size()),
|
||||
generate_bytes([sv] { return from_string_view(sv); }));
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sv);
|
||||
};
|
||||
|
||||
"reader::read<fmt::str8>"_test = [] {
|
||||
using fmt = msgpack::format::str8;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr std::string_view sv = "hello d";
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xd9, sv.size()),
|
||||
generate_bytes([sv] { return from_string_view(sv); }));
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sv);
|
||||
};
|
||||
|
||||
"reader::read<fmt::str16>"_test = [] {
|
||||
using fmt = msgpack::format::str16;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr std::string_view sv = "hello world";
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xda, 0x00, sv.size()),
|
||||
generate_bytes([sv] { return from_string_view(sv); }));
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sv);
|
||||
};
|
||||
|
||||
"reader::read<fmt::str32>"_test = [] {
|
||||
using fmt = msgpack::format::str32;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr std::string_view sv = "hello world";
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xdb, 0x00, 0x00, 0x00, sv.size()),
|
||||
generate_bytes([sv] { return from_string_view(sv); }));
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == sv);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Binary payloads
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
"reader::read<fmt::bin8>"_test = [] {
|
||||
using fmt = msgpack::format::bin8;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr auto bv = make_bytes(0x0, 0x01, 0x02, 0x04);
|
||||
constexpr auto payload = cat(make_bytes(0xc4, bv.size()), bv);
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result.has_value());
|
||||
expect(
|
||||
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end()));
|
||||
};
|
||||
|
||||
"reader::read<fmt::bin16>"_test = [] {
|
||||
using fmt = msgpack::format::bin16;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr auto bv = generate_bytes([] {
|
||||
auto rg = make_contiguous_range(0, 0xff);
|
||||
std::vector<std::byte> rg2(rg.size() * 2);
|
||||
auto itr = std::copy(rg.begin(), rg.end(), rg2.begin());
|
||||
std::copy(rg.begin(), rg.end(), itr);
|
||||
return rg2;
|
||||
});
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xc5, bv.size() >> 8, bv.size() & 0xff), bv);
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(
|
||||
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end()));
|
||||
};
|
||||
|
||||
// TODO: Support this with a proper test payload
|
||||
"reader::read<fmt::bin32>"_test = [] {
|
||||
using fmt = msgpack::format::bin32;
|
||||
using error = msgpack::reader_error;
|
||||
constexpr auto bv = generate_bytes([] {
|
||||
auto rg = make_contiguous_range(0, 0xff);
|
||||
std::vector<std::byte> rg2(rg.size() * 2);
|
||||
auto itr = std::copy(rg.begin(), rg.end(), rg2.begin());
|
||||
std::copy(rg.begin(), rg.end(), itr);
|
||||
return rg2;
|
||||
});
|
||||
constexpr auto payload =
|
||||
cat(make_bytes(0xc6, bv.size() >> 24, bv.size() >> 16, bv.size() >> 8,
|
||||
bv.size() & 0xff),
|
||||
bv);
|
||||
|
||||
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
|
||||
msgpack::reader reader({payload.begin(), itr});
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == tl::make_unexpected(error::premature_end_of_message));
|
||||
}
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(
|
||||
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end()));
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc formats
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
"reader::read<fmt::nil>"_test = [] {
|
||||
using fmt = msgpack::format::nil;
|
||||
constexpr auto payload = make_bytes(0xc0);
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result.has_value());
|
||||
};
|
||||
|
||||
"reader::read<fmt::boolean>"_test = [] {
|
||||
using fmt = msgpack::format::boolean;
|
||||
constexpr auto payload = make_bytes(0xc2, 0xc3);
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == false);
|
||||
result = reader.read<fmt>();
|
||||
expect(result == true);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Structural formats
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
"reader::read<fmt::fixarray>"_test = [] {
|
||||
using fmt = msgpack::format::fixarray;
|
||||
using error = msgpack::reader_error;
|
||||
// A MessagePack array of 5 8-bit unsigned integers.
|
||||
constexpr auto payload = make_bytes(0x95, 0xcc, 0x85, 0xcc, 0x84, 0xcc,
|
||||
0x83, 0xcc, 0x82, 0xcc, 0x81);
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == msgpack::array_desc{5});
|
||||
|
||||
for (std::size_t i = 0; i < (*result).count; ++i) {
|
||||
auto v = reader.read<format::uint8>();
|
||||
expect(v == 0x85 - i);
|
||||
}
|
||||
|
||||
auto end = reader.read<format::uint8>();
|
||||
expect(end == tl::make_unexpected(error::end_of_message));
|
||||
};
|
||||
|
||||
"reader::read<fmt::array16>"_test = [] {
|
||||
using fmt = msgpack::format::array16;
|
||||
using error = msgpack::reader_error;
|
||||
// A MessagePack array of 5 8-bit unsigned integers.
|
||||
constexpr auto payload =
|
||||
make_bytes(0xdc, 0x00, 0x05, 0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc,
|
||||
0x82, 0xcc, 0x81);
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == msgpack::array_desc{5});
|
||||
|
||||
for (std::size_t i = 0; i < (*result).count; ++i) {
|
||||
auto v = reader.read<format::uint8>();
|
||||
expect(v == 0x85 - i);
|
||||
}
|
||||
|
||||
auto end = reader.read<format::uint8>();
|
||||
expect(end == tl::make_unexpected(error::end_of_message));
|
||||
};
|
||||
|
||||
"reader::read<fmt::array32>"_test = [] {
|
||||
using fmt = msgpack::format::array32;
|
||||
using error = msgpack::reader_error;
|
||||
// A MessagePack array of 5 8-bit unsigned integers.
|
||||
constexpr auto payload =
|
||||
make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, 0xcc, 0x85, 0xcc, 0x84, 0xcc,
|
||||
0x83, 0xcc, 0x82, 0xcc, 0x81);
|
||||
|
||||
msgpack::reader reader(payload);
|
||||
auto result = reader.read<fmt>();
|
||||
expect(result == msgpack::array_desc{5});
|
||||
|
||||
for (std::size_t i = 0; i < (*result).count; ++i) {
|
||||
auto v = reader.read<format::uint8>();
|
||||
expect(v == 0x85 - i);
|
||||
}
|
||||
|
||||
auto end = reader.read<format::uint8>();
|
||||
expect(end == tl::make_unexpected(error::end_of_message));
|
||||
};
|
||||
};
|
||||
|
||||
424
tests/msgpack/test_writer.cpp
Normal file
424
tests/msgpack/test_writer.cpp
Normal file
@ -0,0 +1,424 @@
|
||||
#include <msgpack/core/writer.h>
|
||||
|
||||
#include <boost/ut.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace boost::ut;
|
||||
namespace format = msgpack::format;
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename... Bytes>
|
||||
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
|
||||
return {std::byte(std::forward<Bytes>(bytes))...};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t C = 1024> struct oversized_array {
|
||||
std::array<T, C> data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
constexpr auto to_bytes_array_oversized(auto const &container) {
|
||||
oversized_array<std::byte> arr;
|
||||
std::copy(std::begin(container), std::end(container), std::begin(arr.data));
|
||||
arr.size = std::distance(std::begin(container), std::end(container));
|
||||
return arr;
|
||||
}
|
||||
|
||||
consteval auto generate_bytes(auto callable) {
|
||||
constexpr auto oversized = to_bytes_array_oversized(callable());
|
||||
std::array<std::byte, oversized.size> out;
|
||||
std::copy(std::begin(oversized.data),
|
||||
std::next(std::begin(oversized.data), oversized.size),
|
||||
std::begin(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <std::size_t A, std::size_t B>
|
||||
consteval auto cat(std::array<std::byte, A> const &a,
|
||||
std::array<std::byte, B> const &b) {
|
||||
std::array<std::byte, A + B> out;
|
||||
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
|
||||
std::begin(out));
|
||||
std::copy(std::begin(b), std::next(std::begin(b), std::size(b)),
|
||||
std::next(std::begin(out), std::size(a)));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T, typename Itr>
|
||||
constexpr auto zip(T val, Itr begin, Itr end) {
|
||||
std::vector<T> output;
|
||||
for (auto itr = begin; itr != end; ++itr) {
|
||||
output.emplace_back(val);
|
||||
output.emplace_back(*itr);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename T, typename Iterable>
|
||||
constexpr auto zip(T val, Iterable const &itr) {
|
||||
return zip(val, std::begin(itr), std::end(itr));
|
||||
}
|
||||
|
||||
constexpr auto from_string_view(std::string_view sv) {
|
||||
std::vector<std::byte> range;
|
||||
range.resize(sv.size());
|
||||
auto itr = range.begin();
|
||||
for (auto c : sv) {
|
||||
*itr = std::byte(c);
|
||||
++itr;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
constexpr auto make_contiguous_range(std::uint8_t start, std::uint8_t end) {
|
||||
auto count = std::size_t(end) - std::size_t(start) + 1;
|
||||
std::vector<std::byte> range;
|
||||
range.resize(count);
|
||||
for (auto i = std::size_t(start); i <= std::size_t(end); ++i) {
|
||||
range[i - std::size_t(start)] = std::byte(i);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
constexpr auto equal(auto a, auto b) {
|
||||
return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
suite writer = [] {
|
||||
"writer empty span"_test = [] {
|
||||
std::array<std::byte, 16> payload;
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
expect(writer.tell() == 0);
|
||||
};
|
||||
|
||||
"writer::write<format::positive_fixint>"_test = [] {
|
||||
using fmt = format::positive_fixint;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 2> payload;
|
||||
auto constexpr expected = make_bytes(0x32, 0x55);
|
||||
msgpack::writer writer(payload);
|
||||
auto result = writer.write<fmt>(std::uint8_t{0x32});
|
||||
expect(!!result);
|
||||
expect(writer.tell() == 1);
|
||||
expect(*writer.subspan().begin() == std::byte{0x32});
|
||||
expect(writer.write<fmt>(std::uint8_t{0x82}) == tl::make_unexpected(error::bad_value));
|
||||
|
||||
writer.write(std::uint8_t{0x55});
|
||||
expect(writer.tell() == 2);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::uint8>"_test = [] {
|
||||
using fmt = format::uint8;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 4> payload;
|
||||
auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(std::uint8_t{0x32}));
|
||||
expect(writer.tell() == 2);
|
||||
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
|
||||
expect(!!writer.write<fmt>(std::uint8_t{0x82}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::uint16>"_test = [] {
|
||||
using fmt = format::uint16;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 6> payload;
|
||||
auto constexpr expected = make_bytes(0xcd, 0x32, 0xcc, 0xcd, 0xaa, 0xff);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(std::uint16_t{0x32cc}));
|
||||
expect(writer.tell() == 3);
|
||||
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
|
||||
expect(!!writer.write<fmt>(0xaaff));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::uint32>"_test = [] {
|
||||
using fmt = format::uint32;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 5> payload;
|
||||
auto constexpr expected = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(0x01020304));
|
||||
expect(writer.tell() == 5);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::uint64>"_test = [] {
|
||||
using fmt = format::uint64;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 9> payload;
|
||||
auto constexpr expected = make_bytes(
|
||||
0xcf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(0x0102030405060708));
|
||||
expect(writer.tell() == 9);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::negative_fixint>"_test = [] {
|
||||
using fmt = format::negative_fixint;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 2> payload;
|
||||
auto constexpr expected = make_bytes(0xff, 0xe0);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(-1));
|
||||
expect(writer.tell() == 1);
|
||||
expect(*writer.subspan().begin() == std::byte{0xff});
|
||||
expect(writer.write<fmt>(-33) == tl::make_unexpected(error::bad_value));
|
||||
expect(!!writer.write<fmt>(-32));
|
||||
expect(writer.tell() == 2);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(-5) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::int8>"_test = [] {
|
||||
using fmt = format::int8;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 4> payload;
|
||||
auto constexpr expected = make_bytes(0xd0, 0x32, 0xd0, 0xfb);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(std::int8_t{0x32}));
|
||||
expect(writer.tell() == 2);
|
||||
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
|
||||
expect(!!writer.write<fmt>(std::int8_t{-5}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::int16>"_test = [] {
|
||||
using fmt = format::int16;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 6> payload;
|
||||
auto constexpr expected = make_bytes(0xd1, 0x32, 0xcc, 0xd1, 0xff, 0xfa);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(std::int16_t{0x32cc}));
|
||||
expect(writer.tell() == 3);
|
||||
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
|
||||
expect(!!writer.write<fmt>(-6));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::int32>"_test = [] {
|
||||
using fmt = format::int32;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 5> payload;
|
||||
auto constexpr expected = make_bytes(0xd2, 0x01, 0x02, 0x03, 0x04);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(0x01020304));
|
||||
expect(writer.tell() == 5);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::int64>"_test = [] {
|
||||
using fmt = format::int64;
|
||||
using error = msgpack::writer_error;
|
||||
|
||||
std::array<std::byte, 9> payload;
|
||||
auto constexpr expected = make_bytes(
|
||||
0xd3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(std::int64_t{0x0102030405060708}));
|
||||
expect(writer.tell() == 9);
|
||||
expect(equal(writer.subspan(), expected));
|
||||
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
|
||||
};
|
||||
|
||||
"writer::write<format::fixstr>"_test = [] {
|
||||
using fmt = format::fixstr;
|
||||
|
||||
std::array<std::byte, 4> payload;
|
||||
auto constexpr expected = make_bytes(0xa2, 'o', 'h');
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>("oh"));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::str8>"_test = [] {
|
||||
using fmt = format::str8;
|
||||
std::array<std::byte, 46> payload;
|
||||
auto constexpr expected = make_bytes(0xd9, 44,
|
||||
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
|
||||
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
|
||||
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
|
||||
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::string_view txt =
|
||||
"the quick brown fox jumped over the lazy dog";
|
||||
expect(!!writer.write<fmt>(std::move(txt)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::str16>"_test = [] {
|
||||
using fmt = format::str16;
|
||||
std::array<std::byte, 47> payload;
|
||||
auto constexpr expected = make_bytes(0xda, 0, 44,
|
||||
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
|
||||
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
|
||||
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
|
||||
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::string_view txt =
|
||||
"the quick brown fox jumped over the lazy dog";
|
||||
expect(!!writer.write<fmt>(std::move(txt)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::str32>"_test = [] {
|
||||
using fmt = format::str32;
|
||||
std::array<std::byte, 49> payload;
|
||||
auto constexpr expected = make_bytes(0xdb, 0, 0, 0, 44,
|
||||
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
|
||||
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
|
||||
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
|
||||
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::string_view txt =
|
||||
"the quick brown fox jumped over the lazy dog";
|
||||
expect(!!writer.write<fmt>(std::move(txt)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::bin8>"_test = [] {
|
||||
using fmt = format::bin8;
|
||||
std::array<std::byte, 12> payload;
|
||||
auto constexpr expected = make_bytes(0xc4, 0x07, 0x01, 0x02, 0x03, 0x04,
|
||||
0xf8, 0xf9, 0xfa);
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::span<std::byte const> bv(expected.begin() + 2, expected.end());
|
||||
expect(!!writer.write<fmt>(std::move(bv)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::bin16>"_test = [] {
|
||||
using fmt = format::bin16;
|
||||
std::array<std::byte, 12> payload;
|
||||
auto constexpr expected = make_bytes(0xc5, 0x0, 0x07, 0x01, 0x02, 0x03,
|
||||
0x04, 0xf8, 0xf9, 0xfa);
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::span<std::byte const> bv(expected.begin() + 3, expected.end());
|
||||
expect(!!writer.write<fmt>(std::move(bv)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::bin32>"_test = [] {
|
||||
using fmt = format::bin32;
|
||||
std::array<std::byte, 12> payload;
|
||||
auto constexpr expected = make_bytes(0xc6, 0x0, 0x0, 0x0, 0x07, 0x01,
|
||||
0x02, 0x03, 0x04, 0xf8, 0xf9, 0xfa);
|
||||
|
||||
msgpack::writer writer(payload);
|
||||
std::span<std::byte const> bv(expected.begin() + 5, expected.end());
|
||||
expect(!!writer.write<fmt>(std::move(bv)));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::fixmap>"_test = [] {
|
||||
using fmt = format::fixmap;
|
||||
std::array<std::byte, 1> payload;
|
||||
auto constexpr expected = make_bytes(0x83);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::map_desc{3}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::map16>"_test = [] {
|
||||
using fmt = format::map16;
|
||||
std::array<std::byte, 3> payload;
|
||||
auto constexpr expected = make_bytes(0xde, 0x01, 0x00);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::map_desc{256}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::map32>"_test = [] {
|
||||
using fmt = format::map32;
|
||||
std::array<std::byte, 5> payload;
|
||||
auto constexpr expected = make_bytes(0xdf, 0x00, 0x01, 0x00, 0x00);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::map_desc{0x10000}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::fixarray>"_test = [] {
|
||||
using fmt = format::fixarray;
|
||||
std::array<std::byte, 1> payload;
|
||||
auto constexpr expected = make_bytes(0x93);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::array_desc{3}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::array16>"_test = [] {
|
||||
using fmt = format::array16;
|
||||
std::array<std::byte, 3> payload;
|
||||
auto constexpr expected = make_bytes(0xdc, 0x01, 0x00);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::array_desc{256}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::array32>"_test = [] {
|
||||
using fmt = format::array32;
|
||||
std::array<std::byte, 5> payload;
|
||||
auto constexpr expected = make_bytes(0xdd, 0x00, 0x01, 0x00, 0x00);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(msgpack::array_desc{0x10000}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::nil>"_test = [] {
|
||||
using fmt = format::nil;
|
||||
std::array<std::byte, 1> payload;
|
||||
auto constexpr expected = make_bytes(0xc0);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>({}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::invalid>"_test = [] {
|
||||
using fmt = format::invalid;
|
||||
std::array<std::byte, 1> payload;
|
||||
auto constexpr expected = make_bytes(0xc1);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>({}));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
|
||||
"writer::write<format::boolean>"_test = [] {
|
||||
using fmt = format::boolean;
|
||||
std::array<std::byte, 2> payload;
|
||||
auto constexpr expected = make_bytes(0xc2, 0xc3);
|
||||
msgpack::writer writer(payload);
|
||||
expect(!!writer.write<fmt>(false));
|
||||
expect(!!writer.write<fmt>(true));
|
||||
expect(equal(writer.subspan(), expected));
|
||||
};
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user