discovery: Add hierarchical model for chatroom invitation

Change-Id: I19e74745a5998fe075a373357df542fef317ae5f
diff --git a/src/chat-dialog-backend.cpp b/src/chat-dialog-backend.cpp
index 354b9bd..801db7d 100644
--- a/src/chat-dialog-backend.cpp
+++ b/src/chat-dialog-backend.cpp
@@ -25,7 +25,8 @@
 
 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 ndn::Name::Component ROUTING_HINT_SEPARATOR =
+  ndn::name::Component::fromEscapedString("%F0%2E");
 static const int IDENTITY_OFFSET = -3;
 
 ChatDialogBackend::ChatDialogBackend(const Name& chatroomPrefix,
@@ -446,7 +447,7 @@
     m_routableUserChatPrefix = m_userChatPrefix;
   else
     m_routableUserChatPrefix.append(m_localRoutingPrefix)
-      .append(ROUTING_HINT_SEPARATOR, 2)
+      .append(ROUTING_HINT_SEPARATOR)
       .append(m_userChatPrefix);
 
   emit chatPrefixChanged(m_routableUserChatPrefix);
diff --git a/src/chat-dialog.cpp b/src/chat-dialog.cpp
index e993294..3f6070e 100644
--- a/src/chat-dialog.cpp
+++ b/src/chat-dialog.cpp
@@ -24,7 +24,8 @@
 namespace chronochat {
 
 static const Name PRIVATE_PREFIX("/private/local");
-static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E}; // %F0.
+static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
+  ndn::name::Component::fromEscapedString("%F0%2E");
 
 ChatDialog::ChatDialog(const Name& chatroomPrefix,
                        const Name& userChatPrefix,
@@ -68,7 +69,7 @@
     routablePrefix = userChatPrefix;
   else
     routablePrefix.append(routingPrefix)
-      .append(ROUTING_HINT_SEPARATOR, 2)
+      .append(ROUTING_HINT_SEPARATOR)
       .append(userChatPrefix);
 
   updateLabels(routablePrefix);
diff --git a/src/chatroom-discovery-backend.cpp b/src/chatroom-discovery-backend.cpp
index e7ed3ea..933a2f1 100644
--- a/src/chatroom-discovery-backend.cpp
+++ b/src/chatroom-discovery-backend.cpp
@@ -21,7 +21,8 @@
 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.
+static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
+  ndn::name::Component::fromEscapedString("%F0%2E");
 // 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;
@@ -249,7 +250,7 @@
     temp = m_userDiscoveryPrefix;
   else
     temp.append(m_routingPrefix)
-      .append(ROUTING_HINT_SEPARATOR, 2)
+      .append(ROUTING_HINT_SEPARATOR)
       .append(m_userDiscoveryPrefix);
 
   Name routableIdentity = m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET);
@@ -432,7 +433,7 @@
 {
   auto chatroom = m_chatroomList.find(Name::Component(chatroomName.toStdString()));
   if (chatroom != m_chatroomList.end())
-    emit chatroomInfoReady(chatroom->second.info);
+    emit chatroomInfoReady(chatroom->second.info, chatroom->second.isParticipant);
 }
 
 void
diff --git a/src/chatroom-discovery-backend.hpp b/src/chatroom-discovery-backend.hpp
index fe5e211..986c3a1 100644
--- a/src/chatroom-discovery-backend.hpp
+++ b/src/chatroom-discovery-backend.hpp
@@ -119,9 +119,10 @@
    * @brief send chatroom info to front end
    *
    * @param info the chatroom info request by front end
+   * @param isParticipant if the user is a participant of the chatroom
    */
   void
-  chatroomInfoReady(const ChatroomInfo& info);
+  chatroomInfoReady(const ChatroomInfo& info, bool isParticipant);
 
 public slots:
 
diff --git a/src/controller-backend.cpp b/src/controller-backend.cpp
index 804716b..217228e 100644
--- a/src/controller-backend.cpp
+++ b/src/controller-backend.cpp
@@ -28,7 +28,9 @@
 using ndn::OnInterestValidationFailed;
 
 
-static const uint8_t ROUTING_PREFIX_SEPARATOR[2] = {0xF0, 0x2E};
+static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
+  ndn::name::Component::fromEscapedString("%F0%2E");
+static const int MAXIMUM_REQUEST = 3;
 
 ControllerBackend::ControllerBackend(QObject* parent)
   : QThread(parent)
@@ -92,13 +94,16 @@
   QMutexLocker locker(&m_mutex);
 
   Name invitationPrefix;
+  Name requestPrefix;
   Name routingPrefix = getInvitationRoutingPrefix();
   size_t offset = 0;
   if (!routingPrefix.isPrefixOf(m_identity)) {
-    invitationPrefix.append(routingPrefix).append(ROUTING_PREFIX_SEPARATOR, 2);
+    invitationPrefix.append(routingPrefix).append(ROUTING_HINT_SEPARATOR);
+    requestPrefix.append(routingPrefix).append(ROUTING_HINT_SEPARATOR);
     offset = routingPrefix.size() + 1;
   }
   invitationPrefix.append(m_identity).append("CHRONOCHAT-INVITATION");
+  requestPrefix.append(m_identity).append("CHRONOCHAT-INVITATION-REQUEST");
 
   const ndn::RegisteredPrefixId* invitationListenerId =
     m_face.setInterestFilter(invitationPrefix,
@@ -115,6 +120,19 @@
 
   m_invitationListenerId = invitationListenerId;
 
+  const ndn::RegisteredPrefixId* requestListenerId =
+    m_face.setInterestFilter(requestPrefix,
+                             bind(&ControllerBackend::onInvitationRequestInterest,
+                                  this, _1, _2, offset),
+                             [] (const Name& prefix, const std::string& failInfo) {});
+
+  if (m_requestListenerId != 0) {
+    m_face.unregisterPrefix(m_requestListenerId,
+                            []{},
+                            [] (const std::string& failInfo) {});
+  }
+
+  m_requestListenerId = requestListenerId;
 }
 
 ndn::Name
@@ -164,21 +182,45 @@
 }
 
 void
-ControllerBackend::onInvitationRegisterFailed(const Name& prefix, const string& failInfo)
+ControllerBackend::onInvitationRegisterFailed(const Name& prefix, const std::string& failInfo)
 {
   // _LOG_DEBUG("ControllerBackend::onInvitationRegisterFailed: " << failInfo);
 }
 
 void
+ControllerBackend::onInvitationRequestInterest(const ndn::Name& prefix,
+                                               const ndn::Interest& interest,
+                                               size_t routingPrefixOffset)
+{
+  shared_ptr<const Data> data = m_ims.find(interest);
+  if (data != nullptr) {
+    m_face.put(*data);
+    return;
+  }
+  Name interestName = interest.getName();
+  size_t i;
+  for (i = 0; i < interestName.size(); i++)
+    if (interestName.at(i) == Name::Component("CHRONOCHAT-INVITATION-REQUEST"))
+      break;
+  if (i < interestName.size()) {
+    string chatroom = interestName.at(i+1).toUri();
+    string alias = interestName.getSubName(i+2).getPrefix(-1).toUri();
+    emit invitationRequestReceived(QString::fromStdString(alias),
+                                   QString::fromStdString(chatroom),
+                                   interestName);
+  }
+}
+
+void
 ControllerBackend::onInvitationValidated(const shared_ptr<const Interest>& interest)
 {
   Invitation invitation(interest->getName());
   // Should be obtained via a method of ContactManager.
   string alias = invitation.getInviterCertificate().getPublicKeyName().getPrefix(-1).toUri();
 
-  emit invitaionValidated(QString::fromStdString(alias),
-                          QString::fromStdString(invitation.getChatroom()),
-                          interest->getName());
+  emit invitationValidated(QString::fromStdString(alias),
+                           QString::fromStdString(invitation.getChatroom()),
+                           interest->getName());
 }
 
 void
@@ -238,6 +280,36 @@
   }
 }
 
+void
+ControllerBackend::onRequestResponse(const Interest& interest, Data& data)
+{
+  size_t i;
+  Name interestName = interest.getName();
+  for (i = 0; i < interestName.size(); i++) {
+    if (interestName.at(i) == Name::Component("CHRONOCHAT-INVITATION-REQUEST"))
+      break;
+  }
+  Name::Component chatroomName = interestName.at(i+1);
+  Block contentBlock = data.getContent();
+  int res = ndn::readNonNegativeInteger(contentBlock);
+  // if data is true,
+  if (res == 1)
+    emit startChatroom(QString::fromStdString(chatroomName.toUri()), false);
+  else
+    emit invitationRequestResult("You are rejected to enter chatroom: " + chatroomName.toUri());
+}
+
+void
+ControllerBackend::onRequestTimeout(const Interest& interest, int& resendTimes)
+{
+  if (resendTimes < MAXIMUM_REQUEST)
+    m_face.expressInterest(interest,
+                           bind(&ControllerBackend::onRequestResponse, this, _1, _2),
+                           bind(&ControllerBackend::onRequestTimeout, this, _1, resendTimes + 1));
+  else
+    emit invitationRequestResult("Invitation request times out.");
+}
+
 // public slots:
 void
 ControllerBackend::shutdown()
@@ -321,7 +393,7 @@
   else {
     Name wrappedName;
     wrappedName.append(invitationRoutingPrefix)
-      .append(ROUTING_PREFIX_SEPARATOR, 2)
+      .append(ROUTING_HINT_SEPARATOR)
       .append(response->getName());
 
     // _LOG_DEBUG("onInvitationResponded: prepare reply " << wrappedName);
@@ -339,6 +411,41 @@
 }
 
 void
+ControllerBackend::onInvitationRequestResponded(const ndn::Name& invitationResponseName,
+                                                bool accepted)
+{
+  shared_ptr<Data> response = make_shared<Data>(invitationResponseName);
+  if (accepted)
+    response->setContent(ndn::nonNegativeIntegerBlock(tlv::Content, 1));
+  else
+    response->setContent(ndn::nonNegativeIntegerBlock(tlv::Content, 0));
+
+  m_keyChain.signByIdentity(*response, m_identity);
+  m_ims.insert(*response);
+  m_face.put(*response);
+}
+
+void
+ControllerBackend::onSendInvitationRequest(const QString& chatroomName, const QString& prefix)
+{
+  if (prefix.length() == 0)
+    return;
+  Name interestName = getInvitationRoutingPrefix();
+  interestName.append(ROUTING_HINT_SEPARATOR).append(prefix.toStdString());
+  interestName.append("CHRONOCHAT-INVITATION-REQUEST");
+  interestName.append(chatroomName.toStdString());
+  interestName.append(m_identity);
+  interestName.appendTimestamp();
+  Interest interest(interestName);
+  interest.setInterestLifetime(time::milliseconds(10000));
+  interest.setMustBeFresh(true);
+  interest.getNonce();
+  m_face.expressInterest(interest,
+                         bind(&ControllerBackend::onRequestResponse, this, _1, _2),
+                         bind(&ControllerBackend::onRequestTimeout, this, _1, 0));
+}
+
+void
 ControllerBackend::onContactIdListReady(const QStringList& list)
 {
   ContactList contactList;
diff --git a/src/controller-backend.hpp b/src/controller-backend.hpp
index be6c499..a449aa4 100644
--- a/src/controller-backend.hpp
+++ b/src/controller-backend.hpp
@@ -10,6 +10,7 @@
 #ifndef CHRONOCHAT_CONTROLLER_BACKEND_HPP
 #define CHRONOCHAT_CONTROLLER_BACKEND_HPP
 
+#include <QString>
 #include <QThread>
 #include <QStringList>
 #include <QMutex>
@@ -20,6 +21,7 @@
 #include "invitation.hpp"
 #include "validator-invitation.hpp"
 #include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/util/in-memory-storage-persistent.hpp>
 #endif
 
 namespace chronochat {
@@ -61,6 +63,10 @@
                        size_t routingPrefixOffset);
 
   void
+  onInvitationRequestInterest(const ndn::Name& prefix, const ndn::Interest& interest,
+                              size_t routingPrefixOffset);
+
+  void
   onInvitationRegisterFailed(const Name& prefix, const std::string& failInfo);
 
   void
@@ -79,6 +85,12 @@
   void
   updateLocalPrefix(const Name& localPrefix);
 
+  void
+  onRequestResponse(const Interest& interest, Data& data);
+
+  void
+  onRequestTimeout(const Interest& interest, int& resendTimes);
+
 signals:
   void
   identityUpdated(const QString& identity);
@@ -87,11 +99,20 @@
   localPrefixUpdated(const QString& localPrefix);
 
   void
-  invitaionValidated(QString alias, QString chatroom, ndn::Name invitationINterest);
+  invitationValidated(QString alias, QString chatroom, ndn::Name invitationINterest);
+
+  void
+  invitationRequestReceived(QString alias, QString chatroom, ndn::Name invitationRequestInterest);
 
   void
   startChatroomOnInvitation(chronochat::Invitation invitation, bool secured);
 
+  void
+  startChatroom(const QString& chatroomName, bool secured);
+
+  void
+  invitationRequestResult(const std::string& msg);
+
 public slots:
   void
   shutdown();
@@ -111,6 +132,12 @@
   void
   onInvitationResponded(const ndn::Name& invitationName, bool accepted);
 
+  void
+  onInvitationRequestResponded(const ndn::Name& invitationName, bool accepted);
+
+  void
+  onSendInvitationRequest(const QString& chatroomName, const QString& prefix);
+
 private slots:
   void
   onContactIdListReady(const QStringList& list);
@@ -131,11 +158,14 @@
 
   // RegisteredPrefixId
   const ndn::RegisteredPrefixId* m_invitationListenerId;
+  const ndn::RegisteredPrefixId* m_requestListenerId;
 
   // ChatRoomList
   QStringList m_chatDialogList;
 
   QMutex m_mutex;
+
+  ndn::util::InMemoryStoragePersistent m_ims;
 };
 
 } // namespace chronochat
diff --git a/src/controller.cpp b/src/controller.cpp
index dce0b03..b660597 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -48,6 +48,7 @@
   , m_startChatDialog(new StartChatDialog(this))
   , m_profileEditor(new ProfileEditor(this))
   , m_invitationDialog(new InvitationDialog(this))
+  , m_invitationRequestDialog(new InvitationRequestDialog(this))
   , m_contactPanel(new ContactPanel(this))
   , m_browseContactDialog(new BrowseContactDialog(this))
   , m_addContactPanel(new AddContactPanel(this))
@@ -98,6 +99,10 @@
   connect(m_invitationDialog, SIGNAL(invitationResponded(const ndn::Name&, bool)),
           &m_backend, SLOT(onInvitationResponded(const ndn::Name&, bool)));
 
+  // Connection to InvitationRequestDialog
+  connect(m_invitationRequestDialog, SIGNAL(invitationRequestResponded(const ndn::Name&, bool)),
+          &m_backend, SLOT(onInvitationRequestResponded(const ndn::Name&, bool)));
+
   // Connection to AddContactPanel
   connect(m_addContactPanel, SIGNAL(fetchInfo(const QString&)),
           m_backend.getContactManager(), SLOT(onFetchContactInfo(const QString&)));
@@ -170,8 +175,15 @@
           m_settingDialog, SLOT(onLocalPrefixUpdated(const QString&)));
 
   // on invitation validated:
-  connect(&m_backend, SIGNAL(invitaionValidated(QString, QString, ndn::Name)),
+  connect(&m_backend, SIGNAL(invitationValidated(QString, QString, ndn::Name)),
           m_invitationDialog, SLOT(onInvitationReceived(QString, QString, ndn::Name)));
+  connect(&m_backend, SIGNAL(startChatroom(const QString&, bool)),
+          this, SLOT(onStartChatroom(const QString&, bool)));
+
+  // on invitation request received
+  connect(&m_backend, SIGNAL(invitationRequestReceived(QString, QString, ndn::Name)),
+          m_invitationRequestDialog, SLOT(onInvitationRequestReceived(QString, QString,
+                                                                      ndn::Name)));
 
   // on invitation accepted:
   connect(&m_backend, SIGNAL(startChatroomOnInvitation(chronochat::Invitation, bool)),
@@ -211,10 +223,14 @@
           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_chatroomDiscoveryBackend, SIGNAL(chatroomInfoReady(const ChatroomInfo&, bool)),
+          m_discoveryPanel, SLOT(onChatroomInfoReady(const ChatroomInfo&, bool)));
   connect(m_discoveryPanel, SIGNAL(startChatroom(const QString&, bool)),
           this, SLOT(onStartChatroom(const QString&, bool)));
+  connect(m_discoveryPanel, SIGNAL(sendInvitationRequest(const QString&, const QString&)),
+          &m_backend, SLOT(onSendInvitationRequest(const QString&, const QString&)));
+  connect(&m_backend, SIGNAL(invitationRequestResult(const std::string&)),
+          m_discoveryPanel, SLOT(onInvitationRequestResult(const std::string&)));
 
   m_chatroomDiscoveryBackend->start();
 
diff --git a/src/controller.hpp b/src/controller.hpp
index 2b80932..fdce4cd 100644
--- a/src/controller.hpp
+++ b/src/controller.hpp
@@ -20,6 +20,7 @@
 #include "start-chat-dialog.hpp"
 #include "profile-editor.hpp"
 #include "invitation-dialog.hpp"
+#include "invitation-request-dialog.hpp"
 #include "contact-panel.hpp"
 #include "browse-contact-dialog.hpp"
 #include "add-contact-panel.hpp"
@@ -220,6 +221,7 @@
   StartChatDialog*          m_startChatDialog;
   ProfileEditor*            m_profileEditor;
   InvitationDialog*         m_invitationDialog;
+  InvitationRequestDialog*  m_invitationRequestDialog;
   ContactPanel*             m_contactPanel;
   BrowseContactDialog*      m_browseContactDialog;
   AddContactPanel*          m_addContactPanel;
diff --git a/src/discovery-panel.cpp b/src/discovery-panel.cpp
index cbc98e0..20e36ab 100644
--- a/src/discovery-panel.cpp
+++ b/src/discovery-panel.cpp
@@ -13,6 +13,7 @@
 
 #include <QItemSelectionModel>
 #include <QModelIndex>
+#include <QMessageBox>
 
 #ifndef Q_MOC_RUN
 #endif
@@ -21,7 +22,8 @@
 namespace chronochat {
 
 static const time::seconds REFRESH_INTERVAL(60);
-static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E};
+static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
+  ndn::name::Component::fromEscapedString("%F0%2E");
 
 DiscoveryPanel::DiscoveryPanel(QWidget *parent)
   : QDialog(parent)
@@ -43,6 +45,12 @@
           SLOT(onSelectedParticipantChanged(const QItemSelection &, const QItemSelection &)));
   connect(ui->join, SIGNAL(clicked()),
           this, SLOT(onJoinClicked()));
+  connect(ui->requestInvitation, SIGNAL(clicked()),
+          this, SLOT(onRequestInvitation()));
+
+  ui->join->setEnabled(false);
+  ui->requestInvitation->setEnabled(false);
+  ui->InChatroomWarning->clear();
 }
 
 DiscoveryPanel::~DiscoveryPanel()
@@ -73,6 +81,9 @@
   m_chatroom.clear();
   m_chatroomListModel->setStringList(m_chatroomList);
 
+  ui->join->setEnabled(false);
+  ui->requestInvitation->setEnabled(false);
+  ui->InChatroomWarning->clear();
 }
 
 // public slots
@@ -90,7 +101,7 @@
 }
 
 void
-DiscoveryPanel::onChatroomInfoReady(const ChatroomInfo& info)
+DiscoveryPanel::onChatroomInfoReady(const ChatroomInfo& info, bool isParticipant)
 {
   ui->NameData->setText(QString::fromStdString(info.getName().toUri()));
   ui->NameSpaceData->setText(QString::fromStdString(info.getSyncPrefix().toUri()));
@@ -124,10 +135,16 @@
       ui->requestInvitation->setEnabled(false);
     }
   }
+  ui->InChatroomWarning->clear();
+  if (isParticipant) {
+    ui->join->setEnabled(false);
+    ui->requestInvitation->setEnabled(false);
+    ui->InChatroomWarning->setText(QString("You are already in this chatroom"));
+  }
 
   std::list<Name>roster = info.getParticipants();
   m_rosterList.clear();
-  Name::Component routingHint = Name::Component(ROUTING_HINT_SEPARATOR, 2);
+  Name::Component routingHint = Name::Component(ROUTING_HINT_SEPARATOR);
   for (const auto& participant : roster) {
     size_t i;
     for (i = 0; i < participant.size(); ++i) {
@@ -196,6 +213,19 @@
   emit startChatroom(m_chatroom, false);
 }
 
+void
+DiscoveryPanel::onRequestInvitation()
+{
+  emit sendInvitationRequest(m_chatroom, m_participant);
+}
+
+void
+DiscoveryPanel::onInvitationRequestResult(const std::string& message)
+{
+  QMessageBox::information(this, tr("Chatroom Discovery"),
+                           tr(message.c_str()));
+}
+
 } // namespace chronochat
 
 #if WAF
diff --git a/src/discovery-panel.hpp b/src/discovery-panel.hpp
index 1776d37..f15906b 100644
--- a/src/discovery-panel.hpp
+++ b/src/discovery-panel.hpp
@@ -72,6 +72,17 @@
   void
   startChatroom(const QString& chatroomName, bool secured);
 
+  /**
+   * @brief send request for invitation to a chatroom
+   *
+   * This function will be called if request invitation button is clicked.
+   *
+   * @param chatroomName the chatroom to join
+   * @param identity the person that the user send the request to
+   */
+  void
+  sendInvitationRequest(const QString& chatroomName, const QString& identity);
+
 public slots:
   /**
    * @brief reset the panel when identity is updated
@@ -92,9 +103,10 @@
    * @brief print the chatroom info on the panel
    *
    * @param info chatroom info get from discovery backend
+   * @param isParticipant if the user is a participant of the chatroom
    */
   void
-  onChatroomInfoReady(const ChatroomInfo& info);
+  onChatroomInfoReady(const ChatroomInfo& info, bool isParticipant);
 
 private slots:
   void
@@ -108,6 +120,12 @@
   void
   onJoinClicked();
 
+  void
+  onRequestInvitation();
+
+  void
+  onInvitationRequestResult(const std::string& message);
+
 private:
   Ui::DiscoveryPanel* ui;
 
diff --git a/src/discovery-panel.ui b/src/discovery-panel.ui
index def8138..b7307d6 100644
--- a/src/discovery-panel.ui
+++ b/src/discovery-panel.ui
@@ -59,139 +59,145 @@
         <attribute name="title">
          <string>General</string>
         </attribute>
-        <layout class="QVBoxLayout" name="verticalLayout_7">
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
          <item>
-          <layout class="QVBoxLayout" name="verticalLayout_6" stretch="2">
+          <layout class="QVBoxLayout" name="verticalLayout_2">
            <item>
-            <layout class="QVBoxLayout" name="verticalLayout">
+            <layout class="QHBoxLayout" name="horizontalLayout" stretch="35,100">
+             <property name="spacing">
+              <number>-1</number>
+             </property>
              <item>
-              <layout class="QHBoxLayout" name="horizontalLayout" stretch="35,100">
-               <property name="spacing">
-                <number>-1</number>
+              <widget class="QLabel" name="NameSpaceLabel">
+               <property name="font">
+                <font>
+                 <weight>75</weight>
+                 <bold>true</bold>
+                </font>
                </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>
+               <property name="text">
+                <string>Sync Prefix</string>
+               </property>
+              </widget>
              </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>
+              <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>
-              <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>
+              <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>
-              <layout class="QVBoxLayout" name="verticalLayout_3">
+              <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" stretch="0,0,0">
+             <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>
+              <widget class="QLabel" name="InChatroomWarning">
+               <property name="enabled">
+                <bool>true</bool>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_4">
                <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 class="QPushButton" name="join">
+                 <property name="text">
+                  <string>Join</string>
+                 </property>
+                 <property name="autoDefault">
+                  <bool>false</bool>
+                 </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>
+                <widget class="QPushButton" name="requestInvitation">
+                 <property name="text">
+                  <string>Request Invitation</string>
+                 </property>
+                 <property name="autoDefault">
+                  <bool>false</bool>
+                 </property>
+                </widget>
                </item>
               </layout>
              </item>
diff --git a/src/invitation-request-dialog.cpp b/src/invitation-request-dialog.cpp
new file mode 100644
index 0000000..47b63f1
--- /dev/null
+++ b/src/invitation-request-dialog.cpp
@@ -0,0 +1,71 @@
+/* -*- 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 "invitation-request-dialog.hpp"
+#include "ui_invitation-request-dialog.h"
+
+namespace chronochat {
+
+InvitationRequestDialog::InvitationRequestDialog(QWidget* parent)
+  : QDialog(parent)
+  , ui(new Ui::InvitationRequestDialog)
+{
+    ui->setupUi(this);
+
+    connect(ui->okButton, SIGNAL(clicked()),
+            this, SLOT(onOkClicked()));
+    connect(ui->cancelButton, SIGNAL(clicked()),
+            this, SLOT(onCancelClicked()));
+}
+
+InvitationRequestDialog::~InvitationRequestDialog()
+{
+    delete ui;
+}
+
+void
+InvitationRequestDialog::onInvitationRequestReceived(QString alias, QString chatroom,
+                                                     Name interestName)
+{
+  m_invitationInterest = interestName;
+
+  QString msg = QString("%1\n request your invitation to chatroom\n %2 ").arg(alias).arg(chatroom);
+  ui->msgLabel->setText(msg);
+
+  show();
+  raise();
+}
+
+void
+InvitationRequestDialog::onOkClicked()
+{
+  emit invitationRequestResponded(m_invitationInterest, true);
+  this->close();
+
+  ui->msgLabel->clear();
+  m_invitationInterest.clear();
+}
+
+void
+InvitationRequestDialog::onCancelClicked()
+{
+  emit invitationRequestResponded(m_invitationInterest, false);
+  this->close();
+
+  ui->msgLabel->clear();
+  m_invitationInterest.clear();
+}
+
+} // namespace chronochat
+
+#if WAF
+#include "invitation-request-dialog.moc"
+// #include "invitation-request-dialog.cpp.moc"
+#endif
diff --git a/src/invitation-request-dialog.hpp b/src/invitation-request-dialog.hpp
new file mode 100644
index 0000000..bd2bd1d
--- /dev/null
+++ b/src/invitation-request-dialog.hpp
@@ -0,0 +1,59 @@
+/* -*- 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 CHRONOS_INVITATION_REQUEST_DIALOG_HPP
+#define CHRONOS_INVITATION_REQUEST_DIALOG_HPP
+
+#include <QDialog>
+
+#ifndef Q_MOC_RUN
+#include "common.hpp"
+#endif
+
+namespace Ui {
+class InvitationRequestDialog;
+}
+
+namespace chronochat{
+
+class InvitationRequestDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+  explicit
+  InvitationRequestDialog(QWidget* parent = 0);
+
+  ~InvitationRequestDialog();
+
+signals:
+  void
+  invitationRequestResponded(const ndn::Name& invitationName, bool accepted);
+
+public slots:
+  void
+  onInvitationRequestReceived(QString alias, QString chatroom, ndn::Name invitationInterest);
+
+private slots:
+  void
+  onOkClicked();
+
+  void
+  onCancelClicked();
+
+
+private:
+  Ui::InvitationRequestDialog* ui;
+  ndn::Name m_invitationInterest;
+};
+
+} // namespace chronochat
+
+#endif // CHRONOS_INVITATION_REQUEST_DIALOG_HPP
diff --git a/src/invitation-request-dialog.ui b/src/invitation-request-dialog.ui
new file mode 100644
index 0000000..f2051ee
--- /dev/null
+++ b/src/invitation-request-dialog.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InvitationRequestDialog</class>
+ <widget class="QDialog" name="InvitationRequestDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>414</width>
+    <height>150</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout" stretch="5,1">
+     <property name="leftMargin">
+      <number>10</number>
+     </property>
+     <property name="topMargin">
+      <number>10</number>
+     </property>
+     <property name="rightMargin">
+      <number>10</number>
+     </property>
+     <item>
+      <widget class="QLabel" name="msgLabel">
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="buttonLayout" stretch="1,1">
+       <property name="spacing">
+        <number>-1</number>
+       </property>
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QPushButton" name="cancelButton">
+         <property name="text">
+          <string>Reject</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="okButton">
+         <property name="text">
+          <string>Accept</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>