import msgpack, begin work on object API

This commit is contained in:
Kurt Sassenrath 2023-09-06 22:52:33 -07:00 committed by Kurt Sassenrath
parent ec3b953384
commit d3c59248a2
17 changed files with 2847 additions and 2 deletions

View File

@ -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"

View File

@ -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?

View File

@ -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 = [

View File

@ -0,0 +1,6 @@
#ifndef msgpack_9865a64955e64703
#define msgpack_9865a64955e64703
#include "msgpack/core.h"
#endif // msgpack_9865a64955e64703

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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

View File

@ -0,0 +1,2 @@
int main(int, char**) {}

View 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);
};
};

View 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));
}
};
};

View 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));
};
};

View 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));
};
};