rib: redesign of remote prefix registration

Change-Id: I8418de6d5bb9615af898df5dbf9ed4cc2cb6a43a
Refs: #3211, #2413
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 24407c7..682b8e7 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -316,14 +316,19 @@
   ;   }
   ; }
 
-  remote_register
+  auto_prefix_propagate
   {
     cost 15 ; forwarding cost of prefix registered on remote router
-    timeout 10000 ; timeout (in milliseconds) of remote prefix registration command
-    retry 0 ; maximum number of retries for each remote prefix registration command
+    timeout 10000 ; timeout (in milliseconds) of prefix registration command for propagation
 
-    refresh_interval 300 ; interval (in seconds) before refreshing the registration
+    refresh_interval 300 ; interval (in seconds) before refreshing the propagation
     ; This setting should be less than face_system.udp.idle_time,
     ; so that the face is kept alive on the remote router.
+
+    base_retry_wait 50 ; base wait time (in seconds) before retrying propagation
+    max_retry_wait 3600 ; maximum wait time (in seconds) before retrying propagation
+    ; for consequent retries, the wait time before each retry is calculated based on the back-off
+    ; policy. Initially, the wait time is set to base_retry_wait, then it will be doubled for every
+    ; retry unless beyond the max_retry_wait, in which case max_retry_wait is set as the wait time.
   }
 }
diff --git a/rib/auto-prefix-propagator.cpp b/rib/auto-prefix-propagator.cpp
new file mode 100644
index 0000000..12344e2
--- /dev/null
+++ b/rib/auto-prefix-propagator.cpp
@@ -0,0 +1,472 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "auto-prefix-propagator.hpp"
+#include "core/logger.hpp"
+#include "core/scheduler.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <vector>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT("AutoPrefixPropagator");
+
+using ndn::nfd::ControlParameters;
+using ndn::nfd::CommandOptions;
+
+const Name LOCAL_REGISTRATION_PREFIX("/localhost");
+const Name LINK_LOCAL_NFD_PREFIX("/localhop/nfd");
+const name::Component IGNORE_COMMPONENT("nrd");
+const time::seconds PREFIX_PROPAGATION_DEFAULT_REFRESH_INTERVAL = time::seconds(25);
+const time::seconds PREFIX_PROPAGATION_MAX_REFRESH_INTERVAL = time::seconds(600);
+const time::seconds PREFIX_PROPAGATION_DEFAULT_BASE_RETRY_WAIT = time::seconds(50);
+const time::seconds PREFIX_PROPAGATION_DEFAULT_MAX_RETRY_WAIT = time::seconds(3600);
+const uint64_t PREFIX_PROPAGATION_DEFAULT_COST = 15;
+const time::milliseconds PREFIX_PROPAGATION_DEFAULT_TIMEOUT = time::milliseconds(10000);
+
+AutoPrefixPropagator::AutoPrefixPropagator(ndn::nfd::Controller& controller,
+                                           ndn::KeyChain& keyChain,
+                                           Rib& rib)
+  : m_nfdController(controller)
+  , m_keyChain(keyChain)
+  , m_rib(rib)
+  , m_refreshInterval(PREFIX_PROPAGATION_DEFAULT_REFRESH_INTERVAL)
+  , m_baseRetryWait(PREFIX_PROPAGATION_DEFAULT_BASE_RETRY_WAIT)
+  , m_maxRetryWait(PREFIX_PROPAGATION_DEFAULT_MAX_RETRY_WAIT)
+  , m_hasConnectedHub(false)
+{
+}
+
+void
+AutoPrefixPropagator::loadConfig(const ConfigSection& configSection)
+{
+  m_refreshInterval = PREFIX_PROPAGATION_DEFAULT_REFRESH_INTERVAL;
+  m_baseRetryWait = PREFIX_PROPAGATION_DEFAULT_BASE_RETRY_WAIT;
+  m_maxRetryWait = PREFIX_PROPAGATION_DEFAULT_MAX_RETRY_WAIT;
+
+  m_controlParameters
+     .setCost(PREFIX_PROPAGATION_DEFAULT_COST)
+     .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT)// set origin to client.
+     .setFaceId(0);// the remote hub will take the input face as the faceId.
+
+   m_commandOptions
+     .setPrefix(LINK_LOCAL_NFD_PREFIX)
+     .setTimeout(PREFIX_PROPAGATION_DEFAULT_TIMEOUT);
+
+  NFD_LOG_INFO("Load auto_prefix_propagate section in rib section");
+
+  for (auto&& i : configSection) {
+    if (i.first == "cost") {
+      m_controlParameters.setCost(i.second.get_value<uint64_t>());
+    }
+    else if (i.first == "timeout") {
+      m_commandOptions.setTimeout(time::milliseconds(i.second.get_value<size_t>()));
+    }
+    else if (i.first == "refresh_interval") {
+      m_refreshInterval = std::min(PREFIX_PROPAGATION_MAX_REFRESH_INTERVAL,
+                                   time::seconds(i.second.get_value<size_t>()));
+    }
+    else if (i.first == "base_retry_wait") {
+      m_baseRetryWait = time::seconds(i.second.get_value<size_t>());
+    }
+    else if (i.first == "max_retry_wait") {
+      m_maxRetryWait = time::seconds(i.second.get_value<size_t>());
+    }
+    else {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" + i.first +
+                                              "\" in \"auto_prefix_propagate\" section"));
+    }
+  }
+}
+
+void
+AutoPrefixPropagator::enable()
+{
+  m_afterInsertConnection =
+    m_rib.afterInsertEntry.connect(bind(&AutoPrefixPropagator::afterInsertRibEntry, this, _1));
+  m_afterEraseConnection =
+    m_rib.afterEraseEntry.connect(bind(&AutoPrefixPropagator::afterEraseRibEntry, this, _1));
+}
+
+void
+AutoPrefixPropagator::disable()
+{
+  m_afterInsertConnection.disconnect();
+  m_afterEraseConnection.disconnect();
+}
+
+AutoPrefixPropagator::PrefixPropagationParameters
+AutoPrefixPropagator::getPrefixPropagationParameters(const Name& localRibPrefix)
+{
+  // get all identities from the KeyChain
+  std::vector<Name> identities;
+  m_keyChain.getAllIdentities(identities, false); // get all except the default
+  identities.push_back(m_keyChain.getDefaultIdentity()); // get the default
+
+  // shortest prefix matching to all identies.
+  Name propagatedPrefix, signingIdentity;
+  bool isFound = false;
+  for (auto&& i : identities) {
+    Name prefix = !i.empty() && IGNORE_COMMPONENT == i.at(-1) ? i.getPrefix(-1) : i;
+    if (prefix.isPrefixOf(localRibPrefix) && (!isFound || i.size() < signingIdentity.size())) {
+      isFound = true;
+      propagatedPrefix = prefix;
+      signingIdentity = i;
+    }
+  }
+
+  PrefixPropagationParameters propagateParameters;
+  if (!isFound) {
+    propagateParameters.isValid = false;
+  }
+  else {
+    propagateParameters.isValid = true;
+    propagateParameters.parameters = m_controlParameters;
+    propagateParameters.options = m_commandOptions;
+    propagateParameters.parameters.setName(propagatedPrefix);
+    propagateParameters.options.setSigningInfo(signingByIdentity(signingIdentity));
+  }
+
+  return propagateParameters;
+}
+
+void
+AutoPrefixPropagator::afterInsertRibEntry(const Name& prefix)
+{
+  if (LOCAL_REGISTRATION_PREFIX.isPrefixOf(prefix)) {
+    NFD_LOG_INFO("local registration only for " << prefix);
+    return;
+  }
+
+  if (prefix == LINK_LOCAL_NFD_PREFIX) {
+    NFD_LOG_INFO("this is a prefix registered by some hub: " << prefix);
+
+    m_hasConnectedHub = true;
+    return afterHubConnect();
+  }
+
+  auto propagateParameters = getPrefixPropagationParameters(prefix);
+  if (!propagateParameters.isValid) {
+    NFD_LOG_INFO("no signing identity available for: " << prefix);
+    return;
+  }
+
+  auto entryIt = m_propagatedEntries.find(propagateParameters.parameters.getName());
+  if (entryIt != m_propagatedEntries.end()) {
+    BOOST_ASSERT(!entryIt->second.isNew());
+    NFD_LOG_INFO("prefix has already been propagated: "
+                 << propagateParameters.parameters.getName());
+    return;
+  }
+
+  afterRibInsert(propagateParameters.parameters, propagateParameters.options);
+}
+
+void
+AutoPrefixPropagator::afterEraseRibEntry(const Name& prefix)
+{
+  if (LOCAL_REGISTRATION_PREFIX.isPrefixOf(prefix)) {
+    NFD_LOG_INFO("local unregistration only for " << prefix);
+    return;
+  }
+
+  if (prefix == LINK_LOCAL_NFD_PREFIX) {
+    NFD_LOG_INFO("disconnected to hub with prefix: " << prefix);
+
+    m_hasConnectedHub = false;
+    return afterHubDisconnect();
+  }
+
+  auto propagateParameters = getPrefixPropagationParameters(prefix);
+  if (!propagateParameters.isValid) {
+    NFD_LOG_INFO("no signing identity available for: " << prefix);
+    return;
+  }
+
+  auto entryIt = m_propagatedEntries.find(propagateParameters.parameters.getName());
+  if (entryIt == m_propagatedEntries.end()) {
+    NFD_LOG_INFO("prefix has not been propagated yet: "
+                 << propagateParameters.parameters.getName());
+    return;
+  }
+
+  for (auto&& ribTableEntry : m_rib) {
+    if (propagateParameters.parameters.getName().isPrefixOf(ribTableEntry.first) &&
+        propagateParameters.options.getSigningInfo().getSignerName() ==
+        getPrefixPropagationParameters(ribTableEntry.first)
+          .options.getSigningInfo().getSignerName()) {
+      NFD_LOG_INFO("should be kept for another RIB entry: " << ribTableEntry.first);
+      return;
+    }
+  }
+
+  afterRibErase(propagateParameters.parameters.unsetCost(), propagateParameters.options);
+}
+
+bool
+AutoPrefixPropagator::doesCurrentPropagatedPrefixWork(const Name& prefix)
+{
+  auto propagateParameters = getPrefixPropagationParameters(prefix);
+  if (!propagateParameters.isValid) {
+    // no identity can sign the input prefix
+    return false;
+  }
+
+  // there is at least one identity can sign the input prefix, so the prefix selected for
+  // propagation (i.e., propagateParameters.parameters.getName()) must be a prefix of the input
+  // prefix. Namely it's either equal to the input prefix or a better choice.
+  return propagateParameters.parameters.getName().size() == prefix.size();
+}
+
+void
+AutoPrefixPropagator::redoPropagation(PropagatedEntryIt entryIt,
+                                      const ControlParameters& parameters,
+                                      const CommandOptions& options,
+                                      time::seconds retryWaitTime)
+{
+  if (doesCurrentPropagatedPrefixWork(parameters.getName())) {
+    // PROPAGATED / PROPAGATE_FAIL --> PROPAGATING
+    entryIt->second.startPropagation();
+    return startPropagation(parameters, options, retryWaitTime);
+  }
+
+  NFD_LOG_INFO("current propagated prefix does not work any more");
+  m_propagatedEntries.erase(entryIt);
+
+  // re-handle all locally RIB entries that can be covered by this propagated prefix
+  for (auto&& ribTableEntry : m_rib) {
+    if (parameters.getName().isPrefixOf(ribTableEntry.first)) {
+      afterInsertRibEntry(ribTableEntry.first);
+    }
+  }
+}
+
+void
+AutoPrefixPropagator::startPropagation(const ControlParameters& parameters,
+                                       const CommandOptions& options,
+                                       time::seconds retryWaitTime)
+{
+  NFD_LOG_TRACE("start propagate " << parameters.getName());
+
+  ndn::Scheduler::Event refreshEvent =
+    bind(&AutoPrefixPropagator::onRefreshTimer, this, parameters, options);
+  ndn::Scheduler::Event retryEvent =
+    bind(&AutoPrefixPropagator::onRetryTimer, this, parameters, options,
+         std::min(m_maxRetryWait, retryWaitTime * 2));
+
+  m_nfdController.start<ndn::nfd::RibRegisterCommand>(
+     parameters,
+     bind(&AutoPrefixPropagator::afterPropagateSucceed, this, parameters, options, refreshEvent),
+     bind(&AutoPrefixPropagator::afterPropagateFail,
+          this, _1, _2, parameters, options, retryWaitTime, retryEvent),
+     options);
+}
+
+void
+AutoPrefixPropagator::startRevocation(const ControlParameters& parameters,
+                                      const CommandOptions& options,
+                                      time::seconds retryWaitTime)
+{
+  NFD_LOG_INFO("start revoke propagation of " << parameters.getName());
+
+  m_nfdController.start<ndn::nfd::RibUnregisterCommand>(
+     parameters,
+     bind(&AutoPrefixPropagator::afterRevokeSucceed, this, parameters, options, retryWaitTime),
+     bind(&AutoPrefixPropagator::afterRevokeFail, this, _1, _2, parameters, options),
+     options);
+}
+
+void
+AutoPrefixPropagator::afterRibInsert(const ControlParameters& parameters,
+                                     const CommandOptions& options)
+{
+  BOOST_ASSERT(m_propagatedEntries.find(parameters.getName()) == m_propagatedEntries.end());
+
+  // keep valid entries although there is no connectivity to hub
+  auto& entry = m_propagatedEntries[parameters.getName()]
+    .setSigningIdentity(options.getSigningInfo().getSignerName());
+
+  if (!m_hasConnectedHub) {
+    NFD_LOG_INFO("no hub connected to propagate " << parameters.getName());
+    return;
+  }
+
+  // NEW --> PROPAGATING
+  entry.startPropagation();
+  startPropagation(parameters, options, m_baseRetryWait);
+}
+
+void
+AutoPrefixPropagator::afterRibErase(const ControlParameters& parameters,
+                                    const CommandOptions& options)
+{
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  BOOST_ASSERT(entryIt != m_propagatedEntries.end());
+
+  bool hasPropagationSucceeded = entryIt->second.isPropagated();
+
+  // --> "RELEASED"
+  m_propagatedEntries.erase(entryIt);
+
+  if (!m_hasConnectedHub) {
+    NFD_LOG_INFO("no hub connected to revoke propagation of " << parameters.getName());
+    return;
+  }
+
+  if (!hasPropagationSucceeded) {
+    NFD_LOG_INFO("propagation has not succeeded: " << parameters.getName());
+    return;
+  }
+
+  startRevocation(parameters, options, m_baseRetryWait);
+}
+
+void
+AutoPrefixPropagator::afterHubConnect()
+{
+  NFD_LOG_INFO("redo " << m_propagatedEntries.size()
+                       << " propagations when new Hub connectivity is built.");
+
+  std::vector<PropagatedEntryIt> regEntryIterators;
+  for (auto it = m_propagatedEntries.begin() ; it != m_propagatedEntries.end() ; it ++) {
+    BOOST_ASSERT(it->second.isNew());
+    regEntryIterators.push_back(it);
+  }
+
+  for (auto&& it : regEntryIterators) {
+    auto parameters = m_controlParameters;
+    auto options = m_commandOptions;
+
+    redoPropagation(it,
+                     parameters.setName(it->first),
+                     options.setSigningInfo(signingByIdentity(it->second.getSigningIdentity())),
+                     m_baseRetryWait);
+  }
+}
+
+void
+AutoPrefixPropagator::afterHubDisconnect()
+{
+  for (auto&& entry : m_propagatedEntries) {
+    // --> NEW
+    BOOST_ASSERT(!entry.second.isNew());
+    entry.second.initialize();
+  }
+}
+
+void
+AutoPrefixPropagator::afterPropagateSucceed(const ControlParameters& parameters,
+                                            const CommandOptions& options,
+                                            const ndn::Scheduler::Event& refreshEvent)
+{
+  NFD_LOG_TRACE("success to propagate " << parameters.getName());
+
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  if (entryIt == m_propagatedEntries.end()) {
+    // propagation should be revoked if this entry has been erased (i.e., be in RELEASED state)
+    NFD_LOG_DEBUG("Already erased!");
+    ControlParameters newParameters = parameters;
+    return startRevocation(newParameters.unsetCost(), options, m_baseRetryWait);
+  }
+
+  // PROPAGATING --> PROPAGATED
+  BOOST_ASSERT(entryIt->second.isPropagating());
+  entryIt->second.succeed(scheduler::schedule(m_refreshInterval, refreshEvent));
+}
+
+void
+AutoPrefixPropagator::afterPropagateFail(uint32_t code, const std::string& reason,
+                                         const ControlParameters& parameters,
+                                         const CommandOptions& options,
+                                         time::seconds retryWaitTime,
+                                         const ndn::Scheduler::Event& retryEvent)
+{
+  NFD_LOG_TRACE("fail to propagate " << parameters.getName()
+                                     << "\n\t reason:" << reason
+                                     << "\n\t retry wait time: " << retryWaitTime);
+
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  if (entryIt == m_propagatedEntries.end()) {
+    // current state is RELEASED
+    return;
+  }
+
+  // PROPAGATING --> PROPAGATE_FAIL
+  BOOST_ASSERT(entryIt->second.isPropagating());
+  entryIt->second.fail(scheduler::schedule(retryWaitTime, retryEvent));
+}
+
+void
+AutoPrefixPropagator::afterRevokeSucceed(const ControlParameters& parameters,
+                                         const CommandOptions& options,
+                                         time::seconds retryWaitTime)
+{
+  NFD_LOG_TRACE("success to revoke propagation of " << parameters.getName());
+
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  if (m_propagatedEntries.end() != entryIt && !entryIt->second.isPropagateFail()) {
+    // if is not RELEASED or PROPAGATE_FAIL
+    NFD_LOG_DEBUG("propagated entry still exists");
+
+    // PROPAGATING / PROPAGATED --> PROPAGATING
+    BOOST_ASSERT(!entryIt->second.isNew());
+    entryIt->second.startPropagation();
+
+    ControlParameters newParameters = parameters;
+    startPropagation(newParameters.setCost(m_controlParameters.getCost()), options, retryWaitTime);
+  }
+}
+
+void
+AutoPrefixPropagator::afterRevokeFail(uint32_t code, const std::string& reason,
+                                       const ControlParameters& parameters,
+                                       const CommandOptions& options)
+{
+  NFD_LOG_INFO("fail to revoke the propagation of  " << parameters.getName()
+                                                     << "\n\t reason:" << reason);
+}
+
+void
+AutoPrefixPropagator::onRefreshTimer(const ControlParameters& parameters,
+                                     const CommandOptions& options)
+{
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  BOOST_ASSERT(entryIt != m_propagatedEntries.end() && entryIt->second.isPropagated());
+  redoPropagation(entryIt, parameters, options, m_baseRetryWait);
+}
+
+void
+AutoPrefixPropagator::onRetryTimer(const ControlParameters& parameters,
+                                   const CommandOptions& options,
+                                   time::seconds retryWaitTime)
+{
+  auto entryIt = m_propagatedEntries.find(parameters.getName());
+  BOOST_ASSERT(entryIt != m_propagatedEntries.end() && entryIt->second.isPropagateFail());
+  redoPropagation(entryIt, parameters, options, retryWaitTime);
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/auto-prefix-propagator.hpp b/rib/auto-prefix-propagator.hpp
new file mode 100644
index 0000000..11ad67f
--- /dev/null
+++ b/rib/auto-prefix-propagator.hpp
@@ -0,0 +1,418 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_RIB_AUTO_PREFIX_PROPAGATOR_HPP
+#define NFD_RIB_AUTO_PREFIX_PROPAGATOR_HPP
+
+#include "rib.hpp"
+#include "core/config-file.hpp"
+#include "rib-status-publisher.hpp"
+#include "propagated-entry.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/management/nfd-controller.hpp>
+#include <ndn-cxx/management/nfd-control-command.hpp>
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+#include <ndn-cxx/management/nfd-command-options.hpp>
+#include <ndn-cxx/util/signal.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** @brief provides automatic prefix propagation feature
+ *
+ * The AutoPrefixPropagator monitors changes to local RIB, and registers prefixes onto a
+ * connected gateway router (HUB), so that Interests under propagated prefixes will be forwarded
+ * toward the local host by the HUB.
+ *
+ * The route origin of propagated prefix is CLIENT, as shown on the HUB.
+ *
+ * Connectivity to a HUB is indicated with a special RIB entry "ndn:/localhop/nfd".
+ * Currently, the AutoPrefixPropagator can process the connection to at most one HUB.
+ *
+ * For every RIB entry except "ndn:/localhop/nfd" and those starting with "ndn:/localhost", the
+ * AutoPrefixPropagator queries the local KeyChain for signing identities that is authorized
+ * to sign a prefix registration command of a prefix of the RIB prefix.
+ *
+ * If one or more signing identities are found, the identity that can sign a prefix registration
+ * command of the shortest prefix is chosen, and the AutoPrefixPropagator will attempt to
+ * propagte a prefix to the HUB using the shortest prefix. It's noteworthy that no route flags will
+ * be inherited from the local registration.
+ * If no signing identity is available, no prefix of the RIB entry is propagated to the HUB.
+ *
+ * When a RIB entry is erased, the corresponding propagated entry would be revoked,
+ * unless another local RIB entry is causing the propagation of that prefix.
+ *
+ * After a successful propagation, the AutoPrefixPropagator will refresh the propagation
+ * periodically by resending the registration command.
+ *
+ * In case of a failure or timeout in a registration command, the command will be retried with an
+ * exponential back-off strategy.
+ *
+ * The AutoPrefixPropagator can be configured in NFD configuration file, at the
+ * rib.auto_prefix_propagate section.
+ */
+class AutoPrefixPropagator : noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+  AutoPrefixPropagator(ndn::nfd::Controller& controller,
+                       ndn::KeyChain& keyChain,
+                       Rib& rib);
+  /**
+   * @brief load the "auto_prefix_propagate" section from config file
+   *
+   * @param configSection the sub section in "rib" section.
+   */
+  void
+  loadConfig(const ConfigSection& configSection);
+
+  /**
+   * @brief enable automatic prefix propagation
+   */
+  void
+  enable();
+
+  /**
+   * @brief disable automatic prefix propagation
+   */
+  void
+  disable();
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // helpers
+  /**
+   * When a local RIB prefix triggers prefix propagation, we propagate the shortest identity that
+   * can sign this prefix to the HUB.
+   *
+   * Thus, the propagated prefix does not equal to local RIB prefix.
+   * So it needs separate storage instead of storing within the RIB.
+   *
+   * the propagated prefix is used as the key to retrive the corresponding entry.
+   */
+  typedef std::unordered_map<Name, PropagatedEntry> PropagatedEntryList;
+  typedef PropagatedEntryList::iterator PropagatedEntryIt;
+
+  /**
+   * @brief parameters used by the registration commands for prefix propagation
+   *
+   * consists of a ControlParameters and a CommandOptions, as well as a bool variable indicates
+   * whether this set of parameters is valid (i.e., the signing identity exists)
+   */
+  struct PrefixPropagationParameters
+  {
+    bool isValid;
+    ndn::nfd::ControlParameters parameters;
+    ndn::nfd::CommandOptions options;
+  };
+
+  /**
+   * @brief get the required parameters for prefix propagation.
+   *
+   * given a local RIB prefix @p localRibPrefix, find out, in local KeyChain, a proper identity
+   * whose namespace covers the input prefix. If there is no desired identity, return a invalid
+   * PrefixPropagationParameters.
+   *
+   * Otherwise, set the selected identity as the signing identity in CommandOptions. Meanwhile,
+   * set this identity (or its prefix with the last component eliminated if it ends with "nrd")
+   * as the name of ControlParameters.
+   *
+   * @return the PrefixPropagationParameters.
+   */
+  PrefixPropagationParameters
+  getPrefixPropagationParameters(const Name& localRibPrefix);
+
+  /**
+   * @brief check whether current propagated prefix still works
+   *
+   * the current propagated prefix @p prefix will still work if and only if its corresponding
+   * signing identity still exists and there is no better signing identity covering it.
+   *
+   * @return true if current propagated prefix still works, otherwise false
+   */
+  bool
+  doesCurrentPropagatedPrefixWork(const Name& prefix);
+
+  /**
+   * @brief process re-propagation for the given propagated entry
+   *
+   * This function may be invoked in 3 cases:
+   * 1. After the hub is connected to redo some propagations.
+   * 2. On refresh timer to handle refresh requests.
+   * 3. On retry timer to handle retry requests.
+   *
+   * @param entryIt the propagated entry need to do re-propagation
+   * @param parameters the ControlParameters used by registration commands for propagation
+   * @param options the CommandOptions used for registration commands for propagation
+   * @param retryWaitTime the current wait time before retrying propagation
+   */
+  void
+  redoPropagation(PropagatedEntryIt entryIt,
+                  const ndn::nfd::ControlParameters& parameters,
+                  const ndn::nfd::CommandOptions& options,
+                  time::seconds retryWaitTime);
+
+private:
+  /**
+   * @brief send out the registration command for propagation
+   *
+   * Before sending out the command, two events, for refresh and retry respectively, are
+   * established (but not scheduled) and assigned to two callbacks, afterPropagateSucceed and
+   * afterPropagateFail respectively.
+   *
+   * The retry event requires an argument to define the retry wait time, which is calculated
+   * according to the back-off policy based on current retry wait time @p retryWaitTime and the
+   * maxRetryWait.
+   *
+   * The baseRetryWait and maxRetryWait used in back-off strategy are all configured at
+   * rib.auto_prefix_propagate.base_retry_wait and
+   * rib.auto_prefix_propagate.max_retry_wait respectively
+
+   * @param parameters the ControlParameters used by the registration command for propagation
+   * @param options the CommandOptions used by the registration command for propagation
+   * @param retryWaitTime the current wait time before retrying propagation
+   */
+  void
+  startPropagation(const ndn::nfd::ControlParameters& parameters,
+                   const ndn::nfd::CommandOptions& options,
+                   time::seconds retryWaitTime);
+
+  /**
+   * @brief send out the unregistration command to revoke the corresponding propagation.
+   *
+   * @param parameters the ControlParameters used by the unregistration command for revocation
+   * @param options the CommandOptions used by the unregistration command for revocation
+   * @param retryWaitTime the current wait time before retrying propagation
+   */
+  void
+  startRevocation(const ndn::nfd::ControlParameters& parameters,
+                  const ndn::nfd::CommandOptions& options,
+                  time::seconds retryWaitTime);
+
+  /**
+   * @brief invoked when Rib::afterInsertEntry signal is emitted
+   *
+   * step1: if the local RIB prefix @param prefix is for local use only, return
+   * step2: if the local RIB prefix @param prefix is a hub prefix, invoke afterHubConnect
+   * step3: if no valid PrefixPropagationParameters can be found for the local RIB prefix
+   *        @param prefix, or the propagated prefix has already been processed, return
+   * step4: invoke afterRibInsert
+   */
+  void
+  afterInsertRibEntry(const Name& prefix);
+
+  /**
+   * @brief invoked when Rib::afterEraseEntry signal is emitted
+   *
+   * step1: if local RIB prefix @param prefix is for local use only, return
+   * step2: if local RIB prefix @param prefix is a hub prefix, invoke afterHubDisconnect
+   * step3: if no valid PrefixPropagationParameters can be found for local RIB prefix
+   *        @param prefix, or there are other local RIB prefixes can be covered by the propagated
+   *        prefix, return
+   * step4: invoke afterRibErase
+   */
+  void
+  afterEraseRibEntry(const Name& prefix);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // PropagatedEntry state changes
+  /**
+   * @brief invoked after rib insertion for non-hub prefixes
+   * @pre the PropagatedEntry is in RELEASED state
+   * @post the PropagatedEntry will be in NEW state
+   *
+   * @param parameters the ControlParameters used by registration commands for propagation
+   * @param options the CommandOptions used by registration commands for propagation
+   */
+  void
+  afterRibInsert(const ndn::nfd::ControlParameters& parameters,
+                 const ndn::nfd::CommandOptions& options);
+
+  /**
+   * @brief invoked after rib deletion for non-hub prefixes
+   * @pre the PropagatedEntry is not in RELEASED state
+   * @post the PropagatedEntry will be in RELEASED state
+   *
+   * @param parameters the ControlParameters used by registration commands for propagation
+   * @param options the CommandOptions used by registration commands for propagation
+   */
+  void
+  afterRibErase(const ndn::nfd::ControlParameters& parameters,
+                const ndn::nfd::CommandOptions& options);
+
+  /**
+   * @brief invoked after the hub is connected
+   * @pre the PropagatedEntry is in NEW state or RELEASED state
+   * @post the PropagatedEntry will be in PROPAGATING state or keep in RELEASED state
+   *
+   * call redoPropagation for each propagated entry.
+   */
+  void
+  afterHubConnect();
+
+  /**
+   * @brief invoked after the hub is disconnected
+   * @pre the PropagatedEntry is not in NEW state
+   * @post the PropagatedEntry will be in NEW state or keep in RELEASED state
+   *
+   * for each propagated entry, switch its state to NEW and cancel its event.
+   */
+  void
+  afterHubDisconnect();
+
+  /**
+   * @brief invoked after propagation succeeds
+   * @pre the PropagatedEntry is in PROPAGATING state or RELEASED state
+   * @post the PropagatedEntry will be in PROPAGATED state or keep in RELEASED state
+   *
+   * If the PropagatedEntry does not exist (in RELEASED state), an unregistration command is
+   * sent immediately for revocation.
+   *
+   * If the PropagatedEntry is in PROPAGATING state, switch it to PROPAGATED, and schedule a
+   * refresh timer to redo propagation after a duration, which is configured at
+   * rib.auto_prefix_propagate.refresh_interval.
+   *
+   * Otherwise, make a copy of the ControlParameters @p parameters, unset its Cost field, and then
+   * invoke startRevocation with this new ControlParameters.
+   *
+   * @param parameters the ControlParameters used by the registration command for propagation
+   * @param options the CommandOptions used by the registration command for propagation
+   * @param refreshEvent the event of refreshing propagation
+   */
+  void
+  afterPropagateSucceed(const ndn::nfd::ControlParameters& parameters,
+                        const ndn::nfd::CommandOptions& options,
+                        const ndn::Scheduler::Event& refreshEvent);
+
+  /**
+   * @brief invoked after propagation fails.
+   * @pre the PropagatedEntry is in PROPAGATING state or RELEASED state
+   * @post the PropagatedEntry will be in PROPAGATE_FAIL state or keep in RELEASED state
+   *
+   * If the PropagatedEntry still exists, schedule a retry timer to redo propagation
+   * after a duration defined by current retry time @p retryWaitTime
+   *
+   * @param code error code.
+   * @param reason error reason in string.
+   * @param parameters the ControlParameters used by the registration command for propagation
+   * @param options the CommandOptions used by registration command for propagation
+   * @param retryWaitTime the current wait time before retrying propagation
+   * @param retryEvent the event of retrying propagation
+   */
+  void
+  afterPropagateFail(uint32_t code, const std::string& reason,
+                     const ndn::nfd::ControlParameters& parameters,
+                     const ndn::nfd::CommandOptions& options,
+                     time::seconds retryWaitTime,
+                     const ndn::Scheduler::Event& retryEvent);
+
+  /**
+   * @brief invoked after revocation succeeds
+   * @pre the PropagatedEntry is not in NEW state
+   * @post the PropagatedEntry will be in PROPAGATING state, or keep in PROPAGATE_FAIL state,
+   * or keep in RELEASED state
+   *
+   * If the PropagatedEntry still exists and is not in PROPAGATE_FAIL state, switch it to
+   * PROPAGATING. Then make a copy of the ControlParameters @p parameters, reset its Cost, and
+   * invoke startPropagation with this new ControlParameters.
+   *
+   * @param parameters the ControlParameters used by the unregistration command for revocation
+   * @param options the CommandOptions used by the unregistration command for revocation
+   * @param retryWaitTime the current wait time before retrying propagation
+   */
+  void
+  afterRevokeSucceed(const ndn::nfd::ControlParameters& parameters,
+                     const ndn::nfd::CommandOptions& options,
+                     time::seconds retryWaitTime);
+
+  /**
+   * @brief invoked after revocation fails.
+   *
+   * @param code error code.
+   * @param reason error reason in string.
+   * @param parameters the ControlParameters used by the unregistration command for revocation
+   * @param options the CommandOptions used by the unregistration command for revocation
+   */
+  void
+  afterRevokeFail(uint32_t code, const std::string& reason,
+                  const ndn::nfd::ControlParameters& parameters,
+                  const ndn::nfd::CommandOptions& options);
+
+  /**
+   * @brief invoked when the refresh timer is triggered.
+   * @pre the PropagatedEntry is in PROPAGATED state
+   * @post the PropagatedEntry will be in PROPAGATING state
+   *
+   * call redoPropagation to handle this refresh request
+   *
+   * @param parameters the ControlParameters used by registration commands for propagation
+   * @param options the CommandOptions used by registration commands for propagation
+   */
+  void
+  onRefreshTimer(const ndn::nfd::ControlParameters& parameters,
+                 const ndn::nfd::CommandOptions& options);
+
+  /**
+   * @brief invoked when the retry timer is triggered.
+   * @pre the PropagatedEntry is in PROPAGATE_FAIL state
+   * @post the PropagatedEntry will be in PROPAGATING state
+   *
+   * call redoPropagation to handle this retry request
+   *
+   * @param parameters the ControlParameters used by registration commands for propagation
+   * @param options the CommandOptions used by registration commands for propagation
+   * @param retryWaitTime the current wait time before retrying propagation
+   */
+  void
+  onRetryTimer(const ndn::nfd::ControlParameters& parameters,
+               const ndn::nfd::CommandOptions& options,
+               time::seconds retryWaitTime);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  ndn::nfd::Controller& m_nfdController;
+  ndn::KeyChain& m_keyChain;
+  Rib& m_rib;
+  ndn::util::signal::ScopedConnection m_afterInsertConnection;
+  ndn::util::signal::ScopedConnection m_afterEraseConnection;
+  ndn::nfd::ControlParameters m_controlParameters;
+  ndn::nfd::CommandOptions m_commandOptions;
+  time::seconds m_refreshInterval;
+  time::seconds m_baseRetryWait;
+  time::seconds m_maxRetryWait;
+  bool m_hasConnectedHub;
+  PropagatedEntryList m_propagatedEntries;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_RIB_AUTO_PREFIX_PROPAGATOR_HPP
diff --git a/rib/propagated-entry.cpp b/rib/propagated-entry.cpp
new file mode 100644
index 0000000..8dff54a
--- /dev/null
+++ b/rib/propagated-entry.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/logger.hpp"
+#include "propagated-entry.hpp"
+
+namespace nfd {
+namespace rib {
+
+void
+operator<<(std::ostream& out, PropagationStatus status)
+{
+  switch (status) {
+  case PropagationStatus::NEW:
+    out << "NEW";
+    break;
+  case PropagationStatus::PROPAGATING:
+    out << "PROPAGATING";
+    break;
+  case PropagationStatus::PROPAGATED:
+    out << "PROPAGATED";
+    break;
+  case PropagationStatus::PROPAGATE_FAIL:
+    out << "PROPAGATE_FAIL";
+    break;
+  default:
+    out << "undefined status";
+    break;
+  }
+}
+
+PropagatedEntry::PropagatedEntry()
+  : m_propagationStatus(PropagationStatus::NEW)
+{
+}
+
+PropagatedEntry::PropagatedEntry(const PropagatedEntry& other)
+  : m_signingIdentity(other.m_signingIdentity)
+  , m_propagationStatus(other.m_propagationStatus)
+{
+  BOOST_ASSERT(!other.isPropagated() && !other.isPropagateFail());
+}
+
+PropagatedEntry&
+PropagatedEntry::setSigningIdentity(const Name& identity)
+{
+  m_signingIdentity = identity;
+  return *this;
+}
+
+const Name&
+PropagatedEntry::getSigningIdentity() const
+{
+  return m_signingIdentity;
+}
+
+void
+PropagatedEntry::startPropagation()
+{
+  m_propagationStatus = PropagationStatus::PROPAGATING;
+}
+
+void
+PropagatedEntry::succeed(const scheduler::EventId& event)
+{
+  m_propagationStatus = PropagationStatus::PROPAGATED;
+  m_rePropagateEvent = event;
+}
+
+void
+PropagatedEntry::fail(const scheduler::EventId& event)
+{
+  m_propagationStatus = PropagationStatus::PROPAGATE_FAIL;
+  m_rePropagateEvent = event;
+}
+
+void
+PropagatedEntry::initialize()
+{
+  m_propagationStatus = PropagationStatus::NEW;
+  m_rePropagateEvent.cancel();
+}
+
+bool
+PropagatedEntry::isNew() const
+{
+  return PropagationStatus::NEW == m_propagationStatus;
+}
+
+bool
+PropagatedEntry::isPropagating() const
+{
+  return PropagationStatus::PROPAGATING == m_propagationStatus;
+}
+
+bool
+PropagatedEntry::isPropagated() const
+{
+  return PropagationStatus::PROPAGATED == m_propagationStatus;
+}
+
+bool
+PropagatedEntry::isPropagateFail() const
+{
+  return PropagationStatus::PROPAGATE_FAIL == m_propagationStatus;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/propagated-entry.hpp b/rib/propagated-entry.hpp
new file mode 100644
index 0000000..f4e61dd
--- /dev/null
+++ b/rib/propagated-entry.hpp
@@ -0,0 +1,158 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_RIB_PROPAGATED_ENTRY_HPP
+#define NFD_RIB_PROPAGATED_ENTRY_HPP
+
+#include "core/scheduler.hpp"
+
+namespace nfd {
+namespace rib {
+
+enum class PropagationStatus {
+  /// initial status
+  NEW,
+  /// is being propagated
+  PROPAGATING,
+  /// has been propagated successfully
+  PROPAGATED,
+  /// has failed in propagation
+  PROPAGATE_FAIL
+};
+
+void
+operator<<(std::ostream& out, PropagationStatus status);
+
+/**
+ * @brief represents an entry for prefix propagation.
+ * @sa http://redmine.named-data.net/issues/3211
+ *
+ * it consists of a PropagationStatus indicates current state of the state machine, as
+ * well as an event scheduled for refresh or retry (i.e., the RefreshTimer and the RetryTimer of
+ * the state machine respectively). Besides, it stores a copy of signing identity for this entry.
+ */
+class PropagatedEntry
+{
+public:
+  PropagatedEntry();
+
+  /**
+   * @pre other is not in PROPAGATED or PROPAGATE_FAIL state
+   */
+  PropagatedEntry(const PropagatedEntry& other);
+
+  PropagatedEntry&
+  operator=(const PropagatedEntry& other) = delete;
+
+  /**
+   * @brief set the signing identity
+   */
+  PropagatedEntry&
+  setSigningIdentity(const Name& identity);
+
+  /**
+   * @brief get the signing identity
+   *
+   * @return the signing identity
+   */
+  const Name&
+  getSigningIdentity() const;
+
+  /**
+   * @brief switch the propagation status to PROPAGATING.
+   *
+   * this is called before start the propagation process of this entry.
+   */
+  void
+  startPropagation();
+
+  /**
+   * @brief switch the propagation status to PROPAGATED, and set the
+   *        rePropagateEvent to @p event for refresh.
+   *
+   * this is called just after this entry is successfully propagated.
+   */
+  void
+  succeed(const scheduler::EventId& event);
+
+  /**
+   * @brief switch the propagation status to PROPAGATE_FAIL, and then set the
+   *        rePropagateEvent to @p event for retry.
+   *
+   * this is called just after propagation for this entry fails.
+   */
+  void
+  fail(const scheduler::EventId& event);
+
+  /**
+   * @brief cancel the events of re-sending propagation commands.
+   *
+   * switch the propagation status to NEW.
+   */
+  void
+  initialize();
+
+  /**
+   * @brief check whether this entry is a new entry.
+   *
+   * @return true if current status is NEW.
+   */
+  bool
+  isNew() const;
+
+  /**
+   * @brief check whether this entry is being propagated.
+   *
+   * @return true if current status is PROPAGATING.
+   */
+  bool
+  isPropagating() const;
+
+  /**
+   * @brief check whether this entry has been successfully propagated.
+   *
+   * @return true if current status is PROPAGATED.
+   */
+  bool
+  isPropagated() const;
+
+  /**
+   * @brief check whether this entry has failed in propagating.
+   *
+   * @return true if current status is PROPAGATE_FAIL.
+   */
+  bool
+  isPropagateFail() const;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  Name m_signingIdentity;
+  scheduler::ScopedEventId m_rePropagateEvent;
+  PropagationStatus m_propagationStatus;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_RIB_PROPAGATED_ENTRY_HPP
diff --git a/rib/remote-registrator.cpp b/rib/remote-registrator.cpp
deleted file mode 100644
index d33ba1b..0000000
--- a/rib/remote-registrator.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "remote-registrator.hpp"
-#include "core/logger.hpp"
-#include "core/scheduler.hpp"
-#include <ndn-cxx/security/signing-helpers.hpp>
-
-namespace nfd {
-namespace rib {
-
-NFD_LOG_INIT("RemoteRegistrator");
-
-using ndn::nfd::ControlParameters;
-using ndn::nfd::CommandOptions;
-
-const Name RemoteRegistrator::LOCAL_REGISTRATION_PREFIX = "/localhost";
-const Name RemoteRegistrator::REMOTE_HUB_PREFIX = "/localhop/nfd";
-const name::Component RemoteRegistrator::IGNORE_COMMPONENT("rib");
-
-RemoteRegistrator::RemoteRegistrator(ndn::nfd::Controller& controller,
-                                     ndn::KeyChain& keyChain,
-                                     Rib& rib)
-  : m_nfdController(controller)
-  , m_keyChain(keyChain)
-  , m_rib(rib)
-  , m_refreshInterval(time::seconds(25))
-  , m_hasConnectedHub(false)
-  , m_nRetries(0)
-{
-}
-
-RemoteRegistrator::~RemoteRegistrator()
-{
-  // cancel all periodically refresh events.
-  for (auto&& entry : m_regEntries)
-    {
-      scheduler::cancel(entry.second);
-    }
-}
-
-void
-RemoteRegistrator::loadConfig(const ConfigSection& configSection)
-{
-  size_t cost = 15, timeout = 10000;
-  size_t retry = 0;
-  size_t interval = 0;
-  const size_t intervalDef = 25, intervalMax = 600;
-
-  NFD_LOG_INFO("Load remote_register section in rib section");
-  for (auto&& i : configSection)
-    {
-      if (i.first == "cost")
-        {
-          cost = i.second.get_value<size_t>();
-        }
-      else if (i.first == "timeout")
-        {
-          timeout = i.second.get_value<size_t>();
-        }
-      else if (i.first == "retry")
-        {
-          retry = i.second.get_value<size_t>();
-        }
-      else if (i.first == "refresh_interval")
-        {
-          interval = i.second.get_value<size_t>();
-        }
-      else
-        {
-          BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" + i.first +
-                                                  "\" in \"remote-registrator\" section"));
-        }
-    }
-
-   m_controlParameters
-     .setCost(cost)
-     .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT)// set origin to client.
-     .setFaceId(0);// the remote hub will take the input face as the faceId.
-
-   m_commandOptions
-     .setPrefix(REMOTE_HUB_PREFIX)
-     .setTimeout(time::milliseconds(timeout));
-
-   m_nRetries = retry;
-
-   if (interval == 0)
-     {
-       interval = intervalDef;
-     }
-
-   interval = std::min(interval, intervalMax);
-
-   m_refreshInterval = time::seconds(interval);
-}
-
-void
-RemoteRegistrator::enable()
-{
-  // do remote registration after an entry is inserted into the RIB.
-  m_afterInsertConnection =
-    m_rib.afterInsertEntry.connect([this] (const Name& prefix) {
-        registerPrefix(prefix);
-      });
-
-  // do remote unregistration after an entry is erased from the RIB.
-  m_afterEraseConnection =
-    m_rib.afterEraseEntry.connect([this] (const Name& prefix) {
-        unregisterPrefix(prefix);
-      });
-}
-
-void
-RemoteRegistrator::disable()
-{
-  m_afterInsertConnection.disconnect();
-  m_afterEraseConnection.disconnect();
-}
-
-void
-RemoteRegistrator::registerPrefix(const Name& prefix)
-{
-  if (LOCAL_REGISTRATION_PREFIX.isPrefixOf(prefix))
-    {
-      NFD_LOG_INFO("local registration only for " << prefix);
-      return;
-    }
-
-  bool isHubPrefix = prefix == REMOTE_HUB_PREFIX;
-
-  if (isHubPrefix)
-    {
-      NFD_LOG_INFO("this is a prefix registered by some hub: " << prefix);
-
-      m_hasConnectedHub = true;
-
-      redoRegistration();
-      return;
-    }
-
-  if (!m_hasConnectedHub)
-    {
-      NFD_LOG_INFO("no hub connected when registering " << prefix);
-      return;
-    }
-
-  std::pair<Name, size_t> identity = findIdentityForRegistration(prefix);
-
-  if (0 == identity.second)
-    {
-      NFD_LOG_INFO("no proper identity found for registering " << prefix);
-      return;
-    }
-
-  Name prefixForRegistration;
-  if (identity.first.size() == identity.second)
-    {
-      prefixForRegistration = identity.first;
-    }
-  else
-    {
-      prefixForRegistration = identity.first.getPrefix(-1);
-    }
-
-  if (m_regEntries.find(prefixForRegistration) != m_regEntries.end())
-    {
-      NFD_LOG_INFO("registration already in process for " << prefix);
-      return;
-    }
-
-  // make copies of m_controlParameters and m_commandOptions to
-  // avoid unreasonable overwriting during concurrent registration
-  // and unregistration.
-  ControlParameters parameters = m_controlParameters;
-  CommandOptions    options    = m_commandOptions;
-
-  startRegistration(parameters.setName(prefixForRegistration),
-                    options.setSigningInfo(signingByIdentity(identity.first)),
-                    m_nRetries);
-}
-
-void
-RemoteRegistrator::unregisterPrefix(const Name& prefix)
-{
-  if (prefix == REMOTE_HUB_PREFIX)
-    {
-      NFD_LOG_INFO("disconnected to hub with prefix: " << prefix);
-
-      // for phase 1: suppose there is at most one hub connected.
-      // if the hub prefix has been unregistered locally, there may
-      // be no connected hub.
-      m_hasConnectedHub = false;
-
-      clearRefreshEvents();
-      return;
-    }
-
-  if (!m_hasConnectedHub)
-    {
-      NFD_LOG_INFO("no hub connected when unregistering " << prefix);
-      return;
-    }
-
-  std::pair<Name, size_t> identity = findIdentityForRegistration(prefix);
-
-  if (0 == identity.second)
-    {
-      NFD_LOG_INFO("no proper identity found for unregistering " << prefix);
-      return;
-    }
-
-  Name prefixForRegistration;
-  if (identity.first.size() == identity.second)
-    {
-      prefixForRegistration = identity.first;
-    }
-  else
-    {
-      prefixForRegistration = identity.first.getPrefix(-1);
-    }
-
-  RegisteredEntryIt iRegEntry = m_regEntries.find(prefixForRegistration);
-  if (m_regEntries.end() == iRegEntry)
-    {
-      NFD_LOG_INFO("no existing entry found when unregistering " << prefix);
-      return;
-    }
-
-  for (auto&& entry : m_rib)
-    {
-      if (prefixForRegistration.isPrefixOf(entry.first) &&
-          findIdentityForRegistration(entry.first) == identity)
-        {
-          NFD_LOG_INFO("this identity should be kept for other rib entry: "
-                       << entry.first);
-          return;
-        }
-    }
-
-  scheduler::cancel(iRegEntry->second);
-  m_regEntries.erase(iRegEntry);
-
-  // make copies of m_controlParameters and m_commandOptions to
-  // avoid unreasonable overwriting during concurrent registration
-  // and unregistration.
-  ControlParameters parameters = m_controlParameters;
-  CommandOptions    options    = m_commandOptions;
-
-  startUnregistration(parameters.setName(prefixForRegistration).unsetCost(),
-                      options.setSigningInfo(signingByIdentity(identity.first)),
-                      m_nRetries);
-}
-
-std::pair<Name, size_t>
-RemoteRegistrator::findIdentityForRegistration(const Name& prefix)
-{
-  std::pair<Name, size_t> candidateIdentity;
-  std::vector<Name> identities;
-  bool isPrefix = false;
-  size_t maxLength = 0, curLength = 0;
-
-  // get all identies from the key-cahin except the default one.
-  m_keyChain.getAllIdentities(identities, false);
-
-  // get the default identity.
-  identities.push_back(m_keyChain.getDefaultIdentity());
-
-  // longest prefix matching to all indenties.
-  for (auto&& i : identities)
-    {
-      if (!i.empty() && IGNORE_COMMPONENT == i.at(-1))
-        {
-          isPrefix = i.getPrefix(-1).isPrefixOf(prefix);
-          curLength = i.size() - 1;
-        }
-      else
-        {
-          isPrefix = i.isPrefixOf(prefix);
-          curLength = i.size();
-        }
-
-      if (isPrefix && curLength > maxLength)
-        {
-          candidateIdentity.first = i;
-          maxLength = curLength;
-        }
-    }
-
-  candidateIdentity.second = maxLength;
-
-  return candidateIdentity;
-}
-
-void
-RemoteRegistrator::startRegistration(const ControlParameters& parameters,
-                                     const CommandOptions& options,
-                                     int nRetries)
-{
-  NFD_LOG_INFO("start register " << parameters.getName());
-
-  m_nfdController.start<ndn::nfd::RibRegisterCommand>(
-     parameters,
-     bind(&RemoteRegistrator::onRegSuccess,
-          this, parameters, options),
-     bind(&RemoteRegistrator::onRegFailure,
-          this, _1, _2, parameters, options, nRetries),
-     options);
-}
-
-void
-RemoteRegistrator::startUnregistration(const ControlParameters& parameters,
-                                       const CommandOptions& options,
-                                       int nRetries)
-{
-  NFD_LOG_INFO("start unregister " << parameters.getName());
-
-  m_nfdController.start<ndn::nfd::RibUnregisterCommand>(
-     parameters,
-     bind(&RemoteRegistrator::onUnregSuccess,
-          this, parameters, options),
-     bind(&RemoteRegistrator::onUnregFailure,
-          this, _1, _2, parameters, options, nRetries),
-     options);
-}
-
-void
-RemoteRegistrator::onRegSuccess(const ControlParameters& parameters,
-                                const CommandOptions& options)
-{
-  NFD_LOG_INFO("success to register " << parameters.getName());
-
-  RegisteredEntryIt iRegEntry = m_regEntries.find(parameters.getName());
-
-  if (m_regEntries.end() != iRegEntry)
-    {
-      NFD_LOG_DEBUG("Existing Entry: (" << iRegEntry->first
-                                        << ", " << iRegEntry->second
-                                        << ")");
-
-      scheduler::cancel(iRegEntry->second);
-      iRegEntry->second = scheduler::schedule(
-                            m_refreshInterval,
-                            bind(&RemoteRegistrator::startRegistration,
-                                 this, parameters, options, m_nRetries));
-    }
-  else
-    {
-      NFD_LOG_DEBUG("New Entry");
-      m_regEntries.insert(RegisteredEntry(
-                              parameters.getName(),
-                              scheduler::schedule(
-                                m_refreshInterval,
-                                bind(&RemoteRegistrator::startRegistration,
-                                     this, parameters, options, m_nRetries))));
-    }
-}
-
-void
-RemoteRegistrator::onRegFailure(uint32_t code, const std::string& reason,
-                                const ControlParameters& parameters,
-                                const CommandOptions& options,
-                                int nRetries)
-{
-  NFD_LOG_INFO("fail to register " << parameters.getName()
-                                   << "\n\t reason:" << reason
-                                   << "\n\t remain retries:" << nRetries);
-
-  if (nRetries > 0)
-    {
-      startRegistration(parameters, options, nRetries - 1);
-    }
-}
-
-void
-RemoteRegistrator::onUnregSuccess(const ControlParameters& parameters,
-                                  const CommandOptions& options)
-{
-  NFD_LOG_INFO("success to unregister " << parameters.getName());
-}
-
-void
-RemoteRegistrator::onUnregFailure(uint32_t code, const std::string& reason,
-                                  const ControlParameters& parameters,
-                                  const CommandOptions& options,
-                                  int nRetries)
-{
-  NFD_LOG_INFO("fail to unregister " << parameters.getName()
-                                     << "\n\t reason:" << reason
-                                     << "\n\t remain retries:" << nRetries);
-
-  if (nRetries > 0)
-    {
-      startUnregistration(parameters, options, nRetries - 1);
-    }
-}
-
-void
-RemoteRegistrator::redoRegistration()
-{
-  NFD_LOG_INFO("redo " << m_regEntries.size()
-                       << " registration when new Hub connection is built.");
-
-  for (auto&& entry : m_regEntries)
-    {
-      // make copies to avoid unreasonable overwrite.
-      ControlParameters parameters = m_controlParameters;
-      CommandOptions    options    = m_commandOptions;
-      startRegistration(parameters.setName(entry.first),
-                        options.setSigningInfo(signingByIdentity(entry.first)),
-                        m_nRetries);
-    }
-}
-
-void
-RemoteRegistrator::clearRefreshEvents()
-{
-  for (auto&& entry : m_regEntries)
-    {
-      scheduler::cancel(entry.second);
-    }
-}
-
-} // namespace rib
-} // namespace nfd
diff --git a/rib/remote-registrator.hpp b/rib/remote-registrator.hpp
deleted file mode 100644
index 2eea754..0000000
--- a/rib/remote-registrator.hpp
+++ /dev/null
@@ -1,250 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef NFD_RIB_REMOTE_REGISTRATOR_HPP
-#define NFD_RIB_REMOTE_REGISTRATOR_HPP
-
-#include "rib.hpp"
-#include "core/config-file.hpp"
-#include "rib-status-publisher.hpp"
-
-#include <unordered_map>
-#include <ndn-cxx/security/key-chain.hpp>
-#include <ndn-cxx/management/nfd-controller.hpp>
-#include <ndn-cxx/management/nfd-control-command.hpp>
-#include <ndn-cxx/management/nfd-control-parameters.hpp>
-#include <ndn-cxx/management/nfd-command-options.hpp>
-#include <ndn-cxx/util/signal.hpp>
-
-namespace nfd {
-namespace rib {
-
-/**
- * @brief define the RemoteRegistrator class, which handles
- *        the registration/unregistration to remote hub(s).
- */
-class RemoteRegistrator : noncopyable
-{
-public:
-  class Error : public std::runtime_error
-  {
-  public:
-    explicit
-    Error(const std::string& what)
-      : std::runtime_error(what)
-    {
-    }
-  };
-
-  RemoteRegistrator(ndn::nfd::Controller& controller,
-                    ndn::KeyChain& keyChain,
-                    Rib& rib);
-
-  ~RemoteRegistrator();
-
-  /**
-   * @brief load the "remote_register" section from config file
-   *
-   * @param configSection the sub section in "rib" section.
-   */
-  void
-  loadConfig(const ConfigSection& configSection);
-
-  /**
-   * @brief enable remote registration/unregistration.
-   *
-   */
-  void
-  enable();
-
-  /**
-   * @brief disable remote registration/unregistration.
-   *
-   */
-  void
-  disable();
-
-  /**
-   * @brief register a prefix to remote hub(s).
-   *
-   * For the input prefix, we find the longest identity
-   * in the key-chain that can sign it, and then
-   * register this identity to remote hub(s).
-   *
-   * @param prefix the prefix being registered in local RIB.
-   */
-  void
-  registerPrefix(const Name& prefix);
-
-  /**
-   * @brief unregister a prefix from remote hub(s).
-   *
-   * For the input prefix, if the longest identity can sign it
-   * is already registered remotely, that identity should be
-   * unregistered from remote hub(s).
-   *
-   * @param prefix the prefix being unregistered in local RIB.
-   */
-  void
-  unregisterPrefix(const Name& prefix);
-
-private:
-  /**
-   * @brief find the most proper identity that can sign the
-   *        registration/unregistration command for the input prefix.
-   *
-   * @return the identity and the length of the longest match to the
-   *         input prefix.
-   *
-   * @retval { ignored, 0 } no matching identity
-   */
-  std::pair<Name, size_t>
-  findIdentityForRegistration(const Name& prefix);
-
-  /**
-   * @brief make and send the remote registration command.
-   *
-   * @param nRetries remaining number of retries.
-   */
-  void
-  startRegistration(const ndn::nfd::ControlParameters& parameters,
-                    const ndn::nfd::CommandOptions& options,
-                    int nRetries);
-
-  /**
-   * @brief make and send the remote unregistration command.
-   *
-   * @param nRetries remaining number of retries.
-   */
-  void
-  startUnregistration(const ndn::nfd::ControlParameters& parameters,
-                      const ndn::nfd::CommandOptions& options,
-                      int nRetries);
-  /**
-   * @brief refresh the remotely registered entry if registration
-   *        successes by re-sending the registration command.
-   *
-   * The interval of sending refresh command is defined in the
-   * "remote_register" section of the config file.
-   *
-   * @param parameters the same paremeters from startRegistration.
-   * @param options the same options as in startRegistration.
-   */
-  void
-  onRegSuccess(const ndn::nfd::ControlParameters& parameters,
-               const ndn::nfd::CommandOptions& options);
-
-  /**
-   * @brief retry to send registration command if registration fails.
-   *
-   * the number of retries is defined in the "remote_register"
-   * section of the config file.
-   *
-   * @param code error code.
-   * @param reason error reason in string.
-   * @param parameters the same paremeters from startRegistration.
-   * @param options the same options from startRegistration.
-   * @param nRetries remaining number of retries.
-   */
-  void
-  onRegFailure(uint32_t code, const std::string& reason,
-               const ndn::nfd::ControlParameters& parameters,
-               const ndn::nfd::CommandOptions& options,
-               int nRetries);
-
-  void
-  onUnregSuccess(const ndn::nfd::ControlParameters& parameters,
-                 const ndn::nfd::CommandOptions& options);
-
-  /**
-   * @brief retry to send unregistration command if registration fails.
-   *
-   * the number of retries is defined in the "remote_register"
-   * section of the config file.
-   *
-   * @param code error code.
-   * @param reason error reason in string.
-   * @param parameters the same paremeters as in startRegistration.
-   * @param options the same options as in startRegistration.
-   * @param nRetries remaining number of retries.
-   */
-  void
-  onUnregFailure(uint32_t code, const std::string& reason,
-                 const ndn::nfd::ControlParameters& parameters,
-                 const ndn::nfd::CommandOptions& options,
-                 int nRetries);
-
-  /**
-   * @brief re-register all prefixes
-   *
-   * This is called when a HUB connection is established.
-   */
-  void
-  redoRegistration();
-
-  /**
-   * @brief clear all refresh events
-   *
-   * This is called when all HUB connections are lost.
-   */
-  void
-  clearRefreshEvents();
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  /**
-   * When a locally registered prefix triggles remote
-   * registration, we actually register the longest
-   * identity that can sign this prefix to remote hub(s).
-   *
-   * Thus, the remotely reigstered prefix does not equal
-   * to Route Name. So it needs seperate sotrage instead
-   * of storing within the RIB.
-   */
-  typedef std::unordered_map<Name, scheduler::EventId> RegisteredList;
-  typedef RegisteredList::iterator RegisteredEntryIt;
-  typedef RegisteredList::value_type RegisteredEntry;
-  RegisteredList m_regEntries;
-
-private:
-  ndn::nfd::Controller& m_nfdController;
-  ndn::KeyChain& m_keyChain;
-  Rib& m_rib;
-  ndn::util::signal::ScopedConnection m_afterInsertConnection;
-  ndn::util::signal::ScopedConnection m_afterEraseConnection;
-  ndn::nfd::ControlParameters m_controlParameters;
-  ndn::nfd::CommandOptions m_commandOptions;
-  time::seconds m_refreshInterval;
-  bool m_hasConnectedHub;
-  int m_nRetries;
-
-  static const Name LOCAL_REGISTRATION_PREFIX; // /localhost
-  static const Name REMOTE_HUB_PREFIX; // /localhop/nfd
-  static const name::Component IGNORE_COMMPONENT; // rib
-};
-
-} // namespace rib
-} // namespace nfd
-
-#endif // NFD_RIB_REMOTE_REGISTRATOR_HPP
diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp
index 91f0eda..33d1622 100644
--- a/rib/rib-manager.cpp
+++ b/rib/rib-manager.cpp
@@ -81,7 +81,7 @@
   , m_localhopValidator(m_face)
   , m_faceMonitor(m_face)
   , m_isLocalhopEnabled(false)
-  , m_remoteRegistrator(m_nfdController, m_keyChain, m_managedRib)
+  , m_prefixPropagator(m_nfdController, m_keyChain, m_managedRib)
   , m_ribStatusPublisher(m_managedRib, face, LIST_COMMAND_PREFIX, m_keyChain)
   , m_fibUpdater(m_managedRib, m_nfdController)
   , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
@@ -145,7 +145,7 @@
                      bool isDryRun,
                      const std::string& filename)
 {
-  bool isRemoteRegisterEnabled = false;
+  bool isAutoPrefixPropagatorEnabled = false;
 
   for (const auto& item : configSection) {
     if (item.first == "localhost_security") {
@@ -155,24 +155,24 @@
       m_localhopValidator.load(item.second, filename);
       m_isLocalhopEnabled = true;
     }
-    else if (item.first == "remote_register") {
-      m_remoteRegistrator.loadConfig(item.second);
-      isRemoteRegisterEnabled = true;
+    else if (item.first == "auto_prefix_propagate") {
+      m_prefixPropagator.loadConfig(item.second);
+      isAutoPrefixPropagatorEnabled = true;
 
       // Avoid other actions when isDryRun == true
       if (isDryRun) {
         continue;
       }
 
-      m_remoteRegistrator.enable();
+      m_prefixPropagator.enable();
     }
     else {
       BOOST_THROW_EXCEPTION(Error("Unrecognized rib property: " + item.first));
     }
   }
 
-  if (!isRemoteRegisterEnabled) {
-    m_remoteRegistrator.disable();
+  if (!isAutoPrefixPropagatorEnabled) {
+    m_prefixPropagator.disable();
   }
 }
 
diff --git a/rib/rib-manager.hpp b/rib/rib-manager.hpp
index e38ee3c..d7c6819 100644
--- a/rib/rib-manager.hpp
+++ b/rib/rib-manager.hpp
@@ -29,7 +29,7 @@
 #include "rib.hpp"
 #include "core/config-file.hpp"
 #include "rib-status-publisher.hpp"
-#include "remote-registrator.hpp"
+#include "auto-prefix-propagator.hpp"
 #include "fib-updater.hpp"
 
 #include <ndn-cxx/security/validator-config.hpp>
@@ -207,7 +207,7 @@
   ndn::ValidatorConfig m_localhopValidator;
   ndn::nfd::FaceMonitor m_faceMonitor;
   bool m_isLocalhopEnabled;
-  RemoteRegistrator m_remoteRegistrator;
+  AutoPrefixPropagator m_prefixPropagator;
 
   RibStatusPublisher m_ribStatusPublisher;
 
diff --git a/tests/rib/auto-prefix-propagator.t.cpp b/tests/rib/auto-prefix-propagator.t.cpp
new file mode 100644
index 0000000..67e7b1b
--- /dev/null
+++ b/tests/rib/auto-prefix-propagator.t.cpp
@@ -0,0 +1,671 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib/auto-prefix-propagator.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/identity-management-fixture.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace nfd {
+namespace rib {
+namespace tests {
+
+NFD_LOG_INIT("AutoPrefixPropagatorTest");
+
+const Name TEST_LINK_LOCAL_NFD_PREFIX("/localhop/nfd");
+const time::milliseconds TEST_PREFIX_PROPAGATION_TIMEOUT(1000);
+
+class AutoPrefixPropagatorFixture : public nfd::tests::IdentityManagementFixture
+                                  , public nfd::tests::UnitTestTimeFixture
+{
+public:
+  AutoPrefixPropagatorFixture()
+    : m_face(ndn::util::makeDummyClientFace(nfd::tests::UnitTestTimeFixture::g_io, {true, true}))
+    , m_controller(*m_face, m_keyChain)
+    , m_propagator(m_controller, m_keyChain, m_rib)
+    , m_requests(m_face->sentInterests)
+    , m_entries(m_propagator.m_propagatedEntries)
+  {
+    m_propagator.enable();
+    m_propagator.m_controlParameters
+      .setCost(15)
+      .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT)// set origin to client.
+      .setFaceId(0);// the remote hub will take the input face as the faceId.
+    m_propagator.m_commandOptions
+      .setPrefix(TEST_LINK_LOCAL_NFD_PREFIX)
+      .setTimeout(TEST_PREFIX_PROPAGATION_TIMEOUT);
+  }
+
+public: // helpers for test
+  bool
+  insertEntryToRib(const Name& name, const uint64_t& faceId = 0)
+  {
+    if (m_rib.find(name) != m_rib.end()) {
+      NFD_LOG_INFO("RIB entry already exists: " << name);
+      return false;
+    }
+
+    Route route;
+    route.faceId = faceId;
+    m_rib.insert(name, route);
+    advanceClocks(time::milliseconds(1));
+
+    return m_rib.find(name) != m_rib.end(); // return whether afterInserEntry will be triggered
+  }
+
+  bool
+  eraseEntryFromRib(const Name& name)
+  {
+    if (m_rib.find(name) == m_rib.end()) {
+      NFD_LOG_INFO("RIB entry does not exist: " << name);
+      return false;
+    }
+
+    std::vector<Route> routeList;
+    std::copy(m_rib.find(name)->second->begin(), m_rib.find(name)->second->end(),
+              std::back_inserter(routeList));
+    for (auto&& route : routeList) {
+      m_rib.erase(name, route);
+    }
+    advanceClocks(time::milliseconds(1));
+
+    return m_rib.find(name) == m_rib.end(); // return whether afterEraseEntry will be triggered
+  }
+
+  void
+  connectToHub()
+  {
+    insertEntryToRib("/localhop/nfd");
+  }
+
+  void
+  disconnectFromHub()
+  {
+    eraseEntryFromRib("/localhop/nfd");
+  }
+
+public: // helpers for check
+  enum class CheckRequestResult {
+    OK,
+    OUT_OF_BOUNDARY,
+    INVALID_N_COMPONENTS,
+    WRONG_COMMAND_PREFIX,
+    WRONG_VERB,
+    INVALID_PARAMETERS,
+    WRONG_REGISTERING_PREFIX
+  };
+
+  /**
+   * @brief check a request at specified index
+   *
+   * @param idx the index of the specified request in m_requests
+   * @param verb the expected verb of request
+   * @param registeringPrefix the expected registering prefix of request
+
+   * @retval OK the specified request has a valid name, the right verb, a valid ControlParameters
+   *            and the right registering prefix
+   * @retval OUT_OF_BOUNDARY the specified index out of boundary
+   * @retval INVALID_N_COMPONENTS the number of components of the request name is invalid
+   * @retval WRONG_COMMAND_PREFIX the command prefix of the request is wrong
+   * @retval WRONG_VERB the command verb of the request is wrong
+   * @retval INVALID_PARAMETERS no valid parameters can be decoded from the request's name
+   * @retval WRONG_REGISTERING_PREFIX the registering prefix of the request is wrong
+   */
+  CheckRequestResult
+  checkRequest(size_t idx, const std::string& verb, const Name& registeringPrefix)
+  {
+    Name requestName;
+    try {
+      requestName = m_requests.at(idx).getName();
+    }
+    catch (const std::out_of_range&) {
+      return CheckRequestResult::OUT_OF_BOUNDARY;
+    }
+
+    if (requestName.size() < 5) {
+      return CheckRequestResult::INVALID_N_COMPONENTS;
+    }
+
+    if (requestName.getPrefix(2) != TEST_LINK_LOCAL_NFD_PREFIX) {
+      return CheckRequestResult::WRONG_COMMAND_PREFIX;
+    }
+
+    if (requestName.get(3) != Name::Component(verb)) {
+      return CheckRequestResult::WRONG_VERB;
+    }
+
+    ControlParameters parameters;
+    try {
+      parameters.wireDecode(requestName.get(4).blockFromValue());
+    }
+    catch (const tlv::Error&) {
+      return CheckRequestResult::INVALID_PARAMETERS;
+    }
+
+    if (parameters.getName() != registeringPrefix) {
+      return CheckRequestResult::WRONG_REGISTERING_PREFIX;
+    }
+
+    return CheckRequestResult::OK;
+  }
+
+protected:
+  shared_ptr<ndn::util::DummyClientFace>     m_face;
+  ndn::nfd::Controller                       m_controller;
+  Rib                                        m_rib;
+  AutoPrefixPropagator                       m_propagator;
+  std::vector<Interest>&                     m_requests; // store sent out requests
+  AutoPrefixPropagator::PropagatedEntryList& m_entries; // store propagated entries
+};
+
+std::ostream&
+operator<<(std::ostream &os, const AutoPrefixPropagatorFixture::CheckRequestResult& result)
+{
+  switch (result) {
+  case AutoPrefixPropagatorFixture::CheckRequestResult::OK:
+    os << "OK";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::OUT_OF_BOUNDARY:
+    os << "OUT_OF_BOUNDARY";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::INVALID_N_COMPONENTS:
+    os << "INVALID_N_COMPONENTS";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::WRONG_COMMAND_PREFIX:
+    os << "WRONG_COMMAND_PREFIX";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::WRONG_VERB:
+    os << "WRONG_VERB";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::INVALID_PARAMETERS:
+    os << "INVALID_PARAMETERS";
+    break;
+  case AutoPrefixPropagatorFixture::CheckRequestResult::WRONG_REGISTERING_PREFIX:
+    os << "WRONG_REGISTERING_PREFIX";
+    break;
+  default:
+    break;
+  }
+
+  return os;
+}
+
+BOOST_AUTO_TEST_SUITE(Rib)
+
+BOOST_FIXTURE_TEST_SUITE(TestAutoPrefixPropagator, AutoPrefixPropagatorFixture)
+
+BOOST_AUTO_TEST_CASE(EnableDisable)
+{
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  auto testPropagateRevokeBasic = [this] () -> bool {
+    m_propagator.m_propagatedEntries.clear();
+
+    if (!insertEntryToRib("/test/A/app")) {
+      return false;
+    }
+    m_entries["/test/A"].succeed(nullptr);
+    if (!eraseEntryFromRib("/test/A/app")) {
+      return false;
+    }
+    return true;
+  };
+
+  m_propagator.disable();
+  BOOST_REQUIRE(testPropagateRevokeBasic());
+  BOOST_CHECK(m_requests.empty());
+
+  m_propagator.enable();
+  BOOST_REQUIRE(testPropagateRevokeBasic());
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 2);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(1, "unregister", "/test/A"), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(LoadConfiguration)
+{
+  ConfigFile config;
+  config.addSectionHandler("auto_prefix_propagate",
+                           bind(&AutoPrefixPropagator::loadConfig, &m_propagator, _1));
+
+  const std::string CONFIG_STRING =
+    "auto_prefix_propagate\n"
+    "{\n"
+    "  cost 11\n"
+    "  timeout 22\n"
+    "  refresh_interval 33\n"
+    "  base_retry_wait 44\n"
+    "  max_retry_wait 55\n"
+    "}";
+  config.parse(CONFIG_STRING, true, "test-auto-prefix-propagator");
+
+  BOOST_CHECK_EQUAL(m_propagator.m_controlParameters.getCost(), 11);
+  BOOST_CHECK_EQUAL(m_propagator.m_controlParameters.getOrigin(), ndn::nfd::ROUTE_ORIGIN_CLIENT);
+  BOOST_CHECK_EQUAL(m_propagator.m_controlParameters.getFaceId(), 0);
+
+  BOOST_CHECK_EQUAL(m_propagator.m_commandOptions.getPrefix(), TEST_LINK_LOCAL_NFD_PREFIX);
+  BOOST_CHECK_EQUAL(m_propagator.m_commandOptions.getTimeout(), time::milliseconds(22));
+
+  BOOST_CHECK_EQUAL(m_propagator.m_refreshInterval, time::seconds(33));
+  BOOST_CHECK_EQUAL(m_propagator.m_baseRetryWait, time::seconds(44));
+  BOOST_CHECK_EQUAL(m_propagator.m_maxRetryWait, time::seconds(55));
+}
+
+BOOST_AUTO_TEST_SUITE(Helpers)
+
+BOOST_AUTO_TEST_CASE(GetPrefixPropagationParameters)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(addIdentity("/test/A/B"));
+  BOOST_REQUIRE(addIdentity("/test/C/nrd"));
+
+  auto parameters1 = m_propagator.getPrefixPropagationParameters("/none/A/B/app");
+  auto parameters2 = m_propagator.getPrefixPropagationParameters("/test/A/B/app");
+  auto parameters3 = m_propagator.getPrefixPropagationParameters("/test/C/D/app");
+
+  BOOST_CHECK(!parameters1.isValid);
+
+  BOOST_CHECK(parameters2.isValid);
+  BOOST_CHECK_EQUAL(parameters2.parameters.getName(), "/test/A");
+  BOOST_CHECK_EQUAL(parameters2.options.getSigningInfo().getSignerName(), "/test/A");
+
+  BOOST_CHECK(parameters3.isValid);
+  BOOST_CHECK_EQUAL(parameters3.parameters.getName(), "/test/C");
+  BOOST_CHECK_EQUAL(parameters3.options.getSigningInfo().getSignerName(), "/test/C/nrd");
+}
+
+BOOST_AUTO_TEST_CASE(CheckCurrentPropagatedPrefix)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(addIdentity("/test/B/nrd"));
+  BOOST_REQUIRE(addIdentity("/test/A/B"));
+
+  BOOST_CHECK(!m_propagator.doesCurrentPropagatedPrefixWork("/test/E")); // does not exist
+  BOOST_CHECK(!m_propagator.doesCurrentPropagatedPrefixWork("/test/A/B")); // has a better option
+  BOOST_CHECK(m_propagator.doesCurrentPropagatedPrefixWork("/test/A"));
+  BOOST_CHECK(m_propagator.doesCurrentPropagatedPrefixWork("/test/B"));
+}
+
+BOOST_AUTO_TEST_CASE(RedoPropagation)
+{
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(addIdentity("/test/B"));
+  BOOST_REQUIRE(addIdentity("/test/B/C"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app"));
+  BOOST_REQUIRE(insertEntryToRib("/test/B/C/app"));
+  BOOST_REQUIRE(insertEntryToRib("/test/B/D/app"));
+
+  auto testRedoPropagation = [this] (const Name& signingIdentity) {
+    m_requests.clear();
+
+    m_entries[signingIdentity].setSigningIdentity(signingIdentity);
+
+    auto parameters = m_propagator.m_controlParameters;
+    auto options = m_propagator.m_commandOptions;
+    ndn::security::SigningInfo info(ndn::security::SigningInfo::SIGNER_TYPE_ID, signingIdentity);
+    m_propagator.redoPropagation(m_entries.find(signingIdentity),
+                                  parameters.setName(signingIdentity),
+                                  options.setSigningInfo(info),
+                                  time::seconds(0));
+    advanceClocks(time::milliseconds(1));
+  };
+
+  testRedoPropagation("/test/A"); // current propagated prefix still works
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+  BOOST_CHECK(m_entries.find("test/A") != m_entries.end());
+
+  BOOST_CHECK_NO_THROW(m_keyChain.deleteIdentity("/test/B"));
+  testRedoPropagation("/test/B"); // signingIdentity no longer exists
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/B/C"), CheckRequestResult::OK);
+  BOOST_CHECK(m_entries.find("/test/B") == m_entries.end());
+  BOOST_CHECK(m_entries.find("/test/B/C") != m_entries.end());
+
+  testRedoPropagation("/test/B"); // no alternative identity
+  BOOST_CHECK(m_requests.empty());
+  BOOST_CHECK(m_entries.find("/test/B") == m_entries.end());
+
+  m_entries["/test/B/C"].succeed(nullptr);
+  testRedoPropagation("/test/B"); // alternative identity has been propagated
+  BOOST_CHECK(m_requests.empty());
+  BOOST_CHECK(m_entries.find("/test/B") == m_entries.end());
+
+  BOOST_REQUIRE(addIdentity("/test/B/"));
+  testRedoPropagation("/test/B/C"); // better option exists: /test/B
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/B"), CheckRequestResult::OK);
+  BOOST_CHECK(m_entries.find("test/B/C") == m_entries.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Helpers
+
+BOOST_AUTO_TEST_SUITE(PropagateRevokeSemantics)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  connectToHub(); // ensure connectivity to the hub
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app")); // ensure afterInsertEntry signal emitted
+  m_entries["/test/A"].succeed(nullptr); // ensure there is a valid entry inserted
+  BOOST_REQUIRE(eraseEntryFromRib("/test/A/app")); // ensure afterEraseEntry signal emitted
+
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 2);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(1, "unregister", "/test/A"), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(LocalPrefix)
+{
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/localhost/A"));
+
+  BOOST_REQUIRE(insertEntryToRib("/localhost/A/app"));
+  BOOST_CHECK(m_requests.empty());
+
+  m_propagator.m_propagatedEntries["/localhost/A"].succeed(nullptr);
+  BOOST_REQUIRE(eraseEntryFromRib("/localhost/A/app"));
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(InvalidPropagationParameters)
+{
+  connectToHub();
+
+  // no identity can be found
+  BOOST_CHECK(!m_propagator.getPrefixPropagationParameters("/test/A/app").isValid);
+
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app"));
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries["/test/A"].succeed(nullptr);
+  BOOST_REQUIRE(eraseEntryFromRib("/test/A/app"));
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(NoHubConnectivity)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app"));
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries["/test/A"].succeed(nullptr);
+  BOOST_REQUIRE(eraseEntryFromRib("/test/A/app"));
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(PropagatedEntryExists)
+{
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app1"));
+
+  m_requests.clear();
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app2")); // /test/A has been propagated by /test/A/app1
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(PropagatedEntryShouldKeep)
+{
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app1"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app2"));
+
+  m_requests.clear();
+  BOOST_REQUIRE(eraseEntryFromRib("/test/A/app2")); // /test/A should be kept for /test/A/app1
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(BackOffRetryPolicy)
+{
+  m_propagator.m_commandOptions.setTimeout(time::milliseconds(1));
+  m_propagator.m_baseRetryWait = time::seconds(1);
+  m_propagator.m_maxRetryWait = time::seconds(2);
+
+  connectToHub();
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app"));
+
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+
+  advanceClocks(time::milliseconds(1), 1050); // wait for the 1st retry
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 2);
+  BOOST_CHECK_EQUAL(checkRequest(1, "register", "/test/A"), CheckRequestResult::OK);
+
+  advanceClocks(time::milliseconds(1), 2050); // wait for the 2nd retry, 2 times
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 3);
+  BOOST_CHECK_EQUAL(checkRequest(2, "register", "/test/A"), CheckRequestResult::OK);
+
+  advanceClocks(time::milliseconds(1), 2050); // wait for the 3rd retry, reach the upper bound
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 4);
+  BOOST_CHECK_EQUAL(checkRequest(3, "register", "/test/A"), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // PropagateRevokeSemantics
+
+BOOST_AUTO_TEST_SUITE(PropagatedEntryStateChanges)
+
+BOOST_AUTO_TEST_CASE(AfterRibInsert)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  auto testAfterRibInsert = [this] (const Name& ribEntryPrefix) {
+    m_requests.clear();
+    m_propagator.m_propagatedEntries.clear(); // ensure entry does not exist
+
+    auto propagateParameters = m_propagator.getPrefixPropagationParameters(ribEntryPrefix);
+    m_propagator.afterRibInsert(propagateParameters.parameters, propagateParameters.options);
+    advanceClocks(time::milliseconds(1));
+  };
+
+  testAfterRibInsert("/test/A/app1");
+  BOOST_CHECK(m_requests.empty()); // no connectivity now
+  BOOST_CHECK(m_entries.find("/test/A") != m_entries.end());
+
+  connectToHub();
+  testAfterRibInsert("/test/A/app2");
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+  BOOST_CHECK(m_entries.find("/test/A") != m_entries.end());
+}
+
+BOOST_AUTO_TEST_CASE(AfterRibErase)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  auto testAfterRibInsert = [this] (const Name& localUnregPrefix) {
+    m_requests.clear();
+
+    auto propagateParameters = m_propagator.getPrefixPropagationParameters(localUnregPrefix);
+    m_propagator.afterRibErase(propagateParameters.parameters.unsetCost(),
+                               propagateParameters.options);
+    advanceClocks(time::milliseconds(1));
+  };
+
+  m_entries["/test/A"].succeed(nullptr);
+  testAfterRibInsert("/test/A/app");
+  BOOST_CHECK(m_requests.empty()); // no connectivity
+  BOOST_CHECK(m_entries.find("/test/A") == m_entries.end()); // has been erased
+
+  connectToHub();
+  m_entries["/test/A"].fail(nullptr);
+  testAfterRibInsert("/test/A/app");
+  BOOST_CHECK(m_requests.empty()); // previous propagation has not succeeded
+  BOOST_CHECK(m_entries.find("/test/A") == m_entries.end()); // has been erased
+
+  m_entries["/test/A"].succeed(nullptr);
+  testAfterRibInsert("/test/A/app");
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "unregister", "/test/A"), CheckRequestResult::OK);
+  BOOST_CHECK(m_entries.find("/test/A") == m_entries.end()); // has been erased
+}
+
+BOOST_AUTO_TEST_CASE(AfterHubConnectDisconnect)
+{
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  BOOST_REQUIRE(addIdentity("/test/B"));
+  BOOST_REQUIRE(addIdentity("/test/C"));
+  BOOST_REQUIRE(insertEntryToRib("/test/A/app"));
+  BOOST_REQUIRE(insertEntryToRib("/test/B/app"));
+
+  // recorder the prefixes that will be propagated in order
+  std::vector<Name> propagatedPrefixes;
+
+  BOOST_CHECK(m_requests.empty()); // no request because there is no connectivity to the hub now
+  BOOST_REQUIRE_EQUAL(m_entries.size(), 2); // valid entries will be kept
+
+  connectToHub(); // 2 cached entries will be processed
+  for (auto&& entry : m_entries) {
+    propagatedPrefixes.push_back(entry.first);
+  }
+
+  BOOST_REQUIRE(insertEntryToRib("/test/C/app")); // will be processed directly
+  propagatedPrefixes.push_back("/test/C");
+  BOOST_REQUIRE_EQUAL(m_entries.size(), 3);
+
+  BOOST_REQUIRE(m_entries["/test/A"].isPropagating());
+  BOOST_REQUIRE(m_entries["/test/B"].isPropagating());
+  BOOST_REQUIRE(m_entries["/test/C"].isPropagating());
+
+  disconnectFromHub(); // all 3 entries are initialized
+  BOOST_CHECK(m_entries["/test/A"].isNew());
+  BOOST_CHECK(m_entries["/test/B"].isNew());
+  BOOST_CHECK(m_entries["/test/C"].isNew());
+
+  connectToHub(); // all 3 entries will be processed
+  for (auto&& entry : m_entries) {
+    propagatedPrefixes.push_back(entry.first);
+  }
+
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 6);
+  BOOST_REQUIRE_EQUAL(propagatedPrefixes.size(), 6);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", propagatedPrefixes[0]), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(1, "register", propagatedPrefixes[1]), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(2, "register", propagatedPrefixes[2]), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(3, "register", propagatedPrefixes[3]), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(4, "register", propagatedPrefixes[4]), CheckRequestResult::OK);
+  BOOST_CHECK_EQUAL(checkRequest(5, "register", propagatedPrefixes[5]), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(AfterPropagateSucceed)
+{
+  bool wasRefreshEventTriggered = false;
+  auto testAfterPropagateSucceed = [&] (const Name& ribEntryPrefix) {
+    m_requests.clear();
+    wasRefreshEventTriggered = false;
+
+    auto propagateParameters = m_propagator.getPrefixPropagationParameters(ribEntryPrefix);
+    m_propagator.afterPropagateSucceed(propagateParameters.parameters, propagateParameters.options,
+                                       [&]{ wasRefreshEventTriggered = true; });
+    advanceClocks(time::milliseconds(1));
+  };
+
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  m_propagator.m_refreshInterval = time::seconds(0); // event will be executed at once
+  m_entries["/test/A"].startPropagation(); // set to be in PROPAGATING state
+
+  testAfterPropagateSucceed("/test/A/app"); // will trigger refresh event
+  BOOST_CHECK(wasRefreshEventTriggered);
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries.erase(m_entries.find("/test/A")); // set to be in RELEASED state
+  testAfterPropagateSucceed("/test/A/app"); // will call startRevocation
+  BOOST_CHECK(!wasRefreshEventTriggered);
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "unregister", "/test/A"), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(AfterPropagateFail)
+{
+  bool wasRetryEventTriggered = false;
+  auto testAfterPropagateFail = [&] (const Name& ribEntryPrefix) {
+    m_requests.clear();
+    wasRetryEventTriggered = false;
+
+    auto propagateParameters = m_propagator.getPrefixPropagationParameters(ribEntryPrefix);
+    m_propagator.afterPropagateFail(400, "test", propagateParameters.parameters, propagateParameters.options,
+                                     time::seconds(0), [&]{ wasRetryEventTriggered = true; });
+    advanceClocks(time::milliseconds(1));
+  };
+
+  BOOST_REQUIRE(addIdentity("/test/A"));
+  m_entries["/test/A"].startPropagation(); // set to be in PROPAGATING state
+
+  testAfterPropagateFail("/test/A/app"); // will trigger retry event
+  BOOST_CHECK(wasRetryEventTriggered);
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries.erase(m_entries.find("/test/A")); // set to be in RELEASED state
+  testAfterPropagateFail("/test/A/app"); // will do nothing
+  BOOST_CHECK(!wasRetryEventTriggered);
+  BOOST_CHECK(m_requests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(AfterRevokeSucceed)
+{
+  auto testAfterRevokeSucceed = [&] (const Name& ribEntryPrefix) {
+    m_requests.clear();
+    auto propagateParameters = m_propagator.getPrefixPropagationParameters(ribEntryPrefix);
+    m_propagator.afterRevokeSucceed(propagateParameters.parameters,
+                                    propagateParameters.options,
+                                    time::seconds(0));
+    advanceClocks(time::milliseconds(1));
+  };
+
+  BOOST_REQUIRE(addIdentity("/test/A"));
+
+  testAfterRevokeSucceed("/test/A/app"); // in RELEASED state
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries["/test/A"].fail(nullptr); // in PROPAGATE_FAIL state
+  testAfterRevokeSucceed("/test/A/app");
+  BOOST_CHECK(m_requests.empty());
+
+  m_entries["/test/A"].succeed(nullptr); // in PROPAGATED state
+  testAfterRevokeSucceed("/test/A/app");
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+
+  m_entries["/test/A"].startPropagation(); // in PROPAGATING state
+  testAfterRevokeSucceed("/test/A/app");
+  BOOST_REQUIRE_EQUAL(m_requests.size(), 1);
+  BOOST_CHECK_EQUAL(checkRequest(0, "register", "/test/A"), CheckRequestResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // PropagatedEntryStateChanges
+
+BOOST_AUTO_TEST_SUITE_END() // TestAutoPrefixPropagator
+BOOST_AUTO_TEST_SUITE_END() // Rib
+
+} // namespace tests
+} // namespace rib
+} // namespace nfd
diff --git a/tests/rib/propagated-entry.t.cpp b/tests/rib/propagated-entry.t.cpp
new file mode 100644
index 0000000..8c8b1d2
--- /dev/null
+++ b/tests/rib/propagated-entry.t.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib/propagated-entry.hpp"
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace rib {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Rib)
+
+BOOST_FIXTURE_TEST_SUITE(TestPropagatedEntry, nfd::tests::BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Identity)
+{
+  PropagatedEntry entry;
+  BOOST_CHECK(entry.m_signingIdentity.empty());
+
+  entry.setSigningIdentity("/test");
+  BOOST_CHECK_EQUAL(entry.m_signingIdentity, "/test");
+  BOOST_CHECK_EQUAL(entry.getSigningIdentity(), "/test");
+}
+
+BOOST_AUTO_TEST_CASE(Start)
+{
+  PropagatedEntry entry;
+  BOOST_CHECK(!entry.isPropagating());
+
+  entry.startPropagation();
+  BOOST_CHECK_EQUAL(PropagationStatus::PROPAGATING, entry.m_propagationStatus);
+  BOOST_CHECK(entry.isPropagating());
+}
+
+BOOST_AUTO_TEST_CASE(Succeed)
+{
+  PropagatedEntry entry;
+  entry.succeed(nullptr);
+  BOOST_CHECK_EQUAL(PropagationStatus::PROPAGATED, entry.m_propagationStatus);
+  BOOST_CHECK(entry.isPropagated());
+}
+
+BOOST_AUTO_TEST_CASE(Fail)
+{
+  PropagatedEntry entry;
+  entry.fail(nullptr);
+  BOOST_CHECK_EQUAL(PropagationStatus::PROPAGATE_FAIL, entry.m_propagationStatus);
+  BOOST_CHECK(entry.isPropagateFail());
+}
+
+BOOST_AUTO_TEST_CASE(Initialize)
+{
+  PropagatedEntry entry;
+  entry.startPropagation();
+  BOOST_CHECK_EQUAL(PropagationStatus::PROPAGATING, entry.m_propagationStatus);
+
+  entry.initialize();
+  BOOST_CHECK_EQUAL(PropagationStatus::NEW, entry.m_propagationStatus);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPropagatedEntry
+BOOST_AUTO_TEST_SUITE_END() // Rib
+
+} // namespace tests
+} // namespace rib
+} // namespace nfd
diff --git a/tests/rib/remote-registrator.t.cpp b/tests/rib/remote-registrator.t.cpp
deleted file mode 100644
index dc41cda..0000000
--- a/tests/rib/remote-registrator.t.cpp
+++ /dev/null
@@ -1,520 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "rib/remote-registrator.hpp"
-
-#include "tests/limited-io.hpp"
-#include "tests/identity-management-fixture.hpp"
-#include <ndn-cxx/util/dummy-client-face.hpp>
-
-namespace nfd {
-namespace rib {
-namespace tests {
-
-class RemoteRegistratorFixture : public nfd::tests::IdentityManagementFixture
-                               , public nfd::tests::UnitTestTimeFixture
-{
-public:
-  RemoteRegistratorFixture()
-    : face(ndn::util::makeDummyClientFace(getGlobalIoService()))
-    , controller(make_shared<ndn::nfd::Controller>(std::ref(*face), m_keyChain))
-    , remoteRegistrator(make_shared<RemoteRegistrator>(std::ref(*controller),
-                                                       m_keyChain,
-                                                       rib))
-    , COMMAND_PREFIX("/localhop/nfd/rib")
-    , REGISTER_VERB("register")
-    , UNREGISTER_VERB("unregister")
-  {
-    readConfig();
-
-    remoteRegistrator->enable();
-
-    advanceClocks(time::milliseconds(1));
-    face->sentInterests.clear();
-  }
-
-  void
-  readConfig(bool isSetRetry = false)
-  {
-    ConfigFile config;
-    config.addSectionHandler("remote_register",
-                             bind(&RemoteRegistrator::loadConfig, remoteRegistrator, _1));
-
-
-    if (isSetRetry) {
-      const std::string CONFIG_STRING =
-      "remote_register\n"
-      "{\n"
-      "  cost 15\n"
-      "  timeout 1000\n"
-      "  retry 1\n"
-      "  refresh_interval 5\n"
-      "}";
-
-      config.parse(CONFIG_STRING, true, "test-remote-register");
-    }
-    else {
-      const std::string CONFIG_STRING =
-      "remote_register\n"
-      "{\n"
-      "  cost 15\n"
-      "  timeout 100000\n"
-      "  retry 0\n"
-      "  refresh_interval 5\n"
-      "}";
-
-      config.parse(CONFIG_STRING, true, "test-remote-register");
-    }
-  }
-
-  void
-  waitForTimeout()
-  {
-    advanceClocks(time::milliseconds(100), time::seconds(1));
-  }
-
-  void
-  insertEntryWithIdentity(Name identity,
-                          name::Component appName = DEFAULT_APP_NAME,
-                          uint64_t faceId = 0)
-  {
-    BOOST_CHECK_EQUAL(addIdentity(identity), true);
-
-    Route route;
-    route.faceId = faceId;
-
-    rib.insert(identity.append(appName), route);
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  insertEntryWithoutIdentity(Name identity,
-                             name::Component appName = DEFAULT_APP_NAME,
-                             uint64_t faceId = 0)
-  {
-    Route route;
-    route.faceId = faceId;
-
-    rib.insert(identity.append(appName), route);
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  eraseEntryWithIdentity(Name identity,
-                         name::Component appName = DEFAULT_APP_NAME,
-                         uint64_t faceId = 0)
-  {
-    BOOST_CHECK_EQUAL(addIdentity(identity), true);
-
-    Route route;
-    route.faceId = faceId;
-
-    rib.erase(identity.append(appName), route);
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  eraseEntryWithoutIdentity(Name identity,
-                            name::Component appName = DEFAULT_APP_NAME,
-                            uint64_t faceId = 0)
-  {
-    Route route;
-    route.faceId = faceId;
-
-    rib.erase(identity.append(appName), route);
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  eraseFace(uint64_t faceId)
-  {
-    for (const Rib::NameAndRoute& item : rib.findRoutesWithFaceId(faceId)) {
-      rib.erase(item.first, item.second);
-    }
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  connectToHub()
-  {
-    rib.insert(Name("/localhop/nfd"), Route());
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  disconnectToHub()
-  {
-    rib.erase(Name("/localhop/nfd"), Route());
-
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  extractParameters(Interest& interest, Name::Component& verb,
-                    ndn::nfd::ControlParameters& extractedParameters)
-  {
-    const Name& name = interest.getName();
-    verb = name[COMMAND_PREFIX.size()];
-    const Name::Component& parameterComponent = name[COMMAND_PREFIX.size() + 1];
-
-    Block rawParameters = parameterComponent.blockFromValue();
-    extractedParameters.wireDecode(rawParameters);
-  }
-
-public:
-  Rib rib;
-  shared_ptr<ndn::util::DummyClientFace> face;
-  shared_ptr<ndn::nfd::Controller> controller;
-  shared_ptr<RemoteRegistrator> remoteRegistrator;
-
-  const Name COMMAND_PREFIX;
-  const name::Component REGISTER_VERB;
-  const name::Component UNREGISTER_VERB;
-
-  static const name::Component DEFAULT_APP_NAME;
-};
-
-const name::Component RemoteRegistratorFixture::DEFAULT_APP_NAME("app");
-
-BOOST_FIXTURE_TEST_SUITE(TestRemoteRegistrator, RemoteRegistratorFixture)
-
-BOOST_FIXTURE_TEST_CASE(AutoTest, RemoteRegistratorFixture)
-{
-  BOOST_REQUIRE_EQUAL(1, 1);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterWithoutConnection, RemoteRegistratorFixture)
-{
-  insertEntryWithIdentity("/remote/register");
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterWithoutIdentity, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  insertEntryWithoutIdentity("/remote/register");
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterWithHubPrefix, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterWithLocalPrefix, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  insertEntryWithIdentity("/localhost/prefix");
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterBasic, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-  insertEntryWithIdentity(identity);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
-
-  Interest& request = face->sentInterests[0];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterAdvanced, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-  Name identityAddRib("/remote/register/rib");
-  insertEntryWithIdentity(identityAddRib);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
-
-  Interest& request = face->sentInterests[0];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterWithRedundantCallback, RemoteRegistratorFixture)
-{
-  remoteRegistrator->enable();
-
-  connectToHub();
-
-  Name identity("/remote/register");
-  insertEntryWithIdentity(identity);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
-
-  Interest& request = face->sentInterests[0];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(RegisterRetry, RemoteRegistratorFixture)
-{
-  // setRetry
-  readConfig(true);
-
-  connectToHub();
-
-  Name identity("/remote/register");
-  insertEntryWithIdentity(identity);
-
-  waitForTimeout();
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  Interest& requestFirst  = face->sentInterests[0];
-  Interest& requestSecond = face->sentInterests[1];
-
-  ndn::nfd::ControlParameters extractedParametersFirst, extractedParametersSecond;
-  Name::Component verbFirst, verbSecond;
-  extractParameters(requestFirst,  verbFirst,  extractedParametersFirst);
-  extractParameters(requestSecond, verbSecond, extractedParametersSecond);
-
-  BOOST_CHECK_EQUAL(verbFirst,  REGISTER_VERB);
-  BOOST_CHECK_EQUAL(verbSecond, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParametersFirst.getName(),  identity);
-  BOOST_CHECK_EQUAL(extractedParametersSecond.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(UnregisterWithoutInsert, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  eraseEntryWithIdentity("/remote/register");
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(UnregisterWithoutConnection, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  disconnectToHub();
-
-  Name indentity("/remote/register");
-  remoteRegistrator->m_regEntries.insert(
-            nfd::rib::RemoteRegistrator::RegisteredEntry(indentity, scheduler::EventId()));
-
-  eraseEntryWithIdentity(indentity);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(UnregisterWithoutSuccessfullRegistration,
-                        RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-
-  insertEntryWithIdentity(identity);
-
-  eraseEntryWithIdentity(identity);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
-
-  Interest& request = face->sentInterests[0];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(UnregisterBasic, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-
-  insertEntryWithIdentity(identity);
-
-  scheduler::EventId event;
-
-  remoteRegistrator->m_regEntries.insert(
-          nfd::rib::RemoteRegistrator::RegisteredEntry(identity, event));
-
-  eraseEntryWithIdentity(identity);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  Interest& request = face->sentInterests[1];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, UNREGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(UnregisterAdvanced, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identityShort("/remote/register");
-  Name identityLong("/remote/register/long");
-
-  scheduler::EventId eventShort;
-  scheduler::EventId eventLong;
-
-  insertEntryWithIdentity(identityShort, name::Component("appA"));
-
-  remoteRegistrator->m_regEntries.insert(
-          nfd::rib::RemoteRegistrator::RegisteredEntry(identityShort,
-                                                       eventShort));
-
-  insertEntryWithIdentity(identityShort, name::Component("appB"));
-
-  insertEntryWithIdentity(identityLong);
-
-  remoteRegistrator->m_regEntries.insert(
-          nfd::rib::RemoteRegistrator::RegisteredEntry(identityLong,
-                                                       eventLong));
-
-  // two registration commands are generated for identityShort and identityLong
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  eraseEntryWithIdentity(identityShort, name::Component("appA"));
-
-  // no unregistration command is generated as appB also exists
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  eraseEntryWithIdentity(identityShort, name::Component("appB"));
-
-  // one unregistration command is generated for identityShort
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 3);
-
-  Interest& request = face->sentInterests[2];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, UNREGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identityShort);
-}
-
-BOOST_FIXTURE_TEST_CASE(EraseFace, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-  uint64_t faceId = 517;
-
-  insertEntryWithIdentity(identity, DEFAULT_APP_NAME, faceId);
-
-  scheduler::EventId event;
-
-  remoteRegistrator->m_regEntries.insert(
-          nfd::rib::RemoteRegistrator::RegisteredEntry(identity, event));
-
-  eraseFace(faceId);
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  Interest& request = face->sentInterests[1];
-
-  ndn::nfd::ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, UNREGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), identity);
-}
-
-BOOST_FIXTURE_TEST_CASE(RebuildConnection, RemoteRegistratorFixture)
-{
-  connectToHub();
-
-  Name identity("/remote/register");
-
-  insertEntryWithIdentity(identity);
-
-  scheduler::EventId event;
-
-  remoteRegistrator->m_regEntries.insert(
-          nfd::rib::RemoteRegistrator::RegisteredEntry(identity, event));
-
-  disconnectToHub();
-
-  connectToHub();
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 2);
-
-  Interest& request1 = face->sentInterests[0];
-  Interest& request2 = face->sentInterests[1];
-
-  ndn::nfd::ControlParameters extractedParameters1, extractedParameters2;
-  Name::Component verb1, verb2;
-  extractParameters(request1, verb1, extractedParameters1);
-  extractParameters(request2, verb2, extractedParameters2);
-
-  BOOST_CHECK_EQUAL(verb1, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(verb2, REGISTER_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters1.getName(),
-                    extractedParameters2.getName());
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // namespace tests
-} // namespace rib
-} // namespace nfd