summaryrefslogtreecommitdiff
path: root/include/erebos
diff options
context:
space:
mode:
Diffstat (limited to 'include/erebos')
-rw-r--r--include/erebos/attach.h49
-rw-r--r--include/erebos/contact.h98
-rw-r--r--include/erebos/frp.h284
-rw-r--r--include/erebos/identity.h109
-rw-r--r--include/erebos/list.h116
-rw-r--r--include/erebos/merge.h73
-rw-r--r--include/erebos/message.h169
-rw-r--r--include/erebos/network.h135
-rw-r--r--include/erebos/pairing.h133
-rw-r--r--include/erebos/service.h44
-rw-r--r--include/erebos/set.h101
-rw-r--r--include/erebos/state.h107
-rw-r--r--include/erebos/storage.h839
-rw-r--r--include/erebos/sync.h32
-rw-r--r--include/erebos/time.h20
-rw-r--r--include/erebos/uuid.h41
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;
+ }
+ };
+}