rib: redesign of remote prefix registration

Change-Id: I8418de6d5bb9615af898df5dbf9ed4cc2cb6a43a
Refs: #3211, #2413
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