Many more supported types in token reader.

This commit is contained in:
Kurt Sassenrath 2023-09-17 22:40:49 -07:00
parent 14620c4487
commit c5c03a2a40
6 changed files with 776 additions and 176 deletions

View File

@ -66,7 +66,7 @@ namespace format {
unsigned_int,
signed_int,
string,
bytes,
binary,
nil,
boolean,
array,
@ -74,8 +74,9 @@ namespace format {
};
// Flags that may control the behavior of readers/writers.
enum flag {
apply_mask = 1
enum flag : std::uint8_t {
apply_mask = 1,
fixed_size = 2,
};
// MessagePack formats break down into one of the following schemes:
@ -84,7 +85,7 @@ namespace format {
//
// In addition, there are "fix" types that embed the literal value or the
// payload length within the marker byte.
enum payload {
enum payload : std::uint8_t {
fixed,
variable
};
@ -94,20 +95,72 @@ namespace format {
constexpr static flag flags = {};
};
// Traits describing a particular format.
struct traits {
std::uint8_t flags{};
std::uint8_t size{};
std::byte mask{};
type token_type{};
constexpr auto operator<=>(traits const&) const noexcept = default;
};
// "Sentinel" traits instance indicating no trait found
constexpr static traits no_traits {};
struct fixtype_format : base_format {};
template <class>
struct resolve_token_type {
constexpr static type value = type::invalid;
};
template <std::integral T>
struct resolve_token_type<T> {
constexpr static type value = std::is_signed_v<T> ?
type::signed_int : type::unsigned_int;
};
template <>
struct resolve_token_type<std::string_view> {
constexpr static type value = type::string;
};
template <>
struct resolve_token_type<std::span<std::byte const>> {
constexpr static type value = type::binary;
};
template <>
struct resolve_token_type<bool> {
constexpr static type value = type::boolean;
};
template <>
struct resolve_token_type<nil> {
constexpr static type value = type::nil;
};
template <>
struct resolve_token_type<invalid> {
constexpr static type value = type::invalid;
};
template <typename T>
constexpr static auto resolve_type_v = resolve_token_type<T>::value;
// The library's representation of certain data types does not always
// match the underlying data serialized in the message. For example,
// arrays and maps are encoded as a marker byte + fixed length value, but
// msgpack will present them wrapped in a structure for stronger type
// semantics.
template <std::uint8_t Marker, typename First>
template <std::uint8_t Marker, typename First, typename Value = First>
struct format : base_format {
using first_type = First;
using value_type = first_type; // Can be overridden
using value_type = Value; // Can be overridden
constexpr static std::byte marker{Marker};
constexpr static payload payload_type{payload::fixed};
constexpr static std::uint8_t flags{};
};
template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t>
@ -127,10 +180,10 @@ namespace format {
// 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 = {};
constexpr static flag flags{};
};
struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
constexpr static flag flags = {};
constexpr static flag flags{};
};
struct uint8 : format<0xcc, std::uint8_t> {};
@ -146,9 +199,20 @@ namespace format {
/*
* 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; };
struct nil : fixtype<0xc0, 0x00> {
using value_type = msgpack::nil;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct invalid : fixtype<0xc1, 0x00> {
using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct boolean : fixtype<0xc2, 0x01> {
using value_type = bool;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
/*
* Maps
@ -183,6 +247,7 @@ namespace format {
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>> {};
@ -280,6 +345,38 @@ concept format_type = std::is_base_of_v<format::base_format, T>;
template <typename T>
concept is_fixtype = std::is_base_of_v<format::fixtype_format, T>;
namespace format {
// This template instantiates a format::traits object for a given format.
// This should only occur once per format type, and should happen at
// compile time.
//
// This isn't the prettiest way to go about it.
template <format_type Format>
inline traits const& get_traits() noexcept {
constexpr static auto inst = []{
// Fixtypes define the size within the identifier byte, so the
// trait size must be zero.
auto size = is_fixtype<Format> ?
0 : sizeof(typename Format::first_type);
traits inst{
.flags = Format::flags,
.size = std::uint8_t(size),
.token_type = resolve_type_v<typename Format::value_type>
};
// Add in the fixed_size flag for integral types.
if constexpr (std::is_integral_v<typename Format::value_type>) {
inst.flags |= flag::fixed_size;
}
if constexpr (is_fixtype<Format>) {
inst.mask = Format::mask;
}
return inst;
}();
return inst;
};
} // namespace format
template <format_type... Formats>
using format_list = format::detail::type_list<Formats...>;

View File

@ -32,18 +32,89 @@ namespace msgpack {
namespace detail {
constexpr inline decltype(auto) read(std::size_t size, auto& inp)
constexpr std::int64_t sign_extend(std::size_t size, auto bytes) noexcept {
switch (size) {
case 0:
case 1: return std::int8_t(std::bit_cast<std::int64_t>(bytes));
case 2: return std::int16_t(std::bit_cast<std::int64_t>(bytes));
case 4: return std::int32_t(std::bit_cast<std::int64_t>(bytes));
case 8: return std::bit_cast<std::int64_t>(bytes);
default: break; // Not reachable.
}
return {};
}
constexpr decltype(auto) read(std::size_t size, auto& inp)
noexcept {
std::array<std::byte, token::word_size> value;
std::array<std::byte, token::word_size> value{};
be_to_host(inp, inp + size, std::begin(value));
inp += size;
return value;
}
template <format::type FormatType>
constexpr inline decltype(auto) read_value(std::size_t size, auto& inp) {
constexpr inline decltype(auto) make_token(auto bytes) {
using value_type = typename token_traits<FormatType>::storage_type;
return token{std::bit_cast<value_type>(read(size, inp))};
return token{std::bit_cast<value_type>(bytes)};
}
// Define in token/type.h instead? Avoid instantiations in the core API.
constexpr inline format::traits const& traits_lookup(std::byte id) {
switch (id) {
case format::uint8::marker:
return format::get_traits<format::uint8>();
case format::uint16::marker:
return format::get_traits<format::uint16>();
case format::uint32::marker:
return format::get_traits<format::uint32>();
case format::uint64::marker:
return format::get_traits<format::uint64>();
case format::int8::marker:
return format::get_traits<format::int8>();
case format::int16::marker:
return format::get_traits<format::int16>();
case format::int32::marker:
return format::get_traits<format::int32>();
case format::int64::marker:
return format::get_traits<format::int64>();
case format::str8::marker:
return format::get_traits<format::str8>();
case format::str16::marker:
return format::get_traits<format::str16>();
case format::str32::marker:
return format::get_traits<format::str32>();
case format::bin8::marker:
return format::get_traits<format::bin8>();
case format::bin16::marker:
return format::get_traits<format::bin16>();
case format::bin32::marker:
return format::get_traits<format::bin32>();
case format::nil::marker:
return format::get_traits<format::nil>();
case format::invalid::marker:
return format::get_traits<format::invalid>();
case format::boolean::marker:
case format::boolean::marker | std::byte{1}:
return format::get_traits<format::boolean>();
default:
break;
}
// TODO(ksassenrath): Handle fixtype formats.
if ((id & ~format::fixstr::mask) == format::fixstr::marker) {
return format::get_traits<format::fixstr>();
}
if ((id & ~format::negative_fixint::mask) == format::negative_fixint::marker) {
return format::get_traits<format::negative_fixint>();
}
if ((id & ~format::positive_fixint::mask) == format::positive_fixint::marker) {
return format::get_traits<format::positive_fixint>();
}
return format::no_traits;
}
} // namespace detail
@ -73,100 +144,80 @@ public:
return tl::make_unexpected(error::end_of_message);
}
// curr_ will be updated after everything succeeds.
auto curr = curr_;
// Enumerate the current byte first by switch statement, then by
// fix types.
long int size = 0;
std::size_t var_size = 0;
auto id = *curr;
traits&
format::type token_type;
++curr;
switch (id) {
case format::uint8::marker:
size = sizeof(format::uint8::value_type);
token_type = format::type::unsigned_int;
break;
case format::uint16::marker:
size = sizeof(format::uint16::value_type);
token_type = format::type::unsigned_int;
break;
case format::uint32::marker:
size = sizeof(format::uint32::value_type);
token_type = format::type::unsigned_int;
break;
case format::uint64::marker:
size = sizeof(format::uint64::value_type);
token_type = format::type::unsigned_int;
break;
case format::int8::marker:
size = sizeof(format::int8::value_type);
token_type = format::type::signed_int;
break;
case format::int16::marker:
size = sizeof(format::int16::value_type);
token_type = format::type::signed_int;
break;
case format::int32::marker:
size = sizeof(format::int32::value_type);
token_type = format::type::signed_int;
break;
case format::int64::marker:
size = sizeof(format::int64::value_type);
token_type = format::type::signed_int;
break;
case format::str8::marker:
size = sizeof(format::str8::first_type);
token_type = format::type::string;
break;
case format::str16::marker:
size = sizeof(format::str16::first_type);
token_type = format::type::string;
break;
case format::str32::marker:
size = sizeof(format::str32::first_type);
token_type = format::type::string;
break;
case format::bin8::marker:
size = sizeof(format::bin8::first_type);
token_type = format::type::bytes;
break;
case format::bin16::marker:
size = sizeof(format::bin16::first_type);
token_type = format::type::bytes;
break;
case format::bin32::marker:
size = sizeof(format::bin32::first_type);
token_type = format::type::bytes;
break;
default:
return tl::make_unexpected(error::not_implemented);
auto id = *curr++;
auto const& traits = detail::traits_lookup(id);
if (traits == format::no_traits) {
return tl::make_unexpected(error::not_implemented);
}
if (remaining(curr) < size) {
if (remaining(curr) < traits.size) {
return tl::make_unexpected(error::incomplete_message);
}
switch (token_type) {
case format::type::unsigned_int:
tok = detail::read_value<format::type::unsigned_int>(size, curr);
break;
case format::type::signed_int:
tok = detail::read_value<format::type::signed_int>(size, curr);
break;
case format::type::string:
var_size = std::bit_cast<std::size_t>(detail::read(size, curr));
if (std::size_t(remaining(curr)) < var_size) {
return tl::make_unexpected(error::incomplete_message);
// This is either the value of the format, or the size of the format.
auto first_bytes = [&]{
if (traits.size) {
return detail::read(traits.size, curr);
} else {
auto lsb = id;
if (traits.flags & format::flag::apply_mask) {
lsb &= std::byte(traits.mask);
}
return std::array<std::byte, token::word_size>{lsb};
}
}();
// This indicates first_bytes is the size of the format payload.
if (!(traits.flags & format::flag::fixed_size)) {
var_size = std::bit_cast<std::size_t>(first_bytes);
if (std::size_t(remaining(curr)) < var_size) {
return tl::make_unexpected(error::incomplete_message);
}
// For variable-sized types we don't actually read anything yet.
}
switch (traits.token_type) {
case format::type::boolean:
tok = token{bool(first_bytes[0] & traits.mask)};
break;
case format::type::invalid:
tok = token{invalid{}};
break;
case format::type::nil:
tok = token{nil{}};
break;
case format::type::unsigned_int:
tok = detail::make_token<format::type::unsigned_int>(first_bytes);
break;
case format::type::signed_int: {
auto value = detail::sign_extend(traits.size, first_bytes);
tok = token{value};
break;
}
case format::type::string: {
using type = token_traits<format::type::string>::storage_type;
{
auto ptr = reinterpret_cast<type>(&*curr);
tok = token{std::string_view{ptr, var_size}};
curr += var_size;
}
default: break;
break;
}
case format::type::binary: {
using type = token_traits<format::type::binary>::storage_type;
{
auto ptr = reinterpret_cast<type>(&*curr);
tok = token{std::span<std::byte const>{ptr, var_size}};
curr += var_size;
}
break;
}
default:
return tl::make_unexpected(error::not_implemented);
}
curr_ = curr;

View File

@ -128,7 +128,7 @@ struct token_traits<format::type::string> {
};
template <>
struct token_traits<format::type::bytes> {
struct token_traits<format::type::binary> {
using storage_type = std::byte const*;
};
@ -175,11 +175,19 @@ public:
template <std::convertible_to<std::span<std::byte const>> T>
explicit token_base(T const& value) noexcept {
std::span<std::byte const> bv(value);
size_and_type_.set_enum(format::type::bytes);
size_and_type_.set_enum(format::type::binary);
size_and_type_.set_size(bv.size());
value_.bp = bv.data();
}
explicit token_base(nil) noexcept {
size_and_type_.set_enum(format::type::nil);
}
explicit token_base(invalid) noexcept {
size_and_type_.set_enum(format::type::invalid);
}
constexpr format::type type() const noexcept {
return size_and_type_.get_enum();
}
@ -217,7 +225,7 @@ private:
typename token_traits<format::type::unsigned_int>::storage_type u;
typename token_traits<format::type::signed_int>::storage_type i;
typename token_traits<format::type::string>::storage_type str;
typename token_traits<format::type::bytes>::storage_type bp;
typename token_traits<format::type::binary>::storage_type bp;
typename token_traits<format::type::boolean>::storage_type b;
token_base* obj;
} value_;
@ -249,7 +257,7 @@ inline tl::expected<std::vector<std::byte>, error> token_base<8>::get()
const noexcept
{
tl::expected<std::vector<std::byte>, error> result;
if (type() != format::type::bytes) {
if (type() != format::type::binary) {
result = tl::make_unexpected(error::wrong_type);
} else {
result = std::vector<std::byte>(value_.bp,
@ -262,12 +270,32 @@ template<>
constexpr tl::expected<std::span<std::byte const>, error>
token_base<8>::get() const noexcept
{
if (type() != format::type::bytes) {
if (type() != format::type::binary) {
return tl::make_unexpected(error::wrong_type);
}
return std::span<std::byte const>(value_.bp, size_and_type_.get_size());
}
template<>
constexpr tl::expected<nil, error>
token_base<8>::get() const noexcept
{
if (type() != format::type::nil) {
return tl::make_unexpected(error::wrong_type);
}
return nil{};
}
template<>
constexpr tl::expected<invalid, error>
token_base<8>::get() const noexcept
{
if (type() != format::type::invalid) {
return tl::make_unexpected(error::wrong_type);
}
return invalid{};
}
using token = token_base<sizeof(void*)>;
} // namespace msgpack

View File

@ -121,9 +121,10 @@ constexpr auto value_cast(Array&& data) noexcept {
*/
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)));
constexpr T byte_swap(T val) noexcept {
using array_type = std::array<std::byte, sizeof(T)>;
return std::bit_cast<T>(
maybe_swap<From, To>(std::bit_cast<array_type>(val)));
}
} // namespace detail;
@ -144,7 +145,7 @@ struct endian {
template <typename T>
constexpr T be_to_host(T val) noexcept {
return detail::swap<endian::big, endian::host>(val);
return detail::byte_swap<endian::big, endian::host>(val);
}
template <typename Iter, typename OutIter>
@ -154,17 +155,17 @@ constexpr void be_to_host(Iter begin, Iter end, OutIter dest) noexcept {
template <typename T>
constexpr T host_to_be(T val) noexcept {
return detail::swap<endian::host, endian::big>(val);
return detail::byte_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);
return detail::byte_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);
return detail::byte_swap<endian::host, endian::little>(val);
}
#endif // msgpack_endianness_d8ae54f45851ed13

View File

@ -118,7 +118,7 @@ suite assignment_and_access = [] {
std::vector<std::byte> extracted_val;
{
auto val = make_bytes(0x32, 0xff, 0xaa, 0xce);
msgpack::token obj(val); expect(obj.type() == msgpack::format::type::bytes);
msgpack::token obj(val); expect(obj.type() == msgpack::format::type::binary);
auto retrieved = obj.get<std::span<std::byte const>>();
expect(bool(retrieved));
expect(std::equal(retrieved->begin(), retrieved->end(),

View File

@ -1,104 +1,527 @@
#include <msgpack/token.h>
#include <fmt/format.h>
#include <boost/ut.hpp>
#include <magic_enum.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 T>
constexpr bool operator==(std::span<T const> a, std::span<T const> b) noexcept {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
template <typename T, std::size_t C = 1024> struct oversized_array {
std::array<T, C> data;
std::size_t size;
template <typename T>
constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(std::forward<T>(t));
}
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 * 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;
using value_type = std::decay_t<decltype(container[0])>;
oversized_array<value_type> 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)),
consteval auto generate_bytes(auto callable) {
constexpr auto oversized = to_bytes_array_oversized(callable());
using value_type = std::decay_t<decltype(oversized.data[0])>;
std::array<value_type, oversized.size> out;
std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size),
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;
return out;
}
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) {
consteval auto build_string(auto callable) {
constexpr auto string_array = generate_bytes(callable);
return string_array;
}
template <std::size_t ... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept {
std::array<std::byte, (Sizes + ...)> out;
std::size_t index{};
((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...);
return out;
}
constexpr auto repeat(std::span<std::byte const> sv, std::size_t count) {
std::vector<std::byte> range;
range.reserve(sv.size() * count);
for (decltype(count) i = 0; i < count; ++i) {
std::copy_n(sv.begin(), sv.size(), std::back_inserter(range));
}
return range;
}
constexpr auto repeat(std::string_view sv, std::size_t count) {
std::vector<char> range;
range.reserve(sv.size() * count);
for (decltype(count) i = 0; i < count; ++i) {
std::copy_n(sv.begin(), sv.size(), std::back_inserter(range));
}
return range;
}
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;
}
template <typename First, typename... Others>
constexpr bool wrong_types(auto const& obj) {
auto err = tl::make_unexpected(msgpack::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;
}
return range;
}
suite reader = [] {
"read uint32"_test = [] {
constexpr auto payload = make_bytes(0xce, 0x01, 0x02, 0x03, 0x09);
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;
}
bool test_incomplete_message(auto const& payload) {
// Test incomplete message.
for (decltype(payload.size()) i = 1; i < payload.size() - 1; ++i) {
// Test incomplete message.
msgpack::token_reader reader(std::span(payload.data(), i));
auto token = reader.read_one();
if (token != tl::make_unexpected(msgpack::error::incomplete_message)) {
fmt::print("Got the wrong response reading subview[0,{}] of payload: {}\n",
i,
token->get<std::string_view>().value());
return false;
}
}
return true;
}
bool test_end_of_message(auto& reader) {
return reader.read_one() ==
tl::make_unexpected(msgpack::error::end_of_message);
}
}
suite reader_tests = [] {
//--------------------------------------------------------------------------
// Unsigned integer format tests
//--------------------------------------------------------------------------
"read format::positive_fixint"_test = [] {
static constexpr auto payload = make_bytes(0x35);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() == 0x01020309);
expect(token->get<std::uint64_t>() == 0x01020309);
token = reader.read_one();
expect(token == tl::make_unexpected(msgpack::error::end_of_message));
expect(token->get<std::uint8_t>() == 0x35);
expect(token->get<std::uint16_t>() == 0x35);
expect(token->get<std::uint32_t>() == 0x35);
expect(token->get<std::uint64_t>() == 0x35);
// EOM
expect(test_end_of_message(reader));
};
"read str8"_test = [] {
constexpr std::string_view sv = "hello d";
"read format::uint8"_test = [] {
static constexpr auto payload = make_bytes(0xcc, 0x02);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() == 0x02);
expect(token->get<std::uint16_t>() == 0x02);
expect(token->get<std::uint32_t>() == 0x02);
expect(token->get<std::uint64_t>() == 0x02);
// EOM
expect(test_end_of_message(reader));
};
"read format::uint16"_test = [] {
constexpr auto payload = make_bytes(0xcd, 0x01, 0x02);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == 0x0102);
expect(token->get<std::uint32_t>() == 0x0102);
expect(token->get<std::uint64_t>() == 0x0102);
// EOM
expect(test_end_of_message(reader));
};
"read format::uint32"_test = [] {
constexpr auto payload = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() == 0x01020304);
expect(token->get<std::uint64_t>() == 0x01020304);
// EOM
expect(test_end_of_message(reader));
};
"read format::uint64"_test = [] {
constexpr auto payload = make_bytes(0xcf,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint64_t>() == 0x0102030405060708);
// EOM
expect(test_end_of_message(reader));
};
//--------------------------------------------------------------------------
// Signed integer format tests
//--------------------------------------------------------------------------
"read format::negative_fixint"_test = [] {
static constexpr auto payload = make_bytes(0xe5);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() == -27);
expect(token->get<std::int16_t>() == -27);
expect(token->get<std::int32_t>() == -27);
expect(token->get<std::int64_t>() == -27);
// EOM
expect(test_end_of_message(reader));
};
"read format::int8"_test = [] {
static constexpr auto payload = make_bytes(0xd0, 0xff);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() == -1);
expect(token->get<std::int16_t>() == -1);
expect(token->get<std::int32_t>() == -1);
expect(token->get<std::int64_t>() == -1);
// EOM
expect(test_end_of_message(reader));
};
"read format::int16"_test = [] {
constexpr auto payload = make_bytes(0xd1, 0xff, 0x00);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() == -256);
expect(token->get<std::int32_t>() == -256);
expect(token->get<std::int64_t>() == -256);
// EOM
expect(test_end_of_message(reader));
};
"read format::int32"_test = [] {
constexpr auto payload = make_bytes(0xd2, 0xff, 0xff, 0x00, 0x00);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() == -65536);
expect(token->get<std::int64_t>() == -65536);
// EOM
expect(test_end_of_message(reader));
};
"read format::int64"_test = [] {
constexpr auto payload = make_bytes(0xd3,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int64_t>() == -4294967296);
// EOM
expect(test_end_of_message(reader));
};
//--------------------------------------------------------------------------
// String format tests
//--------------------------------------------------------------------------
"read format::fixstr"_test = [] {
constexpr std::string_view sv = "hello";
constexpr auto payload =
cat(make_bytes(0xa5),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::string);
expect(token->get<std::string_view>() == sv);
// EOM
expect(test_end_of_message(reader));
};
"read format::str8"_test = [] {
constexpr std::string_view sv = "hello world";
constexpr auto payload =
cat(make_bytes(0xd9, sv.size()),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::string);
expect(token->get<std::string_view>() == sv);
// EOM
expect(test_end_of_message(reader));
};
"read format::str16"_test = [] {
static constexpr auto sa = build_string([]{
return repeat("0123456789abcdef", 17);
});
constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint16_t(sv.size()));
auto payload =
cat(make_bytes(0xda), as_bytes(sv_size),
generate_bytes([] { return from_string_view(sv); }));
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::string);
expect(token->get<std::string_view>() == sv);
// EOM
expect(test_end_of_message(reader));
};
"read format::str32"_test = [] {
static constexpr auto sa = build_string([]{
return repeat("0123456789abcdef", 4097);
});
constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint32_t(sv.size()));
constexpr auto payload =
cat(make_bytes(0xdb), as_bytes(sv_size),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::string);
expect(token->get<std::string_view>() == sv);
// EOM
expect(test_end_of_message(reader));
};
//--------------------------------------------------------------------------
// Binary format tests
//--------------------------------------------------------------------------
"read format::bin8"_test = [] {
constexpr auto bytes = make_bytes(
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10
);
constexpr auto bytes_size = host_to_be(std::uint8_t(bytes.size()));
constexpr auto payload =
cat(make_bytes(0xc4), as_bytes(bytes_size), bytes);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::binary);
auto value = token->get<std::span<std::byte const>>().value();
expect(value == std::span(bytes.data(), bytes.size()));
// EOM
expect(test_end_of_message(reader));
};
"read format::bin16"_test = [] {
static constexpr auto bytes = generate_bytes([]{
return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
16);
});
constexpr auto bytes_size = host_to_be(std::uint16_t(bytes.size()));
auto payload = cat(make_bytes(0xc5), as_bytes(bytes_size), bytes);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::binary);
auto value = token->get<std::span<std::byte const>>().value();
expect(value == std::span(bytes.data(), bytes.size()));
// EOM
expect(test_end_of_message(reader));
};
"read format::bin32"_test = [] {
static constexpr auto bytes = generate_bytes([]{
return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
8193);
});
constexpr auto bytes_size = host_to_be(std::uint32_t(bytes.size()));
constexpr auto payload =
cat(make_bytes(0xc6), as_bytes(bytes_size), bytes);
// Test incomplete message.
expect(test_incomplete_message(payload));
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::binary);
auto value = token->get<std::span<std::byte const>>().value();
expect(value == std::span(bytes.data(), bytes.size()));
// EOM
expect(test_end_of_message(reader));
};
//--------------------------------------------------------------------------
// Assorted format tests
//--------------------------------------------------------------------------
"read format::boolean"_test = [] {
constexpr auto payload = make_bytes(0xc2, 0xc3);
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::boolean);
expect(token->get<bool>() == false);
token = reader.read_one();
expect(token == tl::make_unexpected(msgpack::error::end_of_message));
expect(token && token->type() == msgpack::format::type::boolean);
expect(token->get<bool>() == true);
// EOM
expect(test_end_of_message(reader));
};
"read format::nil"_test = [] {
constexpr auto payload = make_bytes(0xc0);
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::nil);
expect(token->get<msgpack::nil>().has_value());
// EOM
expect(test_end_of_message(reader));
};
"read format::invalid"_test = [] {
constexpr auto payload = make_bytes(0xc1);
// Test complete message.
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::invalid);
expect(token->get<msgpack::invalid>().has_value());
// EOM
expect(test_end_of_message(reader));
};
};