diff --git a/source/include/parselink/msgpack/core/format.h b/source/include/parselink/msgpack/core/format.h index 1cdcdc8..95c767b 100644 --- a/source/include/parselink/msgpack/core/format.h +++ b/source/include/parselink/msgpack/core/format.h @@ -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 + struct resolve_token_type { + constexpr static type value = type::invalid; + }; + + template + struct resolve_token_type { + constexpr static type value = std::is_signed_v ? + type::signed_int : type::unsigned_int; + }; + + template <> + struct resolve_token_type { + constexpr static type value = type::string; + }; + + template <> + struct resolve_token_type> { + constexpr static type value = type::binary; + }; + + template <> + struct resolve_token_type { + constexpr static type value = type::boolean; + }; + + template <> + struct resolve_token_type { + constexpr static type value = type::nil; + }; + + template <> + struct resolve_token_type { + constexpr static type value = type::invalid; + }; + + template + constexpr static auto resolve_type_v = resolve_token_type::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 + template 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 @@ -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> {}; struct str8 : string_format> {}; struct str16 : string_format> {}; @@ -280,6 +345,38 @@ concept format_type = std::is_base_of_v; template concept is_fixtype = std::is_base_of_v; +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 + 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 ? + 0 : sizeof(typename Format::first_type); + + traits inst{ + .flags = Format::flags, + .size = std::uint8_t(size), + .token_type = resolve_type_v + }; + // Add in the fixed_size flag for integral types. + if constexpr (std::is_integral_v) { + inst.flags |= flag::fixed_size; + } + if constexpr (is_fixtype) { + inst.mask = Format::mask; + } + return inst; + }(); + return inst; + }; +} // namespace format + template using format_list = format::detail::type_list; diff --git a/source/include/parselink/msgpack/token/reader.h b/source/include/parselink/msgpack/token/reader.h index 4a28bec..d1d33fa 100644 --- a/source/include/parselink/msgpack/token/reader.h +++ b/source/include/parselink/msgpack/token/reader.h @@ -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(bytes)); + case 2: return std::int16_t(std::bit_cast(bytes)); + case 4: return std::int32_t(std::bit_cast(bytes)); + case 8: return std::bit_cast(bytes); + default: break; // Not reachable. + } + return {}; +} + +constexpr decltype(auto) read(std::size_t size, auto& inp) noexcept { - std::array value; + std::array value{}; be_to_host(inp, inp + size, std::begin(value)); inp += size; return value; } template -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::storage_type; - return token{std::bit_cast(read(size, inp))}; + return token{std::bit_cast(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(); + case format::uint16::marker: + return format::get_traits(); + case format::uint32::marker: + return format::get_traits(); + case format::uint64::marker: + return format::get_traits(); + + case format::int8::marker: + return format::get_traits(); + case format::int16::marker: + return format::get_traits(); + case format::int32::marker: + return format::get_traits(); + case format::int64::marker: + return format::get_traits(); + + case format::str8::marker: + return format::get_traits(); + case format::str16::marker: + return format::get_traits(); + case format::str32::marker: + return format::get_traits(); + case format::bin8::marker: + return format::get_traits(); + case format::bin16::marker: + return format::get_traits(); + case format::bin32::marker: + return format::get_traits(); + + case format::nil::marker: + return format::get_traits(); + case format::invalid::marker: + return format::get_traits(); + case format::boolean::marker: + case format::boolean::marker | std::byte{1}: + return format::get_traits(); + default: + break; + } + + // TODO(ksassenrath): Handle fixtype formats. + if ((id & ~format::fixstr::mask) == format::fixstr::marker) { + return format::get_traits(); + } + if ((id & ~format::negative_fixint::mask) == format::negative_fixint::marker) { + return format::get_traits(); + } + if ((id & ~format::positive_fixint::mask) == format::positive_fixint::marker) { + return format::get_traits(); + } + + 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(size, curr); - break; - case format::type::signed_int: - tok = detail::read_value(size, curr); - break; - case format::type::string: - var_size = std::bit_cast(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{lsb}; + } + }(); + + // This indicates first_bytes is the size of the format payload. + if (!(traits.flags & format::flag::fixed_size)) { + var_size = std::bit_cast(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(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::storage_type; { auto ptr = reinterpret_cast(&*curr); tok = token{std::string_view{ptr, var_size}}; curr += var_size; } - default: break; + break; + } + case format::type::binary: { + using type = token_traits::storage_type; + { + auto ptr = reinterpret_cast(&*curr); + tok = token{std::span{ptr, var_size}}; + curr += var_size; + } + break; + } + default: + return tl::make_unexpected(error::not_implemented); } curr_ = curr; diff --git a/source/include/parselink/msgpack/token/type.h b/source/include/parselink/msgpack/token/type.h index d058951..c26e960 100644 --- a/source/include/parselink/msgpack/token/type.h +++ b/source/include/parselink/msgpack/token/type.h @@ -128,7 +128,7 @@ struct token_traits { }; template <> -struct token_traits { +struct token_traits { using storage_type = std::byte const*; }; @@ -175,11 +175,19 @@ public: template > T> explicit token_base(T const& value) noexcept { std::span 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::storage_type u; typename token_traits::storage_type i; typename token_traits::storage_type str; - typename token_traits::storage_type bp; + typename token_traits::storage_type bp; typename token_traits::storage_type b; token_base* obj; } value_; @@ -249,7 +257,7 @@ inline tl::expected, error> token_base<8>::get() const noexcept { tl::expected, error> result; - if (type() != format::type::bytes) { + if (type() != format::type::binary) { result = tl::make_unexpected(error::wrong_type); } else { result = std::vector(value_.bp, @@ -262,12 +270,32 @@ template<> constexpr tl::expected, 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(value_.bp, size_and_type_.get_size()); } +template<> +constexpr tl::expected +token_base<8>::get() const noexcept +{ + if (type() != format::type::nil) { + return tl::make_unexpected(error::wrong_type); + } + return nil{}; +} + +template<> +constexpr tl::expected +token_base<8>::get() const noexcept +{ + if (type() != format::type::invalid) { + return tl::make_unexpected(error::wrong_type); + } + return invalid{}; +} + using token = token_base; } // namespace msgpack diff --git a/source/include/parselink/msgpack/util/endianness.h b/source/include/parselink/msgpack/util/endianness.h index 4e7151f..e7734bd 100644 --- a/source/include/parselink/msgpack/util/endianness.h +++ b/source/include/parselink/msgpack/util/endianness.h @@ -121,9 +121,10 @@ constexpr auto value_cast(Array&& data) noexcept { */ template requires std::is_trivial_v> -constexpr T swap(T val) noexcept { - return value_cast( - maybe_swap(raw_cast(val))); +constexpr T byte_swap(T val) noexcept { + using array_type = std::array; + return std::bit_cast( + maybe_swap(std::bit_cast(val))); } } // namespace detail; @@ -144,7 +145,7 @@ struct endian { template constexpr T be_to_host(T val) noexcept { - return detail::swap(val); + return detail::byte_swap(val); } template @@ -154,17 +155,17 @@ constexpr void be_to_host(Iter begin, Iter end, OutIter dest) noexcept { template constexpr T host_to_be(T val) noexcept { - return detail::swap(val); + return detail::byte_swap(val); } template constexpr T le_to_host(T val) noexcept { - return detail::swap(val); + return detail::byte_swap(val); } template constexpr T host_to_le(T val) noexcept { - return detail::swap(val); + return detail::byte_swap(val); } #endif // msgpack_endianness_d8ae54f45851ed13 diff --git a/tests/msgpack/test_token.cpp b/tests/msgpack/test_token.cpp index 36eb2e4..332b210 100644 --- a/tests/msgpack/test_token.cpp +++ b/tests/msgpack/test_token.cpp @@ -118,7 +118,7 @@ suite assignment_and_access = [] { std::vector 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>(); expect(bool(retrieved)); expect(std::equal(retrieved->begin(), retrieved->end(), diff --git a/tests/msgpack/test_token_reader.cpp b/tests/msgpack/test_token_reader.cpp index e36d13f..1c20014 100644 --- a/tests/msgpack/test_token_reader.cpp +++ b/tests/msgpack/test_token_reader.cpp @@ -1,104 +1,527 @@ #include +#include #include +#include using namespace boost::ut; namespace { - template - constexpr std::array make_bytes(Bytes &&...bytes) { - return {std::byte(std::forward(bytes))...}; - } +template +constexpr bool operator==(std::span a, std::span b) noexcept { + return std::equal(a.begin(), a.end(), b.begin(), b.end()); +} -template struct oversized_array { - std::array data; - std::size_t size; +template +constexpr std::array as_bytes(T&& t) { + return std::bit_cast>(std::forward(t)); +} + +template +constexpr std::array make_bytes(Bytes &&...bytes) { + return {std::byte(std::forward(bytes))...}; +} + +template struct oversized_array { + std::array data; + std::size_t size; }; + constexpr auto to_bytes_array_oversized(auto const &container) { - oversized_array 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; + oversized_array 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 out; - std::copy(std::begin(oversized.data), - std::next(std::begin(oversized.data), oversized.size), - std::begin(out)); - return out; - } - -template -consteval auto cat(std::array const &a, - std::array const &b) { - std::array 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; + std::array 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 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 +constexpr auto cat(std::arrayconst&... a) noexcept { + std::array out; + std::size_t index{}; + ((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...); + return out; +} + +constexpr auto repeat(std::span sv, std::size_t count) { + std::vector 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 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 range; + range.resize(sv.size()); + auto itr = range.begin(); + for (auto c : sv) { *itr = std::byte(c); ++itr; - } - return range; - } - - template - constexpr bool wrong_types(auto const& obj) { - auto err = tl::make_unexpected(msgpack::error::wrong_type); - if (obj.template get() != err) return false; - if constexpr (sizeof...(Others)) { - return wrong_types(obj); - } else { - return true; - } - } - - template - std::ostream &operator<<(std::ostream &os, tl::expected 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 + std::ostream &operator<<(std::ostream &os, tl::expected 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().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() == tl::make_unexpected(msgpack::error::will_truncate)); - expect(token->get() == tl::make_unexpected(msgpack::error::will_truncate)); - expect(token->get() == 0x01020309); - expect(token->get() == 0x01020309); - token = reader.read_one(); - expect(token == tl::make_unexpected(msgpack::error::end_of_message)); + expect(token->get() == 0x35); + expect(token->get() == 0x35); + expect(token->get() == 0x35); + expect(token->get() == 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() == 0x02); + expect(token->get() == 0x02); + expect(token->get() == 0x02); + expect(token->get() == 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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == 0x0102); + expect(token->get() == 0x0102); + expect(token->get() == 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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == 0x01020304); + expect(token->get() == 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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == 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() == -27); + expect(token->get() == -27); + expect(token->get() == -27); + expect(token->get() == -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() == -1); + expect(token->get() == -1); + expect(token->get() == -1); + expect(token->get() == -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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == -256); + expect(token->get() == -256); + expect(token->get() == -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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == -65536); + expect(token->get() == -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() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == + tl::make_unexpected(msgpack::error::will_truncate)); + expect(token->get() == -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() == 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() == 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() == 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() == 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>().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>().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>().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() == 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() == 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().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().has_value()); + // EOM + expect(test_end_of_message(reader)); }; };