From a2d480e723c3004db38f3867b4fd4e2697028304 Mon Sep 17 00:00:00 2001 From: Kurt Sassenrath Date: Thu, 25 Jan 2024 22:18:00 -0800 Subject: [PATCH] More unpacker work, reflection support --- MODULE.bazel.lock | 36 +++- include/parselink/BUILD | 18 +- include/parselink/logging/theme.h | 10 + .../msgpack/core/detail/unpack_map.h | 76 ++++++++ include/parselink/proto/message.h | 9 +- include/parselink/proto/parser.h | 35 ++++ include/parselink/utility/ctreflect.h | 174 ++++++++++++++++++ include/parselink/utility/ctstring.h | 6 + source/client/client.cpp | 3 +- source/proto/parser.cpp | 11 +- source/server/udp_server.cpp | 7 + tests/msgpack/unpacker/BUILD | 6 +- tests/msgpack/unpacker/maps.cpp | 82 +++++++++ tests/msgpack/unpacker/unsigned.cpp | 2 +- 14 files changed, 449 insertions(+), 26 deletions(-) create mode 100644 include/parselink/msgpack/core/detail/unpack_map.h create mode 100644 include/parselink/utility/ctreflect.h create mode 100644 tests/msgpack/unpacker/maps.cpp diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 964faf9..0e3a8c4 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -641,12 +641,13 @@ "name": "apple_support~1.5.0~apple_cc_configure_extension~local_config_apple_cc_toolchains" } } - } + }, + "recordedRepoMappingEntries": [] } }, "@@bazel_tools//tools/cpp:cc_configure.bzl%cc_configure_extension": { "general": { - "bzlTransitiveDigest": "O9sf6ilKWU9Veed02jG9o2HM/xgV/UAyciuFBuxrFRY=", + "bzlTransitiveDigest": "mcsWHq3xORJexV5/4eCvNOLxFOQKV6eli3fkr+tEaqE=", "accumulatedFileDigests": {}, "envVariables": {}, "generatedRepoSpecs": { @@ -664,7 +665,14 @@ "name": "bazel_tools~cc_configure_extension~local_config_cc_toolchains" } } - } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_tools", + "bazel_tools", + "bazel_tools" + ] + ] } }, "@@bazel_tools//tools/osx:xcode_configure.bzl%xcode_configure_extension": { @@ -682,7 +690,8 @@ "remote_xcode": "" } } - } + }, + "recordedRepoMappingEntries": [] } }, "@@bazel_tools//tools/sh:sh_configure.bzl%sh_configure_extension": { @@ -698,12 +707,13 @@ "name": "bazel_tools~sh_configure_extension~local_config_sh" } } - } + }, + "recordedRepoMappingEntries": [] } }, "@@rules_java~7.1.0//java:extensions.bzl%toolchains": { "general": { - "bzlTransitiveDigest": "iUIRqCK7tkhvcDJCAfPPqSd06IHG0a8HQD0xeQyVAqw=", + "bzlTransitiveDigest": "D02GmifxnV/IhYgspsJMDZ/aE8HxAjXgek5gi6FSto4=", "accumulatedFileDigests": {}, "envVariables": {}, "generatedRepoSpecs": { @@ -1238,7 +1248,19 @@ "build_file": "\nconfig_setting(\n name = \"prefix_version_setting\",\n values = {\"java_runtime_version\": \"remotejdk_21\"},\n visibility = [\"//visibility:private\"],\n)\nconfig_setting(\n name = \"version_setting\",\n values = {\"java_runtime_version\": \"21\"},\n visibility = [\"//visibility:private\"],\n)\nalias(\n name = \"version_or_prefix_version_setting\",\n actual = select({\n \":version_setting\": \":version_setting\",\n \"//conditions:default\": \":prefix_version_setting\",\n }),\n visibility = [\"//visibility:private\"],\n)\ntoolchain(\n name = \"toolchain\",\n target_compatible_with = [\"@platforms//os:windows\", \"@platforms//cpu:x86_64\"],\n target_settings = [\":version_or_prefix_version_setting\"],\n toolchain_type = \"@bazel_tools//tools/jdk:runtime_toolchain_type\",\n toolchain = \"@remotejdk21_win//:jdk\",\n)\ntoolchain(\n name = \"bootstrap_runtime_toolchain\",\n # These constraints are not required for correctness, but prevent fetches of remote JDK for\n # different architectures. As every Java compilation toolchain depends on a bootstrap runtime in\n # the same configuration, this constraint will not result in toolchain resolution failures.\n exec_compatible_with = [\"@platforms//os:windows\", \"@platforms//cpu:x86_64\"],\n target_settings = [\":version_or_prefix_version_setting\"],\n toolchain_type = \"@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type\",\n toolchain = \"@remotejdk21_win//:jdk\",\n)\n" } } - } + }, + "recordedRepoMappingEntries": [ + [ + "rules_java~7.1.0", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_java~7.1.0", + "remote_java_tools", + "rules_java~7.1.0~toolchains~remote_java_tools" + ] + ] } } } diff --git a/include/parselink/BUILD b/include/parselink/BUILD index 96311f7..9513de5 100644 --- a/include/parselink/BUILD +++ b/include/parselink/BUILD @@ -3,7 +3,7 @@ cc_library( name = "msgpack", hdrs = glob(["msgpack/**/*.h"]), includes = ["."], - deps = ["@expected", "//include:path"], + deps = ["@expected", "//include:path", "utility"], visibility = ["//visibility:public"], ) @@ -23,14 +23,6 @@ cc_library( visibility = ["//visibility:public"], ) -cc_library( - name = "proto", - hdrs = glob(["proto/**/*.h"]), - includes = ["."], - deps = ["@expected", "//include:path"], - visibility = ["//visibility:public"], -) - cc_library( name = "utility", hdrs = glob(["utility/**/*.h"]), @@ -39,6 +31,14 @@ cc_library( visibility = ["//visibility:public"], ) +cc_library( + name = "proto", + hdrs = glob(["proto/**/*.h"]), + includes = ["."], + deps = ["@expected", "//include:path", "utility"], + visibility = ["//visibility:public"], +) + exports_files( ["server.h"], visibility = ["//visibility:public"], diff --git a/include/parselink/logging/theme.h b/include/parselink/logging/theme.h index 3eb0518..56074ca 100644 --- a/include/parselink/logging/theme.h +++ b/include/parselink/logging/theme.h @@ -62,6 +62,16 @@ struct format_arg { type v; }; +template <> +struct format_arg { + using type = std::string_view; + + constexpr format_arg(std::string_view const& t) noexcept + : v(t) {} + + std::string_view v; +}; + template struct format_arg { using type = void const*; diff --git a/include/parselink/msgpack/core/detail/unpack_map.h b/include/parselink/msgpack/core/detail/unpack_map.h new file mode 100644 index 0000000..baea1d9 --- /dev/null +++ b/include/parselink/msgpack/core/detail/unpack_map.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Support for unpacking maps with known structure. +// +// Maps contain key, value pairs. These pairs may arrive in an +// implementation-defined order; this code aims to provide the user of the +// interface with a means to pass a set of handlers for each key. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef msgpack_core_detail_unpack_map_9f12af74a689aa51 +#define msgpack_core_detail_unpack_map_9f12af74a689aa51 + +#include "parselink/utility/ctstring.h" +#include +#include + +namespace msgpack { +namespace map { + +struct key_base {}; + +template +struct key : key_base { + static constexpr auto const& value = key_value; + callable handler; + + constexpr key(callable fn) noexcept + : handler(std::move(fn)) {} +}; + +template +constexpr auto on_key(auto callable) noexcept { + return key(std::move(callable)); +} + +template +constexpr auto on_key(auto callable) noexcept { + return key(std::move(callable)); +} + +template +concept is_key = std::is_base_of_v; + +consteval auto get_key(is_key auto const& key) { + return std::decay_t::value; +} + +struct policy_type {}; + +struct required : policy_type {}; + +template +struct key_group { + constexpr key_group(Keys&&... keys) noexcept + : keys_(std::make_tuple(std::forward(keys)...)){}; + + std::tuple keys_; + constexpr static std::size_t size = sizeof...(Keys); +}; // namespace map + +} // namespace map +} // namespace msgpack + +#endif // msgpack_core_detail_unpack_map_9f12af74a689aa51 diff --git a/include/parselink/proto/message.h b/include/parselink/proto/message.h index 66dbd4d..af8d877 100644 --- a/include/parselink/proto/message.h +++ b/include/parselink/proto/message.h @@ -24,7 +24,6 @@ #include #include - namespace parselink { namespace proto { @@ -45,15 +44,17 @@ struct message : base_message { }; template -concept message_type = std::is_base_of_v; +concept message_type = requires { T::message_name(); }; + +struct error_message { + static constexpr auto message_name() noexcept { return "error"; } -struct error_message : message<"error"> { std::uint32_t code; // An error code std::string_view what; // A string }; // C->S: Request to (re)connect. -struct connect_message : base_message { +struct connect_message : message<"connect"> { std::uint32_t version; // The version of the client. std::string_view user_id; // The user id. std::span session_token; // An optional existing session token. diff --git a/include/parselink/proto/parser.h b/include/parselink/proto/parser.h index e9ee486..0d07e42 100644 --- a/include/parselink/proto/parser.h +++ b/include/parselink/proto/parser.h @@ -21,10 +21,45 @@ #ifndef parselink_proto_parser_ad351d41fe4c72dd #define parselink_proto_parser_ad351d41fe4c72dd +#include "parselink/logging.h" #include "parselink/proto/error.h" #include "parselink/proto/message.h" +#include "parselink/msgpack/core/unpacker.h" + #include +#include + +namespace parselink { +logging::logger& get_logger(); +} + +namespace msgpack { + +template +struct unpacker_adapter { + static constexpr auto unpack(auto& unpacker) noexcept + -> tl::expected { + auto verify_name = unpacker.template unpack(); + + if (!verify_name || verify_name != M::message_name()) { + fmt::print("{} {}\n", verify_name, M::message_name()); + return tl::make_unexpected(error::wrong_type); + } + + auto mapsize = unpacker.template unpack(); + + if (!mapsize) { + return tl::make_unexpected(error::wrong_type); + } else if (mapsize->count != boost::pfr::tuple_size_v) { + return tl::make_unexpected(error::wrong_type); + } + + return M{}; + } +}; + +} // namespace msgpack namespace parselink { namespace proto { diff --git a/include/parselink/utility/ctreflect.h b/include/parselink/utility/ctreflect.h new file mode 100644 index 0000000..80755a6 --- /dev/null +++ b/include/parselink/utility/ctreflect.h @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: Utility +// +// Compile-time reflection for structures. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef parselink_ct_reflect_080596cd36d52770 +#define parselink_ct_reflect_080596cd36d52770 + +#include +#include +#include +#include + +namespace parselink { +namespace ct { + +namespace detail { + +// This allows us to reference a type instance without ever instantiating it. +template +extern T const phantom; + +// This type binds to anything, allowing us to sus out the number of data +// members in an aggregate. +struct any_type { + template + constexpr operator T(); +}; + +// clang-format off +// This template takes a wrapped pointer to a data member of a theoretical +// instance of that type, in order to retrieve the name at compile time. +// Examples: +// gcc: +// constexpr auto parselink::ct::detail::src_string() [with auto Member = const_ptr{(& phantom.structure::field)}] +// +// clang: +// auto parselink::ct::detail::src_string() [Member = const_ptr{&phantom.field}] +// clang-format on +template +constexpr auto src_string() { + return std::source_location::current().function_name(); +} + +template +struct base_parse_traits { + static constexpr char start_delim = StartDelim; + static constexpr char end_delim = EndDelim; +}; + +#ifdef __clang__ +struct parse : base_parse_traits<'.', '}'> {}; +#elif defined(__GNUC__) +struct parse : base_parse_traits<':', ')'> {}; +#endif + +static constexpr std::size_t max_members = 8; + +// TODO: Logarithmic approach +// This needs to be limited to max_members, otherwise clang will go off into +// the weeds. +template + requires(sizeof...(Members) <= max_members) +consteval std::size_t count_members(Members&&... members) { + if constexpr (requires { T{any_type{}, members...}; } == false) { + return sizeof...(members); + } else if constexpr (sizeof...(members) <= max_members) { + return count_members(members..., any_type{}); + } else { + throw "Too many members / something went wrong!"; + } +}; + +// This function takes an aggregate and attempts to bind it into a tuple. +// Unfortunately, without variadic packs for structured bindings, this will +// rely on some heavy boilerplate. +template ()> + requires(MemberCount <= 8) +consteval decltype(auto) as_tuple(T&& t) { + if constexpr (MemberCount == 0) { + return std::tuple{}; + } else if constexpr (MemberCount == 1) { + auto&& [a0] = t; + return std::tie(a0); + } else if constexpr (MemberCount == 2) { + auto&& [a0, a1] = t; + return std::tie(a0, a1); + } else if constexpr (MemberCount == 3) { + auto&& [a0, a1, a2] = t; + return std::tie(a0, a1, a2); + } else if constexpr (MemberCount == 4) { + auto&& [a0, a1, a2, a3] = t; + return std::tie(a0, a1, a2, a3); + } else if constexpr (MemberCount == 5) { + auto&& [a0, a1, a2, a3, a4] = t; + return std::tie(a0, a1, a2, a3, a4); + } else if constexpr (MemberCount == 6) { + auto&& [a0, a1, a2, a3, a4, a5] = t; + return std::tie(a0, a1, a2, a3, a4, a5); + } else if constexpr (MemberCount == 7) { + auto&& [a0, a1, a2, a3, a4, a5, a6] = t; + return std::tie(a0, a1, a2, a3, a4, a5, a6); + } else if constexpr (MemberCount == 8) { + auto&& [a0, a1, a2, a3, a4, a5, a6, a7] = t; + return std::tie(a0, a1, a2, a3, a4, a5, a6, a7); + } else { + throw "Too many members!"; + } +} + +template +struct const_ptr { + T const* ptr; +}; + +template +consteval decltype(auto) as_ptr(T&& t) noexcept { + auto&& member = std::get(as_tuple(t)); + using member_ptr = std::decay_t; + return const_ptr(&member); +} + +template +consteval decltype(auto) raw_member_name() noexcept { + return src_string(phantom)>(); +} + +template +consteval decltype(auto) get_member_name() noexcept { + std::string_view raw = raw_member_name(); + auto s = raw.rfind(parse::start_delim); + auto e = raw.rfind(parse::end_delim); + if (s == raw.npos || e == raw.npos) throw "name parsing is broken!"; + return raw.substr(s + 1, e - (s + 1)); +} + +template +consteval decltype(auto) get_member_names(std::index_sequence) noexcept { + return std::array{get_member_name()...}; +} + +} // namespace detail + +template +consteval decltype(auto) member_name() noexcept { + std::string_view raw = detail::raw_member_name(); + auto s = raw.rfind(detail::parse::start_delim); + auto e = raw.rfind(detail::parse::end_delim); + if (s == raw.npos || e == raw.npos) throw "name_of is broken!"; + return raw.substr(s + 1, e - (s + 1)); +} + +template +consteval decltype(auto) member_names() noexcept { + return detail::get_member_names( + std::make_index_sequence()>{}); +} + +} // namespace ct +} // namespace parselink + +#endif // parselink_ct_reflect_080596cd36d52770 diff --git a/include/parselink/utility/ctstring.h b/include/parselink/utility/ctstring.h index 0f14474..07d8ad1 100644 --- a/include/parselink/utility/ctstring.h +++ b/include/parselink/utility/ctstring.h @@ -16,6 +16,10 @@ // // License TBD. //----------------------------------------------------------------------------- + +#ifndef ct_string_4fab12ca46f0d931 +#define ct_string_4fab12ca46f0d931 + #include #include @@ -116,3 +120,5 @@ static_assert(string("hello") + string(" world") == string("hello world")); } // namespace ct } // namespace parselink + +#endif // ct_string_4fab12ca46f0d931 diff --git a/source/client/client.cpp b/source/client/client.cpp index a94035b..db3bf90 100644 --- a/source/client/client.cpp +++ b/source/client/client.cpp @@ -101,7 +101,8 @@ awaitable simple_client::connect_to_server() noexcept { logger.debug("Connecting to {}", r.endpoint()); std::array buff; msgpack::packer packer(buff); - packer.pack("hello"); + packer.pack("error"); + packer.pack(msgpack::map_desc{2}); auto span = packer.subspan(); auto [ec, bw] = co_await socket.async_send_to( net::buffer(span.data(), span.size()), r.endpoint(), diff --git a/source/proto/parser.cpp b/source/proto/parser.cpp index 4a967c1..61758cc 100644 --- a/source/proto/parser.cpp +++ b/source/proto/parser.cpp @@ -22,12 +22,15 @@ #include "parselink/msgpack/core/unpacker.h" namespace parselink { + +namespace { +logging::logger logger{"parser"}; +} + namespace proto { namespace { -logging::logger logger{"parser"}; - constexpr auto assign(auto& param, msgpack::unpacker& unpacker) noexcept -> tl::expected { auto do_assign = [¶m](auto&& v) { param = v; }; @@ -46,7 +49,7 @@ tl::expected parse_connect_message( constexpr static tl::unexpected bad_data(error::bad_data); auto type = unpacker.unpack(); - if (!type || type != "connect") return bad_data; + if (!type || type != connect_message::name()) return bad_data; auto entries = unpacker.unpack(); if (!entries) return bad_data; @@ -68,3 +71,5 @@ tl::expected parse_connect_message( } // namespace proto } // namespace parselink + +parselink::logging::logger& parselink::get_logger() { return logger; } diff --git a/source/server/udp_server.cpp b/source/server/udp_server.cpp index da080d1..3c403c9 100644 --- a/source/server/udp_server.cpp +++ b/source/server/udp_server.cpp @@ -64,7 +64,14 @@ net::awaitable udp_server::listen() noexcept { auto [ec, br] = co_await listener.async_receive_from( net::buffer(buff), remote, no_ex_coro); if (!ec) { + auto sp = std::span(buff.begin(), br); logger.debug("From {}: {}", remote, std::span(buff.begin(), br)); + msgpack::unpacker unpacker(sp); + unpacker.unpack() + .map([](auto msg) { logger.debug("Got error message"); }) + .map_error([](auto err) { + logger.error("Error getting msg: {}", err); + }); } } diff --git a/tests/msgpack/unpacker/BUILD b/tests/msgpack/unpacker/BUILD index ba9fa0a..b9c678d 100644 --- a/tests/msgpack/unpacker/BUILD +++ b/tests/msgpack/unpacker/BUILD @@ -5,5 +5,9 @@ cc_test( "*.cpp", "*.h", ]), - deps = ["//tests/msgpack:common", "@rapidcheck"], + deps = [ + "//tests/msgpack:common", + "@rapidcheck", + "@boost//:pfr", + ], ) diff --git a/tests/msgpack/unpacker/maps.cpp b/tests/msgpack/unpacker/maps.cpp new file mode 100644 index 0000000..c486bd7 --- /dev/null +++ b/tests/msgpack/unpacker/maps.cpp @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Unpacker tests for maps. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include "parselink/msgpack/core/detail/unpack_map.h" +#include "parselink/utility/ctstring.h" +#include "test_unpacker.h" + +#include "parselink/utility/ctreflect.h" + +#include + +using namespace boost::ut; + +struct S { + int abra; + int bar; + int cat; + int dog; + int lbue; + int bue; + int ue; +}; + +suite test_on_key = [] { + "on_key"_test = [] { + bool expected = false; + auto key = msgpack::map::on_key<5>([&] { expected = true; }); + expect(!expected); + key.handler(); + expect(expected); + }; + "on_key"_test = [] { + bool expected = false; + auto key = msgpack::map::on_key<"hello">([&] { expected = true; }); + expect(!expected); + key.handler(); + expect(expected); + }; + + "key_group"_test = [] { + auto keys = msgpack::map::key_group( + msgpack::map::on_key<5>([] {}), msgpack::map::on_key<1>([] {})); + constexpr auto size = decltype(keys)::size; + constexpr auto first_key = get_key(std::get<0>(keys.keys_)); + constexpr auto second_key = get_key(std::get<1>(keys.keys_)); + static_assert(size == 2); + expect(first_key == 5); + expect(second_key == 1); + }; + "key_group"_test = [] { + auto keys = + msgpack::map::key_group(msgpack::map::on_key<"hello">([] {}), + msgpack::map::on_key<"world">([] {})); + constexpr auto size = decltype(keys)::size; + constexpr auto first_key = get_key(std::get<0>(keys.keys_)); + constexpr auto second_key = get_key(std::get<1>(keys.keys_)); + static_assert(size == 2); + expect(first_key == "hello"); + expect(second_key == "world"); + }; + "key_group_from_struct_names"_test = [] { + constexpr auto namefn = parselink::ct::member_name(); + static_assert(parselink::ct::member_name() == "bar"); + fmt::print("{}\n", namefn); + constexpr auto names = parselink::ct::member_names(); + fmt::print("{}\n", names); + }; +}; diff --git a/tests/msgpack/unpacker/unsigned.cpp b/tests/msgpack/unpacker/unsigned.cpp index 2e3d98e..e994a66 100644 --- a/tests/msgpack/unpacker/unsigned.cpp +++ b/tests/msgpack/unpacker/unsigned.cpp @@ -9,7 +9,7 @@ // Author: Kurt Sassenrath // Module: msgpack // -// Default packer tests. +// Unpacker tests for unsigned integers. // // Copyright (c) 2023 Kurt Sassenrath. //