From 091f6dac5ced180e4eee05fc9e27c6cefe61f1ea Mon Sep 17 00:00:00 2001 From: Ralph Alexander Bariz <ralph.bariz@pm.me> Date: Sat, 18 Jan 2025 11:09:58 +0100 Subject: [PATCH] - introduced keeper concept - introduced bouncer keeper - fixes --- CMakeLists.txt | 8 +- include/causal/core/aspect.hpp | 4 +- include/causal/core/wirks.hpp | 5 +- include/causal/data/channel.hpp | 96 ++++++++------ include/causal/data/crypto.hpp | 2 +- include/causal/data/keeper.hpp | 219 ++++++++++++++++++++++++++++++++ include/causal/data/memory.hpp | 21 +-- test/data/channel.cpp | 26 ++-- test/data/keeper.cpp | 104 +++++++++++++++ 9 files changed, 416 insertions(+), 69 deletions(-) create mode 100644 include/causal/data/keeper.hpp create mode 100644 test/data/keeper.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f6a0ac8d..440144b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,12 +110,14 @@ add_library(causal-data src/data/junction.cpp src/data/fs.cpp) target_link_libraries(causal-data causal-core ${GnuTLS_LIBRARIES}) install(FILES include/causal/data/channel.hpp + include/causal/data/crypto.hpp + include/causal/data/keeper.hpp include/causal/data/junction.hpp include/causal/data/memory.hpp include/causal/data/fs.hpp - COMPONENT core-dev + COMPONENT data-dev DESTINATION include/causal/data) -install(TARGETS causal-data COMPONENT core DESTINATION lib) +install(TARGETS causal-data COMPONENT data DESTINATION lib) find_package(Msgpack) if(Msgpack_FOUND) @@ -196,7 +198,6 @@ if(Msgpack_FOUND AND OpenDHT_FOUND) ${OpenDHT_INCLUDE_DIRS} ) install(FILES include/causal/data/opendht.hpp - include/causal/data/crypto.hpp COMPONENT data-source-opendht-dev DESTINATION include/causal/data) install(TARGETS causal-data-opendht COMPONENT data-source-opendht DESTINATION lib) @@ -292,6 +293,7 @@ if(GTEST_FOUND) if(Msgpack_FOUND) add_causal_test(NAME test_causal-data_msgpack SOURCES test/data/msgpack.cpp LIBS causal-msgpack) add_causal_test(NAME test_causal-data_memory SOURCES test/data/memory.cpp LIBS causal-data causal-msgpack) + add_causal_test(NAME test_causal-data_keeper SOURCES test/data/keeper.cpp LIBS causal-data causal-msgpack) add_causal_test(NAME test_causal-data_fs SOURCES test/data/fs.cpp LIBS causal-data causal-msgpack) endif() diff --git a/include/causal/core/aspect.hpp b/include/causal/core/aspect.hpp index 29b50117..27688f9b 100644 --- a/include/causal/core/aspect.hpp +++ b/include/causal/core/aspect.hpp @@ -217,7 +217,7 @@ namespace causal { /** @brief dispose an aspect identified by given id from space * @param id of aspect (id's of types incompatible with space are ignored) */ - virtual void dispose(const std::any id) noexcept {} + virtual void dispose(const std::any id) noexcept = 0; /** @brief destroy an aspect's local representation * @param id of aspect (id's of types incompatible with space are ignored) @@ -792,7 +792,7 @@ namespace causal { EXCEPTION_ERROR(facet_active, "trying to modify an active facet") /// @brief exception thrown when trying to use an invalid facet - EXCEPTION_ERROR(facet_invalid, "trying to use an ivalid facet") + EXCEPTION_ERROR(facet_invalid, "trying to use an invalid facet") /// @brief abstract base for facets of aspect(s) class facet : public focusable, public borrowable {}; diff --git a/include/causal/core/wirks.hpp b/include/causal/core/wirks.hpp index 48e49152..7e8a541c 100644 --- a/include/causal/core/wirks.hpp +++ b/include/causal/core/wirks.hpp @@ -637,11 +637,10 @@ namespace causal { */ template<typename... MsgT> class vortex { - private: + protected: /// @brief branch to trigger wirks in const std::shared_ptr<branch> brnch; - - protected: + /// @brief mutex synchronizing vortex inteactions mutable std::shared_mutex mtx; diff --git a/include/causal/data/channel.hpp b/include/causal/data/channel.hpp index 562fa824..1ed8f4dd 100644 --- a/include/causal/data/channel.hpp +++ b/include/causal/data/channel.hpp @@ -8,9 +8,12 @@ #include <set> #include <shared_mutex> #include <functional> +#include <type_traits> +#include <unordered_map> #include "causal/core/aspect.hpp" #include "causal/core/wirks.hpp" +#include "causal/trace.hpp" #ifndef _DATA_WRAP_SESSION_SIZE #define _DATA_WRAP_SESSION_SIZE 64 @@ -63,31 +66,6 @@ namespace causal { std::string session, peer; }; - struct IdentifyRequest { - std::string session; - std::string id; - std::string key; - }; - struct AnonRequest { - std::string session; - }; - struct IdentifyResponse { - std::string session; - std::string id; - std::string sign; - }; - - struct Identified { - std::string session, peer; - std::string id; - std::string sign; - std::shared_ptr<source<IdSessionMeta>> src; - }; - struct SignedOut { - std::string session, peer; - std::string id; - }; - /** @brief abstract base of channel * @tparam T message data type * @tparam M metadata type attached to messages @@ -120,12 +98,11 @@ namespace causal { class sourced_channel : public channel<std::string, M> { friend class source<M>; protected: - const std::shared_ptr<source<M>> src; - virtual void push_pipe(const std::string msg) const { if(this->src) this->src->push(msg); } + sourced_channel(std::shared_ptr<source<M>> s) : src(std::move(s)) { ASSERT_MSG(scope_data, this->src, "trying to create channel without valid source") @@ -133,6 +110,8 @@ namespace causal { } public: + const std::shared_ptr<source<M>> src; + virtual ~sourced_channel() { this->src->remove_channel(*this); } @@ -146,18 +125,10 @@ namespace causal { protected: mutable std::shared_mutex mtx; std::set<const sourced_channel<M>*> channels; - - /** @brief pulls a message from source to registered channels - * @param msg copy of message data - * @param meta copy of message metadata - */ - void pull(const std::string msg, const M meta) const { - std::shared_lock l(this->mtx); - for(auto c : this->channels) - c->pull(msg, meta); - } public: + using metaT = M; + virtual ~source() { std::unique_lock l(this->mtx); ASSERT_MSG(scope_data, this->channels.empty(), "destroying junction while channels are connected to it") @@ -180,6 +151,16 @@ namespace causal { std::unique_lock l(this->mtx); ASSERT_MSG(scope_data, this->channels.erase(&c), "channel is not registered for source") } + + /** @brief pulls a message from source to registered channels + * @param msg copy of message data + * @param meta copy of message metadata + */ + void pull(const std::string msg, const M meta) const { + std::shared_lock l(this->mtx); + for(auto c : this->channels) + c->pull(msg, meta); + } /** @brief pushes message to source * @param msg lvalue reference to message @@ -281,11 +262,51 @@ namespace causal { } }; + template<typename SM, typename M=NoMeta> + class wrapping_source final : public source<M> { + static_assert(std::is_base_of<SM, M>::value || !std::is_same<SM, M>::value, "a wrapping source can only use metadata derriving from inner source's metadata"); + protected: + class channel final : public sourced_channel<SM> { + protected: + virtual void pull(const std::string d, const SM meta) const override { + this->_parent->pull(d, this->_parent->meta); + } + + public: + const wrapping_source<SM, M> *const _parent; + + channel(const wrapping_source<SM, M> *const p, std::shared_ptr<source<SM>> s) : sourced_channel<SM>(s), _parent(p) {} + }; + + channel inner; + + wrapping_source(std::shared_ptr<source<SM>> i, M m) + : inner(this, i), meta(m) {} + + public: + static std::shared_ptr<wrapping_source<SM, M>> make(std::shared_ptr<source<SM>> inner, M meta = {}) { + return std::shared_ptr<wrapping_source<SM, M>>(new wrapping_source<SM, M>(inner, meta)); + } + + const M meta; + + virtual ~wrapping_source() = default; + + virtual void push(const std::string& msg) const override { + this->inner.src->pull(msg, this->meta); + } + + virtual void push(std::string&& msg) const override { + this->inner.src->pull(std::forward<std::string>(msg), this->meta); + } + }; + template<typename... MsgT> class gate : public cc::vortex<SessionCreated, DisposeSession, SessionDisposed, MsgT...> { protected: gate(std::shared_ptr<cc::branch> b) : cc::vortex<SessionCreated, DisposeSession, SessionDisposed, MsgT...>(b) {}; + virtual ~gate() = default; }; /// @brief loopback gate @@ -329,7 +350,6 @@ namespace causal { * @param j junction to expose */ static std::shared_ptr<wrap> make(std::shared_ptr<cc::branch> b); - ~wrap() = default; const std::pair<std::string, std::shared_ptr<source<metaT>>> forge(); diff --git a/include/causal/data/crypto.hpp b/include/causal/data/crypto.hpp index 95d5b56f..e80d2f72 100644 --- a/include/causal/data/crypto.hpp +++ b/include/causal/data/crypto.hpp @@ -123,7 +123,7 @@ namespace causal { static void init(); }; - std::string gnutls_derrive_sha_key(const std::string& password, u_short key_length); + std::string gnutls_derrive_key_sha(const std::string& password, u_short key_length); EXCEPTION_ERROR(sha_hash_lenght_invalid, "hash_length should be larger than or equal to 256") diff --git a/include/causal/data/keeper.hpp b/include/causal/data/keeper.hpp new file mode 100644 index 00000000..62919dac --- /dev/null +++ b/include/causal/data/keeper.hpp @@ -0,0 +1,219 @@ +// Licensed under the terms of the AGPL-3.0-only (GNU AFFERO GENERAL PUBLIC LICENSE version 3) +// Copyright (C) 2018-2024 by Ralph Alexander Bariz + +#pragma once + +#include <msgpack/adaptor/define_decl.hpp> +#include <mutex> +#include <memory> +#include <set> +#include <shared_mutex> +#include <functional> +#include <type_traits> +#include <unordered_map> + +#include "causal/core/aspect.hpp" +#include "causal/core/wirks.hpp" +#include "causal/data/channel.hpp" +#include "causal/data/crypto.hpp" +#include "causal/data/msgpack.hpp" +#include "causal/trace.hpp" + +#ifndef _DATA_WRAP_SESSION_SIZE +#define _DATA_WRAP_SESSION_SIZE 64 +#endif + +namespace causal { + namespace data { + namespace cc = causal::core; + + struct IdentifyRequest { + std::string session; + std::string id; + std::string password; + + MSGPACK_DEFINE_MAP(session, id, password) + }; + struct AnonRequest { + std::string session; + + MSGPACK_DEFINE_MAP(session) + }; + struct IdentifyResponse { + std::string session; + std::string id; + std::string sign; + + MSGPACK_DEFINE_MAP(session, id, sign) + }; + + struct LeaveRequest { + std::string session; + std::string id; + + MSGPACK_DEFINE_MAP(session, id) + }; + + struct LeaveResponse { + std::string session; + std::string id; + + MSGPACK_DEFINE_MAP(session, id) + }; + + struct Identified { + std::string session, peer; + std::string id; + std::string sign; + std::shared_ptr<source<IdSessionMeta>> src; + }; + struct Left { + std::string session, peer; + std::string id; + }; + + template<typename channelT, typename gateT, typename... MsgT> + class keeper : public cc::vortex<Identified, Left, MsgT...> { + private: + struct Session { + SessionCreated event; + std::shared_ptr<channelT> channel; + std::shared_ptr<wrapping_source<SessionMeta, IdSessionMeta>> source; + }; + protected: + using sessionsT = std::unordered_map<std::string, Session>; + + std::set<std::shared_ptr<gateT>> gate; + + cc::aspect_ptr<const bool> dismissed = cc::forge<bool>(false); + cc::aspect_ptr<const sessionsT> sessions = cc::spawn<sessionsT>(); + + keeper(std::shared_ptr<cc::branch> b) + : cc::vortex<Identified, Left, MsgT...>(b) {}; + + virtual void connect(const std::shared_ptr<gateT>& g) = 0; + + static void Dismiss(bool &dismissed) { + dismissed = true; + } + + public: + virtual ~keeper() { + this->brnch->act(Dismiss, this->dismissed); + }; + + void attach(std::shared_ptr<gateT> g) { + const auto ret = this->gate.insert(g); + + if(ret.second) + this->connect(*ret.first); + } + }; + + template<typename channelT, typename gateT, typename spaceT> + class bouncer final : public keeper<channelT, gateT>, public std::enable_shared_from_this<bouncer<channelT, gateT, spaceT>> { + public: + using ptrT = std::shared_ptr<const bouncer<channelT, gateT, spaceT>>; + using keeperT = keeper<channelT, gateT>; + private: + using _ptrT = std::shared_ptr<bouncer<channelT, gateT, spaceT>>; + + std::shared_ptr<spaceT> space; + + static void Reg(const bool &dismissed, const std::string id, const std::string password, std::string &key) { + if(dismissed) { + TRACE_WARNING(scope_data) << "Bouncer cannot register '" << id << "' because bouncer is dismissed"; + return; + } + + // key is the hash from concatenation of id and password to avoid hash compare attacks + key = gnutls_derrive_key_sha(id+password, 256); + TRACE_INFO(scope_data) << "Bouncer succeffuly registered '" << id << "'"; + } + + static void Identify(ptrT b, const bool &dismissed, typename keeperT::sessionsT &sessions, const IdentifyRequest req, const SessionMeta meta, cc::aspect_ptr<std::string> &key) { + TRACE(scope_data) << "tick bouncer::Identify"; + if(dismissed) { + TRACE_WARNING(scope_data) << "Bouncer cannot identify because bouncer is dismissed"; + return; + } + + const auto kkk = *key; + if(key && *key == gnutls_derrive_key_sha(req.id+req.password, 256)) { + const IdSessionMeta id_meta{meta, {.id=req.id, .sign=req.id}}; + sessions[meta.session].source = wrapping_source<SessionMeta, IdSessionMeta>::make( + sessions[meta.session].event.src, + id_meta + ); + b->trigger(Identified{.session=meta.session, .peer=meta.peer, .id=req.id, .sign=req.id, .src=std::dynamic_pointer_cast<source<IdSessionMeta>>(sessions[meta.session].source)}); + sessions[meta.session].channel->push(IdentifyResponse{.session = meta.session, .id = req.id, .sign = id_meta.sign}); + TRACE_INFO(scope_data) << "Bouncer succeffuly identified '" << req.id << "'"; + } else { + TRACE_INFO(scope_data) << "Bouncer cannot identify '" << req.id << "'"; + } + TRACE(scope_data) << "ticked bouncer::Identify"; + } + + static void SourceOnIdentifyRequest(const IdentifyRequest e, const SessionMeta meta, ptrT b, const bool &dismissed) { + TRACE(scope_data) << "tick bouncer::SourceOnIdentifyRequest"; + if(dismissed) + TRACE_WARNING(scope_data) << "Bouncer cannot prepare to identify because bouncer is dismissed"; + else + cc::act_fwd(Identify, b, b->dismissed, b->sessions, e, meta, b->space->template get<std::string>(e.id)); + TRACE(scope_data) << "ticked bouncer::SourceOnIdentifyRequest"; + } + + static void SourceOnLeaveRequest(const LeaveRequest e, const SessionMeta meta, ptrT b, const bool &dismissed, typename keeperT::sessionsT &sessions) { + TRACE(scope_data) << "tick bouncer::SourceOnLeaveRequest"; + sessions[meta.session].source = nullptr; + b->trigger(Left{.session=meta.session, .peer=meta.peer, .id=e.id}); + sessions[meta.session].channel->push(LeaveResponse{.session = meta.session, .id = e.id,}); + TRACE(scope_data) << "ticked bouncer::SourceOnLeaveRequest"; + } + + static void GateSessionCreated(const SessionCreated e, _ptrT b, const bool &dismissed, typename keeperT::sessionsT &sessions) { + TRACE(scope_data) << "tick bouncer::GateSessionCreated"; + if(dismissed) { + TRACE_WARNING(scope_data) << "Bouncer cannot accept session because bouncer is dismissed"; + return; + } + + sessions[e.session] = {.event=e, .channel=channelT::make(e.src, cc::action::current().context)}; + sessions[e.session].channel->template subscribe<IdentifyRequest>(SourceOnIdentifyRequest, b, b->dismissed); + sessions[e.session].channel->template subscribe<LeaveRequest>(SourceOnLeaveRequest, b, b->dismissed, b->sessions); + TRACE(scope_data) << "ticked bouncer::GateSessionCreated"; + } + + static void GateOnSessionDisposed(const SessionDisposed s, ptrT b, typename keeperT::sessionsT &sessions) { + TRACE(scope_data) << "tick bouncer::GateOnSessionDisposed"; + sessions.erase(s.session); + TRACE(scope_data) << "ticked bouncer::GateOnSessionDisposed"; + } + + bouncer(std::shared_ptr<cc::branch> b, std::shared_ptr<spaceT> s) + : keeper<channelT, gateT>(b), space(s) { + ASSERT_MSG(cc::scope_core, s != nullptr, "Bouncer requires a set space for storing and recalling registrations") + } + + protected: + virtual void connect(const std::shared_ptr<gateT>& g) override { + g->template subscribe<SessionCreated>(GateSessionCreated, this->shared_from_this(), this->dismissed, this->sessions); + g->template subscribe<SessionDisposed>(GateOnSessionDisposed, this->shared_from_this(), this->sessions); + } + + public: + static std::shared_ptr<bouncer<channelT, gateT, spaceT>> make(std::shared_ptr<cc::branch> b, std::shared_ptr<spaceT> s) { + return std::shared_ptr<bouncer<channelT, gateT, spaceT>>(new bouncer<channelT, gateT, spaceT>(b, s)); + } + + void reg(const std::string id, const std::string password) const { + this->brnch->act(Reg, this->dismissed, id, password, this->space->template take<std::string>(id)); + } + + void dereg(const std::string id) const { + this->space->dispose(id); + TRACE_INFO(scope_data) << "Bouncer deregistered '" << id << "'"; + } + }; + } +} \ No newline at end of file diff --git a/include/causal/data/memory.hpp b/include/causal/data/memory.hpp index d2ee7e0d..38d07613 100644 --- a/include/causal/data/memory.hpp +++ b/include/causal/data/memory.hpp @@ -55,7 +55,7 @@ namespace causal { private: mutable std::recursive_mutex mtx; - std::unordered_map<ID, cc::weak_essence_ptr<>> aspects; + std::unordered_map<ID, cc::essence_ptr<>> aspects; template<typename GID> requires(std::is_same_v<ID, std::string>) static inline ID get_id(const GID& gid) { @@ -73,9 +73,8 @@ namespace causal { cc::aspect_ptr<T> r; std::unique_lock l(this->mtx); auto it = this->aspects.find(id); - if(it != this->aspects.end() && !it->second.expired()) { - auto a = it->second.lock(); - r = a->template as_typed<std::remove_cv_t<T>>(); + if(it != this->aspects.end()) { + r = it->second->template as_typed<std::remove_cv_t<T>>(); } if(!r) { @@ -83,6 +82,8 @@ namespace causal { r = cc::essence<std::remove_cv_t<T>>::make_certain(id, this->shared_from_this(), std::forward<Args>(args)...); else r = cc::essence<std::remove_cv_t<T>>::make(id, this->shared_from_this(), std::forward<Args>(args)...); + + this->aspects.insert({id, r.get_ptr()->as_untyped()}); } return r; @@ -104,13 +105,14 @@ namespace causal { } } - void destroy(const std::any id) noexcept override { + void dispose(const std::any id) noexcept override { if(id.type() == typeid(ID)) { std::unique_lock l(this->mtx); auto it = this->aspects.find(std::any_cast<ID>(id)); - if(it->second.expired()) + if(it != this->aspects.end()) { this->aspects.erase(it); - } + } + } } template<typename T, typename... Args> @@ -140,9 +142,8 @@ namespace causal { cc::aspect_ptr<T> r; std::unique_lock l(this->mtx); auto it = this->aspects.find(id); - if(it != this->aspects.end() && !it->second.expired()) { - auto a = it->second.lock(); - r = a->template as_typed<std::remove_cv_t<T>>(); + if(it != this->aspects.end()) { + r = it->second->template as_typed<std::remove_cv_t<T>>(); } return r; diff --git a/test/data/channel.cpp b/test/data/channel.cpp index 489aff94..f9cc6d35 100644 --- a/test/data/channel.cpp +++ b/test/data/channel.cpp @@ -57,33 +57,33 @@ TEST(causal_data_channel, msg_channel) { chk(2,3); } -TEST(causal_data_channel, httpd_gate_manage_sess) { +TEST(causal_data_channel, wrap) { struct Context { std::string session; std::shared_ptr<cd::source<cd::SessionMeta>> src; - static void Create(cd::SessionCreated e, Context &ctx) { - TRACE(cc::scope_core) << "tick Session_created '" << e.session << "'"; + static void Create(const cd::SessionCreated e, Context &ctx) { + TRACE(cd::scope_data) << "tick Session_created '" << e.session << "'"; ctx.session = e.session; ctx.src = e.src; - TRACE(cc::scope_core) << "ticked Session_created '" << e.session << "'"; + TRACE(cd::scope_data) << "ticked Session_created '" << e.session << "'"; } - static void Dispose(cd::SessionDisposed e, const Context &ctx) { - TRACE(cc::scope_core) << "tick Session_closed '" << e.session << "'"; + static void Dispose(const cd::SessionDisposed e, const Context &ctx) { + TRACE(cd::scope_data) << "tick Session_closed '" << e.session << "'"; EXPECT_EQ(ctx.session, e.session); - TRACE(cc::scope_core) << "ticked Session_closed '" << e.session << "'"; + TRACE(cd::scope_data) << "ticked Session_closed '" << e.session << "'"; }; }; const auto ctx = cc::spawn<Context>(); auto d = cp::simple_dispatcher<>::make(); auto b = cc::branch::make(d); - const auto n = cd::wrap::make(b); - n->subscribe<cd::SessionCreated>(Context::Create, ctx); - n->subscribe<cd::SessionDisposed>(Context::Dispose, ctx); + const auto w = cd::wrap::make(b); + w->subscribe<cd::SessionCreated>(Context::Create, ctx); + w->subscribe<cd::SessionDisposed>(Context::Dispose, ctx); // initializing session - const auto s = n->forge(); + const auto s = w->forge(); EXPECT_TRUE(d->do_tick()); @@ -92,7 +92,9 @@ TEST(causal_data_channel, httpd_gate_manage_sess) { EXPECT_EQ(ctx->src, s.second); } - n->trigger(cd::DisposeSession{.session=s.first}); + // TODO expand for triggering data as in keeper bouncer test + + w->trigger(cd::DisposeSession{.session=s.first}); EXPECT_TRUE(d->do_tick()); } diff --git a/test/data/keeper.cpp b/test/data/keeper.cpp new file mode 100644 index 00000000..6478e6a1 --- /dev/null +++ b/test/data/keeper.cpp @@ -0,0 +1,104 @@ +// Licensed under the terms of the AGPL-3.0-only (GNU AFFERO GENERAL PUBLIC LICENSE version 3) +// Copyright (C) 2018-2024 by Ralph Alexander Bariz + +#include <cstddef> +#include <memory> + +#include <gtest/gtest.h> + +#include "causal/core/aspect.hpp" +#include "causal/core/wirks.hpp" +#include "causal/data/keeper.hpp" +#include "causal/data/memory.hpp" +#include "causal/data/msgpack.hpp" +#include "causal/process/simple.hpp" +#include "causal/process/thread.hpp" +#include "causal/data/channel.hpp" +#include "causal/trace.hpp" + +namespace cc = causal::core; +namespace cp = causal::process; +namespace cd = causal::data; + +TEST(causal_data_keeper, bouncer_login) { + using wrap_bouncer = cd::bouncer<cd::msgpack_channel<cd::SessionMeta>, cd::wrap, cd::memory_space<std::string>>; + struct Context { + std::string session; + std::string id; + std::string sign; + bool left = false; + std::shared_ptr<cd::source<cd::IdSessionMeta>> src; + + static void OnIdentifyResponse(const cd::IdentifyResponse r, Context &ctx) { + TRACE(cd::scope_data) << "tick OnIdentifyResponse '" << r.id << "' with '" << r.sign << "' for '" << r.session << "'"; + EXPECT_EQ(ctx.session, r.session); + ctx.id = r.id; ctx.sign = r.sign; + TRACE(cd::scope_data) << "ticked OnIdentifyResponse '" << r.session << "'"; + } + + static void OnLeaveResponse(const cd::LeaveResponse r, Context &ctx) { + TRACE(cd::scope_data) << "tick OnLeaveResponse '" << r.id << "' for '" << r.session << "'"; + EXPECT_EQ(ctx.session, r.session); + EXPECT_EQ(ctx.id, r.id); + ctx.left = true; + TRACE(cd::scope_data) << "ticked OnLeaveResponse '" << r.session << "'"; + } + + static void OnIdentified(const cd::Identified e, Context &ctx) { + TRACE(cd::scope_data) << "tick OnIdentified '" << e.id << "' with '" << e.sign << "' for '" << e.session << "' on '" << e.peer << "'"; + ctx.session = e.session; + ctx.src = e.src; + TRACE(cd::scope_data) << "ticked OnIdentified '" << e.session << "'"; + } + static void OnLeft(const cd::Left e, const Context &ctx) { + TRACE(cd::scope_data) << "tick OnLeft '" << e.id << "' from '" << e.session << "' on '" << e.peer << "'"; + EXPECT_EQ(ctx.session, e.session); + TRACE(cd::scope_data) << "ticked OnLeft '" << e.session << "'"; + }; + }; + + const auto ctx = cc::spawn<Context>(); + auto d = cp::simple_dispatcher<>::make(); auto b = cc::branch::make(d); + const auto w = cd::wrap::make(b); + const auto bspc = causal::data::memory_space<>::make(); + const auto k = wrap_bouncer::make(b, bspc); + k->subscribe<cd::Identified>(Context::OnIdentified, ctx); + k->subscribe<cd::Left>(Context::OnLeft, ctx); + k->reg("testuser", "testpassword"); + + k->attach(w); + + // initializing session + const auto s = w->forge(); + + while(d->do_tick()) {}; + + const auto c = cd::msgpack_channel<cd::SessionMeta>::make(s.second, b); + c->subscribe<cd::IdentifyResponse>(Context::OnIdentifyResponse, ctx); + c->subscribe<cd::LeaveResponse>(Context::OnLeaveResponse, ctx); + + c->push(cd::IdentifyRequest{.session=s.first, .id="testuser", .password="testpassword"}); + + while(d->do_tick()) {}; + + { cc::opener o(ctx); + EXPECT_EQ(ctx->id, "testuser"); + EXPECT_FALSE(ctx->sign.empty()); + } + + c->push(cd::LeaveRequest{.session=s.first, .id="testuser"}); + + while(d->do_tick()) {}; + + { cc::opener o(ctx); + EXPECT_TRUE(ctx->left); + } + + k->dereg("testuser"); + + w->trigger(cd::DisposeSession{.session=s.first}); + + while(d->do_tick()) {}; +} + +int main(int argc, char** argv){testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();} \ No newline at end of file -- GitLab