summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Smrž <roman.smrz@seznam.cz>2022-12-18 18:56:19 +0100
committerRoman Smrž <roman.smrz@seznam.cz>2022-12-18 21:37:11 +0100
commit86293ff20d9f8625615e855d98249175e8cd5cd3 (patch)
tree7eb1fe3a34ce1d6a24a9338f3dd3008b500fffe0
parent77fc16d21158c6542addcbaaff47b801d3b5f5c7 (diff)
Contact service and contacts using stored set
-rw-r--r--include/erebos/contact.h65
-rw-r--r--include/erebos/set.h12
-rw-r--r--src/contact.cpp173
-rw-r--r--src/contact.h14
-rw-r--r--src/main.cpp49
-rw-r--r--src/set.cpp21
-rw-r--r--test/contact.test149
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"