diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/erebos/attach.h | 49 | ||||
-rw-r--r-- | include/erebos/contact.h | 98 | ||||
-rw-r--r-- | include/erebos/frp.h | 284 | ||||
-rw-r--r-- | include/erebos/identity.h | 109 | ||||
-rw-r--r-- | include/erebos/list.h | 116 | ||||
-rw-r--r-- | include/erebos/merge.h | 73 | ||||
-rw-r--r-- | include/erebos/message.h | 169 | ||||
-rw-r--r-- | include/erebos/network.h | 135 | ||||
-rw-r--r-- | include/erebos/pairing.h | 133 | ||||
-rw-r--r-- | include/erebos/service.h | 44 | ||||
-rw-r--r-- | include/erebos/set.h | 101 | ||||
-rw-r--r-- | include/erebos/state.h | 107 | ||||
-rw-r--r-- | include/erebos/storage.h | 839 | ||||
-rw-r--r-- | include/erebos/sync.h | 32 | ||||
-rw-r--r-- | include/erebos/time.h | 20 | ||||
-rw-r--r-- | include/erebos/uuid.h | 41 |
16 files changed, 2350 insertions, 0 deletions
diff --git a/include/erebos/attach.h b/include/erebos/attach.h new file mode 100644 index 0000000..6ea3d64 --- /dev/null +++ b/include/erebos/attach.h @@ -0,0 +1,49 @@ +#pragma once + +#include <erebos/pairing.h> + +#include <future> +#include <mutex> +#include <optional> +#include <string> +#include <vector> + +namespace erebos { + +using std::mutex; +using std::optional; +using std::promise; +using std::string; +using std::vector; + +struct AttachIdentity; + +class AttachService : public PairingService<AttachIdentity> +{ +public: + AttachService(Config &&, const Server &); + virtual ~AttachService(); + + UUID uuid() const override; + + void attachTo(const Peer &); + +protected: + virtual Stored<AttachIdentity> handlePairingComplete(const Peer &) override; + virtual void handlePairingResult(Context &, Stored<AttachIdentity>) override; + + mutex handlerLock; +}; + +template<class T> class Signed; + +struct AttachIdentity +{ + Stored<Signed<struct IdentityData>> identity; + vector<vector<uint8_t>> keys; + + static AttachIdentity load(const Ref &); + Ref store(const Storage &) const; +}; + +} diff --git a/include/erebos/contact.h b/include/erebos/contact.h new file mode 100644 index 0000000..9008ce7 --- /dev/null +++ b/include/erebos/contact.h @@ -0,0 +1,98 @@ +#pragma once + +#include <erebos/identity.h> +#include <erebos/list.h> +#include <erebos/pairing.h> +#include <erebos/set.h> +#include <erebos/state.h> +#include <erebos/storage.h> + +#include <memory> +#include <optional> +#include <string> +#include <vector> + +namespace erebos { + +using std::optional; +using std::shared_ptr; +using std::string; +using std::vector; + +struct ContactData; + +class Contact +{ +public: + Contact(vector<Stored<ContactData>> data); + Contact(const Contact &) = default; + Contact(Contact &&) = default; + Contact & operator=(const Contact &) = default; + Contact & operator=(Contact &&) = default; + + optional<Identity> identity() const; + optional<string> customName() const; + Contact customName(const Storage & st, const string & name) const; + string name() const; + + bool operator==(const Contact &) const; + bool operator!=(const Contact &) const; + + vector<Stored<ContactData>> data() const; + Digest leastRoot() const; + +private: + struct Priv; + shared_ptr<Priv> p; + Contact(shared_ptr<Priv> p): p(p) {} + + friend class ContactService; +}; + +DECLARE_SHARED_TYPE(Set<Contact>) + +struct ContactData +{ + static ContactData load(const Ref &); + Ref store(const Storage &) const; + + vector<Stored<ContactData>> prev; + vector<StoredIdentityPart> identity; + optional<string> name; +}; + +template<> struct Mergeable<Contact> +{ + using Component = ContactData; + static vector<Stored<ContactData>> components(const Contact & c) { return c.data(); } + static Contact merge(vector<Stored<ContactData>> x) { return Contact(move(x)); } +}; + +struct ContactAccepted; + +class ContactService : public PairingService<ContactAccepted> +{ +public: + ContactService(Config &&, const Server &); + virtual ~ContactService(); + + UUID uuid() const override; + + void request(const Peer &); + +protected: + virtual Stored<ContactAccepted> handlePairingComplete(const Peer &) override; + virtual void handlePairingResult(Context &, Stored<ContactAccepted>) override; + + const Server & server; +}; + +template<class T> class Signed; + +struct ContactAccepted +{ + static ContactAccepted load(const Ref &); + Ref store(const Storage &) const; +}; + +} diff --git a/include/erebos/frp.h b/include/erebos/frp.h new file mode 100644 index 0000000..72b5cc9 --- /dev/null +++ b/include/erebos/frp.h @@ -0,0 +1,284 @@ +#pragma once + +#include <functional> +#include <memory> +#include <optional> +#include <functional> +#include <tuple> +#include <variant> +#include <vector> + +namespace erebos { + +using std::enable_if_t; +using std::function; +using std::is_same_v; +using std::make_shared; +using std::monostate; +using std::optional; +using std::shared_ptr; +using std::static_pointer_cast; +using std::vector; +using std::weak_ptr; + +class BhvCurTime; + +class BhvTime +{ + BhvTime(uint64_t t): t(t) {} + friend BhvCurTime; +public: + BhvTime(const BhvCurTime &); + + bool operator==(const BhvTime & other) const { return t == other.t; } + bool operator!=(const BhvTime & other) const { return t != other.t; } + bool operator<(const BhvTime & other) const { return t < other.t; } + bool operator<=(const BhvTime & other) const { return t <= other.t; } + bool operator>(const BhvTime & other) const { return t > other.t; } + bool operator>=(const BhvTime & other) const { return t >= other.t; } + +private: + uint64_t t; +}; + +class BhvCurTime +{ +public: + BhvCurTime(); + ~BhvCurTime(); + BhvCurTime(const BhvCurTime &) = delete; + BhvCurTime(BhvCurTime &&); + + BhvCurTime & operator=(const BhvCurTime &) = delete; + BhvCurTime & operator=(BhvCurTime &&); + + BhvTime time() const { return t.value(); } + +private: + optional<BhvTime> t; +}; + +template<typename T> +class Watched +{ +public: + Watched() = default; + Watched(const Watched<T> &) = default; + Watched & operator=(const Watched<T> &) = default; + Watched(Watched<T> &&) = default; + Watched & operator=(Watched<T> &&) = default; + + Watched(shared_ptr<function<void(const BhvCurTime &)>> && cb): + cb(move(cb)) {} + ~Watched(); + +private: + shared_ptr<function<void(const BhvCurTime &)>> cb; +}; + +template<typename T> +Watched<T>::~Watched() +{ + BhvCurTime ctime; + cb.reset(); +} + +class BhvImplBase : public std::enable_shared_from_this<BhvImplBase> +{ +public: + virtual ~BhvImplBase(); + +protected: + void dependsOn(const BhvCurTime &, shared_ptr<BhvImplBase> other); + void updated(const BhvCurTime &); + virtual bool needsUpdate(const BhvCurTime &) const; + virtual void doUpdate(const BhvCurTime &); + + bool isDirty(const BhvCurTime &) const { return dirty; } + + vector<weak_ptr<function<void(const BhvCurTime &)>>> watchers; +private: + void markDirty(const BhvCurTime &, vector<shared_ptr<BhvImplBase>> &); + void updateDirty(const BhvCurTime &); + + bool dirty = false; + vector<shared_ptr<BhvImplBase>> depends; + vector<weak_ptr<BhvImplBase>> rdepends; + + template<typename A, typename B> friend class BhvFun; +}; + +template<typename A, typename B> +class BhvImpl : public BhvImplBase +{ +public: + virtual B get(const BhvCurTime &, const A &) const = 0; +}; + +template<typename A> +using BhvSource = BhvImpl<monostate, A>; + +template<typename A, typename B> +class BhvFun +{ +public: + BhvFun(shared_ptr<BhvImpl<A, B>> impl): + impl(move(impl)) {} + + template<typename T> BhvFun(shared_ptr<T> impl): + BhvFun(static_pointer_cast<BhvImpl<A, B>>(impl)) {} + + B get(const A & x) const + { + BhvCurTime ctime; + return impl->get(ctime, x); + } + + template<typename C> BhvFun<A, C> lens() const; + + const shared_ptr<BhvImpl<A, B>> impl; +}; + +template<typename A> +class BhvFun<monostate, A> +{ +public: + BhvFun(shared_ptr<BhvSource<A>> impl): + impl(move(impl)) {} + + template<typename T> BhvFun(shared_ptr<T> impl): + BhvFun(static_pointer_cast<BhvSource<A>>(impl)) {} + + A get() const + { + BhvCurTime ctime; + return impl->get(ctime, monostate()); + } + Watched<A> watch(function<void(const A &)>); + + template<typename C> BhvFun<monostate, C> lens() const; + + const shared_ptr<BhvSource<A>> impl; +}; + +template<typename A> +using Bhv = BhvFun<monostate, A>; + +template<typename A> +Watched<A> Bhv<A>::watch(function<void(const A &)> f) +{ + BhvCurTime ctime; + auto & impl = BhvFun<monostate, A>::impl; + if (impl->needsUpdate(ctime)) + impl->doUpdate(ctime); + + auto cb = make_shared<function<void(const BhvCurTime &)>>( + [impl = BhvFun<monostate, A>::impl, f] (const BhvCurTime & ctime) { + f(impl->get(ctime, monostate())); + }); + + impl->watchers.push_back(cb); + f(impl->get(ctime, monostate())); + return Watched<A>(move(cb)); +} + + +template<typename A, typename B> +class BhvLambda : public BhvImpl<A, B> +{ +public: + BhvLambda(function<B(const A &)> f): f(f) {} + + B get(const BhvCurTime &, const A & x) const override + { return f(x); } + +private: + function<B(const A &)> f; +}; + +template<typename A, typename B> +BhvFun<A, B> bfun(function<B(const A &)> f) +{ + return make_shared<BhvLambda<A, B>>(f); +} + + +template<typename A, typename B, typename C> class BhvComp; +template<typename A, typename B, typename C> +BhvFun<A, C> operator>>(const BhvFun<A, B> & f, const BhvFun<B, C> & g); + +template<typename A, typename B, typename C> +class BhvComp : public BhvImpl<A, C> +{ +public: + BhvComp(const BhvFun<A, B> & f, const BhvFun<B, C>): + f(f), g(g) {} + + C get(const BhvCurTime & ctime, const A & x) const override + { return g.impl.get(ctime, f.impl.get(ctime, x)); } + +private: + BhvFun<A, B> f; + BhvFun<B, C> g; + + friend BhvFun<A, C> operator>> <A, B, C>(const BhvFun<A, B> &, const BhvFun<B, C> &); +}; + +template<typename B, typename C> +class BhvComp<monostate, B, C> : public BhvSource<C> +{ +public: + BhvComp(const BhvFun<monostate, B> & f, const BhvFun<B, C> & g): + f(f), g(g) {} + + bool needsUpdate(const BhvCurTime & ctime) const override + { return !x || g.impl->get(ctime, f.impl->get(ctime, monostate())) != x.value(); } + + void doUpdate(const BhvCurTime & ctime) override + { x = g.impl->get(ctime, f.impl->get(ctime, monostate())); } + + C get(const BhvCurTime & ctime, const monostate & m) const override + { return x && !BhvImplBase::isDirty(ctime) ? x.value() : g.impl->get(ctime, f.impl->get(ctime, m)); } + +private: + BhvFun<monostate, B> f; + BhvFun<B, C> g; + optional<C> x; + + friend BhvFun<monostate, C> operator>> <monostate, B, C>(const BhvFun<monostate, B> &, const BhvFun<B, C> &); +}; + +template<typename A, typename B, typename C> +BhvFun<A, C> operator>>(const BhvFun<A, B> & f, const BhvFun<B, C> & g) +{ + BhvCurTime ctime; + auto impl = make_shared<BhvComp<A, B, C>>(f, g); + impl->dependsOn(ctime, f.impl); + impl->dependsOn(ctime, g.impl); + return impl; +} + + +template<typename A, typename B> +class BhvLens : public BhvImpl<A, B> +{ +public: + B get(const BhvCurTime &, const A & x) const override + { return A::template lens<B>(x); } +}; + +template<typename A, typename B> +template<typename C> +BhvFun<A, C> BhvFun<A, B>::lens() const +{ + return *this >> BhvFun<B, C>(make_shared<BhvLens<B, C>>()); +} + +template<typename A> +template<typename C> +BhvFun<monostate, C> BhvFun<monostate, A>::lens() const +{ + return *this >> BhvFun<A, C>(make_shared<BhvLens<A, C>>()); +} + +} diff --git a/include/erebos/identity.h b/include/erebos/identity.h new file mode 100644 index 0000000..fa8fde3 --- /dev/null +++ b/include/erebos/identity.h @@ -0,0 +1,109 @@ +#pragma once + +#include <erebos/state.h> +#include <erebos/storage.h> + +namespace erebos { + +using std::optional; +using std::vector; + +template<class T> class Signed; +struct IdentityData; +struct StoredIdentityPart; + +class Identity +{ +public: + Identity(const Identity &) = default; + Identity(Identity &&) = default; + Identity & operator=(const Identity &) = default; + Identity & operator=(Identity &&) = default; + + static std::optional<Identity> load(const Ref &); + static std::optional<Identity> load(const std::vector<Ref> &); + static std::optional<Identity> load(const std::vector<Stored<Signed<IdentityData>>> &); + static std::optional<Identity> load(const std::vector<StoredIdentityPart> &); + std::vector<Ref> store() const; + std::vector<Ref> store(const Storage & st) const; + vector<Stored<Signed<IdentityData>>> data() const; + vector<StoredIdentityPart> extData() const; + + std::optional<std::string> name() const; + std::optional<Identity> owner() const; + const Identity & finalOwner() const; + + Stored<class PublicKey> keyIdentity() const; + Stored<class PublicKey> keyMessage() const; + + bool sameAs(const Identity &) const; + bool operator==(const Identity & other) const; + bool operator!=(const Identity & other) const; + + std::optional<Ref> ref() const; + std::optional<Ref> extRef() const; + std::vector<Ref> refs() const; + std::vector<Ref> extRefs() const; + std::vector<Ref> updates() const; + + class Builder + { + public: + Identity commit() const; + + void name(const std::string &); + void owner(const Identity &); + + private: + friend class Identity; + struct Priv; + const std::shared_ptr<Priv> p; + Builder(Priv * p); + }; + + static Builder create(const Storage &); + Builder modify() const; + Identity update(const vector<Stored<Signed<IdentityData>>> &) const; + Identity update(const vector<StoredIdentityPart> &) const; + +private: + struct Priv; + std::shared_ptr<const Priv> p; + Identity(const Priv * p); + Identity(std::shared_ptr<const Priv> && p); +}; + +struct IdentityData; +struct IdentityExtension; + +struct StoredIdentityPart +{ + using Part = variant< + Stored<Signed<IdentityData>>, + Stored<Signed<IdentityExtension>>>; + + StoredIdentityPart(Part p): part(move(p)) {} + + static StoredIdentityPart load(const Ref &); + Ref store(const Storage & st) const; + + bool operator==(const StoredIdentityPart & other) const + { return part == other.part; } + bool operator<(const StoredIdentityPart & other) const + { return part < other.part; } + + const Ref & ref() const; + const Stored<Signed<IdentityData>> & base() const; + + vector<StoredIdentityPart> previous() const; + vector<Digest> roots() const; + optional<string> name() const; + optional<StoredIdentityPart> owner() const; + bool isSignedBy(const Stored<PublicKey> &) const; + + Part part; +}; + +DECLARE_SHARED_TYPE(optional<Identity>) + +} diff --git a/include/erebos/list.h b/include/erebos/list.h new file mode 100644 index 0000000..f5f2d3f --- /dev/null +++ b/include/erebos/list.h @@ -0,0 +1,116 @@ +#pragma once + +#include <functional> +#include <memory> +#include <mutex> +#include <variant> + +namespace erebos { + +using std::function; +using std::make_shared; +using std::make_unique; +using std::move; +using std::shared_ptr; +using std::unique_ptr; +using std::variant; + +template<typename T> +class List +{ +public: + struct Nil { bool operator==(const Nil &) const { return true; } }; + struct Cons { + T head; List<T> tail; + bool operator==(const Cons & x) const { return head == x.head && tail == x.tail; } + }; + + List(); + List(const T head, List<T> tail); + + const T & front() const; + const List & tail() const; + + bool empty() const; + + bool operator==(const List<T> &) const; + bool operator!=(const List<T> &) const; + + List push_front(T x) const; + +private: + struct Priv; + shared_ptr<Priv> p; +}; + +template<typename T> +struct List<T>::Priv +{ + variant<Nil, Cons> value; + + function<void()> eval = {}; + mutable std::once_flag once = {}; +}; + +template<typename T> +List<T>::List(): + p(shared_ptr<Priv>(new Priv { Nil() })) +{ + std::call_once(p->once, [](){}); +} + +template<typename T> +List<T>::List(T head, List<T> tail): + p(shared_ptr<Priv>(new Priv { + Cons { move(head), move(tail) } + })) +{ + std::call_once(p->once, [](){}); +} + +template<typename T> +const T & List<T>::front() const +{ + std::call_once(p->once, p->eval); + return std::get<Cons>(p->value).head; +} + +template<typename T> +const List<T> & List<T>::tail() const +{ + std::call_once(p->once, p->eval); + return std::get<Cons>(p->value).tail; +} + +template<typename T> +bool List<T>::empty() const +{ + std::call_once(p->once, p->eval); + return std::holds_alternative<Nil>(p->value); +} + +template<typename T> +bool List<T>::operator==(const List<T> & other) const +{ + if (p == other.p) + return true; + + std::call_once(p->once, p->eval); + std::call_once(other.p->once, other.p->eval); + return p->value == other.p->value; + +} + +template<typename T> +bool List<T>::operator!=(const List<T> & other) const +{ + return !(*this == other); +} + +template<typename T> +List<T> List<T>::push_front(T x) const +{ + return List<T>(move(x), *this); +} + +} diff --git a/include/erebos/merge.h b/include/erebos/merge.h new file mode 100644 index 0000000..9705e94 --- /dev/null +++ b/include/erebos/merge.h @@ -0,0 +1,73 @@ +#pragma once + +#include <erebos/storage.h> + +#include <optional> +#include <vector> + +namespace erebos +{ + +using std::nullopt; +using std::optional; +using std::vector; + +template<class T> struct Mergeable +{ +}; + +template<> struct Mergeable<vector<Stored<Object>>> +{ + using Component = Object; + + static vector<Stored<Object>> components(const vector<Stored<Object>> & x) { return x; } + static vector<Stored<Object>> merge(const vector<Stored<Object>> & x) { return x; } +}; + +vector<Stored<Object>> findPropertyObjects(const vector<Stored<Object>> & leaves, const string & prop); + +template<typename T> +optional<Stored<typename Mergeable<T>::Component>> findPropertyComponent(const vector<Stored<typename Mergeable<T>::Component>> & components, const string & prop) +{ + vector<Stored<Object>> leaves; + leaves.reserve(components.size()); + + for (const auto & c : components) + leaves.push_back(Stored<Object>::load(c.ref())); + + auto candidates = findPropertyObjects(leaves, prop); + if (!candidates.empty()) + return Stored<typename Mergeable<T>::Component>::load(candidates[0].ref()); + return nullopt; +} + +template<typename T> +optional<Stored<typename Mergeable<T>::Component>> findPropertyComponent(const T & x, const string & prop) +{ + return findPropertyComponent(x.components(), prop); +} + +template<typename T> +vector<Stored<typename Mergeable<T>::Component>> findPropertyComponents(const vector<Stored<typename Mergeable<T>::Component>> & components, const string & prop) +{ + vector<Stored<Object>> leaves; + leaves.reserve(components.size()); + + for (const auto & c : components) + leaves.push_back(Stored<Object>::load(c.ref())); + + auto candidates = findPropertyObjects(leaves, prop); + vector<Stored<typename Mergeable<T>::Component>> result; + result.reserve(candidates.size()); + for (const auto & obj : candidates) + result.push_back(Stored<typename Mergeable<T>::Component>::load(obj.ref())); + return result; +} + +template<typename T> +vector<Stored<typename Mergeable<T>::Component>> findPropertyComponents(const T & x, const string & prop) +{ + return findPropertyComponents(x.components(), prop); +} + +} diff --git a/include/erebos/message.h b/include/erebos/message.h new file mode 100644 index 0000000..b52b84b --- /dev/null +++ b/include/erebos/message.h @@ -0,0 +1,169 @@ +#pragma once + +#include <erebos/merge.h> +#include <erebos/service.h> + +#include <condition_variable> +#include <deque> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <tuple> + +namespace erebos { + +using std::condition_variable; +using std::deque; +using std::mutex; +using std::tuple; +using std::unique_ptr; + +class Contact; +class Identity; +struct DirectMessageState; + +class DirectMessage +{ +public: + const std::optional<Identity> & from() const; + const std::optional<struct ZonedTime> & time() const; + std::string text() const; + +private: + friend class DirectMessageThread; + friend class DirectMessageService; + struct Priv; + DirectMessage(Priv *); + std::shared_ptr<Priv> p; +}; + +class DirectMessageThread +{ +public: + class Iterator + { + struct Priv; + Iterator(Priv *); + public: + using iterator_category = std::forward_iterator_tag; + using value_type = DirectMessage; + using difference_type = ssize_t; + using pointer = const DirectMessage *; + using reference = const DirectMessage &; + + Iterator(const Iterator &); + ~Iterator(); + Iterator & operator=(const Iterator &); + Iterator & operator++(); + value_type operator*() const; + bool operator==(const Iterator &) const; + bool operator!=(const Iterator &) const; + + private: + friend DirectMessageThread; + std::unique_ptr<Priv> p; + }; + + Iterator begin() const; + Iterator end() const; + + size_t size() const; + DirectMessage at(size_t) const; + + const Identity & peer() const; + +private: + friend class DirectMessageService; + friend class DirectMessageThreads; + struct Priv; + DirectMessageThread(Priv *); + std::shared_ptr<Priv> p; +}; + +class DirectMessageThreads +{ +public: + DirectMessageThreads(); + DirectMessageThreads(Stored<DirectMessageState>); + DirectMessageThreads(vector<Stored<DirectMessageState>>); + + static DirectMessageThreads load(const vector<Ref> & refs); + vector<Ref> store() const; + vector<Stored<DirectMessageState>> data() const; + + bool operator==(const DirectMessageThreads &) const; + bool operator!=(const DirectMessageThreads &) const; + + DirectMessageThread thread(const Identity &) const; + +private: + vector<Stored<DirectMessageState>> state; + + friend class DirectMessageService; +}; + +DECLARE_SHARED_TYPE(DirectMessageThreads) + +template<> struct Mergeable<DirectMessageThreads> +{ + using Component = DirectMessageState; + static vector<Stored<DirectMessageState>> components(const DirectMessageThreads &); + static Contact merge(vector<Stored<DirectMessageState>>); +}; + +class DirectMessageService : public Service +{ +public: + using ThreadWatcher = std::function<void(const DirectMessageThread &, ssize_t, ssize_t)>; + + class Config + { + public: + Config & onUpdate(ThreadWatcher); + + private: + friend class DirectMessageService; + vector<ThreadWatcher> watchers; + }; + + DirectMessageService(Config &&, const Server &); + virtual ~DirectMessageService(); + + UUID uuid() const override; + void handle(Context &) override; + + DirectMessageThread thread(const Identity &); + + static DirectMessage send(const Head<LocalState> &, const Identity &, const std::string &); + static DirectMessage send(const Head<LocalState> &, const Contact &, const std::string &); + static DirectMessage send(const Head<LocalState> &, const Peer &, const std::string &); + + DirectMessage send(const Identity &, const std::string &); + DirectMessage send(const Contact &, const std::string &); + DirectMessage send(const Peer &, const std::string &); + +private: + void updateHandler(const DirectMessageThreads &); + void peerWatcher(size_t, const class Peer *); + void syncWithPeer(const DirectMessageThread &, const Peer &); + void doSyncWithPeers(); + void doSyncWithPeer(const DirectMessageThread &, const Peer &); + + const Config config; + const Server & server; + + vector<Stored<DirectMessageState>> prevState; + mutex stateMutex; + + mutex peerSyncMutex; + condition_variable peerSyncCond; + bool peerSyncRun; + deque<tuple<DirectMessageThread, Peer>> peerSyncQueue; + std::thread peerSyncThread; + + Watched<DirectMessageThreads> watched; +}; + +} diff --git a/include/erebos/network.h b/include/erebos/network.h new file mode 100644 index 0000000..2761a40 --- /dev/null +++ b/include/erebos/network.h @@ -0,0 +1,135 @@ +#pragma once + +#include <erebos/service.h> +#include <erebos/state.h> + +#include <functional> +#include <typeinfo> + +struct sockaddr_in6; + +namespace erebos { + +using std::vector; +using std::unique_ptr; + +class ServerConfig; +class Peer; + +class Server +{ + struct Priv; +public: + Server(const Head<LocalState> &, ServerConfig &&); + Server(const std::shared_ptr<Priv> &); + ~Server(); + + Server(const Server &) = delete; + Server & operator=(const Server &) = delete; + + const Head<LocalState> & localHead() const; + const Bhv<LocalState> & localState() const; + + Identity identity() const; + template<class S> S & svc(); + + class PeerList & peerList() const; + optional<erebos::Peer> peer(const Identity &) const; + void addPeer(const string & node) const; + void addPeer(const string & node, const string & service) const; + + struct Peer; +private: + Service & svcHelper(const std::type_info &); + + const std::shared_ptr<Priv> p; +}; + +class ServerConfig +{ +public: + ServerConfig() = default; + ServerConfig(const ServerConfig &) = delete; + ServerConfig(ServerConfig &&) = default; + ServerConfig & operator=(const ServerConfig &) = delete; + ServerConfig & operator=(ServerConfig &&) = default; + + template<class S> + typename S::Config & service(); + +private: + friend class Server; + vector<function<unique_ptr<Service>(const Server &)>> services; +}; + +template<class S> +S & Server::svc() +{ + return dynamic_cast<S&>(svcHelper(typeid(S))); +} + +template<class S> +typename S::Config & ServerConfig::service() +{ + auto config = make_shared<typename S::Config>(); + auto & configRef = *config; + + services.push_back([config = move(config)](const Server & server) { + return make_unique<S>(move(*config), server); + }); + + return configRef; +} + +class Peer +{ +public: + struct Priv; + Peer(const std::shared_ptr<Priv> & p); + ~Peer(); + + Server server() const; + + const Storage & tempStorage() const; + const PartialStorage & partialStorage() const; + + std::string name() const; + std::optional<Identity> identity() const; + const struct sockaddr_in6 & address() const; + string addressStr() const; + uint16_t port() const; + + bool send(UUID, const Ref &) const; + bool send(UUID, const Object &) const; + + bool operator==(const Peer & other) const; + bool operator!=(const Peer & other) const; + bool operator<(const Peer & other) const; + bool operator<=(const Peer & other) const; + bool operator>(const Peer & other) const; + bool operator>=(const Peer & other) const; + +private: + bool send(UUID, const Ref &, const Object &) const; + std::shared_ptr<Priv> p; +}; + +class PeerList +{ +public: + struct Priv; + PeerList(); + PeerList(const std::shared_ptr<Priv> & p); + ~PeerList(); + + size_t size() const; + Peer at(size_t n) const; + + void onUpdate(std::function<void(size_t, const Peer *)>); + +private: + friend Server; + const std::shared_ptr<Priv> p; +}; + +} diff --git a/include/erebos/pairing.h b/include/erebos/pairing.h new file mode 100644 index 0000000..71c9288 --- /dev/null +++ b/include/erebos/pairing.h @@ -0,0 +1,133 @@ +#pragma once + +#include <erebos/identity.h> +#include <erebos/network.h> +#include <erebos/service.h> + +#include <future> +#include <map> +#include <mutex> +#include <string> +#include <variant> +#include <vector> + +namespace erebos { + +using std::function; +using std::future; +using std::map; +using std::mutex; +using std::promise; +using std::string; +using std::variant; +using std::vector; + +/** + * Template-less base class for the paring functionality that does not depend + * on the result parameter. + */ +class PairingServiceBase : public Service +{ +public: + enum class Outcome + { + Success, + PeerRejected, + UserRejected, + UnexpectedMessage, + NonceMismatch, + Stale, + }; + + using RequestInitHook = function<void(const Peer &)>; + using ConfirmHook = function<future<bool>(const Peer &, string, future<Outcome> &&)>; + using RequestNonceFailedHook = function<void(const Peer &)>; + + class Config + { + public: + Config & onRequestInit(RequestInitHook); + Config & onResponse(PairingServiceBase::ConfirmHook); + Config & onRequest(PairingServiceBase::ConfirmHook); + Config & onRequestNonceFailed(RequestNonceFailedHook); + + private: + friend class PairingServiceBase; + RequestInitHook requestInitHook; + ConfirmHook responseHook; + ConfirmHook requestHook; + RequestNonceFailedHook requestNonceFailedHook; + }; + + PairingServiceBase(Config &&); + virtual ~PairingServiceBase(); + +protected: + void requestPairing(UUID serviceId, const Peer & peer); + virtual void handle(Context &) override; + virtual Ref handlePairingCompleteRef(const Peer &) = 0; + virtual void handlePairingResult(Context &) = 0; + +private: + static vector<uint8_t> nonceDigest(const Identity & id1, const Identity & id2, + const vector<uint8_t> & nonce1, const vector<uint8_t> & nonce2); + static string confirmationNumber(const vector<uint8_t> &); + + const Config config; + optional<Ref> result; + + enum class StatePhase { + NoPairing, + OurRequest, + OurRequestConfirm, + OurRequestReady, + PeerRequest, + PeerRequestConfirm, + PairingDone, + PairingFailed + }; + + struct State { + mutex lock; + StatePhase phase; + optional<Identity> idReq; + optional<Identity> idRsp; + vector<uint8_t> nonce; + vector<uint8_t> peerCheck; + promise<Outcome> outcome; + }; + + map<Peer, shared_ptr<State>> peerStates; + mutex stateLock; + + void waitForConfirmation(Peer peer, weak_ptr<State> state, string confirm, ConfirmHook hook); +}; + +template<class Result> +class PairingService : public PairingServiceBase +{ +public: + PairingService(Config && config): + PairingServiceBase(move(config)) {} + +protected: + virtual Stored<Result> handlePairingComplete(const Peer &) = 0; + virtual void handlePairingResult(Context &, Stored<Result>) = 0; + + virtual Ref handlePairingCompleteRef(const Peer &) override final; + virtual void handlePairingResult(Context &) override final; +}; + +template<class Result> +Ref PairingService<Result>::handlePairingCompleteRef(const Peer & peer) +{ + return handlePairingComplete(peer).ref(); +} + +template<class Result> +void PairingService<Result>::handlePairingResult(Context & ctx) +{ + handlePairingResult(ctx, Stored<Result>::load(ctx.ref())); +} + +} diff --git a/include/erebos/service.h b/include/erebos/service.h new file mode 100644 index 0000000..7e037f8 --- /dev/null +++ b/include/erebos/service.h @@ -0,0 +1,44 @@ +#pragma once + +#include <erebos/state.h> +#include <erebos/uuid.h> + +#include <memory> + +namespace erebos { + +class Server; + +class Service +{ +public: + Service(); + virtual ~Service(); + + using Config = monostate; + + class Context + { + public: + struct Priv; + Context(Priv *); + Priv & priv(); + + const class Ref & ref() const; + const class Peer & peer() const; + + const Stored<LocalState> & local() const; + void local(const LocalState &); + + void afterCommit(function<void()>); + void runAfterCommitHooks() const; + + private: + std::unique_ptr<Priv> p; + }; + + virtual UUID uuid() const = 0; + virtual void handle(Context &) = 0; +}; + +} diff --git a/include/erebos/set.h b/include/erebos/set.h new file mode 100644 index 0000000..e4a5c91 --- /dev/null +++ b/include/erebos/set.h @@ -0,0 +1,101 @@ +#pragma once + +#include <erebos/merge.h> +#include <erebos/storage.h> + +namespace erebos +{ + +class SetViewBase; +template<class T> class SetView; + +class SetBase +{ +protected: + struct Priv; + + SetBase(); + SetBase(const vector<Ref> &); + SetBase(shared_ptr<const Priv>); + + shared_ptr<const Priv> add(const Storage &, const vector<Ref> &) const; + + vector<vector<Ref>> toList() const; + +public: + bool operator==(const SetBase &) const; + bool operator!=(const SetBase &) const; + + vector<Digest> digests() const; + vector<Ref> store() const; + +protected: + shared_ptr<const Priv> p; +}; + +template<class T> +class Set : public SetBase +{ + Set(shared_ptr<const Priv> p): SetBase(p) {}; +public: + Set() = default; + Set(const vector<Ref> & refs): SetBase(move(refs)) {} + Set(const Set<T> &) = default; + Set(Set<T> &&) = default; + Set & operator=(const Set<T> &) = default; + Set & operator=(Set<T> &&) = default; + + static Set<T> load(const vector<Ref> & refs) { return Set<T>(move(refs)); } + + Set<T> add(const Storage &, const T &) const; + + template<class F> + SetView<T> view(F && cmp) const; +}; + +template<class T> +class SetView +{ +public: + template<class F> + SetView(F && cmp, const vector<vector<Ref>> & refs); + + size_t size() const { return items.size(); } + typename vector<T>::const_iterator begin() const { return items.begin(); } + typename vector<T>::const_iterator end() const { return items.end(); } + +private: + vector<T> items; +}; + +template<class T> +Set<T> Set<T>::add(const Storage & st, const T & x) const +{ + return Set<T>(SetBase::add(st, storedRefs(Mergeable<T>::components(x)))); +} + +template<class T> +template<class F> +SetView<T> Set<T>::view(F && cmp) const +{ + return SetView<T>(std::move(cmp), toList()); +} + +template<class T> +template<class F> +SetView<T>::SetView(F && cmp, const vector<vector<Ref>> & refs) +{ + items.reserve(refs.size()); + for (const auto & crefs : refs) { + vector<Stored<typename Mergeable<T>::Component>> comps; + comps.reserve(crefs.size()); + for (const auto & r : crefs) + comps.push_back(Stored<typename Mergeable<T>::Component>::load(r)); + + filterAncestors(comps); + items.push_back(Mergeable<T>::merge(comps)); + } + std::sort(items.begin(), items.end(), cmp); +} + +} diff --git a/include/erebos/state.h b/include/erebos/state.h new file mode 100644 index 0000000..16be464 --- /dev/null +++ b/include/erebos/state.h @@ -0,0 +1,107 @@ +#pragma once + +#include <erebos/storage.h> +#include <erebos/uuid.h> + +#include <memory> +#include <optional> +#include <vector> + +namespace erebos { + +using std::optional; +using std::shared_ptr; +using std::vector; + +template<typename T> +struct SharedType +{ + static const UUID id; + static T(*const load)(const vector<Ref> &); + static vector<Ref>(*const store)(const T &); +}; + +#define DECLARE_SHARED_TYPE(T) \ + template<> const UUID erebos::SharedType<T>::id; \ + template<> T(*const erebos::SharedType<T>::load)(const std::vector<erebos::Ref> &); \ + template<> std::vector<erebos::Ref>(*const erebos::SharedType<T>::store) (const T &); + +#define DEFINE_SHARED_TYPE(T, id_, load_, store_) \ + template<> const UUID erebos::SharedType<T>::id { id_ }; \ + template<> T(*const erebos::SharedType<T>::load)(const vector<Ref> &) { load_ }; \ + template<> std::vector<erebos::Ref>(*const erebos::SharedType<T>::store) (const T &) { store_ }; + +class Identity; + +class LocalState +{ +public: + LocalState(); + explicit LocalState(const Ref &); + static LocalState load(const Ref & ref) { return LocalState(ref); } + Ref store(const Storage &) const; + + static const UUID headTypeId; + + const optional<Identity> & identity() const; + LocalState identity(const Identity &) const; + + template<class T> T shared() const; + template<class T> LocalState shared(const T & x) const; + + vector<Ref> sharedRefs() const; + LocalState sharedRefAdd(const Ref &) const; + + template<typename T> static T lens(const LocalState &); + +private: + vector<Ref> lookupShared(UUID) const; + LocalState updateShared(UUID, const vector<Ref> &) const; + + struct Priv; + std::shared_ptr<Priv> p; +}; + +class SharedState +{ +public: + template<class T> T get() const; + template<typename T> static T lens(const SharedState &); + + bool operator==(const SharedState &) const; + bool operator!=(const SharedState &) const; + +private: + vector<Ref> lookup(UUID) const; + + struct Priv; + SharedState(shared_ptr<Priv> && p): p(std::move(p)) {} + shared_ptr<Priv> p; + friend class LocalState; +}; + +template<class T> +T LocalState::shared() const +{ + return SharedType<T>::load(lookupShared(SharedType<T>::id)); +} + +template<class T> +LocalState LocalState::shared(const T & x) const +{ + return updateShared(SharedType<T>::id, SharedType<T>::store(x)); +} + +template<class T> +T SharedState::get() const +{ + return SharedType<T>::load(lookup(SharedType<T>::id)); +} + +template<class T> +T SharedState::lens(const SharedState & x) +{ + return x.get<T>(); +} + +} diff --git a/include/erebos/storage.h b/include/erebos/storage.h new file mode 100644 index 0000000..96a27d4 --- /dev/null +++ b/include/erebos/storage.h @@ -0,0 +1,839 @@ +#pragma once + +#include <erebos/frp.h> +#include <erebos/time.h> +#include <erebos/uuid.h> + +#include <algorithm> +#include <array> +#include <cstring> +#include <filesystem> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <stdexcept> +#include <string> +#include <thread> +#include <variant> +#include <vector> + +namespace erebos { + +class Storage; +class PartialStorage; +class Digest; +class Ref; +class PartialRef; + +template<class S> class RecordT; +typedef RecordT<Storage> Record; +typedef RecordT<PartialStorage> PartialRecord; +template<class S> class ObjectT; +typedef ObjectT<Storage> Object; +typedef ObjectT<PartialStorage> PartialObject; +class Blob; + +template<typename T> class Stored; +template<typename T> class Head; + +using std::bind; +using std::call_once; +using std::make_unique; +using std::monostate; +using std::move; +using std::optional; +using std::shared_ptr; +using std::string; +using std::variant; +using std::vector; + +class PartialStorage +{ +public: + typedef erebos::PartialRef Ref; + + PartialStorage(const PartialStorage &) = default; + PartialStorage & operator=(const PartialStorage &) = delete; + virtual ~PartialStorage() = default; + + bool operator==(const PartialStorage &) const; + bool operator!=(const PartialStorage &) const; + + PartialRef ref(const Digest &) const; + + std::optional<PartialObject> loadObject(const Digest &) const; + PartialRef storeObject(const PartialObject &) const; + PartialRef storeObject(const PartialRecord &) const; + PartialRef storeObject(const Blob &) const; + +protected: + friend class Storage; + friend erebos::Ref; + friend erebos::PartialRef; + struct Priv; + const std::shared_ptr<const Priv> p; + PartialStorage(const std::shared_ptr<const Priv> & p): p(p) {} + +public: + // For test usage + const Priv & priv() const { return *p; } +}; + +class Storage : public PartialStorage +{ +public: + typedef erebos::Ref Ref; + + Storage(const std::filesystem::path &); + Storage(const Storage &) = default; + Storage & operator=(const Storage &) = delete; + + Storage deriveEphemeralStorage() const; + PartialStorage derivePartialStorage() const; + + std::optional<Ref> ref(const Digest &) const; + Ref zref() const; + + std::optional<Object> loadObject(const Digest &) const; + Ref storeObject(const Object &) const; + Ref storeObject(const Record &) const; + Ref storeObject(const Blob &) const; + + std::variant<Ref, std::vector<Digest>> copy(const PartialRef &) const; + std::variant<Ref, std::vector<Digest>> copy(const PartialObject &) const; + Ref copy(const Ref &) const; + Ref copy(const Object &) const; + + template<typename T> Stored<T> store(const T &) const; + + template<typename T> std::optional<Head<T>> head(UUID id) const; + template<typename T> std::vector<Head<T>> heads() const; + template<typename T> Head<T> storeHead(const T &) const; + template<typename T> Head<T> storeHead(const Stored<T> &) const; + + void storeKey(Ref pubref, const std::vector<uint8_t> &) const; + std::optional<std::vector<uint8_t>> loadKey(Ref pubref) const; + +protected: + template<typename T> friend class Head; + template<typename T> friend class WatchedHead; + + Storage(const std::shared_ptr<const Priv> & p): PartialStorage(p) {} + + std::optional<Ref> headRef(UUID type, UUID id) const; + std::vector<std::tuple<UUID, Ref>> headRefs(UUID type) const; + static UUID storeHead(UUID type, const Ref & ref); + static bool replaceHead(UUID type, UUID id, const Ref & old, const Ref & ref); + static std::optional<Ref> updateHead(UUID type, UUID id, const Ref & old, const std::function<Ref(const Ref &)> &); + int watchHead(UUID type, UUID id, const std::function<void(const Ref &)>) const; + void unwatchHead(UUID type, UUID id, int watchId) const; +}; + +class Digest +{ +public: + static constexpr size_t size = 32; + + Digest(const Digest &) = default; + Digest & operator=(const Digest &) = default; + + explicit Digest(std::array<uint8_t, size> value): value(value) {} + explicit Digest(const std::string &); + explicit operator std::string() const; + bool isZero() const; + + static Digest of(const std::vector<uint8_t> & content); + template<class S> static Digest of(const ObjectT<S> &); + + const std::array<uint8_t, size> & arr() const { return value; } + + bool operator==(const Digest & other) const { return value == other.value; } + bool operator!=(const Digest & other) const { return value != other.value; } + bool operator<(const Digest & other) const { return value < other.value; } + bool operator<=(const Digest & other) const { return value <= other.value; } + bool operator>(const Digest & other) const { return value > other.value; } + bool operator>=(const Digest & other) const { return value >= other.value; } + +private: + std::array<uint8_t, size> value; +}; + +template<class S> +Digest Digest::of(const ObjectT<S> & obj) +{ + return Digest::of(obj.encode()); +} + +class PartialRef +{ +public: + PartialRef(const PartialRef &) = default; + PartialRef(PartialRef &&) = default; + PartialRef & operator=(const PartialRef &) = default; + PartialRef & operator=(PartialRef &&) = default; + + static PartialRef create(const PartialStorage &, const Digest &); + static PartialRef zcreate(const PartialStorage &); + + const Digest & digest() const; + + operator bool() const; + const PartialObject operator*() const; + std::unique_ptr<PartialObject> operator->() const; + + const PartialStorage & storage() const; + +protected: + friend class Storage; + struct Priv; + std::shared_ptr<const Priv> p; + PartialRef(const std::shared_ptr<const Priv> p): p(p) {} +}; + +class Ref : public PartialRef +{ +public: + Ref(const Ref &) = default; + Ref(Ref &&) = default; + Ref & operator=(const Ref &) = default; + Ref & operator=(Ref &&) = default; + + bool operator==(const Ref &) const; + bool operator!=(const Ref &) const; + + static std::optional<Ref> create(const Storage &, const Digest &); + static Ref zcreate(const Storage &); + + explicit constexpr operator bool() const { return true; } + const Object operator*() const; + std::unique_ptr<Object> operator->() const; + + const Storage & storage() const; + + vector<Ref> previous() const; + class Generation generation() const; + vector<Digest> roots() const; + +private: + class Generation generationLocked() const; + class vector<Digest> rootsLocked() const; + +protected: + Ref(const std::shared_ptr<const Priv> p): PartialRef(p) {} +}; + +template<class S> +class RecordT +{ +public: + class Item; + class Items; + +private: + RecordT(const std::shared_ptr<std::vector<Item>> & ptr): + ptr(ptr) {} + +public: + RecordT(): RecordT(std::vector<Item> {}) {} + RecordT(const std::vector<Item> &); + RecordT(std::vector<Item> &&); + std::vector<uint8_t> encode() const; + + Items items() const; + Item item(const std::string & name) const; + Item operator[](const std::string & name) const; + Items items(const std::string & name) const; + +private: + friend ObjectT<S>; + std::vector<uint8_t> encodeInner() const; + static std::optional<RecordT<S>> decode(const S &, + std::vector<uint8_t>::const_iterator, + std::vector<uint8_t>::const_iterator); + + const std::shared_ptr<const std::vector<Item>> ptr; +}; + +template<class S> +class RecordT<S>::Item +{ +public: + struct UnknownType + { + string type; + string value; + }; + + struct Empty {}; + + using Integer = int; + using Text = string; + using Binary = vector<uint8_t>; + using Date = ZonedTime; + using UUID = erebos::UUID; + using Ref = typename S::Ref; + + using Variant = variant< + monostate, + Empty, + int, + string, + vector<uint8_t>, + ZonedTime, + UUID, + typename S::Ref, + UnknownType>; + + Item(const string & name): + Item(name, monostate()) {} + Item(const string & name, Variant value): + name(name), value(value) {} + template<typename T> + Item(const string & name, const Stored<T> & value): + Item(name, value.ref()) {} + + Item(const Item &) = default; + Item & operator=(const Item &) = delete; + + operator bool() const; + + optional<Empty> asEmpty() const; + optional<Integer> asInteger() const; + optional<Text> asText() const; + optional<Binary> asBinary() const; + optional<Date> asDate() const; + optional<UUID> asUUID() const; + optional<Ref> asRef() const; + optional<UnknownType> asUnknown() const; + + template<typename T> optional<Stored<T>> as() const; + + const string name; + const Variant value; +}; + +template<class S> +class RecordT<S>::Items +{ +public: + using Empty = typename Item::Empty; + using Integer = typename Item::Integer; + using Text = typename Item::Text; + using Binary = typename Item::Binary; + using Date = typename Item::Date; + using UUID = typename Item::UUID; + using Ref = typename Item::Ref; + using UnknownType = typename Item::UnknownType; + + Items(shared_ptr<const vector<Item>> items); + Items(shared_ptr<const vector<Item>> items, string filter); + + class Iterator + { + Iterator(const Items & source, size_t idx); + friend Items; + public: + using iterator_category = std::forward_iterator_tag; + using value_type = Item; + using difference_type = ssize_t; + using pointer = const Item *; + using reference = const Item &; + + Iterator(const Iterator &) = default; + ~Iterator() = default; + Iterator & operator=(const Iterator &) = default; + Iterator & operator++(); + value_type operator*() const { return (*source.items)[idx]; } + bool operator==(const Iterator & other) const { return idx == other.idx; } + bool operator!=(const Iterator & other) const { return idx != other.idx; } + + private: + const Items & source; + size_t idx; + }; + + Iterator begin() const; + Iterator end() const; + + vector<Empty> asEmpty() const; + vector<Integer> asInteger() const; + vector<Text> asText() const; + vector<Binary> asBinary() const; + vector<Date> asDate() const; + vector<UUID> asUUID() const; + vector<Ref> asRef() const; + vector<UnknownType> asUnknown() const; + + template<typename T> vector<Stored<T>> as() const; + +private: + const shared_ptr<const vector<Item>> items; + const optional<string> filter; +}; + +extern template class RecordT<Storage>; +extern template class RecordT<PartialStorage>; + +class Blob +{ +public: + Blob(const std::vector<uint8_t> &); + + const std::vector<uint8_t> & data() const { return *ptr; } + std::vector<uint8_t> encode() const; + +private: + friend Object; + friend PartialObject; + std::vector<uint8_t> encodeInner() const; + static Blob decode( + std::vector<uint8_t>::const_iterator, + std::vector<uint8_t>::const_iterator); + + Blob(std::shared_ptr<std::vector<uint8_t>> ptr): ptr(ptr) {} + + const std::shared_ptr<const std::vector<uint8_t>> ptr; +}; + +template<class S> +class ObjectT +{ +public: + typedef std::variant< + RecordT<S>, + Blob, + std::monostate> Variants; + + ObjectT(const ObjectT<S> &) = default; + ObjectT(Variants content): content(content) {} + ObjectT<S> & operator=(const ObjectT<S> &) = default; + + static std::optional<std::tuple<ObjectT<S>, std::vector<uint8_t>::const_iterator>> + decodePrefix(const S &, + std::vector<uint8_t>::const_iterator, + std::vector<uint8_t>::const_iterator); + + static std::optional<ObjectT<S>> decode(const S &, const std::vector<uint8_t> &); + static std::optional<ObjectT<S>> decode(const S &, + std::vector<uint8_t>::const_iterator, + std::vector<uint8_t>::const_iterator); + static std::vector<ObjectT<S>> decodeMany(const S &, const std::vector<uint8_t> &); + std::vector<uint8_t> encode() const; + static ObjectT<S> load(const typename S::Ref &); + + operator bool() const; + + std::optional<RecordT<S>> asRecord() const; + std::optional<Blob> asBlob() const; + +private: + friend RecordT<S>; + friend Blob; + + Variants content; +}; + +extern template class ObjectT<Storage>; +extern template class ObjectT<PartialStorage>; + +template<class S> +template<typename T> +std::optional<Stored<T>> RecordT<S>::Item::as() const +{ + if (auto ref = asRef()) + return Stored<T>::load(ref.value()); + return std::nullopt; +} + +template<class S> +template<typename T> +vector<Stored<T>> RecordT<S>::Items::as() const +{ + auto refs = asRef(); + vector<Stored<T>> res; + res.reserve(refs.size()); + for (const auto & ref : refs) + res.push_back(Stored<T>::load(ref)); + return res; +} + +class Generation +{ +public: + Generation(); + static Generation next(const vector<Generation> &); + + explicit operator string() const; + +private: + Generation(size_t); + size_t gen; +}; + +template<typename T> +class Stored +{ + Stored(Ref ref, T x); + friend class Storage; + friend class Head<T>; +public: + Stored() = default; + Stored(const Stored &) = default; + Stored(Stored &&) = default; + Stored & operator=(const Stored &) = default; + Stored & operator=(Stored &&) = default; + + Stored(Ref); + static Stored<T> load(const Ref &); + Ref store(const Storage &) const; + + bool operator==(const Stored<T> & other) const + { return p->ref.digest() == other.p->ref.digest(); } + bool operator!=(const Stored<T> & other) const + { return p->ref.digest() != other.p->ref.digest(); } + bool operator<(const Stored<T> & other) const + { return p->ref.digest() < other.p->ref.digest(); } + bool operator<=(const Stored<T> & other) const + { return p->ref.digest() <= other.p->ref.digest(); } + bool operator>(const Stored<T> & other) const + { return p->ref.digest() > other.p->ref.digest(); } + bool operator>=(const Stored<T> & other) const + { return p->ref.digest() >= other.p->ref.digest(); } + + void init() const; + const T & operator*() const { init(); return *p->val; } + const T * operator->() const { init(); return p->val.get(); } + + Generation generation() const { return p->ref.generation(); } + + std::vector<Stored<T>> previous() const; + bool precedes(const Stored<T> &) const; + + std::vector<Digest> roots() const { return p->ref.roots(); } + + const Ref & ref() const { return p->ref; } + +private: + struct Priv { + const Ref ref; + mutable std::once_flag once {}; + mutable std::unique_ptr<T> val {}; + mutable std::function<T()> init {}; + }; + std::shared_ptr<Priv> p; +}; + +template<typename T> +void Stored<T>::init() const +{ + call_once(p->once, [this]() { + p->val = std::make_unique<T>(p->init()); + p->init = decltype(p->init)(); + }); +} + +template<typename T> +Stored<T> Storage::store(const T & val) const +{ + return Stored(val.store(*this), val); +} + +template<typename T> +Stored<T>::Stored(Ref ref, T x): + p(new Priv { + .ref = move(ref), + .val = make_unique<T>(move(x)), + }) +{ + call_once(p->once, [](){}); +} + +template<typename T> +Stored<T>::Stored(Ref ref): + p(new Priv { + .ref = move(ref), + }) +{ + p->init = [p = p.get()]() { return T::load(p->ref); }; +} + +template<typename T> +Stored<T> Stored<T>::load(const Ref & ref) +{ + return Stored(ref); +} + +template<typename T> +Ref Stored<T>::store(const Storage & st) const +{ + if (st == p->ref.storage()) + return p->ref; + return st.storeObject(*p->ref); +} + +template<typename T> +std::vector<Stored<T>> Stored<T>::previous() const +{ + auto refs = p->ref.previous(); + vector<Stored<T>> res; + res.reserve(refs.size()); + for (const auto & r : refs) + res.push_back(Stored<T>::load(r)); + return res; +} + +template<typename T> +bool precedes(const T & ancestor, const T & descendant) +{ + for (const auto & x : descendant.previous()) { + if (ancestor == x || precedes(ancestor, x)) + return true; + } + return false; +} + +template<typename T> +bool Stored<T>::precedes(const Stored<T> & other) const +{ + return erebos::precedes(*this, other); +} + +template<typename T> +void filterAncestors(std::vector<T> & xs) +{ + if (xs.size() < 2) + return; + + std::sort(xs.begin(), xs.end()); + xs.erase(std::unique(xs.begin(), xs.end()), xs.end()); + + std::vector<T> old; + old.swap(xs); + + for (auto i = old.begin(); i != old.end(); i++) { + bool add = true; + for (const auto & x : xs) + if (precedes(*i, x)) { + add = false; + break; + } + if (add) + for (auto j = i + 1; j != old.end(); j++) + if (precedes(*i, *j)) { + add = false; + break; + } + if (add) + xs.push_back(std::move(*i)); + } +} + +template<class T> class WatchedHead; +template<class T> class HeadBhv; + +template<class T> +class Head +{ + Head(UUID id, Stored<T> stored): + mid(id), mstored(move(stored)) {} + Head(UUID id, Ref ref, T val): + mid(id), mstored(move(ref), move(val)) {} + friend class Storage; +public: + Head(UUID id, Ref ref): mid(id), mstored(ref) {} + + const T & operator*() const { return *mstored; } + const T * operator->() const { return &(*mstored); } + + UUID id() const { return mid; } + const Stored<T> & stored() const { return mstored; } + const Ref & ref() const { return mstored.ref(); } + const Storage & storage() const { return mstored.ref().storage(); } + + optional<Head<T>> reload() const; + std::optional<Head<T>> update(const std::function<Stored<T>(const Stored<T> &)> &) const; + WatchedHead<T> watch(const std::function<void(const Head<T> &)> &) const; + + Bhv<T> behavior() const; + +private: + UUID mid; + Stored<T> mstored; +}; + +/** + * Manages registered watch callbacks to Head<T> object using RAII principle. + */ +template<class T> +class WatchedHead : public Head<T> +{ + friend class Head<T>; + friend class HeadBhv<T>; + + WatchedHead(const Head<T> & h): + Head<T>(h), watcherId(-1) {} + WatchedHead(const Head<T> & h, int watcherId): + Head<T>(h), watcherId(watcherId) {} + + int watcherId; + +public: + WatchedHead(WatchedHead<T> && h): + Head<T>(h), watcherId(h.watcherId) + { h.watcherId = -1; } + + WatchedHead<T> & operator=(WatchedHead<T> && h) + { watcherId = h.watcherId; h.watcherId = -1; return *this; } + + WatchedHead<T> & operator=(const Head<T> & h) { + if (Head<T>::id() != h.id()) + throw std::runtime_error("WatchedHead ID mismatch"); + static_cast<Head<T> &>(*this) = h; + return *this; + } + + /// Destructor stops the watching started with Head<T>::watch call. + /** + * Once the WatchedHead object is destroyed, no further Head<T> changes + * will trigger the associated callback. + * + * The destructor also ensures that any scheduled callback run + * triggered by a previous change to the head is executed and finished + * before the destructor returns. The exception is when the destructor + * is called directly from the callback itself, in which case the + * destructor returns immediately. + */ + ~WatchedHead(); +}; + +template<class T> +class HeadBhv : public BhvSource<T> +{ +public: + HeadBhv(const Head<T> & head): + whead(head) + {} + + T get(const BhvCurTime &, const std::monostate &) const { return *whead; } + +private: + friend class Head<T>; + + void init() + { + whead = whead.watch([wp = weak_ptr<BhvImplBase>(BhvImplBase::shared_from_this()), this] (const Head<T> & cur) { + // make sure this object still exists + if (auto ptr = wp.lock()) { + BhvCurTime ctime; + whead = cur; + BhvImplBase::updated(ctime); + } + }); + } + + WatchedHead<T> whead; +}; + +template<typename T> +std::optional<Head<T>> Storage::head(UUID id) const +{ + if (auto ref = headRef(T::headTypeId, id)) + return Head<T>(id, *ref); + return std::nullopt; +} + +template<typename T> +std::vector<Head<T>> Storage::heads() const +{ + std::vector<Head<T>> res; + for (const auto & x : headRefs(T::headTypeId)) + res.emplace_back(std::get<UUID>(x), std::get<Ref>(x)); + return res; +} + +template<typename T> +Head<T> Storage::storeHead(const T & val) const +{ + auto ref = val.store(*this); + auto id = storeHead(T::headTypeId, ref); + return Head(id, ref, val); +} + +template<typename T> +Head<T> Storage::storeHead(const Stored<T> & val) const +{ + auto id = storeHead(T::headTypeId, val.ref()); + return Head(id, val); +} + +template<typename T> +optional<Head<T>> Head<T>::reload() const +{ + return storage().template head<T>(id()); +} + +template<typename T> +std::optional<Head<T>> Head<T>::update(const std::function<Stored<T>(const Stored<T> &)> & f) const +{ + auto res = Storage::updateHead(T::headTypeId, mid, ref(), [&f, this](const Ref & r) { + return f(r.digest() == ref().digest() ? stored() : Stored<T>::load(r)).ref(); + }); + + if (!res) + return std::nullopt; + if (res->digest() == ref().digest()) + return *this; + return Head<T>(mid, *res); +} + +template<typename T> +WatchedHead<T> Head<T>::watch(const std::function<void(const Head<T> &)> & watcher) const +{ + int wid = storage().watchHead(T::headTypeId, id(), [id = id(), watcher] (const Ref & ref) { + watcher(Head<T>(id, ref)); + }); + return WatchedHead<T>(*this, wid); +} + +template<typename T> +Bhv<T> Head<T>::behavior() const +{ + auto cur = reload(); + auto ret = make_shared<HeadBhv<T>>(cur ? *cur : *this); + ret->init(); + return ret; +} + +template<class T> +WatchedHead<T>::~WatchedHead() +{ + if (watcherId >= 0) + Head<T>::storage().unwatchHead( + T::headTypeId, Head<T>::id(), watcherId); +} + +template<class T> +vector<Ref> storedRefs(const vector<Stored<T>> & v) +{ + vector<Ref> res; + res.reserve(v.size()); + for (const auto & x : v) + res.push_back(x.ref()); + return res; +} + +} + +namespace std +{ + template<> struct hash<erebos::Digest> + { + std::size_t operator()(const erebos::Digest & dgst) const noexcept + { + std::size_t res; + std::memcpy(&res, dgst.arr().data(), sizeof res); + return res; + } + }; +} diff --git a/include/erebos/sync.h b/include/erebos/sync.h new file mode 100644 index 0000000..662a558 --- /dev/null +++ b/include/erebos/sync.h @@ -0,0 +1,32 @@ +#pragma once + +#include <erebos/service.h> +#include <erebos/state.h> +#include <erebos/storage.h> + +#include <optional> +#include <mutex> +#include <vector> + +namespace erebos { + +using std::vector; + +class SyncService : public Service +{ +public: + SyncService(Config &&, const Server &); + virtual ~SyncService(); + + UUID uuid() const override; + void handle(Context &) override; + +private: + void peerWatcher(size_t, const class Peer *); + void localStateWatcher(const vector<Ref> &); + + const Server & server; + Watched<vector<Ref>> watchedLocal; +}; + +} diff --git a/include/erebos/time.h b/include/erebos/time.h new file mode 100644 index 0000000..d8ff5b1 --- /dev/null +++ b/include/erebos/time.h @@ -0,0 +1,20 @@ +#pragma once + +#include <chrono> +#include <string> + +namespace erebos { + +struct ZonedTime +{ + explicit ZonedTime(std::string); + ZonedTime(std::chrono::system_clock::time_point t): time(t), zone(0) {} + explicit operator std::string() const; + + static ZonedTime now(); + + std::chrono::system_clock::time_point time; + std::chrono::minutes zone; // zone offset +}; + +} diff --git a/include/erebos/uuid.h b/include/erebos/uuid.h new file mode 100644 index 0000000..d6ccf50 --- /dev/null +++ b/include/erebos/uuid.h @@ -0,0 +1,41 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <cstring> +#include <optional> +#include <string> + +namespace erebos { + +struct UUID +{ + UUID(): uuid({}) {} + explicit UUID(const std::string &); + explicit operator std::string() const; + + static std::optional<UUID> fromString(const std::string &); + static bool fromString(const std::string &, UUID &); + + static UUID generate(); + + bool operator==(const UUID &) const; + bool operator!=(const UUID &) const; + + std::array<uint8_t, 16> uuid; +}; + +} + +namespace std +{ + template<> struct hash<erebos::UUID> + { + std::size_t operator()(const erebos::UUID & uuid) const noexcept + { + std::size_t res; + std::memcpy(&res, uuid.uuid.data(), sizeof res); + return res; + } + }; +} |