src: Add chatroom discovery
Change-Id: I45e17a8d8bbcdef6dc5f93c528cde91181f3b578
diff --git a/src/chatroom-discovery-backend.cpp b/src/chatroom-discovery-backend.cpp
new file mode 100644
index 0000000..e7ed3ea
--- /dev/null
+++ b/src/chatroom-discovery-backend.cpp
@@ -0,0 +1,455 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2013, Regents of the University of California
+ *
+ * BSD license, See the LICENSE file for more information
+ *
+ * Author: Qiuhan Ding <qiuhanding@cs.ucla.edu>
+ * Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#include "chatroom-discovery-backend.hpp"
+#include <QStringList>
+
+
+#ifndef Q_MOC_RUN
+
+#endif
+
+namespace chronochat {
+
+static const time::milliseconds FRESHNESS_PERIOD(60000);
+static const time::seconds REFRESH_INTERVAL(60);
+static const time::seconds HELLO_INTERVAL(60);
+static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E}; // %F0.
+// a count enforced when a manager himself find another one publish chatroom data
+static const int MAXIMUM_COUNT = 3;
+static const int IDENTITY_OFFSET = -1;
+
+ChatroomDiscoveryBackend::ChatroomDiscoveryBackend(const Name& routingPrefix,
+ const Name& identity,
+ QObject* parent)
+ : QThread(parent)
+ , m_routingPrefix(routingPrefix)
+ , m_identity(identity)
+ , m_randomGenerator(static_cast<unsigned int>(std::time(0)))
+ , m_rangeUniformRandom(m_randomGenerator, boost::uniform_int<>(500,2000))
+ , m_shouldResume(false)
+{
+ m_discoveryPrefix.append("ndn")
+ .append("broadcast")
+ .append("ChronoChat")
+ .append("Discovery");
+ m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
+ updatePrefixes();
+}
+
+ChatroomDiscoveryBackend::~ChatroomDiscoveryBackend()
+{
+}
+
+void
+ChatroomDiscoveryBackend::run()
+{
+ bool shouldResume = false;
+ do {
+ initializeSync();
+
+ if (m_face == nullptr)
+ break;
+
+ m_face->getIoService().run();
+
+ m_mutex.lock();
+ shouldResume = m_shouldResume;
+ m_shouldResume = false;
+ m_mutex.unlock();
+
+ } while (shouldResume);
+
+ std::cerr << "Bye!" << std::endl;
+}
+
+void
+ChatroomDiscoveryBackend::initializeSync()
+{
+ BOOST_ASSERT(m_sock == nullptr);
+
+ m_face = shared_ptr<ndn::Face>(new ndn::Face);
+ m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
+
+ m_sock = make_shared<chronosync::Socket>(m_discoveryPrefix,
+ Name(),
+ ref(*m_face),
+ bind(&ChatroomDiscoveryBackend::processSyncUpdate,
+ this, _1));
+
+ // add an timer to refresh front end
+ if (m_refreshPanelId != nullptr) {
+ m_scheduler->cancelEvent(m_refreshPanelId);
+ }
+ m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
+ [this] { sendChatroomList(); });
+}
+
+void
+ChatroomDiscoveryBackend::close()
+{
+ m_scheduler->cancelAllEvents();
+ m_refreshPanelId.reset();
+ m_chatroomList.clear();
+ m_sock.reset();
+}
+
+void
+ChatroomDiscoveryBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
+{
+ if (updates.empty()) {
+ return;
+ }
+ for (const auto& update : updates) {
+ m_sock->fetchData(update.session, update.high,
+ bind(&ChatroomDiscoveryBackend::processChatroomData, this, _1), 2);
+ }
+}
+
+void
+ChatroomDiscoveryBackend::processChatroomData(const ndn::shared_ptr<const ndn::Data>& data)
+{
+ // extract chatroom name by get(-3)
+ Name::Component chatroomName = data->getName().get(-3);
+ auto it = m_chatroomList.find(chatroomName);
+ if (it == m_chatroomList.end()) {
+ m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
+ m_chatroomList[chatroomName].count = 0;
+ m_chatroomList[chatroomName].isPrint = false;
+ m_chatroomList[chatroomName].isParticipant = false;
+ m_chatroomList[chatroomName].isManager = false;
+ it = m_chatroomList.find(chatroomName);
+ }
+ // If the user is the manager of this chatroom, he should not receive any data from this chatroom
+ if (it->second.isManager) {
+ if (it->second.count < MAXIMUM_COUNT) {
+ it->second.count++;
+ return;
+ }
+ else {
+ it->second.count = 0;
+ if (m_routableUserDiscoveryPrefix < data->getName()) {
+ // when two managers exist, the one with "smaller" name take the control
+ sendUpdate(chatroomName);
+ return;
+ }
+ else {
+ if (it->second.helloTimeoutEventId != nullptr) {
+ m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
+ }
+ it->second.helloTimeoutEventId = nullptr;
+ it->second.isManager = false;
+ }
+
+ }
+ }
+
+ else if (it->second.isParticipant) {
+ if (it->second.localChatroomTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
+
+ // If a user start a random timer it means that he think his own chatroom is not alive
+ // But when he receive some packet, it means that this chatroom is alive, so he can
+ // cancel the timer
+ if (it->second.managerSelectionTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.managerSelectionTimeoutEventId);
+ it->second.managerSelectionTimeoutEventId = nullptr;
+
+ it->second.localChatroomTimeoutEventId =
+ m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
+ bind(&ChatroomDiscoveryBackend::localSessionTimeout,
+ this, chatroomName));
+ }
+ else {
+ if (!data->getContent().empty()) {
+ ChatroomInfo chatroom;
+ chatroom.wireDecode(data->getContent().blockFromValue());
+ it->second.info = chatroom;
+ }
+
+ if (it->second.remoteChatroomTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
+
+ it->second.remoteChatroomTimeoutEventId =
+ m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
+ bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
+ this, chatroomName));
+ }
+ // if this is a chatroom that haven't been print on the discovery panel, print it.
+ if(!it->second.isPrint) {
+ sendChatroomList();
+ it->second.isPrint = true;
+ }
+}
+
+void
+ChatroomDiscoveryBackend::localSessionTimeout(const Name::Component& chatroomName)
+{
+ auto it = m_chatroomList.find(chatroomName);
+ if (it == m_chatroomList.end() || it->second.isParticipant == false)
+ return;
+ it->second.managerSelectionTimeoutEventId =
+ m_scheduler->scheduleEvent(time::milliseconds(m_rangeUniformRandom()),
+ bind(&ChatroomDiscoveryBackend::randomSessionTimeout,
+ this, chatroomName));
+}
+
+void
+ChatroomDiscoveryBackend::remoteSessionTimeout(const Name::Component& chatroomName)
+{
+ m_chatroomList.erase(chatroomName);
+}
+
+void
+ChatroomDiscoveryBackend::randomSessionTimeout(const Name::Component& chatroomName)
+{
+ Name prefix = m_routableUserDiscoveryPrefix;
+ prefix.append(chatroomName);
+ m_sock->addSyncNode(prefix);
+
+ emit chatroomInfoRequest(chatroomName.toUri(), true);
+}
+
+void
+ChatroomDiscoveryBackend::sendUpdate(const Name::Component& chatroomName)
+{
+ auto it = m_chatroomList.find(chatroomName);
+ if (it != m_chatroomList.end() && it->second.isManager) {
+ ndn::Block buf = it->second.info.wireEncode();
+
+ if (it->second.helloTimeoutEventId != nullptr) {
+ m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
+ }
+
+ m_sock->publishData(buf.wire(), buf.size(), FRESHNESS_PERIOD, it->second.chatroomPrefix);
+
+ it->second.helloTimeoutEventId =
+ m_scheduler->scheduleEvent(HELLO_INTERVAL,
+ bind(&ChatroomDiscoveryBackend::sendUpdate, this, chatroomName));
+ // if this is a chatroom that haven't been print on the discovery panel, print it.
+ if(!it->second.isPrint) {
+ sendChatroomList();
+ it->second.isPrint = true;
+ }
+ }
+}
+
+void
+ChatroomDiscoveryBackend::updatePrefixes()
+{
+ Name temp;
+ if (m_routingPrefix.isPrefixOf(m_userDiscoveryPrefix))
+ temp = m_userDiscoveryPrefix;
+ else
+ temp.append(m_routingPrefix)
+ .append(ROUTING_HINT_SEPARATOR, 2)
+ .append(m_userDiscoveryPrefix);
+
+ Name routableIdentity = m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET);
+ for (auto& chatroom : m_chatroomList) {
+ if (chatroom.second.isParticipant) {
+ chatroom.second.info.removeParticipant(routableIdentity);
+ chatroom.second.info.addParticipant(temp.getPrefix(IDENTITY_OFFSET));
+ }
+ }
+ m_routableUserDiscoveryPrefix = temp;
+}
+
+void
+ChatroomDiscoveryBackend::updateRoutingPrefix(const QString& routingPrefix)
+{
+ Name newRoutingPrefix(routingPrefix.toStdString());
+ if (!newRoutingPrefix.empty() && newRoutingPrefix != m_routingPrefix) {
+ // Update localPrefix
+ m_routingPrefix = newRoutingPrefix;
+
+ updatePrefixes();
+
+ m_mutex.lock();
+ m_shouldResume = true;
+ m_mutex.unlock();
+
+ close();
+
+ m_face->getIoService().stop();
+ }
+}
+
+void
+ChatroomDiscoveryBackend::onEraseInRoster(ndn::Name sessionPrefix,
+ ndn::Name::Component chatroomName)
+{
+ auto it = m_chatroomList.find(chatroomName);
+ if (it != m_chatroomList.end()) {
+ it->second.info.removeParticipant(sessionPrefix);
+ if (it->second.info.getParticipants().size() == 0) {
+ // Before deleting the chatroom, cancel the hello event timer if exist
+ if (it->second.helloTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
+
+ m_chatroomList.erase(chatroomName);
+ Name prefix = sessionPrefix;
+ prefix.append("CHRONOCHAT-DISCOVERYDATA").append(chatroomName);
+ m_sock->removeSyncNode(prefix);
+ sendChatroomList();
+ return;
+ }
+
+ if (sessionPrefix.isPrefixOf(m_routableUserDiscoveryPrefix)) {
+ it->second.isParticipant = false;
+ it->second.isManager = false;
+ it->second.isPrint = false;
+ it->second.count = 0;
+ if (it->second.helloTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
+ it->second.helloTimeoutEventId = nullptr;
+
+ if (it->second.localChatroomTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
+ it->second.localChatroomTimeoutEventId = nullptr;
+
+ it->second.remoteChatroomTimeoutEventId =
+ m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
+ bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
+ this, chatroomName));
+ }
+
+ if (it->second.isManager) {
+ sendUpdate(chatroomName);
+ }
+ }
+}
+
+void
+ChatroomDiscoveryBackend::onAddInRoster(ndn::Name sessionPrefix,
+ ndn::Name::Component chatroomName)
+{
+ auto it = m_chatroomList.find(chatroomName);
+ if (it != m_chatroomList.end()) {
+ it->second.info.addParticipant(sessionPrefix);
+ if (it->second.isManager)
+ sendUpdate(chatroomName);
+ }
+ else {
+ m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
+ m_chatroomList[chatroomName].info.setName(chatroomName);
+ m_chatroomList[chatroomName].info.addParticipant(sessionPrefix);
+ }
+}
+
+void
+ChatroomDiscoveryBackend::onNewChatroomForDiscovery(Name::Component chatroomName)
+{
+ Name newPrefix = m_routableUserDiscoveryPrefix;
+ newPrefix.append(chatroomName);
+ auto it = m_chatroomList.find(chatroomName);
+ if (it == m_chatroomList.end()) {
+ m_chatroomList[chatroomName].chatroomPrefix = newPrefix;
+ m_chatroomList[chatroomName].isParticipant = true;
+ m_chatroomList[chatroomName].isManager = false;
+ m_chatroomList[chatroomName].count = 0;
+ m_chatroomList[chatroomName].isPrint = false;
+ m_scheduler->scheduleEvent(time::milliseconds(600),
+ bind(&ChatroomDiscoveryBackend::randomSessionTimeout, this,
+ chatroomName));
+ }
+ else {
+ // Entering an existing chatroom
+ it->second.isParticipant = true;
+ it->second.isManager = false;
+ it->second.chatroomPrefix = newPrefix;
+
+ if (it->second.remoteChatroomTimeoutEventId != nullptr)
+ m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
+ it->second.isPrint = false;
+ it->second.remoteChatroomTimeoutEventId = nullptr;
+
+ it->second.localChatroomTimeoutEventId =
+ m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
+ bind(&ChatroomDiscoveryBackend::localSessionTimeout,
+ this, chatroomName));
+ emit chatroomInfoRequest(chatroomName.toUri(), false);
+ }
+}
+
+void
+ChatroomDiscoveryBackend::onRespondChatroomInfoRequest(ChatroomInfo chatroomInfo, bool isManager)
+{
+ if (isManager)
+ chatroomInfo.setManager(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET));
+ Name::Component chatroomName = chatroomInfo.getName();
+ m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
+ m_chatroomList[chatroomName].isManager = isManager;
+ m_chatroomList[chatroomName].count = 0;
+ m_chatroomList[chatroomName].info = chatroomInfo;
+ sendChatroomList();
+ onAddInRoster(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET), chatroomName);
+}
+
+void
+ChatroomDiscoveryBackend::onIdentityUpdated(const QString& identity)
+{
+ m_chatroomList.clear();
+ m_identity = Name(identity.toStdString());
+ m_userDiscoveryPrefix.clear();
+ m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
+ updatePrefixes();
+
+ m_mutex.lock();
+ m_shouldResume = true;
+ m_mutex.unlock();
+
+ close();
+
+ m_face->getIoService().stop();
+}
+
+void
+ChatroomDiscoveryBackend::sendChatroomList()
+{
+ QStringList chatroomList;
+ for (const auto& chatroom : m_chatroomList) {
+ chatroomList << QString::fromStdString(chatroom.first.toUri());
+ }
+
+ emit chatroomListReady(chatroomList);
+ if (m_refreshPanelId != nullptr) {
+ m_scheduler->cancelEvent(m_refreshPanelId);
+ }
+ m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
+ [this] { sendChatroomList(); });
+}
+
+void
+ChatroomDiscoveryBackend::onWaitForChatroomInfo(const QString& chatroomName)
+{
+ auto chatroom = m_chatroomList.find(Name::Component(chatroomName.toStdString()));
+ if (chatroom != m_chatroomList.end())
+ emit chatroomInfoReady(chatroom->second.info);
+}
+
+void
+ChatroomDiscoveryBackend::shutdown()
+{
+ m_mutex.lock();
+ m_shouldResume = false;
+ m_mutex.unlock();
+
+ close();
+
+ m_face->getIoService().stop();
+}
+
+
+} // namespace chronochat
+
+#if WAF
+#include "chatroom-discovery-backend.moc"
+#endif