//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: Server // // Server implementation. Currently, a monolithic server which: // * Communicates with users via TCP (msgpack). // * Runs the websocket server for overlays to read. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #include "parselink/proto/session.h" #include "parselink/logging.h" #include "parselink/msgpack/token.h" #include using namespace parselink; using namespace parselink::proto; template <> struct fmt::formatter { template constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } template auto format(msgpack::token const& v, FormatContext& ctx) const { using parselink::logging::themed_arg; auto out = fmt::format_to( ctx.out(), "()))); break; case msgpack::format::type::signed_int: out = fmt::format_to( out, "{}", themed_arg(*(v.get()))); break; case msgpack::format::type::boolean: out = fmt::format_to(out, "{}", themed_arg(*(v.get()))); break; case msgpack::format::type::string: out = fmt::format_to( out, "{}", themed_arg(*(v.get()))); break; case msgpack::format::type::binary: out = fmt::format_to(out, "{}", themed_arg(*(v.get>()))); break; case msgpack::format::type::map: out = fmt::format_to(out, "(arity: {})", themed_arg(v.get()->count)); break; case msgpack::format::type::array: out = fmt::format_to(out, "(arity: {})", themed_arg(v.get()->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, ">"); } }; namespace { logging::logger logger("session"); } tl::expected session::parse_header( std::span buffer) noexcept { auto reader = msgpack::token_reader(buffer); auto magic = reader.read_one(); if (!magic || *magic != "prs") { logger.error("Failed to parse magic"); return tl::unexpected(magic ? error::bad_data : error::incomplete); } auto size = reader.read_one().and_then( [](auto t) { return t.template get(); }); if (!size || !*size) { logger.error("Failed to get valid message size"); return tl::unexpected(magic ? error::bad_data : error::incomplete); } if (*size > max_message_size) { logger.error("Message {} exceeds max size {}", *size, max_message_size); return tl::unexpected(error::too_large); } std::uint32_t amt = std::distance(buffer.begin(), reader.current()); return tl::expected(tl::in_place, *size, amt); } tl::expected session::handle_connect( std::span tokens) noexcept { auto message_type = tokens.begin()->get(); if (message_type && message_type == "connect") { logger.debug("Received '{}' packet. Parsing body", *message_type); auto map = msgpack::map_view(tokens.subspan(1)); constexpr auto find_version = [](auto const& kv) { return kv.first == "version"; }; auto field = std::ranges::find_if(map, find_version); if (field != map.end() && (*field).second == 1u) { logger.debug("Version {}", (*field).second.get()); } else { logger.error("connect failed: missing / unsupported version"); return tl::make_unexpected(error::unsupported); } for (auto const& [k, v] : msgpack::map_view(tokens.subspan(1))) { tl::expected result; if (k.type() != msgpack::format::type::string) { logger.error("connect failed: non-string key {}", k); return tl::make_unexpected(error::bad_data); } if (k == "user_id") { result = v.get().map( [this](auto uid) { user_id = std::move(uid); }); } if (!result) { logger.error( "connect failed: {} -> {}: {}", k, v, result.error()); return tl::make_unexpected(error::bad_data); } } } else { logger.error("Did not get message type: {}", message_type.error()); return tl::make_unexpected(error::bad_data); } return {}; }