Compare commits

...

14 Commits

Author SHA1 Message Date
b2a6f25306 Start proto::parser, remove msgpack::reader
- proto::parser will likely contain helper functions for parsing
  messages from a buffer, for now it will explicitly define message
  parsers for each available message. It also leverages the new unpacker
  API, which has type safety in mind. These messages should not be
  unstructured, so it doesn't make sense to use the token API.
2024-01-12 19:36:56 -08:00
c0945246de Refactor packer tests to make recompilation faster 2024-01-12 13:37:39 -08:00
eb7a02d5c4 Refactor tests, add formatters, etc. 2024-01-12 12:53:00 -08:00
Kurt Sassenrath
db601fb770 WIP unpacking 2024-01-08 19:58:12 -08:00
0e6f05dd68 WIP Server stuff: key gen 2024-01-08 10:39:35 -08:00
Kurt Sassenrath
e48ba60c68 more tests, allow signed int -> positive_fixint 2024-01-04 15:25:17 -08:00
Kurt Sassenrath
8f4ac703f4 Rapidcheck component, initial test cases rewritten 2024-01-03 15:51:22 -08:00
Kurt Sassenrath
0cafb9bcd7 Additional concepts work 2024-01-02 22:43:29 -08:00
b48eddb8d0 Range packing. 2024-01-02 07:55:40 -08:00
3d7ba40289 Add msgpack packer support for ranges of objects. 2023-12-30 19:18:09 -08:00
Kurt Sassenrath
cb2b5b47b0 WIP: Migrate packing to pass context object. 2023-12-28 15:51:13 -08:00
Kurt Sassenrath
a027d6b946 Add builtin_packer for array_desc / map_desc 2023-12-28 15:35:40 -08:00
Kurt Sassenrath
0e00edc378 Remove builtin_pack_helper. 2023-12-28 09:24:48 -08:00
c89d7cd541 Begin refactor of msgpack packer 2023-12-26 23:45:47 -08:00
46 changed files with 4123 additions and 463 deletions

View File

@ -18,7 +18,7 @@ BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
IncludeBlocks: Regroup
#IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"parselink/'
Priority: 1

65
BUILD.rapidcheck Normal file
View File

@ -0,0 +1,65 @@
cc_library(
name = "headers",
includes = ["include"],
visibility = ["//visibility:private"],
copts = ["-DRC_DONT_USE_RTTI"],
hdrs = glob([
"include/**/*.hpp",
"include/**/*.h",
]),
)
cc_library(
name = "random",
deps = [":headers"],
visibility = ["//visibility:private"],
copts = ["-DRC_DONT_USE_RTTI", "-O3"],
srcs = [
"src/Random.cpp",
],
)
cc_library(
name = "rapidcheck",
visibility = ["//visibility:public"],
deps = [":headers", ":random"],
copts = ["-DRC_DONT_USE_RTTI"],
includes = ["include"],
srcs = [
"src/BeforeMinimalTestCase.cpp",
"src/Check.cpp",
"src/Classify.cpp",
"src/GenerationFailure.cpp",
"src/Log.cpp",
"src/Show.cpp",
"src/detail/Any.cpp",
"src/detail/Assertions.cpp",
"src/detail/Base64.cpp",
"src/detail/Configuration.cpp",
"src/detail/DefaultTestListener.cpp",
"src/detail/FrequencyMap.cpp",
"src/detail/ImplicitParam.cpp",
"src/detail/LogTestListener.cpp",
"src/detail/MapParser.cpp",
"src/detail/MulticastTestListener.cpp",
"src/detail/ParseException.cpp",
"src/detail/Platform.cpp",
"src/detail/Property.cpp",
"src/detail/PropertyContext.cpp",
"src/detail/ReproduceListener.cpp",
"src/detail/Results.cpp",
"src/detail/Serialization.cpp",
"src/detail/StringSerialization.cpp",
"src/detail/TestMetadata.cpp",
"src/detail/TestParams.cpp",
"src/detail/Testing.cpp",
"src/gen/Numeric.cpp",
"src/gen/Text.cpp",
"src/gen/detail/ExecHandler.cpp",
"src/gen/detail/GenerationHandler.cpp",
"src/gen/detail/Recipe.cpp",
"src/gen/detail/ScaleInteger.cpp",
] + glob(["src/detail/*.h"]),
)

6
MODULE.bazel Normal file
View File

@ -0,0 +1,6 @@
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################

1245
MODULE.bazel.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ boost_deps()
#-------------------------------------------------------------------------------
# magic_enum: Used in logging implementation for enum names.
#-------------------------------------------------------------------------------
magic_enum_version = "0.8.2"
magic_enum_version = "0.9.5"
magic_enum_base_url = \
"https://github.com/Neargye/magic_enum/archive/refs/tags/v"
magic_enum_sha256 = \
@ -33,7 +33,7 @@ magic_enum_sha256 = \
http_archive(
name = "magic_enum",
sha256 = magic_enum_sha256,
# sha256 = magic_enum_sha256,
url = magic_enum_base_url + magic_enum_version + ".zip",
strip_prefix = "magic_enum-" + magic_enum_version,
)
@ -101,7 +101,7 @@ cc_library(
)
#-------------------------------------------------------------------------------
# ut: Unit test framework.
# ut: Testing framework.
# TODO(kss): Only if tests are needed?
#-------------------------------------------------------------------------------
ut_version = "1.1.9"
@ -124,6 +124,22 @@ cc_library(
strip_prefix = "ut-" + ut_version,
)
#-------------------------------------------------------------------------------
# rapidcheck: Property-based-testing framework.
# TODO(kss): Only if tests are needed?
#-------------------------------------------------------------------------------
rapidcheck_commit = "ff6af6fc683159deb51c543b065eba14dfcf329b"
rapidcheck_base_url = "https://github.com/emil-e/rapidcheck"
rapidcheck_branch = "master"
git_repository(
name = "rapidcheck",
remote = rapidcheck_base_url,
commit = rapidcheck_commit,
build_file = "@//:BUILD.rapidcheck"
)
#-------------------------------------------------------------------------------
# Support compile_commands.json generation for LSP.
#-------------------------------------------------------------------------------

0
bazel/BUILD Normal file
View File

View File

@ -118,7 +118,6 @@ struct fmt::formatter<T> : fmt::formatter<void const*> {
}
};
// TODO(ksassenrath): Re-enable when expected has been integrated
template <typename T, typename Err>
struct fmt::formatter<tl::expected<T, Err>> {
template <typename ParseContext>

View File

@ -0,0 +1,288 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for packing various types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_builtin_packable_types_41bd88d512497527
#define msgpack_core_detail_builtin_packable_types_41bd88d512497527
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <algorithm>
#include <limits>
#include <ranges>
namespace msgpack {
namespace detail {
// This is a generic helper function for writing integral bytes
template <typename T, typename Itr>
constexpr auto pack_integral(T value, Itr out, std::size_t sz) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
struct dummy_packer {
std::byte* out;
};
// Defines a range of bytes for binary formats.
template <typename T>
concept byte_range = std::ranges::contiguous_range<T>
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
} // namespace detail
struct builtin_packer_base {
static constexpr std::size_t rep_size(auto const&) noexcept { return 0; }
static constexpr std::size_t len_size(auto const& value) noexcept {
return 0;
}
static constexpr auto total_size(auto const& value) noexcept {
return 1 + len_size(value) + rep_size(value);
}
};
template <typename>
struct builtin_packer;
////////////////////////////////////////////////////////////////////////////////
// Built-in packer specializations
////////////////////////////////////////////////////////////////////////////////
template <>
struct builtin_packer<invalid> : builtin_packer_base {
static constexpr void pack(auto, auto& packer) noexcept {
*(packer.out)++ = format::invalid::marker;
}
};
template <>
struct builtin_packer<nil> : builtin_packer_base {
static constexpr void pack(auto, auto& packer) noexcept {
*(packer.out)++ = format::nil::marker;
}
};
template <>
struct builtin_packer<bool> : builtin_packer_base {
static constexpr void pack(bool value, auto& packer) noexcept {
*(packer.out)++ =
format::boolean::marker | static_cast<std::byte>(value);
}
};
template <std::unsigned_integral T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr std::size_t rep_size(std::uint64_t value) noexcept {
if (value < 128) return 0;
auto bytes_needed =
static_cast<std::uint64_t>((std::bit_width(value) + 7) >> 3);
return std::bit_ceil(bytes_needed);
}
static constexpr std::byte marker(std::uint64_t value) noexcept {
switch (rep_size(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::uint8::marker;
case 2: return format::uint16::marker;
case 4: return format::uint32::marker;
default: return format::uint64::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(value, packer.out, rep_size(value));
}
};
template <std::signed_integral T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr std::size_t rep_size(std::int64_t value) noexcept {
// Probably a better way to do this; fixints
if (value < 128 && value >= -32) return 0;
auto underlying = static_cast<std::uint64_t>(value);
// save a branch; these should be cheap on modern hardware.
std::uint64_t counts[2] = {
static_cast<std::uint64_t>(std::countl_zero(underlying)),
static_cast<std::uint64_t>(std::countl_one(underlying))};
std::uint64_t width = 1 + std::numeric_limits<std::uint64_t>::digits
- counts[underlying >> 63];
return std::bit_ceil((width + 7) >> 3);
}
static constexpr std::byte marker(std::int64_t value) noexcept {
switch (rep_size(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::int8::marker;
case 2: return format::int16::marker;
case 4: return format::int32::marker;
default: return format::int64::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(value, packer.out, rep_size(value));
}
};
template <detail::byte_range T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr uint32_t max_payload_length = 4;
static constexpr std::size_t rep_size(T const& value) noexcept {
return std::ranges::size(value);
}
static constexpr std::size_t len_size(T const& value) noexcept {
auto const len = rep_size(value);
auto const rounded_bytes = std::bit_ceil(static_cast<std::uint32_t>(
((std::bit_width(std::ranges::size(value)) + 7) >> 3)));
return std::min(max_payload_length, rounded_bytes);
}
static constexpr auto marker(T const& value) noexcept {
switch (len_size(value)) {
case 1: return format::bin8::marker;
case 2: return format::bin16::marker;
case 4:
default: return format::bin32::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(
std::ranges::size(value), packer.out, len_size(value));
packer.out = std::ranges::copy(value, packer.out).out;
}
};
template <>
struct builtin_packer<std::string_view> : builtin_packer_base {
static constexpr uint32_t max_payload_length = 4;
static constexpr std::size_t rep_size(std::string_view value) noexcept {
return std::size(value);
}
static constexpr std::size_t len_size(std::string_view value) noexcept {
auto const len = std::size(value);
if (len <= static_cast<std::uint32_t>(format::fixstr::mask)) return 0;
return std::min(max_payload_length,
std::bit_ceil(std::uint32_t((std::bit_width(len) + 7) >> 3)));
}
static constexpr auto marker(std::string_view value) noexcept {
switch (len_size(value)) {
case 0:
return format::fixstr::marker
| static_cast<std::byte>(rep_size(value));
case 1: return format::str8::marker;
case 2: return format::str16::marker;
case 4:
default: return format::str32::marker;
}
}
static constexpr void pack(std::string_view value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
auto const size = std::size(value);
packer.out = detail::pack_integral(size, packer.out, len_size(value));
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
packer.out = std::ranges::copy(beg, beg + size, packer.out).out;
}
};
// All things that convert to std::string_view should use the string_view
// overload of builtin_packer.
template <std::convertible_to<std::string_view> T>
struct builtin_packer<T> : builtin_packer<std::string_view> {};
template <>
struct builtin_packer<array_desc> : builtin_packer_base {
static constexpr std::size_t rep_size(array_desc value) noexcept {
auto const len = value.count;
if (len <= static_cast<std::size_t>(format::fixarray::mask)) {
return 0;
}
if (len < 0x10000) return 2;
return 4;
}
static constexpr auto marker(array_desc value) noexcept {
switch (rep_size(value)) {
case 0:
return format::fixarray::marker
| static_cast<std::byte>(value.count);
case 2: return format::array16::marker;
case 4:
default: return format::array32::marker;
}
}
static constexpr auto pack(array_desc value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out =
detail::pack_integral(value.count, packer.out, rep_size(value));
}
};
template <>
struct builtin_packer<map_desc> : builtin_packer_base {
static constexpr std::size_t rep_size(map_desc value) noexcept {
auto const len = value.count;
if (len <= static_cast<std::size_t>(format::fixmap::mask)) {
return 0;
}
if (len < 0x10000) return 2;
return 4;
}
static constexpr auto marker(map_desc value) noexcept {
switch (rep_size(value)) {
case 0:
return format::fixmap::marker
| static_cast<std::byte>(value.count);
case 2: return format::map16::marker;
case 4:
default: return format::map32::marker;
}
}
static constexpr void pack(map_desc value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out =
detail::pack_integral(value.count, packer.out, rep_size(value));
}
};
} // namespace msgpack
#endif // msgpack_core_detail_builtin_packable_types_41bd88d512497527

View File

@ -0,0 +1,347 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for unpacking various types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8
#define msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8
#include "parselink/msgpack/core/detail/unpackable_concepts.h"
#include "parselink/msgpack/core/error.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <tl/expected.hpp>
#include <algorithm>
#include <limits>
#include <ranges>
namespace msgpack {
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;
}
// This is a generic helper function for writing integral bytes
template <std::integral T, typename Itr>
constexpr decltype(auto) unpack_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)));
}
}
// Defines a range of bytes for binary formats.
template <typename T>
concept unpackable_byte_range =
std::ranges::contiguous_range<T>
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
template <format_type F, typename Iter>
constexpr inline bool accept_format(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;
}
}
// 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);
}
};
// Using information expressed within each format type, this singular template
// can instantiate the unpacking of most formats. Notable exceptions are
// the array/map types and extension types.
template <format_type F>
constexpr inline auto unpack_format(auto& unpacker) noexcept
-> tl::expected<typename F::value_type, error> {
if (!accept_format<F>(unpacker.in)) {
return tl::make_unexpected(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 = decltype(unpacker.remaining());
if (unpacker.remaining() < diff_type{sizeof(first_type)}) {
return tl::make_unexpected(error::incomplete_message);
}
auto f = unpack_integral<first_type>(unpacker.in);
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
f &= decltype(f)(F::mask);
}
if constexpr (F::payload_type == format::payload::fixed) {
// Prefer constructions that utilize the unpacked value, but allow
// tag types (e.g. invalid, nil) to be directly constructed.
if constexpr (std::constructible_from<value_type, decltype(f)>) {
return value_type(f);
} else {
return value_type{};
}
} 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 (unpacker.remaining() < diff_type(f)) {
return tl::make_unexpected(error::incomplete_message);
}
using adapt = iterator_adapter<value_type, decltype(unpacker.in)>;
auto value = value_type(adapt::convert(unpacker.in), f);
unpacker.in += f;
return value;
}
}
} // namespace detail
template <typename>
struct builtin_unpacker;
////////////////////////////////////////////////////////////////////////////////
// Built-in unpacker specializations
////////////////////////////////////////////////////////////////////////////////
template <>
struct builtin_unpacker<invalid> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::invalid>(unpacker);
}
};
template <>
struct builtin_unpacker<nil> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::nil>(unpacker);
}
};
template <>
struct builtin_unpacker<bool> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::boolean>(unpacker);
}
};
template <>
struct builtin_unpacker<map_desc> {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<map_desc, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & ~format::fixmap::mask) == format::fixmap::marker) {
return detail::unpack_format<format::fixmap>(unpacker);
}
switch (marker) {
case format::map16::marker:
return detail::unpack_format<format::map16>(unpacker);
case format::map32::marker:
return detail::unpack_format<format::map32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
// This unpacker will accept any uintX, positive_fixint types.
struct unsigned_int_unpacker {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::uint64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
}
switch (marker) {
case format::uint8::marker:
return detail::unpack_format<format::uint8>(unpacker);
case format::uint16::marker:
return detail::unpack_format<format::uint16>(unpacker);
case format::uint32::marker:
return detail::unpack_format<format::uint32>(unpacker);
case format::uint64::marker:
return detail::unpack_format<format::uint64>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::unsigned_integral T>
struct builtin_unpacker<T> {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return unsigned_int_unpacker::unpack(unpacker).and_then(convert);
}
};
struct signed_int_unpacker {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::int64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
} else if ((marker & std::byte{0xe0}) == std::byte{0xe0}) {
return detail::unpack_format<format::negative_fixint>(unpacker);
}
switch (marker) {
case format::int8::marker:
return detail::unpack_format<format::int8>(unpacker);
case format::int16::marker:
return detail::unpack_format<format::int16>(unpacker);
case format::int32::marker:
return detail::unpack_format<format::int32>(unpacker);
case format::int64::marker:
return detail::unpack_format<format::int64>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::signed_integral T>
struct builtin_unpacker<T> {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto min = std::numeric_limits<T>::min();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max || value < min) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return signed_int_unpacker::unpack(unpacker).and_then(convert);
}
};
template <>
struct builtin_unpacker<std::string_view> {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::string_view, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & ~format::fixstr::mask) == format::fixstr::marker) {
return detail::unpack_format<format::fixstr>(unpacker);
}
switch (marker) {
case format::str8::marker:
return detail::unpack_format<format::str8>(unpacker);
case format::str16::marker:
return detail::unpack_format<format::str16>(unpacker);
case format::str32::marker:
return detail::unpack_format<format::str32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>>)
struct builtin_unpacker<std::span<T, Extent>> {
template <std::size_t E = Extent>
using expected_type = tl::expected<std::span<T, E>, msgpack::error>;
static constexpr auto unpack_bytes(auto& unpacker) noexcept
-> expected_type<std::dynamic_extent> {
auto marker = *(unpacker.in);
switch (marker) {
case format::bin8::marker:
return detail::unpack_format<format::bin8>(unpacker);
case format::bin16::marker:
return detail::unpack_format<format::bin16>(unpacker);
case format::bin32::marker:
return detail::unpack_format<format::bin32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
static constexpr expected_type<> unpack(auto& unpacker) noexcept {
auto ensure_extent = [](std::span<std::byte const> value) noexcept
-> expected_type<> {
if constexpr (Extent != std::dynamic_extent) {
if (value.size() != Extent) {
return tl::make_unexpected(msgpack::error::wrong_length);
}
return value.template first<Extent>();
} else {
return value;
}
};
return unpack_bytes(unpacker).and_then(ensure_extent);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>> && Extent > 0)
struct builtin_unpacker<std::array<T, Extent>> {
using expected_type = tl::expected<std::array<T, Extent>, msgpack::error>;
static constexpr expected_type unpack(auto& unpacker) noexcept {
constexpr auto copy_to_array = [](auto span) noexcept -> expected_type {
if (span.size() != Extent) {
return tl::make_unexpected(msgpack::error::wrong_length);
} else {
std::array<std::byte, Extent> dest;
std::copy(span.begin(), span.end(), dest.begin());
return dest;
}
};
return builtin_unpacker<std::span<std::byte const>>::unpack(unpacker)
.and_then(copy_to_array);
}
};
} // namespace msgpack
#endif // msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8

View File

@ -0,0 +1,85 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Concepts used for types that are packable by a packer.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_packable_concepts_397b5e0ae6de61bf
#define msgpack_core_detail_packable_concepts_397b5e0ae6de61bf
#include <concepts>
#include <cstddef>
#include <type_traits>
namespace msgpack {
namespace detail {
// This structure models a minimal packer context. It is used to validate
// whether a particular type is considered packable or not.
struct packer_context_model {
std::byte* out;
};
} // namespace detail
// Packers with built-in support will specialize builtin_packer.
template <typename>
struct builtin_packer;
// Packers
template <typename>
struct packer_adapter;
template <typename T, template <typename> typename Packer,
typename Context = detail::packer_context_model>
concept packable_with = requires(T&& value, Context& dest) {
{
Packer<std::remove_cvref_t<T>>::total_size(value)
} -> std::same_as<std::size_t>;
{
Packer<std::remove_cvref_t<T>>::pack(value, dest)
} -> std::same_as<void>;
};
template <typename T>
concept custom_packable = packable_with<T, packer_adapter>;
template <typename T>
concept builtin_packable = packable_with<T, builtin_packer>;
template <typename T>
concept packable = builtin_packable<T> || custom_packable<T>;
namespace detail {
template <typename>
struct packer_impl_s;
template <custom_packable T>
struct packer_impl_s<T> {
using type = packer_adapter<T>;
};
template <builtin_packable T>
struct packer_impl_s<T> {
using type = builtin_packer<T>;
};
template <typename T>
using packer_impl = packer_impl_s<T>::type;
} // namespace detail
} // namespace msgpack
#endif // msgpack_core_detail_packable_concepts_397b5e0ae6de61bf

View File

@ -0,0 +1,104 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for packing ranges of packable types into array/map formats.
//
// If the range size is known at compile time or can be queried at runtime, it
// will be leveraged to find the most efficient version of the format.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_packable_ranges_10d28c24498828c7
#define msgpack_core_detail_packable_ranges_10d28c24498828c7
#include "parselink/msgpack/core/detail/builtin_packable_types.h"
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include <ranges>
#include <tuple>
namespace msgpack {
namespace detail {
//
template <typename T>
concept packable_array =
std::ranges::input_range<T> && !std::convertible_to<T, std::string_view>
&& packable<std::ranges::range_value_t<T>>;
template <typename T>
concept key_value_pairlike = requires(T t) {
typename std::tuple_size<T>::type;
} && std::tuple_size_v<T> == 2;
template <typename T>
concept packable_map =
std::ranges::input_range<T>
&& key_value_pairlike<std::ranges::range_value_t<T>>
&& builtin_packable<
std::tuple_element_t<0, std::ranges::range_value_t<T>>>
&& builtin_packable<
std::tuple_element_t<1, std::ranges::range_value_t<T>>>;
template <typename>
struct desc_traits;
template <packable_map T>
struct desc_traits<T> {
using type = map_desc;
};
template <packable_array T>
struct desc_traits<T> {
using type = array_desc;
};
} // namespace detail
template <typename T>
concept packable_range = detail::packable_array<T> || detail::packable_map<T>;
template <packable_range T>
struct builtin_packer<T> : builtin_packer_base {
using desc_type = detail::desc_traits<T>::type;
static constexpr std::size_t rep_size(T const& value) {
return std::ranges::size(value);
}
static constexpr auto desc(T const& value) {
return desc_type(rep_size(value));
}
static constexpr auto marker(T const& value) {
return builtin_packer<desc_type>::marker(desc(value));
}
static constexpr void pack(T const& value, auto& packer) noexcept {
packer.pack(desc(value));
if constexpr (detail::packable_map<T>) {
for (auto const& [key, value] : value) {
packer.pack(key);
packer.pack(value);
}
} else {
for (auto const& element : value) {
packer.pack(element);
}
}
}
};
} // namespace msgpack
#endif // msgpack_core_detail_packable_ranges_10d28c24498828c7

View File

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Concepts used for types that are deserializable by an unpacker.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_unpackable_concepts_cf142e37b34a598f
#define msgpack_core_detail_unpackable_concepts_cf142e37b34a598f
#include "parselink/msgpack/core/error.h"
#include <tl/expected.hpp>
#include <concepts>
#include <cstddef>
#include <type_traits>
namespace msgpack {
namespace detail {
// This structure models a minimal packer context. It is used to validate
// whether a particular type is considered packable or not.
struct unpacker_context_model {
std::byte* in;
std::size_t remaining() { return 1; }
};
} // namespace detail
// Datatypes with built-in support will specialize builtin_unpacker.
template <typename>
struct builtin_unpacker;
// Datatypes can have a custom adapter for unpacking. This _can_ be used to
// override default behavior if necessary, but its intended for custom types.
template <typename>
struct unpacker_adapter;
template <typename T, typename... U>
concept is_any_of = (std::same_as<T, U> || ...);
template <typename T, template <typename> typename Unpacker,
typename Context = detail::unpacker_context_model>
concept unpackable_with = requires(T&& value, Context& dest) {
{
Unpacker<std::remove_cvref_t<T>>::unpack(dest)
} -> is_any_of<T, tl::expected<T, msgpack::error>>;
};
template <typename T>
concept custom_unpackable = unpackable_with<T, unpacker_adapter>;
template <typename T>
concept builtin_unpackable = unpackable_with<T, builtin_unpacker>;
template <typename T>
concept supports_unpack = builtin_unpackable<T> || custom_unpackable<T>;
namespace detail {
template <typename>
struct unpacker_impl_s;
template <custom_unpackable T>
struct unpacker_impl_s<T> {
using type = unpacker_adapter<T>;
};
template <builtin_unpackable T>
struct unpacker_impl_s<T> {
using type = builtin_unpacker<T>;
};
template <typename T>
using unpacker_impl = unpacker_impl_s<T>::type;
} // namespace detail
} // namespace msgpack
#endif // msgpack_core_detail_unpackable_concepts_cf142e37b34a598f

View File

@ -29,6 +29,7 @@ enum class error {
wrong_type, // The format is incompatible with requested type.
bad_value, // Value does not fit within constraints.
will_truncate, // Integer return type is smaller than stored value.
wrong_length, // Variable-length format does not match desired length.
};
} // namespace msgpack

View File

@ -31,14 +31,14 @@ namespace msgpack {
// Supporting types/tags for formats
//-----------------------------------------------------------------------------
struct nil {
nil() = default;
// This constructor is used by the reader implementation.
nil(auto){};
constexpr nil() noexcept = default;
constexpr bool operator==(nil const& other) const noexcept = default;
};
struct invalid {};
using boolean = bool;
struct invalid {
constexpr invalid() noexcept = default;
constexpr bool operator==(invalid const& other) const noexcept = default;
};
struct array_desc {
constexpr array_desc() noexcept = default;
@ -225,12 +225,12 @@ struct int64 : format<0xd3, std::int64_t> {};
*/
struct nil : fixtype<0xc0, 0x00> {
using value_type = msgpack::nil;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
constexpr static flag flags{flag::fixed_size};
};
struct invalid : fixtype<0xc1, 0x00> {
using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
constexpr static flag flags{flag::fixed_size};
};
struct boolean : fixtype<0xc2, 0x01> {

View File

@ -38,6 +38,10 @@
#ifndef msgpack_core_packer_1d5939e9c1498568
#define msgpack_core_packer_1d5939e9c1498568
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include "parselink/msgpack/core/detail/builtin_packable_types.h"
#include "parselink/msgpack/core/detail/packable_ranges.h"
#include "parselink/msgpack/core/error.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
@ -49,244 +53,8 @@
namespace msgpack {
namespace detail {
// This is a generic helper function for writing integral bytes.
template <typename T, typename Itr>
constexpr auto write_integral(T value, Itr out, std::size_t sz) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
// Depending on the format, a number of bytes will be necessary to represent
// either the value (integer formats) or the length (variable length formats).
template <format::type T>
struct pack_helper {};
template <>
struct pack_helper<format::type::unsigned_int> {
static constexpr std::size_t num_bytes(std::uint64_t value) noexcept {
#ifdef PARSELINK_CFG_MSGPACK_PACKER_USE_BRANCHES
if (count <= 0x7f) return 0;
if (count < 0x100) return 1;
if (count < 0x10000) return 2;
if (count < 0x100000000) return 4;
return 8;
#else
if (value <= std::uint64_t(format::positive_fixint::mask)) return 0;
return std::bit_ceil(std::uint64_t((std::bit_width(value) + 7) >> 3));
#endif
}
static constexpr std::byte marker(std::uint64_t value) noexcept {
switch (num_bytes(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::uint8::marker;
case 2: return format::uint16::marker;
case 4: return format::uint32::marker;
default: return format::uint64::marker;
}
}
};
template <>
struct pack_helper<format::type::signed_int> {
static constexpr std::size_t num_bytes(std::int64_t value) noexcept {
// Probably a better way to do this.
if (value < 0 && value >= -32) return 0;
auto underlying = static_cast<std::uint64_t>(value);
// save a branch; these should be cheap on modern hardware.
std::uint64_t counts[2] = {
static_cast<std::uint64_t>(std::countl_zero(underlying)),
static_cast<std::uint64_t>(std::countl_one(underlying))};
std::uint64_t width = 1 + std::numeric_limits<std::uint64_t>::digits
- counts[underlying >> 63];
return std::bit_ceil((width + 7) >> 3);
}
static constexpr std::byte marker(std::int64_t value) noexcept {
switch (num_bytes(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::int8::marker;
case 2: return format::int16::marker;
case 4: return format::int32::marker;
default: return format::int64::marker;
}
}
};
template <>
struct pack_helper<format::type::binary> {
static constexpr uint32_t max_supported_payload = 4;
static constexpr std::size_t num_bytes(
std::span<std::byte const> value) noexcept {
return std::min(max_supported_payload,
std::bit_ceil(std::uint32_t(
((std::bit_width(value.size()) + 7) >> 3))));
}
static constexpr auto marker(std::span<std::byte const> value) noexcept {
auto const len = num_bytes(value);
switch (len) {
case 1: return format::bin8::marker;
case 2: return format::bin16::marker;
case 4:
default: return format::bin32::marker;
}
}
};
template <>
struct pack_helper<format::type::array> {
static constexpr uint32_t max_supported_payload = 4;
static constexpr std::size_t num_bytes(std::size_t count) noexcept {
if (count < 16) return 0;
return std::min(max_supported_payload,
std::bit_ceil(std::uint32_t(
((std::bit_width(count) + 15) >> 4) << 1)));
}
static constexpr auto marker(std::size_t value) noexcept {
auto const len = num_bytes(value);
switch (len) {
case 0:
case 2: return format::array16::marker;
case 4:
default: return format::bin32::marker;
}
}
};
constexpr auto x = pack_helper<format::type::array>::num_bytes(15);
constexpr auto y = pack_helper<format::type::array>::num_bytes(16);
constexpr auto z = pack_helper<format::type::array>::num_bytes(256);
constexpr auto a = pack_helper<format::type::array>::num_bytes(65536);
template <>
struct pack_helper<format::type::string> {
static constexpr uint32_t max_supported_payload = 4;
static constexpr std::size_t num_bytes(std::string_view value) noexcept {
auto const len = value.size();
if (len <= std::uint32_t(format::fixstr::mask)) return 0;
return std::min(max_supported_payload,
std::bit_ceil(std::uint32_t((std::bit_width(len) + 7) >> 3)));
}
static constexpr std::size_t payload_size(std::string_view value) noexcept {
return value.size();
}
static constexpr auto marker(std::string_view value) noexcept {
auto const len = num_bytes(value);
switch (len) {
case 0: return format::fixstr::marker | std::byte(value.size());
case 1: return format::str8::marker;
case 2: return format::str16::marker;
case 4:
default: return format::str32::marker;
}
}
};
} // namespace detail
// Pack adapter is the basis for packing native values into MessagePack format.
template <typename T>
struct pack_adapter {};
template <typename T>
concept basic_packable_type = requires(T const& t, std::byte* b) {
{ pack_adapter<T>::total_size(t) } -> std::same_as<std::size_t>;
{ pack_adapter<T>::write(t, b) } -> std::same_as<decltype(b)>;
{ pack_adapter<T>::marker(t) } -> std::same_as<std::byte>;
};
template <typename T>
concept packable_type = basic_packable_type<T>;
template <format::type F>
struct builtin_pack_adapter {
static constexpr auto format_type = F;
using pack_helper = detail::pack_helper<F>;
// Retrieves the number of bytes to store the format, aside from the marker
// byte.
static constexpr auto format_size(auto value) noexcept {
return pack_helper::num_bytes(value);
}
// Retrieves the number of bytes to store the payload, if it is
// variable-length.
static constexpr std::size_t payload_size(auto value) noexcept {
if constexpr (requires { pack_helper::payload_size(value); }) {
return pack_helper::payload_size(value);
} else {
return 0;
}
}
static constexpr auto total_size(auto value) noexcept {
return 1 + format_size(value) + payload_size(value);
}
static constexpr auto marker(auto value) noexcept {
return detail::pack_helper<F>::marker(value);
}
};
template <std::signed_integral T>
struct pack_adapter<T> : builtin_pack_adapter<format::type::signed_int> {
template <typename Itr>
static constexpr Itr write(T value, Itr out) noexcept {
return detail::write_integral(value, out, format_size(value));
}
};
template <std::unsigned_integral T>
struct pack_adapter<T> : builtin_pack_adapter<format::type::unsigned_int> {
template <typename Itr>
static constexpr Itr write(T value, Itr out) noexcept {
return detail::write_integral(value, out, format_size(value));
}
};
template <>
struct pack_adapter<std::string_view>
: builtin_pack_adapter<format::type::string> {
template <typename Itr>
static constexpr Itr write(std::string_view value, Itr out) noexcept {
out = detail::write_integral(value.size(), out, format_size(value));
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
std::copy(beg, beg + value.size(), out);
return out + value.size();
}
};
template <std::convertible_to<std::string_view> T>
struct pack_adapter<T> : pack_adapter<std::string_view> {};
template <>
struct pack_adapter<std::span<std::byte const>>
: builtin_pack_adapter<format::type::binary> {
template <typename Itr>
static constexpr Itr write(
std::span<std::byte const> value, Itr out) noexcept {
out = detail::write_integral(value.size(), out, format_size(value));
std::copy(value.begin(), value.end(), out);
return out + value.size();
}
};
class packer {
// Replace with output_range?
using BufferType = std::span<std::byte>;
public:
@ -295,19 +63,37 @@ public:
, curr_(std::begin(buff_))
, end_(std::end(buff_)) {}
template <packable_type T>
struct context {
decltype(std::begin(BufferType{})) out;
decltype(std::begin(BufferType{})) end;
constexpr auto remaining() noexcept {
return std::ranges::distance(out, end);
}
template <packable T>
constexpr tl::expected<tl::monostate, error> pack(T const& v) noexcept {
auto rem = remaining();
using packer_impl = detail::packer_impl<T>;
decltype(rem) needed = packer_impl::total_size(v);
if (needed > rem) {
return tl::make_unexpected(error::out_of_space);
} else {
packer_impl::pack(v, *this);
return tl::monostate{};
}
}
};
template <packable T>
constexpr tl::expected<tl::monostate, error> pack(T&& v) noexcept {
using diff_type =
std::iterator_traits<decltype(curr_)>::difference_type;
diff_type const space_needed = pack_adapter<T>::total_size(v);
if (space_needed > std::distance(curr_, end_)) {
return tl::make_unexpected(error::out_of_space);
context ctx{curr_, end_};
// If packing is successful, it updates iterators.
auto ret = ctx.pack(std::forward<T>(v));
if (ret) {
curr_ = ctx.out;
}
*curr_++ = pack_adapter<T>::marker(v);
if (space_needed > 1) {
curr_ = pack_adapter<T>::write(v, curr_);
}
return tl::monostate{};
return ret;
}
constexpr auto tell() const noexcept {

View File

@ -0,0 +1,94 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default unpacker implementation, utilizing pull-style parsing semantics for
// deserializing data with an expected structure akin to a schema.
//
// The default unpacker maintains some flexibility in how data was originally
// packed.
//
// E.g. If a std::uint8_t is requested, it is perfectly valid to read a uint32
// format, provided the stored value is less than 255.
//
// core/reader (to be renamed verbatim_unpacker) can be utilized in scenarios
// where code size is critical and exact type matching is not a considerable
// downside.
//
// For schemaless data, the token API can be used instead.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_unpacker_910ec357d397ac7e
#define msgpack_core_unpacker_910ec357d397ac7e
#include "parselink/msgpack/core/detail/builtin_unpackable_types.h"
#include "parselink/msgpack/core/detail/unpackable_concepts.h"
#include "parselink/msgpack/core/error.h"
#include <tl/expected.hpp>
#include <cstddef>
#include <span>
namespace msgpack {
class unpacker {
using BufferType = std::span<std::byte const>;
public:
constexpr unpacker(BufferType buff)
: buff_(buff)
, curr_(std::begin(buff_))
, end_(std::end(buff_)) {}
// IDK if we need this yet
struct context {
decltype(std::begin(BufferType{})) in;
decltype(std::begin(BufferType{})) end;
constexpr auto remaining() noexcept {
return std::ranges::distance(in, end);
}
template <supports_unpack T>
constexpr tl::expected<T, error> unpack() noexcept {
if (remaining() == 0)
return tl::make_unexpected(error::end_of_message);
using unpacker_impl = detail::unpacker_impl<T>;
return unpacker_impl::unpack(*this);
}
};
template <supports_unpack T>
constexpr tl::expected<T, error> unpack() noexcept {
context ctx{curr_, end_};
auto ret = ctx.unpack<T>();
if (ret) {
curr_ = ctx.in;
}
return ret;
}
constexpr auto remaining() noexcept {
return std::ranges::distance(curr_, end_);
}
private:
BufferType buff_;
decltype(std::begin(buff_)) curr_;
decltype(std::end(buff_)) end_;
};
} // namespace msgpack
#endif // msgpack_core_unpacker_910ec357d397ac7e

View File

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Extra functionality: fmt formatters for msgpack types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_extra_formatters_d5fd427a7f749dcb
#define msgpack_extra_formatters_d5fd427a7f749dcb
#define FMT_VERSION
#ifdef FMT_VERSION
#include "parselink/msgpack/core/format.h"
#include <fmt/format.h>
template <>
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const noexcept {
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
};
};
template <>
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
}
};
template <>
struct fmt::formatter<msgpack::map_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::map_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::map_desc: {}}}", value.count);
}
};
template <>
struct fmt::formatter<msgpack::array_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::array_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::array_desc: {}}}", value.count);
}
};
#ifdef PARSELINK_MSGPACK_TOKEN_API
template <>
struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const {
using parselink::logging::themed_arg;
auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", themed_arg(v.type()));
switch (v.type()) {
case msgpack::format::type::unsigned_int:
fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::signed_int:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::boolean:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>())));
break;
case msgpack::format::type::string:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
themed_arg(*(v.get<std::span<std::byte const>>())));
break;
case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::map_desc>()->count));
break;
case msgpack::format::type::array:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::array_desc>()->count));
break;
case msgpack::format::type::nil:
out = fmt::format_to(out, "(nil)");
break;
case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)");
break;
default: break;
}
return fmt::format_to(out, ">");
}
};
#endif // PARSELINK_MSGPACK_TOKEN_API
#endif // FMT_VERSION
#endif // msgpack_extra_formatters_d5fd427a7f749dcb

View File

@ -0,0 +1,35 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Error definitions for protocol-related functionality
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef parselink_proto_error_adef9d32bb51411a
#define parselink_proto_error_adef9d32bb51411a
namespace parselink {
namespace proto {
enum class error {
system_error,
incomplete,
unsupported,
bad_data,
too_large,
};
} // namespace proto
} // namespace parselink
#endif // parselink_proto_error_adef9d32bb51411a

View File

@ -21,9 +21,6 @@
#include <cstdint>
#include <span>
#include <string_view>
#include <variant>
#include <tl/expected.hpp>
namespace parselink {
namespace proto {
@ -37,35 +34,40 @@ namespace proto {
// This may be revised in the future. The header could remain as msgpack, or
// switch to something hand-crafted for saving bits.
struct error_message {
struct base_message {};
template <typename T>
concept message_type = std::is_base_of_v<base_message, T>;
struct error_message : base_message {
std::uint32_t code; // An error code
std::string_view what; // A string
};
// C->S: Request to (re)connect.
struct connect_message {
std::uint32_t user_id; // The user id.
struct connect_message : base_message {
std::uint32_t version; // The version of the client.
std::string_view user_id; // The user id.
std::span<std::byte> session_token; // An optional existing session token.
};
// S->C: Challenge to authenticate client as user_id
struct challenge_message {
struct challenge_message : base_message {
std::uint32_t version;
std::span<std::byte> challenge;
};
// C->S: Calculated response to a challenge.
struct response_message {
struct response_message : base_message {
std::span<std::byte> response;
};
// S->C: Session token.
struct session_established_message {
struct session_established_message : base_message {
std::span<std::byte> session_token;
};
struct parser_data_message {
struct parser_data_message : base_message {
std::string_view opts;
std::span<std::byte> payload;
};

View File

@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Parser for extracting messages from msgpack.
//
// Note: Eventually, it would be nice to automatically generate the parser for
// message structures. Definitions below are templated to allow for this.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef parselink_proto_parser_ad351d41fe4c72dd
#define parselink_proto_parser_ad351d41fe4c72dd
#include "parselink/proto/error.h"
#include "parselink/proto/message.h"
#include <tl/expected.hpp>
namespace parselink {
namespace proto {
////////////////////////////////////////////////////////////////////////////////
// Default parse template instantiation -- unsupported message.
////////////////////////////////////////////////////////////////////////////////
template <message_type M>
tl::expected<M, error> parse(std::span<std::byte const> data) noexcept {
return tl::make_unexpected(error::unsupported);
}
////////////////////////////////////////////////////////////////////////////////
// Currently-implemented message types.
////////////////////////////////////////////////////////////////////////////////
#define PARSELINK_PROTO_SUPPORTED_MESSAGE(msgtype) \
tl::expected<msgtype, error> parse_##msgtype( \
std::span<std::byte const> data) noexcept; \
template <> \
constexpr tl::expected<msgtype, error> parse( \
std::span<std::byte const> data) noexcept { \
return parse_##msgtype(data); \
}
PARSELINK_PROTO_SUPPORTED_MESSAGE(connect_message);
#undef PARSELINK_PROTO_SUPPORTED_MESSAGE
} // namespace proto
} // namespace parselink
#endif // parselink_proto_parser_ad351d41fe4c72dd

View File

@ -19,15 +19,17 @@
#define session_07eae057feface79
#include "parselink/msgpack/token.h"
#include "parselink/proto/error.h"
#include "parselink/proto/session_id.h"
#include <tl/expected.hpp>
#include <chrono>
#include <cstdint>
#include <functional>
#include <span>
#include <string>
#include <tl/expected.hpp>
template <>
struct std::hash<std::span<std::byte const>> {
constexpr static std::uint32_t seed_var = 0x811c9dc5;
@ -45,14 +47,6 @@ struct std::hash<std::span<std::byte const>> {
namespace parselink {
namespace proto {
enum class error {
system_error,
incomplete,
unsupported,
bad_data,
too_large,
};
// Structure containing header information parsed from a buffer.
struct header_info {
std::uint32_t message_size; // Size of the message, minus the header.

View File

@ -0,0 +1,156 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Utility
//
// Utility wrappers for dealing with files.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef utility_file_8b49e5e471a7c3e5
#define utility_file_8b49e5e471a7c3e5
#include <fmt/format.h>
#include <tl/expected.hpp>
#include <algorithm>
#include <fcntl.h>
#include <span>
#include <system_error>
#include <unistd.h>
#include <vector>
namespace parselink {
namespace utility {
namespace file {
struct [[nodiscard]] handle {
constexpr handle() noexcept = default;
constexpr handle(int fd) noexcept
: fd_(fd) {
if (fd_ < 0) fd_ = -errno;
}
constexpr handle(handle const&) noexcept = delete;
constexpr handle(handle&& other) noexcept
: fd_(other.fd_) {
other.fd_ = -1;
}
constexpr handle& operator=(handle const&) noexcept = delete;
constexpr handle& operator=(handle&& other) noexcept {
close();
fd_ = other.fd_;
return *this;
}
constexpr operator bool() const noexcept { return fd_ >= 0; }
constexpr int operator*() const noexcept { return fd_; }
void close() {
if (*this) {
::close(fd_);
fd_ = -1;
}
}
int fd_{-1};
};
inline tl::expected<handle, std::errc> open(
std::string_view path, int flags, int mode = {}) noexcept {
handle file(::open(std::string{path}.c_str(), flags, mode));
if (file) {
return file;
} else {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
}
inline tl::expected<std::vector<std::byte>, std::errc> read_some(
handle file, std::size_t amount) noexcept {
std::vector<std::byte> data;
if (!file) {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
data.resize(amount);
auto amt_read = ::read(*file, data.data(), amount);
if (amt_read < data.size()) {
data.resize(amt_read);
}
return data;
}
template <typename T>
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
inline tl::expected<std::vector<T>, std::errc> read(
handle const& file) noexcept {
std::vector<T> data;
if (!file) {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
auto cur = lseek(*file, 0, SEEK_CUR);
auto end = lseek(*file, 0, SEEK_END);
std::size_t amount = end - cur;
lseek(*file, SEEK_SET, cur);
data.resize(amount);
auto amt_read = ::read(*file, data.data(), amount);
if (amt_read < data.size()) {
data.resize(amt_read);
}
return data;
}
template <typename T>
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
inline tl::expected<std::size_t, std::errc> write(
handle const& file, std::span<T> data) noexcept {
if (!file) {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
lseek(*file, 0, SEEK_SET);
auto result = ftruncate(*file, 0);
if (result < 0) {
return tl::make_unexpected(static_cast<std::errc>(errno));
}
auto amt_written = ::write(*file, data.data(), data.size());
if (result < 0) {
return tl::make_unexpected(static_cast<std::errc>(errno));
}
return static_cast<std::size_t>(amt_written);
}
} // namespace file
} // namespace utility
} // namespace parselink
#endif // utility_file_8b49e5e471a7c3e5

View File

@ -3,6 +3,7 @@
cc_library(
name = "proto",
srcs = [
"parser.cpp",
"session.cpp",
"session_id.cpp",
],

70
source/proto/parser.cpp Normal file
View File

@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Parser implementations for various msgpack messages.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/proto/parser.h"
#include "parselink/logging.h"
#include "parselink/msgpack/core/unpacker.h"
namespace parselink {
namespace proto {
namespace {
logging::logger logger{"parser"};
constexpr auto assign(auto& param, msgpack::unpacker& unpacker) noexcept
-> tl::expected<void, error> {
auto do_assign = [&param](auto&& v) { param = v; };
return unpacker.unpack<std::decay_t<decltype(param)>>()
.map(do_assign)
.map_error([](auto) { return error::bad_data; });
}
} // anonymous namespace
tl::expected<connect_message, error> parse_connect_message(
std::span<std::byte const> data) noexcept {
msgpack::unpacker unpacker(data);
connect_message msg;
constexpr static tl::unexpected<error> bad_data(error::bad_data);
auto type = unpacker.unpack<std::string_view>();
if (!type || type != "connect") return bad_data;
auto entries = unpacker.unpack<msgpack::map_desc>();
if (!entries) return bad_data;
for (auto i = entries->count; i != 0; --i) {
auto key = unpacker.unpack<std::string_view>();
if (!key) return bad_data;
if (key == "version") {
if (auto val = assign(msg.version, unpacker)) continue;
} else if (key == "user_id") {
if (auto val = assign(msg.user_id, unpacker)) continue;
}
logger.debug("Unknown key: {}", *key);
return bad_data;
}
return msg;
}
} // namespace proto
} // namespace parselink

View File

@ -19,12 +19,13 @@
//-----------------------------------------------------------------------------
#include "parselink/proto/session.h"
#include "hydrogen.h"
#include "parselink/logging.h"
#include "parselink/msgpack/token.h"
#include <fmt/ranges.h>
#include "hydrogen.h"
namespace {
void ensure_initialized() {
@ -187,14 +188,11 @@ tl::expected<connect_info, error> proto::parse_connect(
}
}
session::session(std::string_view user_id, close_handle hdl) noexcept
session::session(std::string_view user_id) noexcept
: id_()
, user_id_(std::string{user_id})
, closer_(std::move(hdl))
, last_activity_(std::chrono::system_clock::now()) {
logger.debug("New session with id {} created for {}", id_.raw(), user_id_);
}
session::~session() {
if (closer_) closer_("Destroyed");
}
session::~session() {}

View File

@ -21,10 +21,15 @@
#include "parselink/server.h"
#include "parselink/logging.h"
#include "parselink/msgpack/core/writer.h"
#include "parselink/msgpack/token/reader.h"
#include "parselink/msgpack/token/views.h"
#include "parselink/msgpack/core/packer.h"
#include "parselink/msgpack/core/unpacker.h"
#include "parselink/proto/parser.h"
#include "parselink/proto/session.h"
#include "parselink/utility/file.h"
#include <fmt/ranges.h>
#include "hydrogen.h"
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/co_spawn.hpp>
@ -39,8 +44,6 @@
#include <boost/asio/write.hpp>
#include <unordered_set>
#include <fmt/ranges.h>
using namespace parselink;
#include <fmt/ranges.h>
@ -72,6 +75,13 @@ struct fmt::formatter<boost::system::error_code>
}
};
template <>
struct fmt::formatter<tl::monostate> : fmt::formatter<std::string_view> {
constexpr auto format(auto const&, auto& ctx) const {
return fmt::formatter<std::string_view>::format(".", ctx);
}
};
template <>
struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
@ -151,6 +161,7 @@ namespace {
logging::logger logger("server");
constexpr auto no_ex_coro = net::as_tuple(use_awaitable);
constexpr auto no_ex_defer = net::as_tuple(deferred);
} // namespace
#include <parselink/server/memory_session_manager.h>
@ -166,11 +177,14 @@ public:
net::awaitable<tl::expected<proto::session*, proto::error>> create_session(
std::shared_ptr<user_connection> const& conn,
proto::connect_info const& info);
proto::connect_message const& info);
private:
awaitable<void> user_listen();
tl::expected<tl::monostate, std::errc> load_keys() noexcept;
hydro_kx_keypair kp_;
net::io_context io_context_;
net::io_context::strand session_strand_;
memory_session_manager session_mgr_;
@ -261,12 +275,7 @@ public:
co_return;
}
auto reader = msgpack::token_reader(msgbuf);
std::array<msgpack::token, 32> tokens;
auto connect_info =
reader.read_some(tokens)
.map_error([](auto) { return proto::error::bad_data; })
.and_then(proto::parse_connect);
auto connect_info = proto::parse<proto::connect_message>(msgbuf);
if (!connect_info) {
logger.error("Session failed: {}", connect_info.error());
@ -279,8 +288,6 @@ public:
co_return;
}
session_ = *session;
auto writer = msgpack::writer(msgbuf);
}
enum class state { init, authenticated, active };
@ -298,6 +305,8 @@ monolithic_server::monolithic_server(std::string_view address,
, addr_(net::ip::address::from_string(std::string{address}))
, user_port_{user_port}
, websocket_port_{websocket_port} {
logger.debug("Loaded keys: {}", load_keys());
logger.debug("Creating monolithic_server(address = {}, user_port = {}, "
"websocket_port = {})",
address, user_port_, websocket_port_);
@ -329,9 +338,73 @@ std::error_code monolithic_server::run() noexcept {
return {};
}
tl::expected<tl::monostate, std::errc> monolithic_server::load_keys() noexcept {
std::string_view filename = "/home/rihya/server_kp.keys";
enum class result { loaded, generated };
auto load_key = [this](auto const& raw) -> tl::expected<result, std::errc> {
msgpack::unpacker unpacker(raw);
auto pk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.pk)>>();
if (!pk) return tl::make_unexpected(std::errc::bad_message);
auto sk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.sk)>>();
if (!sk) return tl::make_unexpected(std::errc::bad_message);
std::ranges::transform(pk->begin(), pk->end(), std::begin(kp_.pk),
[](auto c) { return std::bit_cast<unsigned char>(c); });
std::ranges::transform(sk->begin(), sk->end(), std::begin(kp_.sk),
[](auto c) { return std::bit_cast<unsigned char>(c); });
return result::loaded;
};
auto generate_keys =
[this](auto const& err) -> tl::expected<result, std::errc> {
logger.warning("Could not load server keys, generating a keypair");
hydro_kx_keygen(&kp_);
return result::generated;
};
auto commit = [this](auto const& handle)
-> tl::expected<tl::monostate, std::errc> {
std::vector<std::byte> buff(4 + sizeof(kp_.pk) + sizeof(kp_.sk));
std::span<std::byte> pk(
reinterpret_cast<std::byte*>(kp_.pk), sizeof(kp_.pk));
std::span<std::byte> sk(
reinterpret_cast<std::byte*>(kp_.sk), sizeof(kp_.pk));
msgpack::packer packer(buff);
packer.pack(pk);
packer.pack(sk);
return utility::file::write(handle, packer.subspan()).map([](auto) {
logger.info("Wrote new keys to disk");
return tl::monostate{};
});
};
auto load_or_generate_keys = [&load_key, &generate_keys, &commit](
auto const& handle)
-> tl::expected<tl::monostate, std::errc> {
return utility::file::read<std::byte>(handle)
.and_then(load_key)
.or_else(generate_keys)
.and_then([&handle, &commit](auto r)
-> tl::expected<tl::monostate, std::errc> {
if (r == result::generated) {
return commit(handle);
}
return tl::monostate{};
});
};
return utility::file::open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)
.and_then(load_or_generate_keys);
}
net::awaitable<tl::expected<proto::session*, proto::error>>
monolithic_server::create_session(std::shared_ptr<user_connection> const& conn,
proto::connect_info const& info) {
proto::connect_message const& info) {
// Move to session strand.
co_await net::post(session_strand_,
net::bind_executor(session_strand_, use_awaitable));

View File

@ -1,10 +1,11 @@
cc_library(
name = "test_deps",
name = "common",
srcs = [
"test_main.cpp",
"rng.h",
],
includes = ["include"],
hdrs = glob(["include/*.h"]),
deps = [
"//include/parselink:msgpack",
"@expected",
@ -12,6 +13,7 @@ cc_library(
"@magic_enum",
"@ut",
],
visibility = ["__subpackages__"],
)
cc_test(
@ -21,16 +23,7 @@ cc_test(
"test_reader_relaxed.cpp",
"test_reader_strict.cpp",
],
deps = ["test_deps"],
)
cc_test(
name = "packer",
size = "small",
srcs = [
"test_packer.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
@ -39,7 +32,7 @@ cc_test(
srcs = [
"test_writer.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
@ -48,7 +41,7 @@ cc_test(
srcs = [
"test_token.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
@ -57,7 +50,7 @@ cc_test(
srcs = [
"test_token_reader.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
@ -66,7 +59,7 @@ cc_test(
srcs = [
"test_token_views.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_binary(
@ -74,5 +67,13 @@ cc_binary(
srcs = [
"test_speed.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_binary(
name = "code",
srcs = [
"test_code.cpp",
],
deps = ["common"],
)

View File

@ -0,0 +1,102 @@
#ifndef msgpack_test_utils_4573e6627d8efe78
#define msgpack_test_utils_4573e6627d8efe78
// clang-format off : Must include fmt/format before extra/formatters.
#include <fmt/format.h>
#include "parselink/msgpack/extra/formatters.h"
// clang-format on
#include <tl/expected.hpp>
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <magic_enum_format.hpp>
#include <source_location>
namespace test {
template <typename T>
inline 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>
inline constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(
Bytes&&... bytes) noexcept {
return {std::byte(std::forward<Bytes>(bytes))...};
}
} // namespace test
// This formatter allows expected values to be formatted for both its expected
// and unexpected types.
template <typename T, typename E>
struct fmt::formatter<tl::expected<T, E>> {
int width = 0;
fmt::formatter<T> t_fmtr;
fmt::formatter<E> e_fmtr;
constexpr auto parse(auto& ctx) {
using char_type =
typename std::remove_reference_t<decltype(ctx)>::char_type;
auto delim = char_type{'|'};
auto close_brace = char_type{'}'};
std::array<char_type, 32> fmtbuf;
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') {
return ctx.begin();
}
bool t_parsed = false;
auto itr = ctx.begin();
auto buf = fmtbuf.begin();
while (*itr != close_brace) {
if (*itr == delim) {
if (t_parsed) {
fmt::throw_format_error("multiple delims encountered");
}
t_parsed = true;
*buf = close_brace;
auto str = basic_string_view<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> subparser(str, 0);
t_fmtr.parse(subparser);
buf = fmtbuf.begin();
} else {
*buf = *itr;
++buf;
}
++itr;
}
*buf = close_brace;
auto str = basic_string_view<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> subparser(str, 0);
if (t_parsed) {
e_fmtr.parse(subparser);
} else {
t_fmtr.parse(subparser);
}
ctx.advance_to(itr);
return itr;
}
auto format(tl::expected<T, E> const& v, auto& ctx) const {
if (v) {
// return fmt::format_to(ctx.out(), "{}", v.value());
return t_fmtr.format(v.value(), ctx);
} else {
return e_fmtr.format(v.error(), ctx);
}
}
};
#endif // msgpack_test_utils_4573e6627d8efe78

View File

@ -0,0 +1,9 @@
cc_test(
name = "packer",
size = "small",
srcs = glob([
"*.cpp",
"*.h",
]),
deps = ["//tests/msgpack:common", "@rapidcheck"],
)

View File

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, byte types
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
using namespace boost::ut;
suite pack_bytes = [] {
#if 0
// Byte ranges -> Binary
"packer::pack<std::span<std::byte>>"_test = [] {
expect(test_deduced<std::span<std::byte const>>());
};
#endif
"packer::pack<std::array<std::byte, N>>"_test = [] {
{
constexpr auto data = test::make_bytes(0x01, 0x02, 0x03, 0x04);
constexpr auto expected =
test::make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04);
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 256> data{};
constexpr std::array<std::byte, 259> expected{
std::byte(0xc5), std::byte(0x01), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 65536> data{};
constexpr std::array<std::byte, std::size(data) + 5> expected{
std::byte(0xc6), std::byte(0x00), std::byte(0x01),
std::byte(0x00), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
};
};

View File

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, ranges (for types that serialize to array / map formats)
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <fmt/ranges.h>
using namespace boost::ut;
namespace {
constexpr bool expect_equal(auto const& expected, auto const& actual) {
if (!std::ranges::equal(expected, actual)) {
fmt::print("\n\tExpected: ");
for (auto x : expected) {
fmt::print("{:02x} ", x);
}
fmt::print("\n\t Actual: ");
for (auto x : actual) {
fmt::print("{:02x} ", x);
}
fmt::print("\n");
return false;
}
return true;
}
} // namespace
suite pack_ranges = [] {
"packer::pack<std::array/std::span<int>>"_test = [] {
constexpr auto data_array = std::to_array<int>({5, 10, 15, 20});
constexpr auto expected = test::make_bytes(0x94, 0x05, 0xa, 0xf, 0x14);
{
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_array));
expect(expect_equal(std::span(expected), payload));
}
{
std::span data_span{data_array};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_span));
expect(expect_equal(expected, payload));
}
};
};

View File

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, signed ints.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
auto check_pack() {
return rc::check([](T value) {
std::array<std::byte, 16> payload;
msgpack::packer packer(payload);
if (!packer.pack(value)) return false;
if (value < 128 && value >= -32) {
// positive_fixint/negative_fixint
return packer.tell() == 1
&& payload[0] == static_cast<std::byte>(value);
} else if (within<std::int8_t>(value)) {
return payload[0] == msgpack::format::int8::marker
&& verify_packed<std::int8_t>(packer, value);
} else if (within<std::int16_t>(value)) {
return payload[0] == msgpack::format::int16::marker
&& verify_packed<std::int16_t>(packer, value);
} else if (within<std::int32_t>(value)) {
return payload[0] == msgpack::format::int32::marker
&& verify_packed<std::int32_t>(packer, value);
} else {
return payload[0] == msgpack::format::int64::marker
&& verify_packed<std::int64_t>(packer, value);
}
});
}
} // anonymous namespace
suite pack_signed_types = [] {
"packer::pack<std::int8_t>"_test = [] {
expect(check_pack<std::int8_t>());
};
"packer::pack<std::int16_t>"_test = [] {
expect(check_pack<std::int16_t>());
};
"packer::pack<std::int32_t>"_test = [] {
expect(check_pack<std::int32_t>());
};
"packer::pack<std::int64_t>"_test = [] {
expect(check_pack<std::int64_t>());
};
};

View File

@ -0,0 +1,50 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, simple types
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
using namespace boost::ut;
suite pack_simple_types = [] {
"packer::pack<nil>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(msgpack::nil{}));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc0});
};
"packer::pack<invalid>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(msgpack::invalid{}));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc1});
};
"packer::pack<bool>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(false));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc2});
expect(!!packer.pack(true));
expect(packer.tell() == 2);
expect(payload[1] == std::byte{0xc3});
};
};

View File

@ -0,0 +1,46 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, strings.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <std::unsigned_integral LenType, typename StrType = std::string>
auto check_string() {
return rc::check([](LenType value) {
auto str = *rc::gen::container<std::string>(
value, rc::gen::character<char>());
std::vector<std::byte> payload;
payload.resize(value + 32);
msgpack::packer packer(payload);
if (!packer.pack(str)) return false;
return true;
});
}
} // anonymous namespace
suite pack_strings = [] {
"packer::pack<std::string>"_test = [] {
expect(check_string<std::uint8_t>());
expect(check_string<std::uint16_t>());
};
};

View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, common code.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef tests_msgpack_packer_6e9a5bf8e14223bc
#define tests_msgpack_packer_6e9a5bf8e14223bc
#include "parselink/msgpack/core/packer.h"
#include "test_utils.h"
template <std::integral T>
constexpr auto within(auto value) noexcept {
return value >= std::numeric_limits<T>::min()
&& value <= std::numeric_limits<T>::max();
}
template <std::integral T>
constexpr auto verify_packed(auto const& packer, auto value) noexcept {
std::array<std::byte, (std::numeric_limits<T>::digits + 7) / 8> raw;
if (packer.tell() != 1 + raw.size()) return false;
auto packed = packer.subspan().subspan(1);
be_to_host(packed.begin(), packed.end(), raw.begin());
auto actual = std::bit_cast<T>(raw);
return actual == value;
}
#endif // tests_msgpack_packer_6e9a5bf8e14223bc

View File

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, unsigned ints.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
auto check_unsigned() {
return rc::check([](T value) {
std::array<std::byte, 16> payload;
msgpack::packer packer(payload);
if (!packer.pack(value)) return false;
if (value < 128) {
// positive_fixint
return packer.tell() == 1
&& payload[0] == static_cast<std::byte>(value);
} else if (within<std::uint8_t>(value)) {
return payload[0] == msgpack::format::uint8::marker
&& verify_packed<std::uint8_t>(packer, value);
} else if (within<std::uint16_t>(value)) {
return payload[0] == msgpack::format::uint16::marker
&& verify_packed<std::uint16_t>(packer, value);
} else if (within<std::uint32_t>(value)) {
return payload[0] == msgpack::format::uint32::marker
&& verify_packed<std::uint32_t>(packer, value);
} else {
return payload[0] == msgpack::format::uint64::marker
&& verify_packed<std::uint64_t>(packer, value);
}
});
}
} // anonymous namespace
suite packer_unsigned_ints = [] {
"packer::pack<std::uint8_t>"_test = [] {
expect(check_unsigned<std::uint8_t>());
};
"packer::pack<std::uint16_t>"_test = [] {
expect(check_unsigned<std::uint16_t>());
};
"packer::pack<std::uint32_t>"_test = [] {
expect(check_unsigned<std::uint32_t>());
};
"packer::pack<std::uint64_t>"_test = [] {
expect(check_unsigned<std::uint64_t>());
};
};

View File

@ -0,0 +1,17 @@
#include <array>
#include <map>
#include <parselink/msgpack/core/packer.h>
int main(int argc, char** argv) {
std::map<int, std::string_view> args{};
for (int i = 0; i < argc; ++i) {
args.emplace(i, argv[i]);
}
std::array<std::byte, 500> buff;
msgpack::packer packer(buff);
packer.pack(args);
for (auto b : packer.subspan()) {
printf("%c", char(b));
}
return 0;
}

View File

@ -22,11 +22,46 @@
#include <fmt/ranges.h>
#include "rng.h"
#include <algorithm>
#include <boost/ut.hpp>
#include <chrono>
using namespace boost::ut;
template <>
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
}
};
template <>
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
}
};
template <>
struct fmt::formatter<msgpack::map_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::map_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::map_desc: {}}}", value.count);
}
};
template <>
struct fmt::formatter<msgpack::array_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::array_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::array_desc: {}}}", value.count);
}
};
namespace {
template <typename... Bytes>
@ -41,6 +76,24 @@ constexpr auto equal(auto a, auto b) {
template <typename T>
struct test_data {};
template <>
struct test_data<msgpack::nil> {
static constexpr msgpack::nil values[] = {{}};
static constexpr auto payload = make_bytes(0xc0);
};
template <>
struct test_data<msgpack::invalid> {
static constexpr msgpack::invalid values[] = {{}};
static constexpr auto payload = make_bytes(0xc1);
};
template <>
struct test_data<bool> {
static constexpr auto values = std::to_array<bool>({false, true});
static constexpr auto payload = make_bytes(0xc2, 0xc3);
};
template <>
struct test_data<std::uint64_t> {
static constexpr auto values = std::to_array<std::uint64_t>({
@ -129,6 +182,42 @@ struct test_data<std::int64_t> {
}
};
template <>
struct test_data<msgpack::array_desc> {
static constexpr auto values = std::to_array<msgpack::array_desc>({
1, // fixarray
15, // fixarray
16, // array16
65535, // array16
65536 // array32
});
static constexpr auto payload = make_bytes(0x91, // fixarray
0x9f, // fixarray
0xdc, 0x00, 0x10, // array16
0xdc, 0xff, 0xff, // array16
0xdd, 0x00, 0x01, 0x00, 0x00 // array32
);
};
template <>
struct test_data<msgpack::map_desc> {
static constexpr auto values = std::to_array<msgpack::map_desc>({
1, // fixmap
15, // fixmap
16, // map16
65535, // map16
65536 // map32
});
static constexpr auto payload = make_bytes(0x81, // fixmap
0x8f, // fixmap
0xde, 0x00, 0x10, // map16
0xde, 0xff, 0xff, // map16
0xdf, 0x00, 0x01, 0x00, 0x00 // map32
);
};
template <std::convertible_to<std::string_view> T>
struct test_data<T> {
static constexpr auto values = std::to_array<T>({
@ -185,7 +274,9 @@ bool test_deduced() noexcept {
msgpack::packer packer(payload);
for (auto const& value : test_data<T>::values) {
if (!test_data<T>::valid(value)) break;
if constexpr (requires { test_data<T>::valid(value); }) {
if (!test_data<T>::valid(value)) break;
}
expect(!!packer.pack(T(value)));
auto expect = std::span(expected_payload.begin(), packer.tell());
auto correct = equal(packer.subspan(), expect);
@ -201,7 +292,14 @@ bool test_deduced() noexcept {
} // anonymous namespace
suite packer = [] {
suite packer_single_format = [] {
"packer::pack<bool>"_test = [] { expect(test_deduced<bool>()); };
"packer::pack<nil>"_test = [] { expect(test_deduced<msgpack::nil>()); };
"packer::pack<invalid>"_test = [] {
expect(test_deduced<msgpack::invalid>());
};
// Unsigned ints
"packer::pack<std::uint8_t>"_test = [] {
expect(test_deduced<std::uint8_t>());
};
@ -215,6 +313,7 @@ suite packer = [] {
expect(test_deduced<std::uint64_t>());
};
// Signed ints
"packer::pack<std::int8_t>"_test = [] {
expect(test_deduced<std::int8_t>());
};
@ -228,60 +327,86 @@ suite packer = [] {
expect(test_deduced<std::int64_t>());
};
// Strings
"packer::pack<std::string_view>"_test = [] {
expect(test_deduced<std::string_view>());
};
#if 0
"packer::pack<char const*>"_test = [] {
expect(test_deduced<char const*>());
};
"packer::pack<std::array<std::byte>>"_test = [] {
#endif
// Byte ranges -> Binary
"packer::pack<std::span<std::byte>>"_test = [] {
expect(test_deduced<std::span<std::byte const>>());
};
"packer::pack<std::array<std::byte, N>>"_test = [] {
{
constexpr auto data = make_bytes(0x01, 0x02, 0x03, 0x04);
constexpr auto expected =
make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04);
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 256> data{};
constexpr std::array<std::byte, 259> expected{
std::byte(0xc5), std::byte(0x01), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 65536> data{};
constexpr std::array<std::byte, std::size(data) + 5> expected{
std::byte(0xc6), std::byte(0x00), std::byte(0x01),
std::byte(0x00), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
};
// array_desc - Just the header of the array.
"packer::pack<msgpack::array_desc>"_test = [] {
expect(test_deduced<msgpack::array_desc>());
};
// map_desc - Just the header of the map.
"packer::pack<msgpack::map_desc>"_test = [] {
expect(test_deduced<msgpack::map_desc>());
};
};
static constexpr auto num_bytes(std::uint64_t count) {
if (count <= 0x7f) return 0;
if (count < 0x100) return 1;
if (count < 0x10000) return 2;
if (count < 0x100000000) return 4;
return 8;
}
suite packer_ranges = [] {
"packer::pack<std::array/std::span<int>>"_test = [] {
constexpr auto data_array = std::to_array<int>({5, 10, 15, 20});
constexpr auto expected =
make_bytes(0x94, 0xd0, 0x05, 0xd0, 0xa, 0xd0, 0xf, 0xd0, 0x14);
static constexpr std::size_t bit_count_bytes(std::uint64_t count) {
if (count <= 0x7f) return 0;
return std::bit_ceil(std::uint64_t((std::bit_width(count) + 7) >> 3));
}
{
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_array));
expect(std::ranges::equal(payload, expected));
}
suite perf = [] {
"test"_test = [] {
for (auto j = 0; j < 10; ++j) {
using namespace std::chrono_literals;
std::vector<std::uint64_t> a;
std::vector<std::uint64_t> r1;
std::vector<std::uint64_t> r2;
a.reserve(10000000);
r1.reserve(10000000);
r2.reserve(10000000);
rng x;
for (auto i = 0; i < 10000000; ++i) {
a.push_back(x.get<std::size_t>());
}
auto start2 = std::chrono::steady_clock::now();
for (auto v : a) {
r2.push_back(num_bytes(v));
}
auto end2 = std::chrono::steady_clock::now();
auto start = std::chrono::steady_clock::now();
for (auto v : a) {
r1.push_back(bit_count_bytes(v));
}
auto end = std::chrono::steady_clock::now();
fmt::print("Time taken: {}us vs {}us\n", (end - start) / 1us,
(end2 - start2) / 1us);
{
std::span data_span{data_array};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_span));
expect(std::ranges::equal(payload, expected));
}
};
};

View File

@ -1,100 +0,0 @@
#ifndef msgpack_test_utils_4573e6627d8efe78
#define msgpack_test_utils_4573e6627d8efe78
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <fmt/format.h>
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>
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) {
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());
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));
return out;
}
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 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;
}
#endif // msgpack_test_utils_4573e6627d8efe78

View File

@ -0,0 +1,9 @@
cc_test(
name = "unpacker",
size = "small",
srcs = glob([
"*.cpp",
"*.h",
]),
deps = ["//tests/msgpack:common", "@rapidcheck"],
)

View File

@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for bytes.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
using namespace boost::ut;
suite unpack_byte_types = [] {
// TODO: Make non-const version?
"unpacker::unpack<std::span<std::byte const>>"_test = [] {
constexpr auto payload =
test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05);
constexpr auto expected =
test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x05);
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const>>();
expect(std::ranges::equal(*actual, expected));
}
{
// Test extent
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const>>();
expect(std::ranges::equal(*actual2, expected));
}
};
// TODO: Make non-const version?
"unpacker::unpack<std::array<std::byte, N>>"_test = [] {
constexpr auto payload =
test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x06);
constexpr auto expected =
test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x06);
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::array<std::byte, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::array<std::byte, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual2, expected));
}
};
};

View File

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for signed integer values
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
struct get_marker;
template <>
struct get_marker<std::int8_t> {
static constexpr std::byte marker = msgpack::format::int8::marker;
};
template <>
struct get_marker<std::int16_t> {
static constexpr std::byte marker = msgpack::format::int16::marker;
};
template <>
struct get_marker<std::int32_t> {
static constexpr std::byte marker = msgpack::format::int32::marker;
};
template <>
struct get_marker<std::int64_t> {
static constexpr std::byte marker = msgpack::format::int64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::min() > value) {
return unpacked_value
== tl::make_unexpected(msgpack::error::will_truncate);
} else {
return unpacked_value.has_value() && unpacked_value == value;
}
});
if (!result) {
fmt::print("Failed on {}\n",
std::source_location::current().function_name());
}
return result;
}
} // anonymous namespace
suite unpack_signed_types = [] {
"unpacker::unpack<std::int8_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int8_t>());
expect(check_unpack_integer<std::int16_t, std::int8_t>());
expect(check_unpack_integer<std::int32_t, std::int8_t>());
expect(check_unpack_integer<std::int64_t, std::int8_t>());
};
"unpacker::unpack<std::int16_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int16_t>());
expect(check_unpack_integer<std::int16_t, std::int16_t>());
expect(check_unpack_integer<std::int32_t, std::int16_t>());
expect(check_unpack_integer<std::int64_t, std::int16_t>());
};
"unpacker::unpack<std::int32_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int32_t>());
expect(check_unpack_integer<std::int16_t, std::int32_t>());
expect(check_unpack_integer<std::int32_t, std::int32_t>());
expect(check_unpack_integer<std::int64_t, std::int32_t>());
};
"unpacker::unpack<std::int64_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int64_t, std::int64_t>());
};
};

View File

@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <fmt/format.h>
using namespace boost::ut;
namespace {} // anonymous namespace
suite unpacker_simple_types = [] {
"unpacker::unpack<invalid>"_test = [] {
tl::expected<int, msgpack::error> x(
tl::make_unexpected(msgpack::error::end_of_message));
static constexpr auto payload = test::make_bytes(0xc1, 0xc1);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<msgpack::invalid>(unpacker));
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<nil>"_test = [] {
static constexpr auto payload = test::make_bytes(0xc0, 0xc0);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<msgpack::nil>(unpacker));
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<bool>"_test = [] {
constexpr auto payload = test::make_bytes(0xc3, 0xc2);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<bool>(unpacker));
auto value = unpacker.unpack<bool>();
expect(value && *value == true);
value = unpacker.unpack<bool>();
expect(value && *value == false);
expect(unpacker.unpack<bool>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -0,0 +1,31 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for strings.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
using namespace boost::ut;
suite unpack_string_types = [] {
"unpacker::unpack<std::string_view>"_test = [] {
static constexpr auto payload = test::make_bytes(0xa2, 'H', 'i');
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<std::string_view>(unpacker));
expect(unpacker.unpack<std::string_view>() == "Hi");
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests, common code.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef tests_msgpack_unpacker_25b118dd14e2b1b4
#define tests_msgpack_unpacker_25b118dd14e2b1b4
#include "parselink/msgpack/core/unpacker.h"
#include "test_utils.h"
// Helper template to check that a type is either equal to itself, or verify
// that it won't unpack correctly.
template <typename T, typename U>
constexpr bool unpack_type_check(msgpack::unpacker unpacker) noexcept {
auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type);
auto compatible_type = std::same_as<T, U>
|| (std::is_unsigned_v<T> && std::is_unsigned_v<U>)
|| (std::is_signed_v<T> && std::is_signed_v<U>);
auto result = compatible_type || unpacker.unpack<U>() == wrong_type;
if (!result) {
fmt::print("Unpack_type_check failed: {}\n",
std::source_location::current().function_name());
}
return result;
}
// Validate the wrong types do not unpack correctly, or fail.
template <typename Expected, typename... OtherTypes>
constexpr bool try_unpacking_types(msgpack::unpacker unpacker) noexcept {
return ((unpack_type_check<Expected, OtherTypes>(unpacker)) && ...);
}
#define SUPPORTED_TYPES \
msgpack::nil, msgpack::invalid, bool, std::uint8_t, std::uint16_t, \
std::uint32_t, std::uint64_t, std::int8_t, std::int16_t, \
std::int32_t, std::int64_t, std::string_view
template <typename Expected>
constexpr bool type_check_unpacker(msgpack::unpacker unpacker) noexcept {
return try_unpacking_types<Expected, SUPPORTED_TYPES>(unpacker);
}
#endif // tests_msgpack_unpacker_25b118dd14e2b1b4

View File

@ -0,0 +1,131 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
constexpr auto equal(auto a, auto b) {
return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, typename U>
constexpr bool test_unpack(msgpack::unpacker unpacker) noexcept {
auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type);
return std::same_as<T, U> || unpacker.unpack<U>() == wrong_type;
}
template <typename Expected, typename... OtherTypes>
constexpr bool test_types(msgpack::unpacker unpacker) noexcept {
return ((test_unpack<Expected, OtherTypes>(unpacker)) && ...);
}
template <typename Expected>
constexpr bool expect_invalid(msgpack::unpacker unpacker) noexcept {
return test_types<Expected, msgpack::nil, msgpack::invalid, bool,
std::uint8_t, std::string_view>(unpacker);
}
template <typename T>
struct get_marker;
template <>
struct get_marker<std::uint8_t> {
static constexpr std::byte marker = msgpack::format::uint8::marker;
};
template <>
struct get_marker<std::uint16_t> {
static constexpr std::byte marker = msgpack::format::uint16::marker;
};
template <>
struct get_marker<std::uint32_t> {
static constexpr std::byte marker = msgpack::format::uint32::marker;
};
template <>
struct get_marker<std::uint64_t> {
static constexpr std::byte marker = msgpack::format::uint64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::min() > value) {
return unpacked_value
== tl::make_unexpected(msgpack::error::will_truncate);
} else {
return unpacked_value.has_value() && unpacked_value == value;
}
});
if (!result) {
fmt::print("Failed on {}\n",
std::source_location::current().function_name());
}
return result;
}
} // anonymous namespace
suite unpack_unsigned_types = [] {
"unpacker::unpack<std::uint8_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint8_t>());
expect(check_unpack_integer<std::uint16_t, std::uint8_t>());
expect(check_unpack_integer<std::uint32_t, std::uint8_t>());
expect(check_unpack_integer<std::uint64_t, std::uint8_t>());
};
"unpacker::unpack<std::uint16_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint16_t>());
expect(check_unpack_integer<std::uint16_t, std::uint16_t>());
expect(check_unpack_integer<std::uint32_t, std::uint16_t>());
expect(check_unpack_integer<std::uint64_t, std::uint16_t>());
};
"unpacker::unpack<std::uint32_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint32_t>());
expect(check_unpack_integer<std::uint16_t, std::uint32_t>());
expect(check_unpack_integer<std::uint32_t, std::uint32_t>());
expect(check_unpack_integer<std::uint64_t, std::uint32_t>());
};
"unpacker::unpack<std::uint64_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint64_t>());
expect(check_unpack_integer<std::uint16_t, std::uint64_t>());
expect(check_unpack_integer<std::uint32_t, std::uint64_t>());
expect(check_unpack_integer<std::uint64_t, std::uint64_t>());
};
};