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));