route: Fix FIB next hop removal bug

refs: #2018

Change-Id: Id107c04d4cdce9cc756acad5d262e5c1e0cc29a8
diff --git a/src/conf-parameter.hpp b/src/conf-parameter.hpp
index b8b5b72..8f2965b 100644
--- a/src/conf-parameter.hpp
+++ b/src/conf-parameter.hpp
@@ -289,12 +289,12 @@
   }
 
   void
-  setMaxFacesPerPrefix(int32_t mfpp)
+  setMaxFacesPerPrefix(uint32_t mfpp)
   {
     m_maxFacesPerPrefix = mfpp;
   }
 
-  int32_t
+  uint32_t
   getMaxFacesPerPrefix() const
   {
     return m_maxFacesPerPrefix;
@@ -353,7 +353,7 @@
   double m_corR;
   double m_corTheta;
 
-  int32_t m_maxFacesPerPrefix;
+  uint32_t m_maxFacesPerPrefix;
 
   std::string m_logDir;
   std::string m_seqFileDir;
diff --git a/src/nlsr.hpp b/src/nlsr.hpp
index 240fffb..00b7877 100644
--- a/src/nlsr.hpp
+++ b/src/nlsr.hpp
@@ -79,7 +79,7 @@
     , m_isRouteCalculationScheduled(false)
     , m_isRoutingTableCalculating(false)
     , m_routingTable(scheduler)
-    , m_fib(*this, m_nlsrFace, scheduler)
+    , m_fib(m_nlsrFace, scheduler, m_adjacencyList, m_confParam)
     , m_namePrefixTable(*this)
     , m_syncLogicHandler(m_nlsrFace, m_nlsrLsdb, m_confParam)
     , m_helloProtocol(*this, scheduler)
diff --git a/src/route/fib.cpp b/src/route/fib.cpp
index 2010858..1815b27 100644
--- a/src/route/fib.cpp
+++ b/src/route/fib.cpp
@@ -24,14 +24,13 @@
 #include <cmath>
 #include <ndn-cxx/common.hpp>
 
-#include "nlsr.hpp"
+#include "adjacency-list.hpp"
+#include "conf-parameter.hpp"
 #include "nexthop-list.hpp"
 #include "face-map.hpp"
 #include "fib.hpp"
 #include "logger.hpp"
 
-
-
 namespace nlsr {
 
 INIT_LOGGER("Fib");
@@ -44,7 +43,7 @@
 static bool
 fibEntryNameCompare(const FibEntry& fibEntry, const ndn::Name& name)
 {
-  return fibEntry.getName() == name ;
+  return fibEntry.getName() == name;
 }
 
 void
@@ -93,84 +92,102 @@
 }
 
 void
-Fib::addNextHopsToFibEntryAndNfd(FibEntry& entry, NexthopList& nextHopList)
+Fib::addNextHopsToFibEntryAndNfd(FibEntry& entry, NexthopList& hopsToAdd)
 {
   const ndn::Name& name = entry.getName();
 
-  nextHopList.sort();
-
-  int numFaces = 0;
-  int maxFaces = getNumberOfFacesForName(nextHopList,
-                                         m_nlsr.getConfParameter().getMaxFacesPerPrefix());
-
-  for (NexthopList::iterator hopIt = nextHopList.begin();
-       hopIt != nextHopList.end() && numFaces < maxFaces; ++hopIt, ++numFaces)
+  for (NexthopList::iterator it = hopsToAdd.begin(); it != hopsToAdd.end(); ++it)
   {
     // Add nexthop to FIB entry
-    entry.getNexthopList().addNextHop(*hopIt);
+    entry.getNexthopList().addNextHop(*it);
 
     if (isPrefixUpdatable(name)) {
       // Add nexthop to NDN-FIB
-      registerPrefix(name, hopIt->getConnectingFaceUri(),
-                     hopIt->getRouteCostAsAdjustedInteger(),
+      registerPrefix(name, it->getConnectingFaceUri(),
+                     it->getRouteCostAsAdjustedInteger(),
                      ndn::time::seconds(m_refreshTime + GRACE_PERIOD),
                      ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
     }
   }
+
+  entry.getNexthopList().sort();
+}
+
+void
+Fib::removeOldNextHopsFromFibEntryAndNfd(FibEntry& entry, const NexthopList& installedHops)
+{
+  _LOG_DEBUG("Fib::removeOldNextHopsFromFibEntryAndNfd Called");
+
+  const ndn::Name& name = entry.getName();
+  NexthopList& entryHopList = entry.getNexthopList();
+
+  for (NexthopList::iterator it = entryHopList.begin(); it != entryHopList.end();) {
+
+    const std::string& faceUri = it->getConnectingFaceUri();
+
+    // See if the nexthop is installed in NFD's FIB
+    NexthopList::const_iterator foundIt = std::find_if(installedHops.cbegin(),
+                                                       installedHops.cend(),
+                                                       bind(&compareFaceUri, _1, faceUri));
+
+    // The next hop is not installed
+    if (foundIt == installedHops.cend()) {
+
+      if (isPrefixUpdatable(name)) {
+        // Remove the nexthop from NDN's FIB
+        unregisterPrefix(name, it->getConnectingFaceUri());
+      }
+
+      // Remove the next hop from the FIB entry
+      _LOG_DEBUG("Removing " << it->getConnectingFaceUri() << " from " << name);
+      // Since the iterator will be invalidated on removal, dereference the original
+      // and increment the copy
+      entryHopList.removeNextHop(*(it++));
+    }
+    else {
+      ++it;
+    }
+  }
 }
 
 void
-Fib::removeOldNextHopsFromFibEntryAndNfd(FibEntry& entry, NexthopList& newHopList)
+Fib::update(const ndn::Name& name, NexthopList& allHops)
 {
-  _LOG_DEBUG("Fib::removeOldNextHopsFromFibEntryAndNfd Called");
-  const ndn::Name& name = entry.getName();
-  NexthopList& entryHopList = entry.getNexthopList();
-  NexthopList itHopList = entryHopList;
+  _LOG_DEBUG("Fib::update called");
 
-  for (NexthopList::iterator it = itHopList.begin(); it != itHopList.end(); ++it) {
+  // Sort all of the next hops so lower cost hops are prioritized
+  allHops.sort();
 
-    // See if the nexthop is in the new nexthop list
-    const std::string& faceUri = it->getConnectingFaceUri();
-    NexthopList::iterator foundIt = std::find_if(newHopList.begin(),
-                                                 newHopList.end(),
-                                                 bind(&compareFaceUri, _1, faceUri));
+  // Get the max possible faces which is the minumum of the configuration setting and
+  // the length of the list of all next hops.
+  unsigned int maxFaces = getNumberOfFacesForName(allHops);
 
-      // The next hop is not in the new nexthop list
-      if (foundIt == newHopList.end()) {
-        // Remove the next hop from the FIB entry
-        _LOG_DEBUG("Removing " << it->getConnectingFaceUri() << " from " << name);
-        entryHopList.removeNextHop(*it);
+  NexthopList hopsToAdd;
+  unsigned int nFaces = 0;
 
-        if (isPrefixUpdatable(name)) {
-          // Remove the nexthop from NDN-FIB
-          unregisterPrefix(name, it->getConnectingFaceUri());
-        }
-      }
-    }
-}
-
-void
-Fib::update(const ndn::Name& name, NexthopList& nextHopList)
-{
-  _LOG_DEBUG("Fib::updateFib Called");
+  // Create a list of next hops to be installed with length == maxFaces
+  for (NexthopList::iterator it = allHops.begin(); it != allHops.end() && nFaces < maxFaces;
+       ++it, ++nFaces)
+  {
+    hopsToAdd.addNextHop(*it);
+  }
 
   std::list<FibEntry>::iterator entryIt = std::find_if(m_table.begin(),
                                                        m_table.end(),
                                                        bind(&fibEntryNameCompare, _1, name));
+
   // New FIB entry
   if (entryIt == m_table.end()) {
     _LOG_DEBUG("New FIB Entry");
 
     // Don't create an entry for a name with no nexthops
-    if (nextHopList.getSize() == 0) {
+    if (hopsToAdd.getSize() == 0) {
       return;
     }
 
     FibEntry entry(name);
 
-    addNextHopsToFibEntryAndNfd(entry, nextHopList);
-
-    entry.getNexthopList().sort();
+    addNextHopsToFibEntryAndNfd(entry, hopsToAdd);
 
     // Set entry's expiration time point and sequence number
     entry.setExpirationTimePoint(ndn::time::system_clock::now() +
@@ -189,15 +206,14 @@
     FibEntry& entry = *entryIt;
 
     // Remove empty FIB entry
-    if (nextHopList.getSize() == 0) {
+    if (hopsToAdd.getSize() == 0) {
       remove(name);
       return;
     }
 
-    addNextHopsToFibEntryAndNfd(entry, nextHopList);
-    removeOldNextHopsFromFibEntryAndNfd(entry, nextHopList);
+    addNextHopsToFibEntryAndNfd(entry, hopsToAdd);
 
-    entry.getNexthopList().sort();
+    removeOldNextHopsFromFibEntryAndNfd(entry, hopsToAdd);
 
     // Set entry's expiration time point
     entry.setExpirationTimePoint(ndn::time::system_clock::now() +
@@ -205,7 +221,7 @@
     // Increment sequence number
     entry.setSeqNo(entry.getSeqNo() + 1);
 
-    // Cancel previosuly scheduled event
+    // Cancel previously scheduled event
     m_scheduler.cancelEvent(entry.getExpiringEventId());
 
     // Schedule entry to be refreshed
@@ -234,23 +250,24 @@
   }
 }
 
-int
-Fib::getNumberOfFacesForName(NexthopList& nextHopList,
-                             uint32_t maxFacesPerPrefix)
+unsigned int
+Fib::getNumberOfFacesForName(NexthopList& nextHopList)
 {
-  int endFace = 0;
-  if ((maxFacesPerPrefix == 0) || (nextHopList.getSize() <= maxFacesPerPrefix)) {
-    return nextHopList.getSize();
+  uint32_t nNextHops = static_cast<uint32_t>(nextHopList.getNextHops().size());
+  uint32_t nMaxFaces = m_confParameter.getMaxFacesPerPrefix();
+
+  // Allow all faces
+  if (nMaxFaces == 0) {
+    return nNextHops;
   }
   else {
-    return maxFacesPerPrefix;
+    return std::min(nNextHops, nMaxFaces);
   }
-  return endFace;
 }
 
 bool
 Fib::isPrefixUpdatable(const ndn::Name& name) {
-  if (!m_nlsr.getAdjacencyList().isNeighbor(name)) {
+  if (!m_adjacencyList.isNeighbor(name)) {
     return true;
   }
 
@@ -313,7 +330,7 @@
                     uint64_t faceCost, const ndn::time::milliseconds& timeout,
                     uint64_t flags, uint8_t times)
 {
-  uint64_t faceId = m_nlsr.getAdjacencyList().getFaceId(faceUri);
+  uint64_t faceId = m_adjacencyList.getFaceId(faceUri);
   if (faceId != 0) {
     ndn::nfd::ControlParameters faceParameters;
     faceParameters
diff --git a/src/route/fib.hpp b/src/route/fib.hpp
index 9893ff2..e49727b 100644
--- a/src/route/fib.hpp
+++ b/src/route/fib.hpp
@@ -30,27 +30,30 @@
 #include <ndn-cxx/util/time.hpp>
 #include "face-map.hpp"
 #include "fib-entry.hpp"
+#include "test-access-control.hpp"
 
 namespace nlsr {
 
 typedef ndn::function<void(const ndn::nfd::ControlParameters&)> CommandSucceedCallback;
 typedef ndn::function<void(uint32_t/*code*/,const std::string&/*reason*/)> CommandFailCallback;
 
-class Nlsr;
-
+class AdjacencyList;
+class ConfParameter;
 
 class Fib
 {
 public:
-  Fib(Nlsr& nlsr, ndn::Face& face, ndn::Scheduler& scheduler)
-    : m_nlsr(nlsr)
-    , m_scheduler(scheduler)
+  Fib(ndn::Face& face, ndn::Scheduler& scheduler, AdjacencyList& adjacencyList, ConfParameter& conf)
+    : m_scheduler(scheduler)
     , m_table()
     , m_refreshTime(0)
     , m_controller(face)
     , m_faceMap()
+    , m_adjacencyList(adjacencyList)
+    , m_confParameter(conf)
   {
   }
+
   ~Fib()
   {
   }
@@ -59,7 +62,7 @@
   remove(const ndn::Name& name);
 
   void
-  update(const ndn::Name& name, NexthopList& nextHopList);
+  update(const ndn::Name& name, NexthopList& allHops);
 
   void
   clean();
@@ -75,17 +78,17 @@
   isPrefixUpdatable(const ndn::Name& name);
 
   void
-  addNextHopsToFibEntryAndNfd(FibEntry& entry, NexthopList& nextHopList);
+  addNextHopsToFibEntryAndNfd(FibEntry& entry, NexthopList& hopsToAdd);
 
   void
-  removeOldNextHopsFromFibEntryAndNfd(FibEntry& entry, NexthopList& newHopList);
+  removeOldNextHopsFromFibEntryAndNfd(FibEntry& entry, const NexthopList& installedHops);
 
   void
   removeHop(NexthopList& nl, const std::string& doNotRemoveHopFaceUri,
             const ndn::Name& name);
 
-  int
-  getNumberOfFacesForName(NexthopList& nextHopList, uint32_t maxFacesPerPrefix);
+  unsigned int
+  getNumberOfFacesForName(NexthopList& nextHopList);
 
   ndn::EventId
   scheduleEntryExpiration(const ndn::Name& name, int32_t feSeqNum,
@@ -178,14 +181,19 @@
                        const std::string& message);
 
 private:
-  Nlsr& m_nlsr;
   ndn::Scheduler& m_scheduler;
 
   std::list<FibEntry> m_table;
   int32_t m_refreshTime;
   ndn::nfd::Controller m_controller;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   FaceMap m_faceMap;
 
+private:
+  AdjacencyList& m_adjacencyList;
+  ConfParameter& m_confParameter;
+
   static const uint64_t GRACE_PERIOD;
 };
 
diff --git a/src/route/nexthop-list.hpp b/src/route/nexthop-list.hpp
index 7c2011e..faf13f0 100644
--- a/src/route/nexthop-list.hpp
+++ b/src/route/nexthop-list.hpp
@@ -73,6 +73,7 @@
   }
 
   typedef std::list<NextHop>::iterator iterator;
+  typedef std::list<NextHop>::const_iterator const_iterator;
 
   iterator
   begin()
@@ -86,6 +87,18 @@
     return m_nexthopList.end();
   }
 
+  const_iterator
+  cbegin() const
+  {
+    return m_nexthopList.begin();
+  }
+
+  const_iterator
+  cend() const
+  {
+    return m_nexthopList.end();
+  }
+
   void
   writeLog();
 
diff --git a/src/test-access-control.hpp b/src/test-access-control.hpp
new file mode 100644
index 0000000..f1667e3
--- /dev/null
+++ b/src/test-access-control.hpp
@@ -0,0 +1,39 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  University of Memphis,
+ *                     Regents of the University of California
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR 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.
+ *
+ * NLSR 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
+ * NLSR, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>
+ *
+ **/
+
+#ifndef NLSR_TEST_ACCESS_CONTROL_HPP
+#define NLSR_TEST_ACCESS_CONTROL_HPP
+
+#include "config.hpp"
+
+#ifdef WITH_TESTS
+#define VIRTUAL_WITH_TESTS virtual
+#define PUBLIC_WITH_TESTS_ELSE_PROTECTED public
+#define PUBLIC_WITH_TESTS_ELSE_PRIVATE public
+#define PROTECTED_WITH_TESTS_ELSE_PRIVATE protected
+#else
+#define VIRTUAL_WITH_TESTS
+#define PUBLIC_WITH_TESTS_ELSE_PROTECTED protected
+#define PUBLIC_WITH_TESTS_ELSE_PRIVATE private
+#define PROTECTED_WITH_TESTS_ELSE_PRIVATE private
+#endif
+
+#endif //NLSR_TEST_ACCESS_CONTROL_HPP
diff --git a/tests/control-commands.hpp b/tests/control-commands.hpp
new file mode 100644
index 0000000..e84deea
--- /dev/null
+++ b/tests/control-commands.hpp
@@ -0,0 +1,56 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  University of Memphis,
+ *                     Regents of the University of California
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR 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.
+ *
+ * NLSR 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
+ * NLSR, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ **/
+
+#ifndef NLSR_TEST_CONTROL_COMMANDS_HPP
+#define NLSR_TEST_CONTROL_COMMANDS_HPP
+
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+
+namespace nlsr {
+namespace test {
+
+inline void
+extractParameters(const ndn::Interest& interest,
+                  ndn::Name::Component& verb,
+                  ndn::nfd::ControlParameters& extractedParameters,
+                  const ndn::Name& commandPrefix)
+{
+  const ndn::Name& name = interest.getName();
+  verb = name[commandPrefix.size()];
+  const ndn::Name::Component& parameterComponent = name[commandPrefix.size() + 1];
+
+  ndn::Block rawParameters = parameterComponent.blockFromValue();
+  extractedParameters.wireDecode(rawParameters);
+}
+
+inline void
+extractRibCommandParameters(const ndn::Interest& interest, ndn::Name::Component& verb,
+                            ndn::nfd::ControlParameters& extractedParameters)
+{
+  extractParameters(interest, verb, extractedParameters, ndn::Name("/localhost/nfd/rib"));
+}
+
+} // namespace test
+} // namespace nlsr
+
+#endif // NLSR_TEST_CONTROL_COMMANDS_HPP
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index dc60c90..b996540 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -25,6 +25,7 @@
 
 #include <boost/asio.hpp>
 #include <boost/test/unit_test.hpp>
+
 #include <ndn-cxx/util/scheduler.hpp>
 
 namespace nlsr {
@@ -46,4 +47,4 @@
 } // namespace test
 } // namespace nlsr
 
-#endif
\ No newline at end of file
+#endif // NLSR_TEST_COMMON_HPP
diff --git a/tests/test-fib.cpp b/tests/test-fib.cpp
new file mode 100644
index 0000000..4ade674
--- /dev/null
+++ b/tests/test-fib.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  University of Memphis,
+ *                     Regents of the University of California
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR 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.
+ *
+ * NLSR 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
+ * NLSR, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **/
+
+#include "test-common.hpp"
+#include "control-commands.hpp"
+#include "dummy-face.hpp"
+
+#include "route/fib.hpp"
+
+#include "adjacency-list.hpp"
+#include "conf-parameter.hpp"
+
+namespace nlsr {
+namespace test {
+
+using ndn::DummyFace;
+using ndn::shared_ptr;
+
+class FibFixture : public BaseFixture
+{
+public:
+  FibFixture()
+    : face(ndn::makeDummyFace())
+    , interests(face->m_sentInterests)
+  {
+    INIT_LOGGERS("/tmp", "DEBUG");
+
+    Adjacent neighbor1(router1Name, router1FaceUri, 0, Adjacent::STATUS_ACTIVE, 0, router1FaceId);
+    adjacencies.insert(neighbor1);
+
+    Adjacent neighbor2(router2Name, router2FaceUri, 0, Adjacent::STATUS_ACTIVE, 0, router2FaceId);
+    adjacencies.insert(neighbor2);
+
+    Adjacent neighbor3(router3Name, router3FaceUri, 0, Adjacent::STATUS_ACTIVE, 0, router3FaceId);
+    adjacencies.insert(neighbor3);
+
+    conf.setMaxFacesPerPrefix(2);
+
+    fib = ndn::make_shared<Fib>(ndn::ref(*face), ndn::ref(g_scheduler),ndn::ref(adjacencies),
+                                ndn::ref(conf));
+
+    fib->m_faceMap.update(router1FaceUri, router1FaceId);
+    fib->m_faceMap.update(router2FaceUri, router2FaceId);
+    fib->m_faceMap.update(router3FaceUri, router3FaceId);
+  }
+
+public:
+  shared_ptr<ndn::DummyFace> face;
+  shared_ptr<Fib> fib;
+
+  AdjacencyList adjacencies;
+  ConfParameter conf;
+  std::vector<ndn::Interest>& interests;
+
+  static const ndn::Name router1Name;
+  static const ndn::Name router2Name;
+  static const ndn::Name router3Name;
+
+  static const std::string router1FaceUri;
+  static const std::string router2FaceUri;
+  static const std::string router3FaceUri;
+
+  static const uint32_t router1FaceId;
+  static const uint32_t router2FaceId;
+  static const uint32_t router3FaceId;
+};
+
+const ndn::Name FibFixture::router1Name = "/ndn/router1";
+const ndn::Name FibFixture::router2Name = "/ndn/router2";
+const ndn::Name FibFixture::router3Name = "/ndn/router3";
+
+const std::string FibFixture::router1FaceUri = "uri://face1";
+const std::string FibFixture::router2FaceUri = "uri://face2";
+const std::string FibFixture::router3FaceUri = "uri://face3";
+
+const uint32_t FibFixture::router1FaceId = 1;
+const uint32_t FibFixture::router2FaceId = 2;
+const uint32_t FibFixture::router3FaceId = 3;
+
+BOOST_FIXTURE_TEST_SUITE(TestFib, FibFixture)
+
+BOOST_AUTO_TEST_CASE(NextHopsAdd)
+{
+  NextHop hop1(router1FaceUri, 10);
+  NextHop hop2(router2FaceUri, 20);
+
+  NexthopList hops;
+  hops.addNextHop(hop1);
+  hops.addNextHop(hop2);
+
+  fib->update("/ndn/name", hops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // Should register faces 1 and 2 for /ndn/name
+  BOOST_REQUIRE_EQUAL(interests.size(), 2);
+
+  ndn::nfd::ControlParameters extractedParameters;
+  ndn::Name::Component verb;
+  std::vector<ndn::Interest>::iterator it = interests.begin();
+
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router1FaceId &&
+              verb == ndn::Name::Component("register"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router2FaceId &&
+              verb == ndn::Name::Component("register"));
+}
+
+
+BOOST_AUTO_TEST_CASE(NextHopsNoChange)
+{
+  NextHop hop1(router1FaceUri, 10);
+  NextHop hop2(router2FaceUri, 20);
+
+  NexthopList oldHops;
+  oldHops.addNextHop(hop1);
+  oldHops.addNextHop(hop2);
+
+  fib->update("/ndn/name", oldHops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(interests.size(), 2);
+  interests.clear();
+
+  fib->update("/ndn/name", oldHops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // Should register face 1 and 2 for /ndn/name
+  BOOST_REQUIRE_EQUAL(interests.size(), 2);
+
+  ndn::nfd::ControlParameters extractedParameters;
+  ndn::Name::Component verb;
+  std::vector<ndn::Interest>::iterator it = interests.begin();
+
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router1FaceId &&
+              verb == ndn::Name::Component("register"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router2FaceId &&
+              verb == ndn::Name::Component("register"));
+}
+
+BOOST_AUTO_TEST_CASE(NextHopsRemoveAll)
+{
+  NextHop hop1(router1FaceUri, 10);
+  NextHop hop2(router2FaceUri, 20);
+
+  NexthopList oldHops;
+  oldHops.addNextHop(hop1);
+  oldHops.addNextHop(hop2);
+
+  fib->update("/ndn/name", oldHops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(interests.size(), 2);
+  interests.clear();
+
+  NexthopList empty;
+
+  fib->update("/ndn/name", empty);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // Should unregister faces 1 and 2 for /ndn/name
+  BOOST_CHECK_EQUAL(interests.size(), 2);
+
+  ndn::nfd::ControlParameters extractedParameters;
+  ndn::Name::Component verb;
+  std::vector<ndn::Interest>::iterator it = interests.begin();
+
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router1FaceId &&
+              verb == ndn::Name::Component("unregister"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router2FaceId &&
+              verb == ndn::Name::Component("unregister"));
+}
+
+BOOST_AUTO_TEST_CASE(NextHopsMaxPrefixes)
+{
+  NextHop hop1(router1FaceUri, 10);
+  NextHop hop2(router2FaceUri, 20);
+  NextHop hop3(router3FaceUri, 30);
+
+  NexthopList hops;
+  hops.addNextHop(hop1);
+  hops.addNextHop(hop2);
+  hops.addNextHop(hop3);
+
+  fib->update("/ndn/name", hops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // Should only register faces 1 and 2 for /ndn/name
+  BOOST_CHECK_EQUAL(interests.size(), 2);
+
+  ndn::nfd::ControlParameters extractedParameters;
+  ndn::Name::Component verb;
+  std::vector<ndn::Interest>::iterator it = interests.begin();
+
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router1FaceId &&
+              verb == ndn::Name::Component("register"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router2FaceId &&
+              verb == ndn::Name::Component("register"));
+}
+
+BOOST_AUTO_TEST_CASE(NextHopsMaxPrefixesAfterRecalculation)
+{
+  NextHop hop1(router1FaceUri, 10);
+  NextHop hop2(router2FaceUri, 20);
+
+  NexthopList hops;
+  hops.addNextHop(hop1);
+  hops.addNextHop(hop2);
+
+  fib->update("/ndn/name", hops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // FIB
+  // Name        NextHops
+  // /ndn/name   (faceId=1, cost=10), (faceId=2, cost=20)
+  BOOST_REQUIRE_EQUAL(interests.size(), 2);
+  interests.clear();
+
+  // Routing table is recalculated; a new more optimal path is found
+  NextHop hop3(router3FaceUri, 5);
+  hops.addNextHop(hop3);
+
+  fib->update("/ndn/name", hops);
+  face->processEvents(ndn::time::milliseconds(1));
+
+  // To maintain a max 2 face requirement, face 3 should be registered and face 2 should be
+  // unregistered. Face 1 will also be re-registered.
+  //
+  // FIB
+  // Name         NextHops
+  // /ndn/name    (faceId=3, cost=5), (faceId=1, cost=10)
+
+  BOOST_CHECK_EQUAL(interests.size(), 3);
+
+  ndn::nfd::ControlParameters extractedParameters;
+  ndn::Name::Component verb;
+  std::vector<ndn::Interest>::iterator it = interests.begin();
+
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router3FaceId &&
+              verb == ndn::Name::Component("register"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router1FaceId &&
+              verb == ndn::Name::Component("register"));
+
+  ++it;
+  extractRibCommandParameters(*it, verb, extractedParameters);
+
+  BOOST_CHECK(extractedParameters.getName() == "/ndn/name" &&
+              extractedParameters.getFaceId() == router2FaceId &&
+              verb == ndn::Name::Component("unregister"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} //namespace test
+} //namespace nlsr