From b2a6f253062cea080a0840e700da66ea24767260 Mon Sep 17 00:00:00 2001 From: Kurt Sassenrath Date: Fri, 12 Jan 2024 19:36:56 -0800 Subject: [PATCH] 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. --- .../core/detail/builtin_unpackable_types.h | 23 +++- include/parselink/proto/error.h | 35 ++++++ include/parselink/proto/message.h | 22 ++-- include/parselink/proto/parser.h | 60 ++++++++++ include/parselink/proto/session.h | 9 +- include/parselink/utility/file.h | 47 ++++++-- source/proto/BUILD | 1 + source/proto/parser.cpp | 70 ++++++++++++ source/server.cpp | 106 ++++++++++++------ 9 files changed, 304 insertions(+), 69 deletions(-) create mode 100644 include/parselink/proto/error.h create mode 100644 include/parselink/proto/parser.h create mode 100644 source/proto/parser.cpp diff --git a/include/parselink/msgpack/core/detail/builtin_unpackable_types.h b/include/parselink/msgpack/core/detail/builtin_unpackable_types.h index f342a2f..25a97ec 100644 --- a/include/parselink/msgpack/core/detail/builtin_unpackable_types.h +++ b/include/parselink/msgpack/core/detail/builtin_unpackable_types.h @@ -59,8 +59,9 @@ constexpr decltype(auto) unpack_integral(Itr& inp) noexcept { // Defines a range of bytes for binary formats. template -concept byte_range = std::ranges::contiguous_range - && std::same_as, std::byte>; +concept unpackable_byte_range = + std::ranges::contiguous_range + && std::same_as, std::byte>; template constexpr inline bool accept_format(Iter& itr) noexcept { @@ -168,6 +169,24 @@ struct builtin_unpacker { } }; +template <> +struct builtin_unpacker { + static constexpr auto unpack(auto& unpacker) noexcept + -> tl::expected { + auto marker = *(unpacker.in); + if ((marker & ~format::fixmap::mask) == format::fixmap::marker) { + return detail::unpack_format(unpacker); + } + switch (marker) { + case format::map16::marker: + return detail::unpack_format(unpacker); + case format::map32::marker: + return detail::unpack_format(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 diff --git a/include/parselink/proto/error.h b/include/parselink/proto/error.h new file mode 100644 index 0000000..23c39aa --- /dev/null +++ b/include/parselink/proto/error.h @@ -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 diff --git a/include/parselink/proto/message.h b/include/parselink/proto/message.h index 87c77f1..5bc081c 100644 --- a/include/parselink/proto/message.h +++ b/include/parselink/proto/message.h @@ -18,12 +18,9 @@ #ifndef message_0c61530748b9f966 #define message_0c61530748b9f966 -#include - #include #include #include -#include 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 +concept message_type = std::is_base_of_v; + +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 { +struct connect_message : base_message { std::uint32_t version; // The version of the client. - std::uint32_t user_id; // The user id. + std::string_view user_id; // The user id. std::span 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 challenge; }; // C->S: Calculated response to a challenge. -struct response_message { +struct response_message : base_message { std::span response; }; // S->C: Session token. -struct session_established_message { +struct session_established_message : base_message { std::span session_token; }; -struct parser_data_message { +struct parser_data_message : base_message { std::string_view opts; std::span payload; }; diff --git a/include/parselink/proto/parser.h b/include/parselink/proto/parser.h new file mode 100644 index 0000000..e9ee486 --- /dev/null +++ b/include/parselink/proto/parser.h @@ -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 + +namespace parselink { +namespace proto { + +//////////////////////////////////////////////////////////////////////////////// +// Default parse template instantiation -- unsupported message. +//////////////////////////////////////////////////////////////////////////////// +template +tl::expected parse(std::span data) noexcept { + return tl::make_unexpected(error::unsupported); +} + +//////////////////////////////////////////////////////////////////////////////// +// Currently-implemented message types. +//////////////////////////////////////////////////////////////////////////////// + +#define PARSELINK_PROTO_SUPPORTED_MESSAGE(msgtype) \ + tl::expected parse_##msgtype( \ + std::span data) noexcept; \ + template <> \ + constexpr tl::expected parse( \ + std::span 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 diff --git a/include/parselink/proto/session.h b/include/parselink/proto/session.h index 064fc44..93b23d8 100644 --- a/include/parselink/proto/session.h +++ b/include/parselink/proto/session.h @@ -19,6 +19,7 @@ #define session_07eae057feface79 #include "parselink/msgpack/token.h" +#include "parselink/proto/error.h" #include "parselink/proto/session_id.h" #include @@ -46,14 +47,6 @@ struct std::hash> { 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. diff --git a/include/parselink/utility/file.h b/include/parselink/utility/file.h index d2918bc..84b4f22 100644 --- a/include/parselink/utility/file.h +++ b/include/parselink/utility/file.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -69,24 +70,22 @@ struct [[nodiscard]] handle { int fd_{-1}; }; -inline tl::expected open( - std::string_view path, int flags) noexcept { - handle file(::open(std::string{path}.c_str(), flags)); +inline tl::expected 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( - std::make_error_code(static_cast(-*file))); + return tl::make_unexpected(static_cast(-*file)); } } -inline tl::expected, std::error_code> read_some( +inline tl::expected, std::errc> read_some( handle file, std::size_t amount) noexcept { std::vector data; if (!file) { - return tl::make_unexpected( - std::make_error_code(static_cast(-*file))); + return tl::make_unexpected(static_cast(-*file)); } data.resize(amount); @@ -102,13 +101,12 @@ inline tl::expected, std::error_code> read_some( template requires(std::is_trivially_default_constructible_v && sizeof(T) == 1) -inline tl::expected, std::error_code> read( - handle file) noexcept { +inline tl::expected, std::errc> read( + handle const& file) noexcept { std::vector data; if (!file) { - return tl::make_unexpected( - std::make_error_code(static_cast(-*file))); + return tl::make_unexpected(static_cast(-*file)); } auto cur = lseek(*file, 0, SEEK_CUR); @@ -126,6 +124,31 @@ inline tl::expected, std::error_code> read( return data; } +template + requires(std::is_trivially_default_constructible_v && sizeof(T) == 1) +inline tl::expected write( + handle const& file, std::span data) noexcept { + if (!file) { + return tl::make_unexpected(static_cast(-*file)); + } + + lseek(*file, 0, SEEK_SET); + + auto result = ftruncate(*file, 0); + + if (result < 0) { + return tl::make_unexpected(static_cast(errno)); + } + + auto amt_written = ::write(*file, data.data(), data.size()); + + if (result < 0) { + return tl::make_unexpected(static_cast(errno)); + } + + return static_cast(amt_written); +} + } // namespace file } // namespace utility } // namespace parselink diff --git a/source/proto/BUILD b/source/proto/BUILD index c42d012..85a6fc4 100644 --- a/source/proto/BUILD +++ b/source/proto/BUILD @@ -3,6 +3,7 @@ cc_library( name = "proto", srcs = [ + "parser.cpp", "session.cpp", "session_id.cpp", ], diff --git a/source/proto/parser.cpp b/source/proto/parser.cpp new file mode 100644 index 0000000..4a967c1 --- /dev/null +++ b/source/proto/parser.cpp @@ -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 { + auto do_assign = [¶m](auto&& v) { param = v; }; + return unpacker.unpack>() + .map(do_assign) + .map_error([](auto) { return error::bad_data; }); +} + +} // anonymous namespace + +tl::expected parse_connect_message( + std::span data) noexcept { + msgpack::unpacker unpacker(data); + connect_message msg; + + constexpr static tl::unexpected bad_data(error::bad_data); + + auto type = unpacker.unpack(); + if (!type || type != "connect") return bad_data; + + auto entries = unpacker.unpack(); + if (!entries) return bad_data; + + for (auto i = entries->count; i != 0; --i) { + auto key = unpacker.unpack(); + 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 diff --git a/source/server.cpp b/source/server.cpp index 9d1c682..f8ea983 100644 --- a/source/server.cpp +++ b/source/server.cpp @@ -21,9 +21,9 @@ #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" @@ -75,6 +75,13 @@ struct fmt::formatter } }; +template <> +struct fmt::formatter : fmt::formatter { + constexpr auto format(auto const&, auto& ctx) const { + return fmt::formatter::format(".", ctx); + } +}; + template <> struct fmt::formatter { template @@ -170,12 +177,12 @@ public: net::awaitable> create_session( std::shared_ptr const& conn, - proto::connect_info const& info); + proto::connect_message const& info); private: awaitable user_listen(); - tl::expected load_keys() noexcept; + tl::expected load_keys() noexcept; hydro_kx_keypair kp_; net::io_context io_context_; @@ -268,12 +275,7 @@ public: co_return; } - auto reader = msgpack::token_reader(msgbuf); - std::array 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(msgbuf); if (!connect_info) { logger.error("Session failed: {}", connect_info.error()); @@ -303,7 +305,7 @@ 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} { - load_keys(); + logger.debug("Loaded keys: {}", load_keys()); logger.debug("Creating monolithic_server(address = {}, user_port = {}, " "websocket_port = {})", @@ -336,43 +338,73 @@ std::error_code monolithic_server::run() noexcept { return {}; } -tl::expected -monolithic_server::load_keys() noexcept { - std::string_view filename = "server_kp.keys"; +tl::expected monolithic_server::load_keys() noexcept { + std::string_view filename = "/home/rihya/server_kp.keys"; - auto load_key = - [this](auto raw) -> tl::expected { - if (sizeof(kp_) != raw.size()) { - return tl::make_unexpected( - std::make_error_code(std::errc::bad_message)); - } - std::ranges::transform(std::begin(raw), - std::next(std::begin(raw), sizeof(kp_.pk)), std::begin(kp_.pk), + enum class result { loaded, generated }; + + auto load_key = [this](auto const& raw) -> tl::expected { + msgpack::unpacker unpacker(raw); + + auto pk = unpacker.unpack>(); + if (!pk) return tl::make_unexpected(std::errc::bad_message); + auto sk = unpacker.unpack>(); + 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(c); }); - return tl::monostate{}; + + std::ranges::transform(sk->begin(), sk->end(), std::begin(kp_.sk), + [](auto c) { return std::bit_cast(c); }); + + return result::loaded; }; - auto generate_keys = [this](auto const& err) - -> tl::expected { + auto generate_keys = + [this](auto const& err) -> tl::expected { logger.warning("Could not load server keys, generating a keypair"); hydro_kx_keygen(&kp_); - std::span pk( - reinterpret_cast(kp_.pk), sizeof(kp_.pk)); - std::span sk( - reinterpret_cast(kp_.sk), sizeof(kp_.pk)); - logger.debug("\n\tPK: {}\n\tSK: {}", pk, sk); - return tl::monostate{}; + return result::generated; }; - return utility::file::open(filename, O_RDWR | O_CREAT) - .and_then(utility::file::read) - .and_then(load_key) - .or_else(generate_keys); + auto commit = [this](auto const& handle) + -> tl::expected { + std::vector buff(4 + sizeof(kp_.pk) + sizeof(kp_.sk)); + std::span pk( + reinterpret_cast(kp_.pk), sizeof(kp_.pk)); + std::span sk( + reinterpret_cast(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 { + return utility::file::read(handle) + .and_then(load_key) + .or_else(generate_keys) + .and_then([&handle, &commit](auto r) + -> tl::expected { + 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> monolithic_server::create_session(std::shared_ptr 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));