diff options
-rw-r--r-- | include/erebos/contact.h | 65 | ||||
-rw-r--r-- | include/erebos/set.h | 12 | ||||
-rw-r--r-- | src/contact.cpp | 173 | ||||
-rw-r--r-- | src/contact.h | 14 | ||||
-rw-r--r-- | src/main.cpp | 49 | ||||
-rw-r--r-- | src/set.cpp | 21 | ||||
-rw-r--r-- | test/contact.test | 149 |
7 files changed, 380 insertions, 103 deletions
diff --git a/include/erebos/contact.h b/include/erebos/contact.h index e56346a..ef9c3b6 100644 --- a/include/erebos/contact.h +++ b/include/erebos/contact.h @@ -2,6 +2,8 @@ #include <erebos/identity.h> #include <erebos/list.h> +#include <erebos/pairing.h> +#include <erebos/set.h> #include <erebos/state.h> #include <erebos/storage.h> @@ -17,31 +19,80 @@ 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; - static List<Contact> prepend(const Storage &, Identity, List<Contact>); - - Identity identity() const; - optional<string> name() const; + optional<Identity> identity() const; + optional<string> customName() const; + string name() const; bool operator==(const Contact &) const; bool operator!=(const Contact &) const; - static List<Contact> loadList(const vector<Ref> &); - vector<Ref> refs() const; + vector<Stored<ContactData>> data() 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<Stored<Signed<IdentityData>>> 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(); + virtual ~ContactService(); + + UUID uuid() const override; + + void serverStarted(const class Server &) override; + + void request(const Peer &); + +protected: + virtual Stored<ContactAccepted> handlePairingComplete(const Peer &) override; + virtual void handlePairingResult(Context &, Stored<ContactAccepted>) override; + + const class Server * server; }; -DECLARE_SHARED_TYPE(List<Contact>) +template<class T> class Signed; + +struct ContactAccepted +{ + static ContactAccepted load(const Ref &); + Ref store(const Storage &) const; +}; } diff --git a/include/erebos/set.h b/include/erebos/set.h index f625cb0..539ffa0 100644 --- a/include/erebos/set.h +++ b/include/erebos/set.h @@ -17,13 +17,17 @@ protected: SetBase(); SetBase(const vector<Ref> &); SetBase(shared_ptr<const Priv>); - - shared_ptr<const Priv> add(Storage &, const vector<Ref> &) const; + + 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; @@ -43,7 +47,7 @@ public: static Set<T> load(const vector<Ref> & refs) { return Set<T>(move(refs)); } - Set<T> add(Storage &, const T &) const; + Set<T> add(const Storage &, const T &) const; template<class F> SetView<T> view(F && cmp) const; @@ -64,7 +68,7 @@ private: }; template<class T> -Set<T> Set<T>::add(Storage & st, const T & x) const +Set<T> Set<T>::add(const Storage & st, const T & x) const { return Set<T>(SetBase::add(st, storedRefs(Mergeable<T>::components(x)))); } diff --git a/src/contact.cpp b/src/contact.cpp index edc33ea..796e896 100644 --- a/src/contact.cpp +++ b/src/contact.cpp @@ -6,42 +6,44 @@ using namespace erebos; using std::move; -DEFINE_SHARED_TYPE(List<Contact>, +DEFINE_SHARED_TYPE(Set<Contact>, "34fbb61e-6022-405f-b1b3-a5a1abecd25e", - &Contact::loadList, - [](const List<Contact> & list) { - if (list.empty()) - return vector<Ref>(); - return list.front().refs(); + &Set<Contact>::load, + [](const Set<Contact> & set) { + return set.store(); }) +static const UUID serviceUUID("d9c37368-0da1-4280-93e9-d9bd9a198084"); -List<Contact> Contact::prepend(const Storage & st, Identity id, List<Contact> list) +Contact::Contact(vector<Stored<ContactData>> data): + p(shared_ptr<Priv>(new Priv { + .data = data, + })) { - auto cd = st.store(ContactData { - .prev = list.empty() ? vector<Stored<ContactData>>() : list.front().p->data, - .identity = id.data(), - .name = nullopt, - }); - return list.push_front( - Contact(shared_ptr<Priv>(new Priv { - .data = { cd }, - .identity = move(id), - })) - ); } -Identity Contact::identity() const +optional<Identity> Contact::identity() const { + p->init(); return p->identity; } -optional<string> Contact::name() const +optional<string> Contact::customName() const { p->init(); return p->name; } +string Contact::name() const +{ + if (auto cust = customName()) + return *cust; + if (auto id = p->identity) + if (auto idname = id->name()) + return *idname; + return ""; +} + bool Contact::operator==(const Contact & other) const { return p->data == other.p->data; @@ -52,66 +54,18 @@ bool Contact::operator!=(const Contact & other) const return p->data != other.p->data; } -List<Contact> Contact::loadList(const vector<Ref> & refs) -{ - vector<Stored<ContactData>> cdata; - cdata.reserve(refs.size()); - - for (const auto & r : refs) - cdata.push_back(Stored<ContactData>::load(r)); - return Priv::loadList(move(cdata), {}); -} - -List<Contact> Contact::Priv::loadList(vector<Stored<ContactData>> && cdata, vector<Identity> && seen) -{ - if (cdata.empty()) - return {}; - - filterAncestors(cdata); - - for (size_t i = 0; i < cdata.size(); i++) { - auto id = Identity::load(cdata[i]->identity); - if (!id) - continue; - - bool skip = false; - for (const auto & sid : seen) { - if (id->sameAs(sid)) { - skip = true; - break; - } - } - if (skip) - continue; - - vector<Stored<ContactData>> next; - next.reserve(cdata.size() - i - 1 + cdata[i]->prev.size()); - for (size_t j = i + 1; j < cdata.size(); j++) - next.push_back(cdata[j]); - for (const auto & x : cdata[i]->prev) - next.push_back(x); - - seen.push_back(*id); - auto p = shared_ptr<Priv>(new Priv { .data = move(cdata), .identity = move(*id) }); - return List(Contact(p), loadList(move(next), move(seen))); - } - - return {}; -} - -vector<Ref> Contact::refs() const +vector<Stored<ContactData>> Contact::data() const { - vector<Ref> res; - res.reserve(p->data.size()); - for (const auto & x : p->data) - res.push_back(x.ref()); - return res; + return p->data; } void Contact::Priv::init() { std::call_once(initFlag, [this]() { - name = identity.name(); + // TODO: property lookup + identity = Identity::load(data[0]->identity); + if (identity) + name = identity->name(); }); } @@ -151,3 +105,74 @@ Ref ContactData::store(const Storage & st) const return st.storeObject(Record(std::move(items))); } + +ContactService::ContactService() = default; +ContactService::~ContactService() = default; + +UUID ContactService::uuid() const +{ + return serviceUUID; +} + +void ContactService::serverStarted(const Server & s) +{ + PairingService<ContactAccepted>::serverStarted(s); + server = &s; +} + +void ContactService::request(const Peer & peer) +{ + requestPairing(serviceUUID, peer); +} + +Stored<ContactAccepted> ContactService::handlePairingComplete(const Peer & peer) +{ + server->localHead().update([&] (const Stored<LocalState> & local) { + auto cdata = local.ref().storage().store(ContactData { + .prev = {}, + .identity = peer.identity()->finalOwner().data(), + .name = std::nullopt, + }); + + Contact contact(shared_ptr<Contact::Priv>(new Contact::Priv { + .data = { cdata }, + })); + + auto contacts = local->shared<Set<Contact>>(); + + return local.ref().storage().store(local->shared<Set<Contact>>( + contacts.add(local.ref().storage(), contact))); + }); + + return peer.tempStorage().store(ContactAccepted {}); +} + +void ContactService::handlePairingResult(Context & ctx, Stored<ContactAccepted>) +{ + auto cdata = ctx.local().ref().storage().store(ContactData { + .prev = {}, + .identity = ctx.peer().identity()->finalOwner().data(), + .name = std::nullopt, + }); + + Contact contact(shared_ptr<Contact::Priv>(new Contact::Priv { + .data = { cdata }, + })); + + auto contacts = ctx.local()->shared<Set<Contact>>(); + + ctx.local(ctx.local()->shared<Set<Contact>>( + contacts.add(ctx.local().ref().storage(), contact))); +} + +ContactAccepted ContactAccepted::load(const Ref &) +{ + return ContactAccepted {}; +} + +Ref ContactAccepted::store(const Storage & st) const +{ + vector<Record::Item> items; + items.emplace_back("accept", ""); + return st.storeObject(Record(std::move(items))); +} diff --git a/src/contact.h b/src/contact.h index 31deceb..6fc0219 100644 --- a/src/contact.h +++ b/src/contact.h @@ -19,24 +19,12 @@ struct IdentityData; struct Contact::Priv { vector<Stored<ContactData>> data; - Identity identity; void init(); std::once_flag initFlag {}; + optional<Identity> identity {}; optional<string> name {}; - - static List<Contact> loadList(vector<Stored<ContactData>> &&, vector<Identity> &&); -}; - -struct ContactData -{ - static ContactData load(const Ref &); - Ref store(const Storage &) const; - - vector<Stored<ContactData>> prev; - vector<Stored<Signed<IdentityData>>> identity; - optional<string> name; }; } diff --git a/src/main.cpp b/src/main.cpp index 387fe4a..de4ffbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include <erebos/attach.h> +#include <erebos/contact.h> #include <erebos/identity.h> #include <erebos/network.h> #include <erebos/set.h> @@ -74,7 +75,7 @@ struct TestPeer Peer peer; size_t id; bool deleted = false; - promise<bool> attachAnswer {}; + promise<bool> pairingAnswer {}; }; vector<TestPeer> testPeers; @@ -245,7 +246,7 @@ future<bool> confirmPairing(string prefix, const Peer & peer, string confirm, fu promise<bool> promise; auto input = promise.get_future(); - getPeer(peer).attachAnswer = move(promise); + getPeer(peer).pairingAnswer = move(promise); ostringstream ss; ss << prefix << " " << getPeer(peer).id << " " << confirm; @@ -264,6 +265,11 @@ void startServer(const vector<string> &) atts->onResponse(bind(confirmPairing, "attach-response", _1, _2, _3)); services.push_back(move(atts)); + auto conts = make_unique<ContactService>(); + conts->onRequest(bind(confirmPairing, "contact-request", _1, _2, _3)); + conts->onResponse(bind(confirmPairing, "contact-response", _1, _2, _3)); + services.push_back(move(conts)); + services.push_back(make_unique<SyncService>()); server.emplace(*h, move(services)); @@ -416,12 +422,43 @@ void attachTo(const vector<string> & params) void attachAccept(const vector<string> & params) { - getPeer(params.at(0)).attachAnswer.set_value(true); + getPeer(params.at(0)).pairingAnswer.set_value(true); } void attachReject(const vector<string> & params) { - getPeer(params.at(0)).attachAnswer.set_value(false); + getPeer(params.at(0)).pairingAnswer.set_value(false); +} + +void contactRequest(const vector<string> & params) +{ + server->svc<ContactService>().request(getPeer(params.at(0)).peer); +} + +void contactAccept(const vector<string> & params) +{ + getPeer(params.at(0)).pairingAnswer.set_value(true); +} + +void contactReject(const vector<string> & params) +{ + getPeer(params.at(0)).pairingAnswer.set_value(false); +} + +void contactList(const vector<string> &) +{ + auto cmp = [](const Contact & x, const Contact & y) { + return x.data() < y.data(); + }; + for (const auto & c : h->behavior().lens<SharedState>().lens<Set<Contact>>().get().view(cmp)) { + ostringstream ss; + ss << "contact-list-item " << c.name(); + if (auto id = c.identity()) + if (auto iname = id->name()) + ss << " " << *iname; + printLine(ss.str()); + } + printLine("contact-list-done"); } vector<Command> commands = { @@ -442,6 +479,10 @@ vector<Command> commands = { { "attach-to", attachTo }, { "attach-accept", attachAccept }, { "attach-reject", attachReject }, + { "contact-request", contactRequest }, + { "contact-accept", contactAccept }, + { "contact-reject", contactReject }, + { "contact-list", contactList }, }; } diff --git a/src/set.cpp b/src/set.cpp index 001bce3..d224af3 100644 --- a/src/set.cpp +++ b/src/set.cpp @@ -32,7 +32,7 @@ SetBase::SetBase(shared_ptr<const Priv> p_): { } -shared_ptr<const SetBase::Priv> SetBase::add(Storage & st, const vector<Ref> & refs) const +shared_ptr<const SetBase::Priv> SetBase::add(const Storage & st, const vector<Ref> & refs) const { auto item = st.store(SetItem { .prev = p->items, @@ -122,6 +122,16 @@ vector<vector<Ref>> SetBase::toList() const return res; } +bool SetBase::operator==(const SetBase & other) const +{ + return p->items == other.p->items; +} + +bool SetBase::operator!=(const SetBase & other) const +{ + return !(*this == other); +} + vector<Digest> SetBase::digests() const { vector<Digest> res; @@ -131,6 +141,15 @@ vector<Digest> SetBase::digests() const return res; } +vector<Ref> SetBase::store() const +{ + vector<Ref> res; + res.reserve(p->items.size()); + for (const auto & i : p->items) + res.push_back(i.ref()); + return res; +} + SetItem SetItem::load(const Ref & ref) { if (auto rec = ref->asRecord()) { diff --git a/test/contact.test b/test/contact.test new file mode 100644 index 0000000..b0005fd --- /dev/null +++ b/test/contact.test @@ -0,0 +1,149 @@ +test Contact: + spawn as p1 + spawn as p2 + spawn as p3 + spawn as p4 + + send "create-identity Device1 Owner1" to p1 + send "create-identity Device2 Owner2" to p2 + send "create-identity Device3 Owner3" to p3 + send "create-identity Device4" to p4 + + send "start-server" to p1 + send "start-server" to p2 + send "start-server" to p3 + send "start-server" to p4 + + expect from p1: + /peer ([0-9]+) addr ${p2.node.ip} 29665/ capture peer1_2 + /peer $peer1_2 id Device2 Owner2/ + /peer ([0-9]+) addr ${p3.node.ip} 29665/ capture peer1_3 + /peer $peer1_3 id Device3 Owner3/ + /peer ([0-9]+) addr ${p4.node.ip} 29665/ capture peer1_4 + /peer $peer1_4 id Device4/ + + expect from p2: + /peer ([0-9]+) addr ${p1.node.ip} 29665/ capture peer2_1 + /peer $peer2_1 id Device1 Owner1/ + /peer ([0-9]+) addr ${p3.node.ip} 29665/ capture peer2_3 + /peer $peer2_3 id Device3 Owner3/ + /peer ([0-9]+) addr ${p4.node.ip} 29665/ capture peer2_4 + /peer $peer2_4 id Device4/ + + expect from p3: + /peer ([0-9]+) addr ${p1.node.ip} 29665/ capture peer3_1 + /peer $peer3_1 id Device1 Owner1/ + /peer ([0-9]+) addr ${p2.node.ip} 29665/ capture peer3_2 + /peer $peer3_2 id Device2 Owner2/ + /peer ([0-9]+) addr ${p4.node.ip} 29665/ capture peer3_4 + /peer $peer3_4 id Device4/ + + expect from p4: + /peer ([0-9]+) addr ${p1.node.ip} 29665/ capture peer4_1 + /peer $peer4_1 id Device1 Owner1/ + /peer ([0-9]+) addr ${p2.node.ip} 29665/ capture peer4_2 + /peer $peer4_2 id Device2 Owner2/ + /peer ([0-9]+) addr ${p3.node.ip} 29665/ capture peer4_3 + /peer $peer4_3 id Device3 Owner3/ + + # Rejected contacts + + send "contact-request $peer1_2" to p1 + expect /contact-request $peer2_1 [0-9]*/ from p2 + expect /contact-response $peer1_2 [0-9]*/ from p1 + send "contact-reject $peer1_2" to p1 + expect /contact-request-failed $peer2_1 rejected/ from p2 + expect /contact-response-failed $peer1_2 user/ from p1 + + send "contact-request $peer1_2" to p1 + expect /contact-request $peer2_1 [0-9]*/ from p2 + expect /contact-response $peer1_2 [0-9]*/ from p1 + send "contact-reject $peer2_1" to p2 + expect /contact-request-failed $peer2_1 user/ from p2 + expect /contact-response-failed $peer1_2 rejected/ from p1 + + # Contact between 1 and 2 + + send "contact-request $peer1_2" to p1 + expect /contact-request $peer2_1 ([0-9]*)/ from p2 capture code1_2 + expect /contact-response $peer1_2 ([0-9]*)/ from p1 capture code1_1 + guard code1_1 == code1_2 + + send "contact-accept $peer1_2" to p1 + send "contact-accept $peer2_1" to p2 + expect /contact-request-done $peer2_1/ from p2 + expect /contact-response-done $peer1_2/ from p1 + + send "contact-list" to p1 + expect from p1: + /contact-list-item Owner2 Owner2/ + /contact-list-(.*)/ capture done1_1 + guard done1_1 == "done" + send "contact-list" to p2 + expect from p2: + /contact-list-item Owner1 Owner1/ + /contact-list-(.*)/ capture done1_2 + guard done1_2 == "done" + + # Attach peer 4 to Owner1 + + send "watch-local-identity" to p4 + expect /local-identity Device4/ from p4 + + send "attach-to $peer4_1" to p4 + expect /attach-request $peer1_4 ([0-9]*)/ from p1 capture code2_1 + expect /attach-response $peer4_1 ([0-9]*)/ from p4 capture code2_4 + guard code2_1 == code2_4 + + send "attach-accept $peer1_4" to p1 + send "attach-accept $peer4_1" to p4 + expect /attach-request-done $peer1_4/ from p1 + expect /attach-response-done $peer4_1/ from p4 + expect /local-identity Device4 Owner1/ from p4 + expect /peer $peer1_4 id Device4 Owner1/ from p1 + expect /peer $peer2_4 id Device4 Owner1/ from p2 + expect /peer $peer3_4 id Device4 Owner1/ from p3 + + # Contact between 3 and 4 + + send "contact-request $peer3_4" to p3 + expect /contact-request $peer4_3 ([0-9]*)/ from p4 capture code3_4 + expect /contact-response $peer3_4 ([0-9]*)/ from p3 capture code3_3 + guard code3_3 == code3_4 + + send "contact-accept $peer3_4" to p3 + send "contact-accept $peer4_3" to p4 + expect /contact-request-done $peer4_3/ from p4 + expect /contact-response-done $peer3_4/ from p3 + + send "contact-list" to p3 + expect from p3: + /contact-list-item Owner1 Owner1/ + /contact-list-(.*)/ capture done2_3 + guard done2_3 == "done" + + send "shared-state-get" to p1 + expect /shared-state-get (.*)/ from p1 capture s1 + send "shared-state-wait $s1" to p4 + expect /shared-state-wait $s1/ from p4 + + send "contact-list" to p4 + expect from p4: + /contact-list-item Owner2 Owner2/ + /contact-list-item Owner3 Owner3/ + /contact-list-(.*)/ capture done2_4 + guard done2_4 == "done" + + # Check sync between 1 and 4 + + send "shared-state-get" to p4 + expect /shared-state-get (.*)/ from p4 capture s4 + send "shared-state-wait $s4" to p1 + expect /shared-state-wait $s4/ from p1 + + send "contact-list" to p1 + expect from p1: + /contact-list-item Owner2 Owner2/ + /contact-list-item Owner3 Owner3/ + /contact-list-(.*)/ capture done2_1 + guard done2_1 == "done" |