src: Add chatroom discovery

Change-Id: I45e17a8d8bbcdef6dc5f93c528cde91181f3b578
diff --git a/src/chat-dialog-backend.cpp b/src/chat-dialog-backend.cpp
index f175615..1ea25ac 100644
--- a/src/chat-dialog-backend.cpp
+++ b/src/chat-dialog-backend.cpp
@@ -26,6 +26,7 @@
 static const time::milliseconds FRESHNESS_PERIOD(60000);
 static const time::seconds HELLO_INTERVAL(60);
 static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E}; // %F0.
+static const int IDENTITY_OFFSET = -3;
 
 ChatDialogBackend::ChatDialogBackend(const Name& chatroomPrefix,
                                      const Name& userChatPrefix,
@@ -264,6 +265,9 @@
 
       // remove roster entry
       m_roster.erase(remoteSessionPrefix);
+
+      emit eraseInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
+                         Name::Component(m_chatroomName));
     }
   }
   else {
@@ -281,6 +285,9 @@
       emit sessionAdded(QString::fromStdString(remoteSessionPrefix.toUri()),
                         QString::fromStdString(msg.from()),
                         msg.timestamp());
+
+      emit addInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
+                       Name::Component(m_chatroomName));
     }
 
     // If we get a new nick for an existing session, update it.
@@ -330,6 +337,9 @@
 
   // remove roster entry
   m_roster.erase(sessionPrefix);
+
+  emit eraseInRoster(sessionPrefix.getPrefix(IDENTITY_OFFSET),
+                     Name::Component(m_chatroomName));
 }
 
 void
@@ -350,7 +360,8 @@
   m_sock->publishData(os.buf()->buf(), os.buf()->size(), FRESHNESS_PERIOD);
 
   std::vector<NodeInfo> nodeInfos;
-  NodeInfo nodeInfo = {QString::fromStdString(m_routableUserChatPrefix.toUri()),
+  Name sessionName = m_sock->getLogic().getSessionName();
+  NodeInfo nodeInfo = {QString::fromStdString(sessionName.toUri()),
                        nextSequence};
   nodeInfos.push_back(nodeInfo);
 
@@ -370,7 +381,8 @@
   m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
                                               bind(&ChatDialogBackend::sendHello, this));
 
-  emit sessionAdded(QString::fromStdString(m_routableUserChatPrefix.toUri()),
+  Name sessionName = m_sock->getLogic().getSessionName();
+  emit sessionAdded(QString::fromStdString(sessionName.toUri()),
                     QString::fromStdString(msg.from()),
                     msg.timestamp());
 }
@@ -393,6 +405,10 @@
   prepareControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
   sendMsg(msg);
 
+  // get my own identity with routable prefix by getPrefix(-2)
+  emit eraseInRoster(m_routableUserChatPrefix.getPrefix(-2),
+                     Name::Component(m_chatroomName));
+
   usleep(5000);
   m_joined = false;
 }
diff --git a/src/chat-dialog-backend.hpp b/src/chat-dialog-backend.hpp
index f01c015..f8133f7 100644
--- a/src/chat-dialog-backend.hpp
+++ b/src/chat-dialog-backend.hpp
@@ -125,6 +125,12 @@
   void
   chatPrefixChanged(ndn::Name newChatPrefix);
 
+  void
+  eraseInRoster(ndn::Name sessionPrefix, ndn::Name::Component chatroomName);
+
+  void
+  addInRoster(ndn::Name sessionPrefix, ndn::Name::Component chatroomName);
+
 public slots:
   void
   sendChatMessage(QString text, time_t timestamp);
diff --git a/src/chat-dialog.cpp b/src/chat-dialog.cpp
index 776e076..ad04d09 100644
--- a/src/chat-dialog.cpp
+++ b/src/chat-dialog.cpp
@@ -38,6 +38,7 @@
   , ui(new Ui::ChatDialog)
   , m_backend(chatroomPrefix, userChatPrefix, routingPrefix, chatroomName, nick, signingId)
   , m_chatroomName(chatroomName)
+  , m_chatroomPrefix(chatroomPrefix)
   , m_nick(nick.c_str())
   , m_isSecured(isSecured)
 {
@@ -172,24 +173,23 @@
   fitView();
 }
 
-shared_ptr<ChatroomInfo>
+ChatroomInfo
 ChatDialog::getChatroomInfo()
 {
-  shared_ptr<ChatroomInfo> chatroomInfo = make_shared<ChatroomInfo>();
-
-  chatroomInfo->setName(Name::Component(m_chatroomName));
-
+  ChatroomInfo chatroomInfo;
+  chatroomInfo.setName(Name::Component(m_chatroomName));
   QStringList prefixList = m_scene->getRosterPrefixList();
   for(QStringList::iterator it = prefixList.begin();
       it != prefixList.end(); ++it ) {
     Name participant = Name(it->toStdString()).getPrefix(-3);
-    chatroomInfo->addParticipant(participant);
+    chatroomInfo.addParticipant(participant);
   }
 
+  chatroomInfo.setSyncPrefix(m_chatroomPrefix);
   if (m_isSecured)
-    chatroomInfo->setTrustModel(ChatroomInfo::TRUST_MODEL_HIERARCHICAL);
+    chatroomInfo.setTrustModel(ChatroomInfo::TRUST_MODEL_HIERARCHICAL);
   else
-    chatroomInfo->setTrustModel(ChatroomInfo::TRUST_MODEL_NONE);
+    chatroomInfo.setTrustModel(ChatroomInfo::TRUST_MODEL_NONE);
   return chatroomInfo;
 }
 
diff --git a/src/chat-dialog.hpp b/src/chat-dialog.hpp
index 74dd76d..919c511 100644
--- a/src/chat-dialog.hpp
+++ b/src/chat-dialog.hpp
@@ -76,7 +76,7 @@
   {
   }
 
-  shared_ptr<ChatroomInfo>
+  ChatroomInfo
   getChatroomInfo();
 
 private:
@@ -117,9 +117,6 @@
   void
   resetIcon();
 
-  void
-  rosterChanged(const chronochat::ChatroomInfo& info);
-
 public slots:
   void
   onShow();
@@ -167,6 +164,7 @@
   ChatDialogBackend m_backend;
 
   std::string m_chatroomName;
+  Name m_chatroomPrefix;
   QString m_nick;
   bool m_isSecured;
 
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
diff --git a/src/chatroom-discovery-backend.hpp b/src/chatroom-discovery-backend.hpp
new file mode 100644
index 0000000..fe5e211
--- /dev/null
+++ b/src/chatroom-discovery-backend.hpp
@@ -0,0 +1,233 @@
+/* -*- 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>
+ */
+
+#ifndef CHRONOCHAT_CHATROOM_DISCOVERY_BACKEND_HPP
+#define CHRONOCHAT_CHATROOM_DISCOVERY_BACKEND_HPP
+
+#include <ndn-cxx/util/scheduler.hpp>
+#include <boost/random.hpp>
+#include <QThread>
+#include <QMutex>
+
+#ifndef Q_MOC_RUN
+#include "common.hpp"
+#include "chatroom-info.hpp"
+#include <socket.hpp>
+#endif
+
+namespace chronochat {
+
+class ChatroomInfoBackend {
+public:
+  std::string chatroomName;
+  Name chatroomPrefix;
+  ChatroomInfo info;
+  // For a chatroom's user to check whether his own chatroom is alive
+  ndn::EventId localChatroomTimeoutEventId;
+  // If the manager no longer exist, set a random timer to compete for manager
+  ndn::EventId managerSelectionTimeoutEventId;
+  // For a user to check the status of the chatroom that he is not in.
+  ndn::EventId remoteChatroomTimeoutEventId;
+  // If the user is manager, he will need the helloEventId to keep track of hello message
+  ndn::EventId helloTimeoutEventId;
+  // To tell whether the user is in this chatroom
+  bool isParticipant;
+  // To tell whether the user is the manager
+  bool isManager;
+  // Variable to tell whether another manager exists
+  int count;
+  // Variable to tell whether to print on the panel
+  bool isPrint;
+
+};
+
+class ChatroomDiscoveryBackend : public QThread
+{
+  Q_OBJECT
+
+public:
+  ChatroomDiscoveryBackend(const Name& routingPrefix,
+                           const Name& identity,
+                           QObject* parent = 0);
+
+  ~ChatroomDiscoveryBackend();
+
+
+protected:
+  void
+  run();
+
+private:
+  void
+  initializeSync();
+
+  void
+  close();
+
+  void
+  processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates);
+
+  void
+  processChatroomData(const ndn::shared_ptr<const ndn::Data>& data);
+
+  void
+  localSessionTimeout(const Name::Component& chatroomName);
+
+  void
+  remoteSessionTimeout(const Name::Component& chatroomName);
+
+  void
+  randomSessionTimeout(const Name::Component& chatroomName);
+
+  void
+  sendUpdate(const Name::Component& chatroomName);
+
+  void
+  updatePrefixes();
+
+  void
+  sendChatroomList();
+
+signals:
+  /**
+   * @brief request to get chatroom info
+   *
+   * This signal will be sent to controller to get chatroom info
+   *
+   * @param chatroomName specify which chatroom's info to request
+   * @param isManager if the user who send the signal is the manager of the chatroom
+   */
+  void
+  chatroomInfoRequest(std::string chatroomName, bool isManager);
+
+  /**
+   * @brief send chatroom list to front end
+   *
+   * @param chatroomList the list of chatrooms
+   */
+  void
+  chatroomListReady(const QStringList& chatroomList);
+
+  /**
+   * @brief send chatroom info to front end
+   *
+   * @param info the chatroom info request by front end
+   */
+  void
+  chatroomInfoReady(const ChatroomInfo& info);
+
+public slots:
+
+  /**
+   * @brief update routing prefix
+   *
+   * @param routingPrefix the new routing prefix
+   */
+  void
+  updateRoutingPrefix(const QString& routingPrefix);
+
+  /**
+   * @brief erase a participant in a chatroom's roster
+   *
+   * This slot is called by chat dialog will erase a participant from a chatroom.
+   * If the user is the manager of this chatroom, he will publish an update.
+   *
+   * @param sessionPrefix the prefix of the participant to erase
+   * @param chatroomName the name of the chatroom
+   */
+  void
+  onEraseInRoster(ndn::Name sessionPrefix, ndn::Name::Component chatroomName);
+
+  /**
+   * @brief add a participant in a chatroom's roster
+   *
+   * This slot is called by chat dialog and will add a participant from a chatroom.
+   * If the user is the manager of this chatroom, he will publish an update.
+   *
+   * @param sessionPrefix the prefix of the participant to erase
+   * @param chatroomName the name of the chatroom
+   */
+  void
+  onAddInRoster(ndn::Name sessionPrefix, ndn::Name::Component chatroomName);
+
+  /**
+   * @brief is called when user himself join a chatroom
+   *
+   * This slot is called by controller and will modify the chatroom list in discovery
+   *
+   * @param chatroomName the name of chatroom the user join
+   */
+  void
+  onNewChatroomForDiscovery(Name::Component chatroomName);
+
+  /**
+   * @brief get chatroom info from chat dialog
+   *
+   * This slot is called by controller. It get the chatroom info of a chatroom and
+   * if the user is the manager, he will publish an update
+   *
+   * @param chatroomInfo chatroom info
+   * @param isManager whether the user is the manager of the chatroom
+   */
+  void
+  onRespondChatroomInfoRequest(ChatroomInfo chatroomInfo, bool isManager);
+
+  /**
+   * @brief reset when the identity updates
+   *
+   * This slot is called when the identity is updated, it will update the identity and
+   * reset the socket
+   *
+   * @param identity new identity
+   */
+  void
+  onIdentityUpdated(const QString& identity);
+
+  /**
+   * @brief prepare chatroom info for discovery panel
+   *
+   * This slot is called by discovery panel when it wants to display the info of a chatroom
+   *
+   * @param chatroomName the name of chatroom the discovery panel requested
+   */
+  void
+  onWaitForChatroomInfo(const QString& chatroomName);
+
+  void
+  shutdown();
+
+private:
+
+  typedef std::map<ndn::Name::Component, ChatroomInfoBackend> ChatroomList;
+
+  Name m_discoveryPrefix;
+  Name m_routableUserDiscoveryPrefix;
+  Name m_routingPrefix;
+  Name m_userDiscoveryPrefix;
+  Name m_identity;
+
+  boost::mt19937 m_randomGenerator;
+  boost::variate_generator<boost::mt19937&, boost::uniform_int<> > m_rangeUniformRandom;
+
+  shared_ptr<ndn::Face> m_face;
+
+  unique_ptr<ndn::Scheduler> m_scheduler;            // scheduler
+  ndn::EventId m_refreshPanelId;
+  shared_ptr<chronosync::Socket> m_sock; // SyncSocket
+
+  ChatroomList m_chatroomList;
+  QMutex m_mutex;
+
+  bool m_shouldResume;
+};
+
+} // namespace chronochat
+
+#endif // CHRONOCHAT_CHATROOM_DISCOVERY_BACKEND_HPP
diff --git a/src/chatroom-info.cpp b/src/chatroom-info.cpp
index a56856c..bd705da 100644
--- a/src/chatroom-info.cpp
+++ b/src/chatroom-info.cpp
@@ -137,11 +137,9 @@
 
   // Chatroom Info
   Block::element_const_iterator i = m_wire.elements_begin();
-
   if (i == m_wire.elements_end() || i->type() != tlv::ChatroomName)
     throw Error("Missing Chatroom Name Info");
   m_chatroomName.wireDecode(i->blockFromValue());
-
   ++i;
 
   // Trust Model
@@ -149,14 +147,12 @@
     throw Error("Missing TrustModel");
   m_trustModel =
       static_cast<TrustModel>(readNonNegativeInteger(*i));
-
   ++i;
 
   // Chatroom Sync Prefix
   if (i == m_wire.elements_end() || i->type() != tlv::ChatroomPrefix)
     throw Error("Missing Chatroom Prefix");
   m_syncPrefix.wireDecode(i->blockFromValue());
-
   ++i;
 
   // Manager Prefix
@@ -209,7 +205,10 @@
 ChatroomInfo::addParticipant(const Name& participant)
 {
   m_wire.reset();
-  m_participants.push_back(participant);
+  if (find(m_participants.begin(), m_participants.end(), participant) ==
+      m_participants.end()) {
+    m_participants.push_back(participant);
+  }
 }
 
 void
diff --git a/src/controller.cpp b/src/controller.cpp
index f716ada..dce0b03 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -13,7 +13,6 @@
 #include <QDir>
 #include <QTimer>
 #include "controller.hpp"
-//#include "chatroom-discovery.h"
 
 #ifndef Q_MOC_RUN
 #include <boost/filesystem.hpp>
@@ -34,6 +33,8 @@
 Q_DECLARE_METATYPE(size_t)
 Q_DECLARE_METATYPE(chronochat::ChatroomInfo)
 Q_DECLARE_METATYPE(chronochat::Invitation)
+Q_DECLARE_METATYPE(std::string)
+Q_DECLARE_METATYPE(ndn::Name::Component)
 
 namespace chronochat {
 
@@ -43,21 +44,25 @@
 Controller::Controller(QWidget* parent)
   : QDialog(parent)
   , m_localPrefixDetected(false)
-  , m_settingDialog(new SettingDialog)
-  , m_startChatDialog(new StartChatDialog)
-  , m_profileEditor(new ProfileEditor)
-  , m_invitationDialog(new InvitationDialog)
-  , m_contactPanel(new ContactPanel)
-  , m_browseContactDialog(new BrowseContactDialog)
-  , m_addContactPanel(new AddContactPanel)
+  , m_settingDialog(new SettingDialog(this))
+  , m_startChatDialog(new StartChatDialog(this))
+  , m_profileEditor(new ProfileEditor(this))
+  , m_invitationDialog(new InvitationDialog(this))
+  , m_contactPanel(new ContactPanel(this))
+  , m_browseContactDialog(new BrowseContactDialog(this))
+  , m_addContactPanel(new AddContactPanel(this))
+  , m_discoveryPanel(new DiscoveryPanel(this))
 {
   qRegisterMetaType<ndn::Name>("ndn.Name");
   qRegisterMetaType<ndn::IdentityCertificate>("ndn.IdentityCertificate");
   qRegisterMetaType<chronochat::EndorseInfo>("chronochat.EndorseInfo");
   qRegisterMetaType<ndn::Interest>("ndn.Interest");
   qRegisterMetaType<size_t>("size_t");
-  qRegisterMetaType<chronochat::ChatroomInfo>("chronos.Chatroom");
-  qRegisterMetaType<chronochat::Invitation>("chronos.Invitation");
+  qRegisterMetaType<chronochat::ChatroomInfo>("chronochat.Chatroom");
+  qRegisterMetaType<chronochat::Invitation>("chronochat.Invitation");
+  qRegisterMetaType<std::string>("std.string");
+  qRegisterMetaType<ndn::Name::Component>("ndn.Component");
+
 
   // Connection to ContactManager
   connect(m_backend.getContactManager(), SIGNAL(warning(const QString&)),
@@ -103,7 +108,6 @@
           m_addContactPanel,
           SLOT(onContactEndorseInfoReady(const chronochat::EndorseInfo&)));
 
-
   // Connection to BrowseContactDialog
   connect(m_browseContactDialog, SIGNAL(directAddClicked()),
           this, SLOT(onDirectAdd()));
@@ -177,6 +181,43 @@
 
   initialize();
 
+  m_chatroomDiscoveryBackend
+    = new ChatroomDiscoveryBackend(m_localPrefix,
+                                   m_identity,
+                                   this);
+
+  // connect to chatroom discovery back end
+  connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
+          m_chatroomDiscoveryBackend, SLOT(updateRoutingPrefix(const QString&)));
+  connect(this, SIGNAL(localPrefixConfigured(const QString&)),
+          m_chatroomDiscoveryBackend, SLOT(updateRoutingPrefix(const QString&)));
+  connect(this, SIGNAL(newChatroomForDiscovery(Name::Component)),
+          m_chatroomDiscoveryBackend, SLOT(onNewChatroomForDiscovery(Name::Component)));
+  connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomInfoRequest(std::string, bool)),
+          this, SLOT(onChatroomInfoRequest(std::string, bool)));
+  connect(this, SIGNAL(respondChatroomInfoRequest(ChatroomInfo, bool)),
+          m_chatroomDiscoveryBackend, SLOT(onRespondChatroomInfoRequest(ChatroomInfo, bool)));
+  connect(this, SIGNAL(shutdownDiscoveryBackend()),
+          m_chatroomDiscoveryBackend, SLOT(shutdown()));
+  connect(this, SIGNAL(identityUpdated(const QString&)),
+          m_chatroomDiscoveryBackend, SLOT(onIdentityUpdated(const QString&)));
+
+  // connect chatroom discovery back end with front end
+  connect(m_discoveryPanel, SIGNAL(waitForChatroomInfo(const QString&)),
+          m_chatroomDiscoveryBackend, SLOT(onWaitForChatroomInfo(const QString&)));
+  connect(m_discoveryPanel, SIGNAL(warning(const QString&)),
+          this, SLOT(onWarning(const QString&)));
+  connect(this, SIGNAL(identityUpdated(const QString&)),
+          m_discoveryPanel, SLOT(onIdentityUpdated(const QString&)));
+  connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomListReady(const QStringList&)),
+          m_discoveryPanel, SLOT(onChatroomListReady(const QStringList&)));
+  connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomInfoReady(const ChatroomInfo&)),
+          m_discoveryPanel, SLOT(onChatroomInfoReady(const ChatroomInfo&)));
+  connect(m_discoveryPanel, SIGNAL(startChatroom(const QString&, bool)),
+          this, SLOT(onStartChatroom(const QString&, bool)));
+
+  m_chatroomDiscoveryBackend->start();
+
   createTrayIcon();
 
   emit updateLocalPrefix();
@@ -288,9 +329,6 @@
   m_startChatroom = new QAction(tr("Start new chat"), this);
   connect(m_startChatroom, SIGNAL(triggered()), this, SLOT(onStartChatAction()));
 
-  m_discoveryAction = new QAction(tr("Ongoing Chatrooms"), this);
-  connect(m_discoveryAction, SIGNAL(triggered()), this, SLOT(onDiscoveryAction()));
-
   m_settingsAction = new QAction(tr("Settings"), this);
   connect(m_settingsAction, SIGNAL(triggered()), this, SLOT(onSettingsAction()));
 
@@ -303,6 +341,9 @@
   m_addContactAction = new QAction(tr("Add contact"), this);
   connect(m_addContactAction, SIGNAL(triggered()), this, SLOT(onAddContactAction()));
 
+  m_chatroomDiscoveryAction = new QAction(tr("Chatroom Discovery"), this);
+  connect(m_chatroomDiscoveryAction, SIGNAL(triggered()), this, SLOT(onChatroomDiscoveryAction()));
+
   m_updateLocalPrefixAction = new QAction(tr("Update local prefix"), this);
   connect(m_updateLocalPrefixAction, SIGNAL(triggered()),
           &m_backend, SLOT(onUpdateLocalPrefixAction()));
@@ -322,7 +363,7 @@
 
   m_trayIconMenu = new QMenu(this);
   m_trayIconMenu->addAction(m_startChatroom);
-  // m_trayIconMenu->addAction(m_discoveryAction); // disable discovery temporarily
+  m_trayIconMenu->addAction(m_chatroomDiscoveryAction);
 
   m_trayIconMenu->addSeparator();
   m_trayIconMenu->addAction(m_settingsAction);
@@ -354,7 +395,7 @@
   QMenu* closeMenu = 0;
 
   menu->addAction(m_startChatroom);
-  // menu->addAction(m_discoveryAction);
+  menu->addAction(m_chatroomDiscoveryAction);
 
   menu->addSeparator();
   menu->addAction(m_settingsAction);
@@ -428,13 +469,18 @@
           this, SLOT(onShowChatMessage(const QString&, const QString&, const QString&)));
   connect(chatDialog, SIGNAL(resetIcon()),
           this, SLOT(onResetIcon()));
-  connect(chatDialog, SIGNAL(rosterChanged(const chronochat::ChatroomInfo&)),
-          this, SLOT(onRosterChanged(const chronochat::ChatroomInfo&)));
   connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
           chatDialog->getBackend(), SLOT(updateRoutingPrefix(const QString&)));
   connect(this, SIGNAL(localPrefixConfigured(const QString&)),
           chatDialog->getBackend(), SLOT(updateRoutingPrefix(const QString&)));
 
+  // connect chat dialog with discovery backend
+  connect(chatDialog->getBackend(), SIGNAL(addInRoster(ndn::Name, ndn::Name::Component)),
+          m_chatroomDiscoveryBackend, SLOT(onAddInRoster(ndn::Name, ndn::Name::Component)));
+  connect(chatDialog->getBackend(), SIGNAL(eraseInRoster(ndn::Name, ndn::Name::Component)),
+          m_chatroomDiscoveryBackend, SLOT(onEraseInRoster(ndn::Name, ndn::Name::Component)));
+
+
   QAction* chatAction = new QAction(chatroomName, this);
   m_chatActionList[chatroomName.toStdString()] = chatAction;
   connect(chatAction, SIGNAL(triggered()),
@@ -446,6 +492,7 @@
           chatDialog, SLOT(shutdown()));
 
   updateMenu();
+  emit newChatroomForDiscovery(Name::Component(chatroomName.toStdString()));
 }
 
 void
@@ -522,12 +569,6 @@
 }
 
 void
-Controller::onDiscoveryAction()
-{
-}
-
-
-void
 Controller::onSettingsAction()
 {
   m_settingDialog->setNick(QString(m_nick.c_str()));
@@ -558,6 +599,13 @@
 }
 
 void
+Controller::onChatroomDiscoveryAction()
+{
+  m_discoveryPanel->show();
+  m_discoveryPanel->raise();
+}
+
+void
 Controller::onDirectAdd()
 {
   m_addContactPanel->show();
@@ -572,6 +620,7 @@
   m_profileEditor->hide();
   m_invitationDialog->hide();
   m_addContactPanel->hide();
+  m_discoveryPanel->hide();
 
   ChatDialogList::iterator it = m_chatDialogList.begin();
   ChatDialogList::iterator end = m_chatDialogList.end();
@@ -593,6 +642,12 @@
   delete m_invitationDialog;
   delete m_browseContactDialog;
   delete m_addContactPanel;
+  delete m_discoveryPanel;
+  if (m_chatroomDiscoveryBackend->isRunning()) {
+    emit shutdownDiscoveryBackend();
+    m_chatroomDiscoveryBackend->wait();
+  }
+  delete m_chatroomDiscoveryBackend;
 
   if (m_backend.isRunning()) {
     emit shutdownBackend();
@@ -609,6 +664,7 @@
   chatroomPrefix.append("ndn")
     .append("broadcast")
     .append("ChronoChat")
+    .append("Chatroom")
     .append(chatroomName.toStdString());
 
   // check if the chatroom exists
@@ -623,7 +679,7 @@
   //(which should be created in the first half of this method
   //, but let's use the default one for now.
   Name chatPrefix;
-  chatPrefix.append(m_identity).append("CHRONOCHAT-DATA").append(chatroomName.toStdString());
+  chatPrefix.append(m_identity).append("CHRONOCHAT-CHATDATA").append(chatroomName.toStdString());
 
   ChatDialog* chatDialog
     = new ChatDialog(chatroomPrefix,
@@ -632,7 +688,8 @@
                      chatroomName.toStdString(),
                      m_nick,
                      true,
-                     m_identity);
+                     m_identity,
+                     this);
 
   addChatDialog(chatroomName, chatDialog);
   chatDialog->show();
@@ -672,11 +729,11 @@
 Controller::onRemoveChatDialog(const QString& chatroomName)
 {
   ChatDialogList::iterator it = m_chatDialogList.find(chatroomName.toStdString());
-
   if (it != m_chatDialogList.end()) {
     ChatDialog* deletedChat = it->second;
     if (deletedChat)
       delete deletedChat;
+
     m_chatDialogList.erase(it);
 
     QAction* chatAction = m_chatActionList[chatroomName.toStdString()];
@@ -707,9 +764,11 @@
 }
 
 void
-Controller::onRosterChanged(const chronochat::ChatroomInfo& info)
+Controller::onChatroomInfoRequest(std::string chatroomName, bool isManager)
 {
-
+  auto it = m_chatDialogList.find(chatroomName);
+  if (it != m_chatDialogList.end())
+    emit respondChatroomInfoRequest(it->second->getChatroomInfo(), isManager);
 }
 
 } // namespace chronochat
diff --git a/src/controller.hpp b/src/controller.hpp
index 1c6ef43..2b80932 100644
--- a/src/controller.hpp
+++ b/src/controller.hpp
@@ -24,6 +24,8 @@
 #include "browse-contact-dialog.hpp"
 #include "add-contact-panel.hpp"
 #include "chat-dialog.hpp"
+#include "chatroom-discovery-backend.hpp"
+#include "discovery-panel.hpp"
 
 #ifndef Q_MOC_RUN
 #include "common.hpp"
@@ -82,6 +84,9 @@
   shutdownBackend();
 
   void
+  shutdownDiscoveryBackend();
+
+  void
   updateLocalPrefix();
 
   void
@@ -112,6 +117,12 @@
   void
   removeChatroom(QString chatroomName);
 
+  void
+  newChatroomForDiscovery(Name::Component chatroomName);
+
+  void
+  respondChatroomInfoRequest(ChatroomInfo chatroomInfo, bool isManager);
+
 private slots:
   void
   onIdentityUpdated(const QString& identity);
@@ -132,9 +143,6 @@
   onStartChatAction();
 
   void
-  onDiscoveryAction();
-
-  void
   onSettingsAction();
 
   void
@@ -156,6 +164,9 @@
   onQuitAction();
 
   void
+  onChatroomDiscoveryAction();
+
+  void
   onStartChatroom(const QString& chatroom, bool secured);
 
   void
@@ -177,7 +188,7 @@
   onError(const QString& msg);
 
   void
-  onRosterChanged(const chronochat::ChatroomInfo& info);
+  onChatroomInfoRequest(std::string chatroomName, bool isManager);
 
 private: // private member
   typedef std::map<std::string, QAction*> ChatActionList;
@@ -189,7 +200,7 @@
 
   // Tray
   QAction*         m_startChatroom;
-  QAction*         m_discoveryAction;
+  //QAction*         m_discoveryAction;
   QAction*         m_minimizeAction;
   QAction*         m_settingsAction;
   QAction*         m_editProfileAction;
@@ -197,6 +208,7 @@
   QAction*         m_addContactAction;
   QAction*         m_updateLocalPrefixAction;
   QAction*         m_quitAction;
+  QAction*         m_chatroomDiscoveryAction;
   QMenu*           m_trayIconMenu;
   QMenu*           m_closeMenu;
   QSystemTrayIcon* m_trayIcon;
@@ -204,14 +216,16 @@
   ChatActionList   m_closeActionList;
 
   // Dialogs
-  SettingDialog*       m_settingDialog;
-  StartChatDialog*     m_startChatDialog;
-  ProfileEditor*       m_profileEditor;
-  InvitationDialog*    m_invitationDialog;
-  ContactPanel*        m_contactPanel;
-  BrowseContactDialog* m_browseContactDialog;
-  AddContactPanel*     m_addContactPanel;
-  ChatDialogList       m_chatDialogList;
+  SettingDialog*            m_settingDialog;
+  StartChatDialog*          m_startChatDialog;
+  ProfileEditor*            m_profileEditor;
+  InvitationDialog*         m_invitationDialog;
+  ContactPanel*             m_contactPanel;
+  BrowseContactDialog*      m_browseContactDialog;
+  AddContactPanel*          m_addContactPanel;
+  ChatDialogList            m_chatDialogList;
+  DiscoveryPanel*           m_discoveryPanel;
+  ChatroomDiscoveryBackend* m_chatroomDiscoveryBackend;
 
   // Conf
   Name m_identity;
diff --git a/src/digest-tree-scene.cpp b/src/digest-tree-scene.cpp
index a68c63f..a38658d 100644
--- a/src/digest-tree-scene.cpp
+++ b/src/digest-tree-scene.cpp
@@ -154,7 +154,7 @@
     it.next();
     DisplayUserPtr p = it.value();
     if (p != DisplayUserNullPtr) {
-      prefixList << "- " + p->getPrefix();
+      prefixList << p->getPrefix();
     }
   }
   return prefixList;
diff --git a/src/discovery-panel.cpp b/src/discovery-panel.cpp
new file mode 100644
index 0000000..cbc98e0
--- /dev/null
+++ b/src/discovery-panel.cpp
@@ -0,0 +1,204 @@
+/* -*- 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 "discovery-panel.hpp"
+#include "ui_discovery-panel.h"
+
+#include <QItemSelectionModel>
+#include <QModelIndex>
+
+#ifndef Q_MOC_RUN
+#endif
+
+
+namespace chronochat {
+
+static const time::seconds REFRESH_INTERVAL(60);
+static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E};
+
+DiscoveryPanel::DiscoveryPanel(QWidget *parent)
+  : QDialog(parent)
+  , ui(new Ui::DiscoveryPanel)
+  , m_chatroomListModel(new QStringListModel)
+  , m_rosterListModel(new QStringListModel)
+{
+  ui->setupUi(this);
+  ui->ChatroomList->setModel(m_chatroomListModel);
+  ui->RosterList->setModel(m_rosterListModel);
+
+  connect(ui->ChatroomList->selectionModel(),
+          SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+          this,
+          SLOT(onSelectedChatroomChanged(const QItemSelection &, const QItemSelection &)));
+  connect(ui->RosterList->selectionModel(),
+          SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+          this,
+          SLOT(onSelectedParticipantChanged(const QItemSelection &, const QItemSelection &)));
+  connect(ui->join, SIGNAL(clicked()),
+          this, SLOT(onJoinClicked()));
+}
+
+DiscoveryPanel::~DiscoveryPanel()
+{
+  if (m_chatroomListModel)
+    delete m_chatroomListModel;
+  if (m_rosterListModel)
+    delete m_rosterListModel;
+  delete ui;
+}
+
+//private methods
+void
+DiscoveryPanel::resetPanel()
+{
+  // Clean up General tag.
+  ui->NameData->clear();
+  ui->NameSpaceData->clear();
+  ui->TrustModelData->clear();
+
+  // Clean up Roster tag.
+  m_rosterList.clear();
+  m_participant.clear();
+  m_rosterListModel->setStringList(m_rosterList);
+
+  // Clean up chatroom list.
+  m_chatroomList.clear();
+  m_chatroom.clear();
+  m_chatroomListModel->setStringList(m_chatroomList);
+
+}
+
+// public slots
+void
+DiscoveryPanel::onIdentityUpdated(const QString& identity)
+{
+  resetPanel();
+}
+
+void
+DiscoveryPanel::onChatroomListReady(const QStringList& list)
+{
+  m_chatroomList = list;
+  m_chatroomListModel->setStringList(m_chatroomList);
+}
+
+void
+DiscoveryPanel::onChatroomInfoReady(const ChatroomInfo& info)
+{
+  ui->NameData->setText(QString::fromStdString(info.getName().toUri()));
+  ui->NameSpaceData->setText(QString::fromStdString(info.getSyncPrefix().toUri()));
+
+  switch(info.getTrustModel()) {
+  case 2:
+    {
+      ui->TrustModelData->setText(QString("Hierarchical"));
+      ui->join->setEnabled(false);
+      ui->requestInvitation->setEnabled(true);
+      break;
+    }
+  case 1:
+    {
+      ui->TrustModelData->setText(QString("Web Of Trust"));
+      ui->join->setEnabled(false);
+      ui->requestInvitation->setEnabled(true);
+      break;
+    }
+  case 0:
+    {
+      ui->TrustModelData->setText(QString("None"));
+      ui->join->setEnabled(true);
+      ui->requestInvitation->setEnabled(false);
+      break;
+    }
+  default:
+    {
+      ui->TrustModelData->setText(QString("Unrecognized"));
+      ui->join->setEnabled(false);
+      ui->requestInvitation->setEnabled(false);
+    }
+  }
+
+  std::list<Name>roster = info.getParticipants();
+  m_rosterList.clear();
+  Name::Component routingHint = Name::Component(ROUTING_HINT_SEPARATOR, 2);
+  for (const auto& participant : roster) {
+    size_t i;
+    for (i = 0; i < participant.size(); ++i) {
+      if (routingHint == participant.at(i))
+        break;
+    }
+    if (i == participant.size())
+      m_rosterList << QString::fromStdString(participant.toUri());
+    else
+      m_rosterList << QString::fromStdString(participant.getSubName(i + 1).toUri());
+  }
+  m_rosterListModel->setStringList(m_rosterList);
+  ui->RosterList->setModel(m_rosterListModel);
+}
+
+// private slots
+void
+DiscoveryPanel::onSelectedChatroomChanged(const QItemSelection &selected,
+                                          const QItemSelection &deselected)
+{
+  QModelIndexList items = selected.indexes();
+  QString chatroomName = m_chatroomListModel->data(items.first(), Qt::DisplayRole).toString();
+
+  bool chatroomFound = false;
+  for (int i = 0; i < m_chatroomList.size(); i++) {
+    if (chatroomName == m_chatroomList[i]) {
+      chatroomFound = true;
+      m_chatroom = m_chatroomList[i];
+      m_participant.clear();
+      break;
+    }
+  }
+
+  if (!chatroomFound) {
+    emit warning("This should not happen: DiscoveryPanel::onSelectedChatroomChanged");
+    return;
+  }
+
+  emit waitForChatroomInfo(m_chatroom);
+}
+
+void
+DiscoveryPanel::onSelectedParticipantChanged(const QItemSelection &selected,
+                                             const QItemSelection &deselected)
+{
+  QModelIndexList items = selected.indexes();
+  QString participant = m_rosterListModel->data(items.first(), Qt::DisplayRole).toString();
+
+  bool participantFound = false;
+  for (int i = 0; i < m_rosterList.size(); i++) {
+    if (participant == m_rosterList[i]) {
+      participantFound = true;
+      m_participant = m_rosterList[i];
+      break;
+    }
+  }
+  if (!participantFound) {
+    emit warning("This should not happen: DiscoveryPanel::onSelectedParticipantChangeds #1");
+    return;
+  }
+}
+
+void
+DiscoveryPanel::onJoinClicked()
+{
+  emit startChatroom(m_chatroom, false);
+}
+
+} // namespace chronochat
+
+#if WAF
+#include "discovery-panel.moc"
+// #include "discovery-panel.cpp.moc"
+#endif
diff --git a/src/discovery-panel.hpp b/src/discovery-panel.hpp
new file mode 100644
index 0000000..1776d37
--- /dev/null
+++ b/src/discovery-panel.hpp
@@ -0,0 +1,127 @@
+/* -*- 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>
+ */
+
+#ifndef CHRONOCHAT_DISCOVERY_PANEL_HPP
+#define CHRONOCHAT_DISCOVERY_PANEL_HPP
+
+#include <QDialog>
+#include <QStringListModel>
+
+#ifndef Q_MOC_RUN
+#include "chatroom-info.hpp"
+#endif
+
+namespace Ui {
+class DiscoveryPanel;
+}
+
+namespace chronochat {
+
+class DiscoveryPanel : public QDialog
+{
+  Q_OBJECT
+
+public:
+  explicit
+  DiscoveryPanel(QWidget* parent = 0);
+
+  virtual
+  ~DiscoveryPanel();
+
+private:
+  void
+  resetPanel();
+
+  void
+  refreshPanel();
+
+signals:
+  /**
+   * @brief get chatroom info from discovery backend
+   *
+   * @param chatroomName the name of chatroom we want to get info from
+   */
+  void
+  waitForChatroomInfo(const QString& chatroomName);
+
+  /**
+   * @brief send warning if strange things happen
+   *
+   * @param msg the message that print in the warning
+   */
+  void
+  warning(const QString& msg);
+
+  /**
+   * @brief join the chatroom the user clicked
+   *
+   * This function will be called if the join button is clicked. The join button is enabled
+   * when there is no trust model for the chatroom.
+   * The user will join the chatroom he choose directly.
+   *
+   * @param chatroomName the chatroom to join
+   * @param secured if security is enabled in this chatroom
+   */
+  void
+  startChatroom(const QString& chatroomName, bool secured);
+
+public slots:
+  /**
+   * @brief reset the panel when identity is updated
+   *
+   */
+  void
+  onIdentityUpdated(const QString& identity);
+
+  /**
+   * @brief print the chatroom list on the panel
+   *
+   * @param list list of chatroom name get from discovery backend
+   */
+  void
+  onChatroomListReady(const QStringList& list);
+
+  /**
+   * @brief print the chatroom info on the panel
+   *
+   * @param info chatroom info get from discovery backend
+   */
+  void
+  onChatroomInfoReady(const ChatroomInfo& info);
+
+private slots:
+  void
+  onSelectedChatroomChanged(const QItemSelection& selected,
+                     const QItemSelection& deselected);
+
+  void
+  onSelectedParticipantChanged(const QItemSelection& selected,
+                                const QItemSelection& deselected);
+
+  void
+  onJoinClicked();
+
+private:
+  Ui::DiscoveryPanel* ui;
+
+  // Models.
+  QStringListModel* m_chatroomListModel;
+  QStringListModel* m_rosterListModel;
+
+  // Internal data structure.
+  QStringList m_chatroomList;
+  QStringList m_rosterList;
+  QString     m_chatroom;
+  QString     m_participant;
+};
+
+} // namespace chronochat
+
+#endif // CHRONOCHAT_DISCOVERY_PANEL_HPP
diff --git a/src/discovery-panel.ui b/src/discovery-panel.ui
new file mode 100644
index 0000000..def8138
--- /dev/null
+++ b/src/discovery-panel.ui
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DiscoveryPanel</class>
+ <widget class="QDialog" name="DiscoveryPanel">
+  <property name="enabled">
+   <bool>true</bool>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>480</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>ChronoChat Discovery</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout_5">
+   <item>
+    <layout class="QHBoxLayout" name="DiscoveryPanelLayout" stretch="3,0">
+     <property name="spacing">
+      <number>10</number>
+     </property>
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <item>
+      <widget class="QListView" name="ChatroomList">
+       <property name="maximumSize">
+        <size>
+         <width>180</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <property name="contextMenuPolicy">
+        <enum>Qt::PreventContextMenu</enum>
+       </property>
+       <property name="acceptDrops">
+        <bool>false</bool>
+       </property>
+       <property name="editTriggers">
+        <set>QAbstractItemView::NoEditTriggers</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTabWidget" name="ChatroomInfo">
+       <property name="currentIndex">
+        <number>0</number>
+       </property>
+       <widget class="QWidget" name="General">
+        <attribute name="title">
+         <string>General</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_7">
+         <item>
+          <layout class="QVBoxLayout" name="verticalLayout_6" stretch="2">
+           <item>
+            <layout class="QVBoxLayout" name="verticalLayout">
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout" stretch="35,100">
+               <property name="spacing">
+                <number>-1</number>
+               </property>
+               <item>
+                <widget class="QLabel" name="NameSpaceLabel">
+                 <property name="font">
+                  <font>
+                   <weight>75</weight>
+                   <bold>true</bold>
+                  </font>
+                 </property>
+                 <property name="text">
+                  <string>Sync Prefix</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QLineEdit" name="NameSpaceData">
+                 <property name="font">
+                  <font>
+                   <family>Lucida Grande</family>
+                  </font>
+                 </property>
+                 <property name="readOnly">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="35,100">
+               <item>
+                <widget class="QLabel" name="NameLabel">
+                 <property name="font">
+                  <font>
+                   <weight>75</weight>
+                   <bold>true</bold>
+                  </font>
+                 </property>
+                 <property name="text">
+                  <string>Name</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QLineEdit" name="NameData">
+                 <property name="font">
+                  <font>
+                   <family>Lucida Grande</family>
+                  </font>
+                 </property>
+                 <property name="readOnly">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="35,100">
+               <item>
+                <widget class="QLabel" name="TrustModel">
+                 <property name="font">
+                  <font>
+                   <weight>75</weight>
+                   <bold>true</bold>
+                  </font>
+                 </property>
+                 <property name="text">
+                  <string>Trust Model</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QLineEdit" name="TrustModelData">
+                 <property name="font">
+                  <font>
+                   <family>Lucida Grande</family>
+                  </font>
+                 </property>
+                 <property name="readOnly">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <layout class="QVBoxLayout" name="verticalLayout_3">
+               <item>
+                <widget class="QListView" name="RosterList">
+                  <property name="contextMenuPolicy">
+                    <enum>Qt::PreventContextMenu</enum>
+                  </property>
+                  <property name="acceptDrops">
+                    <bool>false</bool>
+                  </property>
+                  <property name="editTriggers">
+                    <set>QAbstractItemView::NoEditTriggers</set>
+                  </property>
+                </widget>
+               </item>
+               <item>
+                <layout class="QHBoxLayout" name="horizontalLayout_4">
+                 <item>
+                  <widget class="QPushButton" name="join">
+                   <property name="text">
+                    <string>Join</string>
+                   </property>
+                   <property name="autoDefault">
+                    <bool>false</bool>
+                   </property>
+                  </widget>
+                 </item>
+                 <item>
+                  <widget class="QPushButton" name="requestInvitation">
+                   <property name="text">
+                    <string>Request Invitation</string>
+                   </property>
+                   <property name="autoDefault">
+                    <bool>false</bool>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </item>
+              </layout>
+             </item>
+            </layout>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>