logic: allow manipulating multiple nodes in single logic

Change-Id: Iaa2a2b08e891d41c9aa71c13ffc531bef406b6d8
diff --git a/src/logic.cpp b/src/logic.cpp
index 4c3fc55..08ed60e 100644
--- a/src/logic.cpp
+++ b/src/logic.cpp
@@ -50,6 +50,7 @@
 #endif
 
 const ndn::Name Logic::DEFAULT_NAME;
+const ndn::Name Logic::EMPTY_NAME;
 const ndn::shared_ptr<ndn::Validator> Logic::DEFAULT_VALIDATOR;
 const time::steady_clock::Duration Logic::DEFAULT_RESET_TIMER = time::seconds(0);
 const time::steady_clock::Duration Logic::DEFAULT_CANCEL_RESET_TIMER = time::milliseconds(500);
@@ -62,9 +63,9 @@
 
 Logic::Logic(ndn::Face& face,
              const Name& syncPrefix,
-             const Name& userPrefix,
+             const Name& defaultUserPrefix,
              const UpdateCallback& onUpdate,
-             const Name& signingId,
+             const Name& defaultSigningId,
              ndn::shared_ptr<ndn::Validator> validator,
              const time::steady_clock::Duration& resetTimer,
              const time::steady_clock::Duration& cancelResetTimer,
@@ -73,7 +74,7 @@
              const time::milliseconds& syncReplyFreshness)
   : m_face(face)
   , m_syncPrefix(syncPrefix)
-  , m_userPrefix(userPrefix)
+  , m_defaultUserPrefix(defaultUserPrefix)
   , m_interestTable(m_face.getIoService())
   , m_outstandingInterestId(0)
   , m_isInReset(false)
@@ -88,7 +89,7 @@
   , m_resetInterestLifetime(resetInterestLifetime)
   , m_syncInterestLifetime(syncInterestLifetime)
   , m_syncReplyFreshness(syncReplyFreshness)
-  , m_signingId(signingId)
+  , m_defaultSigningId(defaultSigningId)
   , m_validator(validator)
 {
 #ifdef _DEBUG
@@ -97,6 +98,9 @@
 
   _LOG_DEBUG_ID(">> Logic::Logic");
 
+  addUserNode(m_defaultUserPrefix, m_defaultSigningId);
+
+
   m_syncReset = m_syncPrefix;
   m_syncReset.append("reset");
 
@@ -106,7 +110,6 @@
                              bind(&Logic::onSyncInterest, this, _1, _2),
                              bind(&Logic::onSyncRegisterFailed, this, _1, _2));
 
-  setUserPrefix(m_userPrefix);
 
   _LOG_DEBUG_ID("<< Logic::Logic");
 }
@@ -144,56 +147,132 @@
 }
 
 void
-Logic::setUserPrefix(const Name& userPrefix)
+Logic::setDefaultUserPrefix(const Name& defaultUserPrefix)
 {
-  m_userPrefix = userPrefix;
-
-  m_sessionName = m_userPrefix;
-  m_sessionName.appendNumber(ndn::time::toUnixTimestamp(ndn::time::system_clock::now()).count());
-
-  m_seqNo = 0;
-
-  reset();
+  if (defaultUserPrefix != EMPTY_NAME) {
+    if (m_nodeList.find(defaultUserPrefix) != m_nodeList.end()) {
+      m_defaultUserPrefix = defaultUserPrefix;
+      m_defaultSigningId = m_nodeList[defaultUserPrefix].signingId;
+    }
+  }
 }
 
 void
-Logic::updateSeqNo(const SeqNo& seqNo)
+Logic::addUserNode(const Name& userPrefix, const Name& signingId)
 {
-  _LOG_DEBUG_ID(">> Logic::updateSeqNo");
-  _LOG_DEBUG_ID("seqNo: " << seqNo << " m_seqNo: " << m_seqNo);
-  if (seqNo < m_seqNo || seqNo == 0)
+  if (userPrefix == EMPTY_NAME)
     return;
+  if (m_defaultUserPrefix == EMPTY_NAME) {
+    m_defaultUserPrefix = userPrefix;
+    m_defaultSigningId = signingId;
+  }
+  if (m_nodeList.find(userPrefix) == m_nodeList.end()) {
+    m_nodeList[userPrefix].userPrefix = userPrefix;
+    m_nodeList[userPrefix].signingId = signingId;
+    Name sessionName = userPrefix;
+    sessionName.appendNumber(ndn::time::toUnixTimestamp(ndn::time::system_clock::now()).count());
+    m_nodeList[userPrefix].sessionName = sessionName;
+    m_nodeList[userPrefix].seqNo = 0;
+    reset();
+  }
+}
 
-  m_seqNo = seqNo;
-
-  _LOG_DEBUG_ID("updateSeqNo: m_seqNo " << m_seqNo);
-
-  if (!m_isInReset) {
-    _LOG_DEBUG_ID("updateSeqNo: not in Reset ");
-    ndn::ConstBufferPtr previousRoot = m_state.getRootDigest();
-    {
-      using namespace CryptoPP;
-
-      std::string hash;
-      StringSource(previousRoot->buf(), previousRoot->size(), true,
-                   new HexEncoder(new StringSink(hash), false));
-      _LOG_DEBUG_ID("Hash: " << hash);
+void
+Logic::removeUserNode(const Name& userPrefix)
+{
+  auto userNode = m_nodeList.find(userPrefix);
+  if (userNode != m_nodeList.end()) {
+    m_nodeList.erase(userNode);
+    if (m_defaultUserPrefix == userPrefix) {
+      if (!m_nodeList.empty()) {
+        m_defaultUserPrefix = m_nodeList.begin()->second.userPrefix;
+        m_defaultSigningId = m_nodeList.begin()->second.signingId;
+      }
+      else {
+        m_defaultUserPrefix = EMPTY_NAME;
+        m_defaultSigningId = DEFAULT_NAME;
+      }
     }
+    reset();
+  }
+}
 
-    bool isInserted = false;
-    bool isUpdated = false;
-    SeqNo oldSeq;
-    boost::tie(isInserted, isUpdated, oldSeq) = m_state.update(m_sessionName, seqNo);
+const Name&
+Logic::getSessionName(Name prefix)
+{
+  if (prefix == EMPTY_NAME)
+    prefix = m_defaultUserPrefix;
+  auto node = m_nodeList.find(prefix);
+  if (node != m_nodeList.end())
+    return node->second.sessionName;
+  else
+    throw Error("Refer to non-existent node:" + prefix.toUri());
+}
 
-    _LOG_DEBUG_ID("Insert: " << std::boolalpha << isInserted);
-    _LOG_DEBUG_ID("Updated: " << std::boolalpha << isUpdated);
-    if (isInserted || isUpdated) {
-      DiffStatePtr commit = make_shared<DiffState>();
-      commit->update(m_sessionName, seqNo);
-      commit->setRootDigest(m_state.getRootDigest());
-      insertToDiffLog(commit, previousRoot);
+const SeqNo&
+Logic::getSeqNo(Name prefix)
+{
+  if (prefix == EMPTY_NAME)
+    prefix = m_defaultUserPrefix;
+  auto node = m_nodeList.find(prefix);
+  if (node != m_nodeList.end())
+    return node->second.seqNo;
+  else
+    throw Logic::Error("Refer to non-existent node:" + prefix.toUri());
 
-      satisfyPendingSyncInterests(commit);
+}
+
+void
+Logic::updateSeqNo(const SeqNo& seqNo, const Name &updatePrefix)
+{
+  Name prefix;
+  if (updatePrefix == EMPTY_NAME) {
+    if (m_defaultUserPrefix == EMPTY_NAME)
+      return;
+    prefix = m_defaultUserPrefix;
+  }
+  else
+    prefix = updatePrefix;
+
+  auto it = m_nodeList.find(prefix);
+  if (it != m_nodeList.end()) {
+    NodeInfo& node = it->second;
+    _LOG_DEBUG_ID(">> Logic::updateSeqNo");
+    _LOG_DEBUG_ID("seqNo: " << seqNo << " m_seqNo: " << node.seqNo);
+    if (seqNo < node.seqNo || seqNo == 0)
+      return;
+
+    node.seqNo = seqNo;
+    _LOG_DEBUG_ID("updateSeqNo: m_seqNo " << node.seqNo);
+
+    if (!m_isInReset) {
+      _LOG_DEBUG_ID("updateSeqNo: not in Reset ");
+      ndn::ConstBufferPtr previousRoot = m_state.getRootDigest();
+      {
+        using namespace CryptoPP;
+
+        std::string hash;
+        StringSource(previousRoot->buf(), previousRoot->size(), true,
+                     new HexEncoder(new StringSink(hash), false));
+        _LOG_DEBUG_ID("Hash: " << hash);
+      }
+
+      bool isInserted = false;
+      bool isUpdated = false;
+      SeqNo oldSeq;
+      boost::tie(isInserted, isUpdated, oldSeq) = m_state.update(node.sessionName,
+                                                                 node.seqNo);
+
+      _LOG_DEBUG_ID("Insert: " << std::boolalpha << isInserted);
+      _LOG_DEBUG_ID("Updated: " << std::boolalpha << isUpdated);
+      if (isInserted || isUpdated) {
+        DiffStatePtr commit = make_shared<DiffState>();
+        commit->update(node.sessionName, node.seqNo);
+        commit->setRootDigest(m_state.getRootDigest());
+        insertToDiffLog(commit, previousRoot);
+
+        satisfyPendingSyncInterests(prefix, commit);
+      }
     }
   }
 }
@@ -341,7 +420,7 @@
   // If the digest of incoming interest is an "empty" digest
   if (digest == EMPTY_DIGEST) {
     _LOG_DEBUG_ID("Poor guy, he knows nothing");
-    sendSyncData(name, m_state);
+    sendSyncData(m_defaultUserPrefix, name, m_state);
     return;
   }
 
@@ -349,7 +428,7 @@
   // If the digest of incoming interest can be found from the log
   if (stateIter != m_log.end()) {
     _LOG_DEBUG_ID("It is ok, you are so close");
-    sendSyncData(name, *(*stateIter)->diff());
+    sendSyncData(m_defaultUserPrefix, name, *(*stateIter)->diff());
     return;
   }
 
@@ -370,7 +449,7 @@
     // OK, nobody is helping us, just tell the truth.
     _LOG_DEBUG_ID("OK, nobody is helping us, just tell the truth");
     m_interestTable.erase(digest);
-    sendSyncData(name, m_state);
+    sendSyncData(m_defaultUserPrefix, name, m_state);
   }
 
   _LOG_DEBUG_ID("<< Logic::processSyncInterest");
@@ -389,7 +468,6 @@
                        const Block& syncReplyBlock)
 {
   _LOG_DEBUG_ID(">> Logic::processSyncData");
-
   DiffStatePtr commit = make_shared<DiffState>();
   ndn::ConstBufferPtr previousRoot = m_state.getRootDigest();
 
@@ -411,7 +489,6 @@
         bool isUpdated = false;
         SeqNo oldSeq;
         boost::tie(isInserted, isUpdated, oldSeq) = m_state.update(info, seq);
-
         if (isInserted || isUpdated) {
           commit->update(info, seq);
 
@@ -451,7 +528,7 @@
 }
 
 void
-Logic::satisfyPendingSyncInterests(ConstDiffStatePtr commit)
+Logic::satisfyPendingSyncInterests(const Name& updatedPrefix, ConstDiffStatePtr commit)
 {
   _LOG_DEBUG_ID(">> Logic::satisfyPendingSyncInterests");
   try {
@@ -459,11 +536,10 @@
     for (InterestTable::const_iterator it = m_interestTable.begin();
          it != m_interestTable.end(); it++) {
       ConstUnsatisfiedInterestPtr request = *it;
-
       if (request->isUnknown)
-        sendSyncData(request->interest->getName(), m_state);
+        sendSyncData(updatedPrefix, request->interest->getName(), m_state);
       else
-        sendSyncData(request->interest->getName(), *commit);
+        sendSyncData(updatedPrefix, request->interest->getName(), *commit);
     }
     m_interestTable.clear();
   }
@@ -548,17 +624,18 @@
 }
 
 void
-Logic::sendSyncData(const Name& name, const State& state)
+Logic::sendSyncData(const Name& nodePrefix, const Name& name, const State& state)
 {
   _LOG_DEBUG_ID(">> Logic::sendSyncData");
   shared_ptr<Data> syncReply = make_shared<Data>(name);
   syncReply->setContent(state.wireEncode());
   syncReply->setFreshnessPeriod(m_syncReplyFreshness);
-
-  if (m_signingId.empty())
+  if (m_nodeList.find(nodePrefix) == m_nodeList.end())
+    return;
+  if (m_nodeList[nodePrefix].signingId.empty())
     m_keyChain.sign(*syncReply);
   else
-    m_keyChain.signByIdentity(*syncReply, m_signingId);
+    m_keyChain.signByIdentity(*syncReply, m_nodeList[nodePrefix].signingId);
 
   m_face.put(*syncReply);
 
@@ -589,7 +666,9 @@
     return;
 
   m_isInReset = false;
-  updateSeqNo(m_seqNo);
+  for (const auto& node : m_nodeList) {
+    updateSeqNo(node.second.seqNo, node.first);
+  }
   _LOG_DEBUG_ID("<< Logic::cancelReset");
 }
 
diff --git a/src/logic.hpp b/src/logic.hpp
index acca168..e8ea4f6 100644
--- a/src/logic.hpp
+++ b/src/logic.hpp
@@ -27,7 +27,7 @@
 
 #include "boost-header.h"
 #include <memory>
-#include <map>
+#include <unordered_map>
 
 #include <ndn-cxx/face.hpp>
 #include <ndn-cxx/util/scheduler.hpp>
@@ -48,6 +48,14 @@
  * Instances of this class is usually used as elements of some containers
  * such as std::vector, thus it is copyable.
  */
+class NodeInfo {
+public:
+  Name userPrefix;
+  Name signingId;
+  Name sessionName;
+  SeqNo seqNo;
+};
+
 class MissingDataInfo
 {
 public:
@@ -73,6 +81,17 @@
 class Logic : noncopyable
 {
 public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
   static const time::steady_clock::Duration DEFAULT_RESET_TIMER;
   static const time::steady_clock::Duration DEFAULT_CANCEL_RESET_TIMER;
   static const time::milliseconds DEFAULT_RESET_INTEREST_LIFETIME;
@@ -83,8 +102,9 @@
    * @brief Constructor
    *
    * @param syncPrefix The prefix of the sync group
-   * @param userPrefix The prefix of the user who owns the session
+   * @param defaultUserPrefix The prefix of the first user added to this session
    * @param onUpdate The callback function to handle state updates
+   * @param defaultSigningId The signing Id of the default user
    * @param validator The validator for packet validation
    * @param resetTimer The timer to periodically send Reset Interest
    * @param syncReplyFreshness The FreshnessPeriod of sync reply
@@ -94,9 +114,9 @@
    */
   Logic(ndn::Face& face,
         const Name& syncPrefix,
-        const Name& userPrefix,
+        const Name& defaultUserPrefix,
         const UpdateCallback& onUpdate,
-        const Name& signingId = DEFAULT_NAME,
+        const Name& defaultSigningId = DEFAULT_NAME,
         ndn::shared_ptr<ndn::Validator> validator = DEFAULT_VALIDATOR,
         const time::steady_clock::Duration& resetTimer = DEFAULT_RESET_TIMER,
         const time::steady_clock::Duration& cancelResetTimer = DEFAULT_CANCEL_RESET_TIMER,
@@ -113,36 +133,67 @@
   /**
    * @brief Set user prefix
    *
-   * This method will also change the session name and trigger reset.
+   * This method will also change the default user and signing Id of that user.
    *
-   * @param userPrefix The prefix of user.
+   * @param defaultUserPrefix The prefix of user.
    */
   void
-  setUserPrefix(const Name& userPrefix);
+  setDefaultUserPrefix(const Name& defaultUserPrefix);
 
-  /// @brief Get the name of the local session.
+  /// @brief Get the name of default user.
   const Name&
-  getSessionName() const
+  getDefaultUserPrefix() const
   {
-    return m_sessionName;
+    return m_defaultUserPrefix;
   }
 
-  /// @brief Get current seqNo of the local session.
+  /**
+   * @brief Add user node into the local session.
+   *
+   * This method also reset after adding
+   *
+   * @param userPrefix prefix of the added node
+   * @param signingId signing Id of the added node
+   */
+  void
+  addUserNode(const Name& userPrefix, const Name& signingId = DEFAULT_NAME);
+
+  /// @brief remove the node from the local session
+  void
+  removeUserNode(const Name& userPrefix);
+
+  /**
+   * @brief Get the name of the local session.
+   *
+   * This method gets the session name according to prefix, if prefix is not specified,
+   * it returns the session name of default user.
+   *
+   * @param prefix prefix of the node
+   */
+  const Name&
+  getSessionName(Name prefix = EMPTY_NAME);
+
+  /**
+   * @brief Get current seqNo of the local session.
+   *
+   * This method gets the seqNo according to prefix, if prefix is not specified,
+   * it returns the seqNo of default user.
+   *
+   * @param prefix prefix of the node
+   */
   const SeqNo&
-  getSeqNo() const
-  {
-    return m_seqNo;
-  }
+  getSeqNo(Name prefix = EMPTY_NAME);
 
   /**
    * @brief Update the seqNo of the local session
    *
-   * The method updates the existing seqNo with the supplied seqNo.
+   * The method updates the existing seqNo with the supplied seqNo and prefix.
    *
    * @param seq The new seqNo.
+   * @param updatePrefix The prefix of node to update.
    */
   void
-  updateSeqNo(const SeqNo& seq);
+  updateSeqNo(const SeqNo& seq, const Name& updatePrefix = EMPTY_NAME);
 
   /// @brief Get root digest of current sync tree
   ndn::ConstBufferPtr
@@ -168,6 +219,7 @@
     return m_state;
   }
 
+
 private:
   /**
    * @brief Callback to handle Sync Interest
@@ -304,7 +356,7 @@
    * @param commit The diff.
    */
   void
-  satisfyPendingSyncInterests(ConstDiffStatePtr commit);
+  satisfyPendingSyncInterests(const Name& updatedPrefix, ConstDiffStatePtr commit);
 
   /// @brief Helper method to send normal Sync Interest
   void
@@ -316,7 +368,7 @@
 
   /// @brief Helper method to send Sync Reply
   void
-  sendSyncData(const Name& name, const State& state);
+  sendSyncData(const Name& nodePrefix, const Name& name, const State& state);
 
   /**
    * @brief Unset reset status
@@ -332,9 +384,11 @@
 
 public:
   static const ndn::Name DEFAULT_NAME;
+  static const ndn::Name EMPTY_NAME;
   static const ndn::shared_ptr<ndn::Validator> DEFAULT_VALIDATOR;
 
 private:
+  typedef std::unordered_map<ndn::Name, NodeInfo> NodeList;
 
   static const ndn::ConstBufferPtr EMPTY_DIGEST;
   static const ndn::name::Component RESET_COMPONENT;
@@ -344,11 +398,10 @@
   Name m_syncPrefix;
   const ndn::RegisteredPrefixId* m_syncRegisteredPrefixId;
   Name m_syncReset;
-  Name m_userPrefix;
+  Name m_defaultUserPrefix;
 
   // State
-  Name m_sessionName;
-  SeqNo m_seqNo;
+  NodeList m_nodeList;
   State m_state;
   DiffStateContainer m_log;
   InterestTable m_interestTable;
@@ -382,10 +435,11 @@
   time::milliseconds m_syncReplyFreshness;
 
   // Security
-  ndn::Name m_signingId;
+  ndn::Name m_defaultSigningId;
   ndn::KeyChain m_keyChain;
   ndn::shared_ptr<ndn::Validator> m_validator;
 
+
 #ifdef _DEBUG
   int m_instanceId;
   static int m_instanceCounter;
diff --git a/tests/integrated-tests/test-logic.cpp b/tests/integrated-tests/test-logic.cpp
index 088b8fb..031280a 100644
--- a/tests/integrated-tests/test-logic.cpp
+++ b/tests/integrated-tests/test-logic.cpp
@@ -59,12 +59,6 @@
     logic.updateSeqNo(seqNo);
   }
 
-  void
-  check(const Name& sessionName, const SeqNo& seqNo)
-  {
-    BOOST_CHECK_EQUAL(map[sessionName], seqNo);
-  }
-
   Logic logic;
   std::map<Name, SeqNo> map;
 };
@@ -86,30 +80,6 @@
     faces[2] = make_shared<ndn::Face>(ref(io));
   }
 
-  void
-  createHandler(size_t idx)
-  {
-    handler[idx] = make_shared<Handler>(ref(*faces[idx]), syncPrefix, userPrefix[idx]);
-  }
-
-  void
-  updateSeqNo(size_t idx, const SeqNo& seqNo)
-  {
-    handler[idx]->updateSeqNo(seqNo);
-  }
-
-  void
-  checkSeqNo(size_t sIdx, size_t dIdx, const SeqNo& seqNo)
-  {
-    handler[sIdx]->check(handler[dIdx]->logic.getSessionName(), seqNo);
-  }
-
-  void
-  terminate()
-  {
-    io.stop();
-  }
-
   Name syncPrefix;
   Name userPrefix[3];
 
@@ -138,31 +108,27 @@
 BOOST_AUTO_TEST_CASE(TwoBasic)
 {
   scheduler.scheduleEvent(ndn::time::milliseconds(100),
-                          bind(&LogicFixture::createHandler, this, 0));
+    [this] { handler[0] = make_shared<Handler>(ref(*faces[0]), syncPrefix, userPrefix[0]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(200),
-                          bind(&LogicFixture::createHandler, this, 1));
+    [this] { handler[1] = make_shared<Handler>(ref(*faces[1]), syncPrefix, userPrefix[1]); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(300),
-                          bind(&LogicFixture::updateSeqNo, this, 0, 1));
+  scheduler.scheduleEvent(ndn::time::milliseconds(300), [this] { handler[0]->updateSeqNo(1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1000),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1100),
-                          bind(&LogicFixture::updateSeqNo, this, 0, 2));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1100), [this] { handler[0]->updateSeqNo(2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1800),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1900),
-                          bind(&LogicFixture::updateSeqNo, this, 1, 2));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1900), [this] { handler[1]->updateSeqNo(2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2600),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(2800),
-                          bind(&LogicFixture::terminate, this));
+  scheduler.scheduleEvent(ndn::time::milliseconds(2800), [this] { io.stop(); });
 
   io.run();
 }
@@ -170,43 +136,39 @@
 BOOST_AUTO_TEST_CASE(ThreeBasic)
 {
   scheduler.scheduleEvent(ndn::time::milliseconds(100),
-                          bind(&LogicFixture::createHandler, this, 0));
+    [this] { handler[0] = make_shared<Handler>(ref(*faces[0]), syncPrefix, userPrefix[0]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(200),
-                          bind(&LogicFixture::createHandler, this, 1));
+    [this] { handler[1] = make_shared<Handler>(ref(*faces[1]), syncPrefix, userPrefix[1]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(300),
-                          bind(&LogicFixture::createHandler, this, 2));
+    [this] { handler[2] = make_shared<Handler>(ref(*faces[2]), syncPrefix, userPrefix[2]); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(500),
-                          bind(&LogicFixture::updateSeqNo, this, 0, 1));
+  scheduler.scheduleEvent(ndn::time::milliseconds(500), [this] { handler[0]->updateSeqNo(1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1400),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1450),
-                          bind(&LogicFixture::checkSeqNo, this, 2, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[2]->map[handler[0]->logic.getSessionName()], 1); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1500),
-                          bind(&LogicFixture::updateSeqNo, this, 1, 2));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1500), [this] { handler[1]->updateSeqNo(2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2400),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2450),
-                          bind(&LogicFixture::checkSeqNo, this, 2, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[2]->map[handler[1]->logic.getSessionName()], 2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(2500),
-                          bind(&LogicFixture::updateSeqNo, this, 2, 4));
+  scheduler.scheduleEvent(ndn::time::milliseconds(2500), [this] { handler[2]->updateSeqNo(4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(4400),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[2]->logic.getSessionName()], 4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(4450),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[2]->logic.getSessionName()], 4); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(4500),
-                          bind(&LogicFixture::terminate, this));
+  scheduler.scheduleEvent(ndn::time::milliseconds(4500), [this] { io.stop(); });
 
   io.run();
 }
@@ -214,44 +176,40 @@
 BOOST_AUTO_TEST_CASE(ResetRecover)
 {
   scheduler.scheduleEvent(ndn::time::milliseconds(100),
-                          bind(&LogicFixture::createHandler, this, 0));
+    [this] { handler[0] = make_shared<Handler>(ref(*faces[0]), syncPrefix, userPrefix[0]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(200),
-                          bind(&LogicFixture::createHandler, this, 1));
+    [this] { handler[1] = make_shared<Handler>(ref(*faces[1]), syncPrefix, userPrefix[1]); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(500),
-                          bind(&LogicFixture::updateSeqNo, this, 0, 1));
+  scheduler.scheduleEvent(ndn::time::milliseconds(500), [this] { handler[0]->updateSeqNo(1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1400),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1500),
-                          bind(&LogicFixture::updateSeqNo, this, 1, 2));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1500), [this] { handler[1]->updateSeqNo(2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2400),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2500),
-                          bind(&LogicFixture::createHandler, this, 2));
+    [this] { handler[2] = make_shared<Handler>(ref(*faces[2]), syncPrefix, userPrefix[2]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(3000),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(3050),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(3100),
-                          bind(&LogicFixture::updateSeqNo, this, 2, 4));
+  scheduler.scheduleEvent(ndn::time::milliseconds(3100), [this] { handler[2]->updateSeqNo(4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(4000),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[2]->logic.getSessionName()], 4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(4050),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[2]->logic.getSessionName()], 4); });
 
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(4500),
-                          bind(&LogicFixture::terminate, this));
+  scheduler.scheduleEvent(ndn::time::milliseconds(4500), [this] { io.stop(); });
 
   io.run();
 }
@@ -259,47 +217,83 @@
 BOOST_AUTO_TEST_CASE(RecoverConflict)
 {
   scheduler.scheduleEvent(ndn::time::milliseconds(0),
-                          bind(&LogicFixture::createHandler, this, 0));
+    [this] { handler[0] = make_shared<Handler>(ref(*faces[0]), syncPrefix, userPrefix[0]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(50),
-                          bind(&LogicFixture::createHandler, this, 1));
+    [this] { handler[1] = make_shared<Handler>(ref(*faces[1]), syncPrefix, userPrefix[1]); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(100),
-                          bind(&LogicFixture::createHandler, this, 2));
+    [this] { handler[2] = make_shared<Handler>(ref(*faces[2]), syncPrefix, userPrefix[2]); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(500),
-                          bind(&LogicFixture::updateSeqNo, this, 0, 1));
+  scheduler.scheduleEvent(ndn::time::milliseconds(500), [this] { handler[0]->updateSeqNo(1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1400),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(1400),
-                          bind(&LogicFixture::checkSeqNo, this, 2, 0, 1));
+    [this] { BOOST_CHECK_EQUAL(handler[2]->map[handler[0]->logic.getSessionName()], 1); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1500),
-                          bind(&LogicFixture::updateSeqNo, this, 1, 2));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1500), [this] { handler[1]->updateSeqNo(2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(1500),
-                          bind(&LogicFixture::updateSeqNo, this, 2, 4));
+  scheduler.scheduleEvent(ndn::time::milliseconds(1500), [this] { handler[2]->updateSeqNo(4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2400),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 2); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2450),
-                          bind(&LogicFixture::checkSeqNo, this, 0, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[2]->logic.getSessionName()], 4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2500),
-                          bind(&LogicFixture::checkSeqNo, this, 1, 2, 4));
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[2]->logic.getSessionName()], 4); });
 
   scheduler.scheduleEvent(ndn::time::milliseconds(2550),
-                          bind(&LogicFixture::checkSeqNo, this, 2, 1, 2));
+    [this] { BOOST_CHECK_EQUAL(handler[2]->map[handler[1]->logic.getSessionName()], 2); });
 
-  scheduler.scheduleEvent(ndn::time::milliseconds(4500),
-                          bind(&LogicFixture::terminate, this));
+  scheduler.scheduleEvent(ndn::time::milliseconds(4500), [this] { io.stop(); });
 
   io.run();
 }
 
+BOOST_AUTO_TEST_CASE(MultipleUserUnderOneLogic)
+{
+  scheduler.scheduleEvent(ndn::time::milliseconds(0),
+    [this] { handler[0] = make_shared<Handler>(ref(*faces[0]), syncPrefix, userPrefix[0]); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(50),
+    [this] { handler[1] = make_shared<Handler>(ref(*faces[1]), syncPrefix, userPrefix[2]); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(100),
+    [this] { handler[0]->logic.addUserNode(userPrefix[1]); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(500), [this] { handler[0]->updateSeqNo(1); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(1400),
+    [this] { BOOST_CHECK_EQUAL(handler[1]->map[handler[0]->logic.getSessionName()], 1); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(1500),
+    [this] { handler[0]->logic.updateSeqNo(2, userPrefix[1]); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(2400),
+    [this] {
+             Name sessionName = handler[0]->logic.getSessionName(userPrefix[1]);
+             BOOST_CHECK_EQUAL(handler[1]->map[sessionName], 2);
+           });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(2500), [this] { handler[1]->updateSeqNo(4); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(3200),
+    [this] { BOOST_CHECK_EQUAL(handler[0]->map[handler[1]->logic.getSessionName()], 4); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(3300),
+    [this] { handler[0]->logic.removeUserNode(userPrefix[0]); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(4500),
+    [this] { BOOST_CHECK_EQUAL(handler[1]->logic.getSessionNames().size(), 2); });
+
+  scheduler.scheduleEvent(ndn::time::milliseconds(5000), [this] { io.stop(); });
+
+  io.run();
+}
 
 BOOST_AUTO_TEST_SUITE_END()
 
diff --git a/tests/unit-tests/test-multiple-user.cpp b/tests/unit-tests/test-multiple-user.cpp
new file mode 100644
index 0000000..44209e8
--- /dev/null
+++ b/tests/unit-tests/test-multiple-user.cpp
@@ -0,0 +1,104 @@
+#include "logic.hpp"
+
+#include "boost-test.hpp"
+
+namespace chronosync {
+namespace test {
+
+using std::vector;
+
+
+class Handler
+{
+public:
+  Handler(ndn::Face& face,
+          const Name& syncPrefix,
+          const Name& userPrefix)
+    : logic(face,
+            syncPrefix,
+            userPrefix,
+            bind(&Handler::onUpdate, this, _1))
+  {
+  }
+
+  void
+  onUpdate(const vector<MissingDataInfo>& v)
+  {
+
+  }
+
+  void
+  updateSeqNo(const SeqNo& seqNo)
+  {
+    logic.updateSeqNo(seqNo);
+  }
+
+  void
+  addUserNode(const Name& prefix)
+  {
+    logic.addUserNode(prefix);
+  }
+
+  void
+  removeUserNode(const Name& prefix)
+  {
+    logic.removeUserNode(prefix);
+  }
+
+
+  Logic logic;
+  std::map<Name, SeqNo> map;
+};
+
+class LogicFixture
+{
+public:
+
+  LogicFixture()
+    : syncPrefix("/ndn/broadcast/sync")
+    , scheduler(io)
+  {
+    syncPrefix.appendVersion();
+    userPrefix[0] = Name("/user0");
+    userPrefix[1] = Name("/user1");
+    userPrefix[2] = Name("/user2");
+
+    face = make_shared<ndn::Face>(ref(io));
+  }
+
+  Name syncPrefix;
+  Name userPrefix[3];
+
+  boost::asio::io_service io;
+  shared_ptr<ndn::Face> face;
+  ndn::Scheduler scheduler;
+  shared_ptr<Handler> handler;
+};
+
+BOOST_FIXTURE_TEST_SUITE(LogicTests, LogicFixture)
+
+BOOST_AUTO_TEST_CASE(ThreeUserNode)
+{
+  handler = make_shared<Handler>(ref(*face), syncPrefix, userPrefix[0]);
+  handler->addUserNode(userPrefix[1]);
+  handler->addUserNode(userPrefix[2]);
+  handler->removeUserNode(userPrefix[0]);
+
+  handler->logic.setDefaultUserPrefix(userPrefix[1]);
+  handler->updateSeqNo(1);
+  BOOST_CHECK_EQUAL(handler->logic.getSeqNo(userPrefix[1]), 1);
+
+  handler->logic.updateSeqNo(2, userPrefix[2]);
+  handler->logic.setDefaultUserPrefix(userPrefix[2]);
+
+  BOOST_CHECK_EQUAL(handler->logic.getSeqNo(), 2);
+
+  BOOST_REQUIRE_THROW(handler->logic.getSeqNo(userPrefix[0]), Logic::Error);
+  BOOST_REQUIRE_THROW(handler->logic.getSessionName(userPrefix[0]), Logic::Error);
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace test
+} // namespace chronosync