diff --git a/include/parselink/BUILD b/include/parselink/BUILD index 6c43e38..96311f7 100644 --- a/include/parselink/BUILD +++ b/include/parselink/BUILD @@ -15,6 +15,14 @@ cc_library( visibility = ["//visibility:public"], ) +cc_library( + name = "server", + hdrs = glob(["server/**/*.h"]), + includes = ["."], + deps = ["@expected", "//include:path"], + visibility = ["//visibility:public"], +) + cc_library( name = "proto", hdrs = glob(["proto/**/*.h"]), diff --git a/include/parselink/logging/formatters.h b/include/parselink/logging/formatters.h index 353d734..668bed4 100644 --- a/include/parselink/logging/formatters.h +++ b/include/parselink/logging/formatters.h @@ -91,7 +91,7 @@ struct fmt::formatter : fmt::formatter { template auto format(std::errc const& v, FormatContext& ctx) const { return fmt::formatter::format( - std::make_error_code(v), ctx); + std::make_error_code(v), ctx); } }; diff --git a/include/parselink/logging/theme.h b/include/parselink/logging/theme.h index 7515f6f..1d22a52 100644 --- a/include/parselink/logging/theme.h +++ b/include/parselink/logging/theme.h @@ -35,6 +35,7 @@ #include #include +#include #include #include diff --git a/include/parselink/msgpack/token/writer.h b/include/parselink/msgpack/token/writer.h new file mode 100644 index 0000000..7789256 --- /dev/null +++ b/include/parselink/msgpack/token/writer.h @@ -0,0 +1,283 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// "Token" writing implementation, which supports both tokens and deduction for +// common types. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef msgpack_core_writer_ce48a51aa6ed0858 +#define msgpack_core_writer_ce48a51aa6ed0858 + +#include "parselink/msgpack/core/error.h" +#include "parselink/msgpack/core/format.h" +#include "parselink/msgpack/util/endianness.h" +#include +#include + +#include + +namespace msgpack { + +enum class writer_error { + none, + unsupported, + not_implemented, + bad_value, + out_of_space +}; + +// Helper template for writing a non-integral datatype to an output. +// template +// struct write_adapter { +// template +// static constexpr tl::expected write(T const& t); +//}; + +template +constexpr inline decltype(auto) write_bytes( + std::array&& data, Itr out) noexcept { + for (auto b : data) { + *out++ = b; + } + return out; +} + +#if 0 +// Figure out the smallest +namespace detail { + + constexpr auto const& deduce_format(std::uint64_t value) { + if (value <= format::positive_fixint::mask) return format:: + } + + template + struct write_adapter { + static constexpr auto size(T t) noexcept { + + } + } +} // namespace detail +#endif + +template +struct write_adapter {}; + +template +struct write_adapter { + static constexpr auto size(T) noexcept { return sizeof(T); } + + template + static constexpr auto write(T t, Itr out) noexcept { + return write_bytes(detail::raw_cast(host_to_be(t)), out); + } +}; + +template <> +struct write_adapter { + static constexpr auto size(std::string_view str) noexcept { + return str.size(); + } + + template + static constexpr auto write(std::string_view str, Itr out) noexcept { + std::byte const* beg = + reinterpret_cast(&*str.begin()); + std::copy(beg, beg + str.size(), out); + return out += str.size(); + } +}; + +template <> +struct write_adapter> { + static constexpr auto size(std::span bytes) noexcept { + return bytes.size(); + } + + template + static constexpr auto write( + std::span bytes, Itr out) noexcept { + std::copy(bytes.begin(), bytes.end(), out); + return out += bytes.size(); + } +}; + +template <> +struct write_adapter { + static constexpr auto value(map_desc desc) noexcept { return desc.count; } +}; + +template <> +struct write_adapter { + static constexpr auto value(array_desc desc) noexcept { return desc.count; } +}; + +// TODO: These could be optimized away because invalid/nil never contain real +// data. +template <> +struct write_adapter { + static constexpr auto value(invalid) noexcept { return 0; } +}; + +template <> +struct write_adapter { + static constexpr auto value(nil) noexcept { return 0; } +}; + +namespace detail { +template +using expected = tl::expected; + +template +constexpr inline std::size_t calculate_space( + typename F::value_type val) noexcept { + // At a minimum, one byte is needed to store the format. + std::size_t size = sizeof(typename F::first_type); + + if (!is_fixtype) { + ++size; // For format + } + + if constexpr (F::payload_type == format::payload::variable) { + size += write_adapter::size(val); + } + + return size; +} + +// The "first type" is either the size of the variable length payload or +// a fixed-length value. Additionally, this "first type" may be inlined +// into the marker byte if it's a fix type. + +template +constexpr inline expected pack_first( + typename F::value_type value) noexcept { + using value_type = typename F::value_type; + if constexpr (F::payload_type == format::payload::variable) { + return typename F::first_type(write_adapter::size(value)); + } else { + if constexpr (requires { write_adapter::value; }) { + return typename F::first_type( + write_adapter::value(value)); + } else { + return typename F::first_type(value); + } + } +} + +template +constexpr inline expected write( + typename F::value_type&& value, Itr out, Itr const end) { + using diff_type = typename std::iterator_traits::difference_type; + if (diff_type(calculate_space(value)) > std::distance(out, end)) { + return tl::make_unexpected(error::out_of_space); + } + + auto marker = F::marker; + + auto result = pack_first(value); + if (!result) { + return tl::make_unexpected(result.error()); + } + + if constexpr (is_fixtype) { + if (*result > 0xff) { + return tl::make_unexpected(error::bad_value); + } + if constexpr (F::flags & format::flag::apply_mask) { + marker |= std::byte(*result); + } else { + marker = std::byte(*result); + } + if ((marker & ~F::mask) != F::marker) { + return tl::make_unexpected(error::bad_value); + } + } + + *out++ = marker; + + if constexpr (!is_fixtype) { + out = write_adapter::write( + *result, out); + } + + if constexpr (F::payload_type == format::payload::variable) { + out = write_adapter::write(value, out); + } + + return out; +} + +template +struct format_hint; + +template <> +struct format_hint { + using type = format::positive_fixint; +}; + +template <> +struct format_hint { + using type = format::uint16; +}; +} // namespace detail + +class writer { +public: + template + using expected = detail::expected; + + constexpr writer(std::span dest) + : data(dest) + , curr(std::begin(data)) + , end(std::end(data)) {} + + template + constexpr expected write( + typename F::value_type&& v) noexcept { + using value_type = typename F::value_type; + if (curr == end) return tl::make_unexpected(error::out_of_space); + auto result = detail::write(std::forward(v), curr, end); + if (!result) { + return tl::make_unexpected(result.error()); + } + + curr = *result; + return tl::monostate{}; + } + + template + requires requires { typename detail::format_hint::type; } + constexpr expected write(T&& v) { + return write::type>(std::forward(v)); + } + + constexpr auto pos() const noexcept { return curr; } + + constexpr auto tell() const noexcept { + return std::distance(std::begin(data), curr); + } + + constexpr auto subspan() const noexcept { + return std::span(std::begin(data), tell()); + } + +private: + std::span data; + decltype(data)::iterator curr; + decltype(data)::iterator end; +}; + +} // namespace msgpack + +#endif // msgpack_core_writer_ce48a51aa6ed0858 diff --git a/include/parselink/proto/session.h b/include/parselink/proto/session.h index 3495f3c..f55430c 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/session_id.h" #include #include #include @@ -81,36 +82,34 @@ struct transparent_hash { class session { public: using close_handle = std::function; - session(std::string_view user_id, close_handle hdl) noexcept; + session(std::string_view user_id) noexcept; ~session(); - std::span id() const noexcept { return id_; } + session_id id() const noexcept { return id_; } std::string_view user_id() const noexcept { return user_id_; } auto last_activity() const noexcept { return last_activity_; } void update_last_activity( - std::chrono::steady_clock::time_point = - std::chrono::steady_clock::now()) noexcept {} - - constexpr bool operator==(std::span id) const noexcept { - return std::ranges::equal(id, id_); - } + std::chrono::system_clock::time_point = + std::chrono::system_clock::now()) noexcept {} private: - std::array id_; + session_id id_; std::string user_id_; - close_handle closer_; - std::chrono::steady_clock::time_point last_activity_; + std::chrono::system_clock::time_point last_activity_; }; template <> -struct transparent_hash +struct transparent_hash : transparent_hash {}; + +template <> +struct transparent_hash : transparent_hash> { - [[nodiscard]] auto operator()(session const& s) const { - return std::hash>{}(s.id()); + [[nodiscard]] auto operator()(session_id const& s) const { + return std::hash>{}(s.raw()); } }; diff --git a/include/parselink/server/memory_session_manager.h b/include/parselink/server/memory_session_manager.h new file mode 100644 index 0000000..23e94c2 --- /dev/null +++ b/include/parselink/server/memory_session_manager.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: Server +// +// Simple in-memory session manager. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef memory_session_manager_b3851872babe001d +#define memory_session_manager_b3851872babe001d + +#include +#include +#include +#include + +namespace parselink { + +class memory_session_manager { +public: + memory_session_manager(boost::asio::io_context& ctx); + ~memory_session_manager(); + + // Allocate a new session. + tl::expected create_session( + std::string_view user_id); + + // Destroy an existing session. + tl::expected destroy_session( + proto::session* session); + + // Find a session by its user id. + tl::expected find(std::string_view user_id); + + // Find a session by its ID. + tl::expected find( + proto::session_id const& session); + +private: + std::unordered_map, std::equal_to<>> + sessions_; + + std::unordered_map, std::equal_to<>> + lookup_by_sid_; + + boost::asio::io_context& ctx_; + boost::asio::io_context::strand strand_; +}; + +// Sanity check +static_assert(session_manager); +static_assert(sync_session_manager); +static_assert(!async_session_manager); + +} // namespace parselink + +#endif // memory_session_manager_b3851872babe001d diff --git a/include/parselink/server/server.h b/include/parselink/server/server.h new file mode 100644 index 0000000..c570717 --- /dev/null +++ b/include/parselink/server/server.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: Server +// +// Server interface. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef server_5b46f075be3caa00 +#define server_5b46f075be3caa00 + +#include +#include + +namespace parselink { + +template +concept server_concept = requires(Server& srv) { + { srv.run() } -> std::same_as; +}; + +class server { +public: + virtual ~server() = default; + virtual std::error_code run() noexcept = 0; +}; + +std::unique_ptr make_server(std::string_view address, + std::uint16_t user_port, std::uint16_t websocket_port); + +} // namespace parselink + +#endif // server_5b46f075be3caa00 diff --git a/include/parselink/server/session_manager.h b/include/parselink/server/session_manager.h new file mode 100644 index 0000000..4f620b0 --- /dev/null +++ b/include/parselink/server/session_manager.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: Server +// +// Session manager concept. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef session_manager_779b9fc5781fb66f +#define session_manager_779b9fc5781fb66f + +#include +#include +#include + +namespace parselink { + +using boost::asio::awaitable; + +template +concept sync_session_manager = + requires(T& mgr, std::string_view sv, proto::session* s) { + { + mgr.create_session(sv) + } -> std::same_as>; + } && requires(T& mgr, proto::session* s) { + { + mgr.destroy_session(s) + } -> std::same_as>; + } && requires(T& mgr, std::string_view user_id) { + { + mgr.find(user_id) + } -> std::same_as>; + } && requires(T& mgr, proto::session_id const& sid) { + { + mgr.find(sid) + } -> std::same_as>; + }; + +template +concept async_session_manager = requires(T& mgr, proto::session* s) { + { + mgr.create_session() + } -> std::same_as>>; +} && requires(T& mgr, proto::session* s) { + { + mgr.destroy_session(s) + } -> std::same_as>>; +} && requires(T& mgr, proto::session_id const& sid) { + { + mgr.find(sid) + } -> std::same_as>>; +}; + +template +concept session_manager = sync_session_manager || async_session_manager; + +} // namespace parselink + +#endif // session_manager_779b9fc5781fb66f diff --git a/source/BUILD b/source/BUILD index 1ef99f1..a3b2f74 100644 --- a/source/BUILD +++ b/source/BUILD @@ -18,6 +18,7 @@ cc_binary( "//include/parselink:utility", "//source/logging", "//source/proto", + "//source/server", "@boost//:beast", ], ) diff --git a/source/proto/session.cpp b/source/proto/session.cpp index 8bdc97f..c4d8851 100644 --- a/source/proto/session.cpp +++ b/source/proto/session.cpp @@ -188,12 +188,11 @@ tl::expected proto::parse_connect( } session::session(std::string_view user_id, close_handle hdl) noexcept - : id_(get_random_bytes<32>()) + : id_() , user_id_(std::string{user_id}) , closer_(std::move(hdl)) - , last_activity_(std::chrono::steady_clock::now()) { - logger.debug("New session with id {} created for {} {:x}", id_, user_id_, - transparent_hash{}(*this)); + , last_activity_(std::chrono::system_clock::now()) { + logger.debug("New session with id {} created for {}", id_.raw(), user_id_); } session::~session() { diff --git a/source/server.cpp b/source/server.cpp index 193b186..68ff155 100644 --- a/source/server.cpp +++ b/source/server.cpp @@ -21,6 +21,7 @@ #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/proto/session.h" @@ -152,6 +153,8 @@ constexpr auto no_ex_coro = net::as_tuple(use_awaitable); constexpr auto no_ex_defer = net::as_tuple(deferred); } // namespace +#include + class user_connection; class monolithic_server : public server { @@ -168,12 +171,9 @@ public: private: awaitable user_listen(); - std::unordered_set, - std::equal_to<>> - sessions_; - net::io_context io_context_; net::io_context::strand session_strand_; + memory_session_manager session_mgr_; net::ip::address addr_; std::uint16_t user_port_; std::uint16_t websocket_port_; @@ -273,20 +273,28 @@ public: co_return; } - co_await server_.create_session(shared_from_this(), *connect_info); + auto session = co_await server_.create_session( + shared_from_this(), *connect_info); + if (!session) { + co_return; + } + session_ = *session; + + auto writer = msgpack::writer(msgbuf); } enum class state { init, authenticated, active }; monolithic_server& server_; net::ip::tcp::socket socket_; - proto::session* session_; + proto::session* session_{}; }; monolithic_server::monolithic_server(std::string_view address, std::uint16_t user_port, std::uint16_t websocket_port) : io_context_{1} , session_strand_(io_context_) + , session_mgr_(io_context_) , addr_(net::ip::address::from_string(std::string{address})) , user_port_{user_port} , websocket_port_{websocket_port} { @@ -327,11 +335,9 @@ monolithic_server::create_session(std::shared_ptr const& conn, // Move to session strand. co_await net::post(session_strand_, net::bind_executor(session_strand_, use_awaitable)); - - proto::session session(info.user_id, {}); - - // Now, update the map of sessions - co_return tl::make_unexpected(proto::error::unsupported); + auto session = session_mgr_.create_session(info.user_id); + logger.info("Created session: {}", session); + co_return session; } std::unique_ptr parselink::make_server(std::string_view address, diff --git a/source/server/BUILD b/source/server/BUILD new file mode 100644 index 0000000..dca5fb5 --- /dev/null +++ b/source/server/BUILD @@ -0,0 +1,20 @@ +# parselink + +cc_library( + name = "server", + srcs = [ + "memory_session_manager.cpp", + ], + deps = [ + "//include/parselink:proto", + "//include/parselink:msgpack", + "//include/parselink:server", + "//source/logging", + "@hydrogen", + "@boost//:asio", + ], + visibility = [ + # TODO: Fix visibility + "//visibility:public", + ], +) diff --git a/source/server/memory_session_manager.cpp b/source/server/memory_session_manager.cpp new file mode 100644 index 0000000..ce5578c --- /dev/null +++ b/source/server/memory_session_manager.cpp @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: Server +// +// Simple in-memory session manager. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include +#include + +#include + +using namespace parselink; +using namespace std::chrono_literals; + +namespace { + +logging::logger logger("memory_session_manager"); + +constexpr std::size_t max_open_sessions = 1000; +constexpr auto expires_time = 1min; + +} // namespace + +memory_session_manager::memory_session_manager(boost::asio::io_context& ctx) + : ctx_(ctx) + , strand_(ctx) { + logger.trace("Creating: {}", this); +} + +memory_session_manager::~memory_session_manager() { + logger.trace("Destroying: {}", this); +} + +tl::expected +memory_session_manager::create_session(std::string_view user_id) { + auto [itr, inserted] = sessions_.try_emplace(std::string{user_id}, user_id); + if (!inserted) { + logger.debug("Session already found for {}, last activity" + " {:%Y-%m-%d %H:%M:%S}", + user_id, fmt::gmtime(itr->second.last_activity())); + if (itr->second.last_activity() + expires_time + < std::chrono::system_clock::now()) { + logger.debug("Session expired. Creating new session"); + itr->second = proto::session{user_id}; + } + return &itr->second; + } + lookup_by_sid_.emplace(itr->second.id(), &itr->second); + return &itr->second; +} + +tl::expected +memory_session_manager::destroy_session(proto::session* s) { + return tl::make_unexpected(proto::error::unsupported); +} + +tl::expected memory_session_manager::find( + std::string_view user_id) { + auto x = sessions_.find(user_id); + if (x != sessions_.end()) { + return &x->second; + } + return tl::make_unexpected(proto::error::unsupported); +} + +tl::expected memory_session_manager::find( + proto::session_id const& sid) { + auto x = lookup_by_sid_.find(sid); + if (x != lookup_by_sid_.end()) { + return x->second; + } + return tl::make_unexpected(proto::error::unsupported); +}