nlsr: discover Faces from NFD

refs: #2954

Change-Id: I072972d88bce0e1012e96f33577657048b7df1e1
diff --git a/docs/GETTING-STARTED.rst b/docs/GETTING-STARTED.rst
index 216677f..0dbfafd 100644
--- a/docs/GETTING-STARTED.rst
+++ b/docs/GETTING-STARTED.rst
@@ -14,6 +14,20 @@
 
 :doc:`INSTALL`
 
+
+Important Notes About Configuration
+-----------------------------------
+
+Since v0.4.0, NLSR no longer creates Faces for the neighbors that are
+specified in ``nlsr.conf``. Instead, it relies on the pre-existence of
+the Faces in NFD. NLSR will obtain this information from NFD, and
+configure its neighbors using this information.
+
+For people who were relying on NLSR to automagically configure their
+networks, this must now be done with ``nfdc``. Its documentation can
+be found `here
+<https://named-data.net/doc/NFD/current/manpages/nfdc.html>`_
+
 Configuration
 -------------
 
diff --git a/nlsr.conf b/nlsr.conf
index 329a702..ef569ce 100644
--- a/nlsr.conf
+++ b/nlsr.conf
@@ -11,7 +11,7 @@
   ; lsa-refresh-time is the time in seconds, after which router will refresh its LSAs
   lsa-refresh-time 1800      ; default value 1800. Valid values 240-7200
 
-  ; router-dead-interval is the time in seconds after which an inactive router's
+  ; router-dead-interval is the time in seconds after which an inactive routers
   ; LSAs are removed
   ;router-dead-interval 3600 ; default value: 2*lsa-refresh-time. Value must be larger
                              ; than lsa-refresh-time
@@ -39,7 +39,7 @@
   ;log4cxx-conf /path/to/log4cxx-conf  ; path for log4cxx configuration file (Absolute path)
 }
 
-; the neighbors section contains the configuration for router's neighbors and hello's behavior
+; the neighbor's section contains the configuration for router's neighbors and hellos behavior
 
 neighbors
 {
@@ -65,6 +65,16 @@
 
   first-hello-interval  10   ; Default value 10. Valid values 0-10
 
+  face-dataset-fetch-tries 3 ; default is 3. Valid values 1-10. The FaceDataset is
+                             ; gotten from NFD, and is needed to configure NLSR
+                             ; correctly. It is recommended not to set this
+                             : variable too high, because it could cause
+                             ; congestion for NFD.
+
+  face-dataset-fetch-interval 3600 ; default is 3600. Valid values 1800-5400.
+                                   ; This controls how often (in seconds) NLSR will attempt to
+                                   ; fetch a FaceStatus dataset from NFD.
+
   ; neighbor command is used to configure router's neighbor. Each neighbor will need
   ; one block of neighbor command
 
@@ -105,7 +115,7 @@
 }
 
 
-; the fib section is used to configure fib entry's type to ndn FIB updated by NLSR
+; the fib section is used to configure fib entrys type to ndn FIB updated by NLSR
 
 fib
 {
diff --git a/src/adjacent.hpp b/src/adjacent.hpp
index 4aded01..29d7567 100644
--- a/src/adjacent.hpp
+++ b/src/adjacent.hpp
@@ -115,7 +115,7 @@
   }
 
   uint64_t
-  getFaceId()
+  getFaceId() const
   {
     return m_faceId;
   }
diff --git a/src/conf-file-processor.cpp b/src/conf-file-processor.cpp
index 789efc4..5362a41 100644
--- a/src/conf-file-processor.cpp
+++ b/src/conf-file-processor.cpp
@@ -454,6 +454,33 @@
   if (!adjLsaBuildInterval.parseFromConfigSection(section)) {
     return false;
   }
+  // Set the retry count for fetching the FaceStatus dataset
+  ConfigurationVariable<uint32_t> faceDatasetFetchTries("face-dataset-fetch-tries",
+                                                        std::bind(&ConfParameter::setFaceDatasetFetchTries,
+                                                                  &m_nlsr.getConfParameter(),
+                                                                  _1));
+
+  faceDatasetFetchTries.setMinAndMaxValue(FACE_DATASET_FETCH_TRIES_MIN,
+                                          FACE_DATASET_FETCH_TRIES_MAX);
+  faceDatasetFetchTries.setOptional(FACE_DATASET_FETCH_TRIES_DEFAULT);
+
+  if (!faceDatasetFetchTries.parseFromConfigSection(section)) {
+    return false;
+  }
+
+  // Set the interval between FaceStatus dataset fetch attempts.
+  ConfigurationVariable<ndn::time::seconds> faceDatasetFetchInterval("face-dataset-fetch-interval",
+                                                           bind(&ConfParameter::setFaceDatasetFetchInterval,
+                                                                &m_nlsr.getConfParameter(),
+                                                                _1));
+
+  faceDatasetFetchInterval.setMinAndMaxValue(ndn::time::seconds(FACE_DATASET_FETCH_INTERVAL_MIN),
+                                             ndn::time::seconds(FACE_DATASET_FETCH_INTERVAL_MAX));
+  faceDatasetFetchInterval.setOptional(ndn::time::seconds(FACE_DATASET_FETCH_INTERVAL_DEFAULT));
+
+  if (!faceDatasetFetchInterval.parseFromConfigSection(section)) {
+    return false;
+  }
 
   // first-hello-interval
   ConfigurationVariable<uint32_t> firstHelloInterval("first-hello-interval",
@@ -469,8 +496,7 @@
   for (ConfigSection::const_iterator tn =
            section.begin(); tn != section.end(); ++tn) {
 
-    if (tn->first == "neighbor")
-    {
+    if (tn->first == "neighbor") {
       try {
         ConfigSection CommandAttriTree = tn->second;
         std::string name = CommandAttriTree.get<std::string>("name");
@@ -479,7 +505,7 @@
         ndn::util::FaceUri faceUri;
         try {
           faceUri = ndn::util::FaceUri(uriString);
-        } catch (ndn::util::FaceUri::Error e) {
+        } catch (const ndn::util::FaceUri::Error& e) {
           std::cerr << "Malformed face-uri <" << uriString << "> for " << name << std::endl;
           return false;
         }
diff --git a/src/conf-parameter.hpp b/src/conf-parameter.hpp
index 9bcd95c..6ec21f5 100644
--- a/src/conf-parameter.hpp
+++ b/src/conf-parameter.hpp
@@ -62,6 +62,19 @@
   ROUTING_CALC_INTERVAL_MAX = 15
 };
 
+
+enum {
+  FACE_DATASET_FETCH_TRIES_MIN = 1,
+  FACE_DATASET_FETCH_TRIES_MAX = 10,
+  FACE_DATASET_FETCH_TRIES_DEFAULT = 3
+};
+
+enum {
+  FACE_DATASET_FETCH_INTERVAL_MIN = 1800,
+  FACE_DATASET_FETCH_INTERVAL_MAX = 5400,
+  FACE_DATASET_FETCH_INTERVAL_DEFAULT = 3600
+};
+
 enum {
   HELLO_RETRIES_MIN = 1,
   HELLO_RETRIES_DEFAULT = 3,
@@ -261,6 +274,30 @@
   }
 
   void
+  setFaceDatasetFetchTries(uint32_t count)
+  {
+    m_faceDatasetFetchTries = count;
+  }
+
+  uint32_t
+  getFaceDatasetFetchTries() const
+  {
+    return m_faceDatasetFetchTries;
+  }
+
+  void
+  setFaceDatasetFetchInterval(ndn::time::seconds interval)
+  {
+    m_faceDatasetFetchInterval = interval;
+  }
+
+  const ndn::time::seconds
+  getFaceDatasetFetchInterval() const
+  {
+    return m_faceDatasetFetchInterval;
+  }
+
+  void
   setLogLevel(const std::string& logLevel)
   {
     m_logLevel = logLevel;
@@ -423,6 +460,9 @@
   uint32_t m_firstHelloInterval;
   uint32_t m_routingCalcInterval;
 
+  uint32_t m_faceDatasetFetchTries;
+  ndn::time::seconds m_faceDatasetFetchInterval;
+
   ndn::time::seconds m_lsaInterestLifetime;
   uint32_t  m_routerDeadInterval;
   std::string m_logLevel;
diff --git a/src/hello-protocol.cpp b/src/hello-protocol.cpp
index 52e8997..4a995de 100644
--- a/src/hello-protocol.cpp
+++ b/src/hello-protocol.cpp
@@ -17,8 +17,6 @@
  * 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/>.
  *
- * \author A K M Mahmudul Hoque <ahoque1@memphis.edu>
- *
  **/
 
 #include "nlsr.hpp"
@@ -68,13 +66,6 @@
                       m_nlsr.getConfParameter().getInterestResendTime());
       _LOG_DEBUG("Sending scheduled interest: " << interestName);
     }
-    // If it does not have a Face, we need to give it one.  A
-    // successful registration prompts a callback that sends the hello
-    // Interest to the new Face.
-    else {
-      registerPrefixes((*it).getName(), (*it).getFaceUri().toString(),
-                       (*it).getLinkCost(), ndn::time::milliseconds::max());
-    }
   }
   scheduleInterest(m_nlsr.getConfParameter().getInfoInterestInterval());
 }
@@ -126,12 +117,6 @@
         expressInterest(interestName,
                         m_nlsr.getConfParameter().getInterestResendTime());
       }
-      // If the originator of the Interest currently lacks a Face, we
-      // need to give it one.
-      else {
-        registerPrefixes(adjacent->getName(), adjacent->getFaceUri().toString(),
-                         adjacent->getLinkCost(), ndn::time::milliseconds::max());
-      }
     }
   }
 }
@@ -235,82 +220,4 @@
   _LOG_DEBUG("Validation Error: " << msg);
 }
 
-
-  // Asks the FIB to register the supplied adjacency (in other words,
-  // create a Face for it).
-void
-HelloProtocol::registerPrefixes(const ndn::Name& adjName, const std::string& faceUri,
-                               double linkCost, const ndn::time::milliseconds& timeout)
-{
-  m_nlsr.getFib().registerPrefix(adjName, faceUri, linkCost, timeout,
-                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0,
-                                 std::bind(&HelloProtocol::onRegistrationSuccess,
-                                           this, _1, adjName,timeout),
-                                 std::bind(&HelloProtocol::onRegistrationFailure,
-                                           this, _1, adjName));
-}
-
-  // After we create a new Face, we need to set it up for use. This
-  // function sets the controlling strategy, registers prefixes in
-  // sync, broadcast, and LSA.
-void
-HelloProtocol::onRegistrationSuccess(const ndn::nfd::ControlParameters& commandSuccessResult,
-                                     const ndn::Name& neighbor,const ndn::time::milliseconds& timeout)
-{
-  auto adjacent = m_nlsr.getAdjacencyList().findAdjacent(neighbor);
-  if (adjacent != m_nlsr.getAdjacencyList().end()){
-    adjacent->setFaceId(commandSuccessResult.getFaceId());
-    ndn::Name broadcastKeyPrefix = DEFAULT_BROADCAST_PREFIX;
-    broadcastKeyPrefix.append("KEYS");
-    std::string faceUri = adjacent->getFaceUri().toString();
-    double linkCost = adjacent->getLinkCost();
-    m_nlsr.getFib().registerPrefix(m_nlsr.getConfParameter().getChronosyncPrefix(),
-                                 faceUri, linkCost, timeout,
-                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
-    m_nlsr.getFib().registerPrefix(m_nlsr.getConfParameter().getLsaPrefix(),
-                                 faceUri, linkCost, timeout,
-                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
-    m_nlsr.getFib().registerPrefix(broadcastKeyPrefix,
-                                 faceUri, linkCost, timeout,
-                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
-
-    // Sends a Hello Interest to determine status before the next scheduled.
-    // interest name: /<neighbor>/NLSR/INFO/<router>
-    ndn::Name interestName(neighbor);
-    interestName.append(NLSR_COMPONENT);
-    interestName.append(INFO_COMPONENT);
-    interestName.append(m_nlsr.getConfParameter().getRouterPrefix().wireEncode());
-    expressInterest(interestName,
-                    m_nlsr.getConfParameter().getInterestResendTime());
-  }
-}
-
-void
-HelloProtocol::onRegistrationFailure(const ndn::nfd::ControlResponse& response,
-                                     const ndn::Name& name)
-{
-  _LOG_DEBUG(response.getText() << " (code: " << response.getCode() << ")");
-  /*
-  * If NLSR can not create face for given faceUri then it will treat this
-  * failure as one INFO interest timed out. So that NLSR can move on with
-  * building Adj Lsa and calculate routing table. NLSR does not build Adj
-  * Lsa unless all the neighbors are ACTIVE or DEAD. For considering the
-  * missconfigured(link) neighbour dead this is required.
-  */
-  auto adjacent = m_nlsr.getAdjacencyList().findAdjacent(name);
-  if (adjacent != m_nlsr.getAdjacencyList().end()) {
-    adjacent->setInterestTimedOutNo(adjacent->getInterestTimedOutNo() + 1);
-    Adjacent::Status status = adjacent->getStatus();
-    uint32_t infoIntTimedOutCount = adjacent->getInterestTimedOutNo();
-
-    if (infoIntTimedOutCount == m_nlsr.getConfParameter().getInterestRetryNumber()) {
-      if (status == Adjacent::STATUS_ACTIVE) {
-        adjacent->setStatus(Adjacent::STATUS_INACTIVE);
-      }
-
-      m_nlsr.getLsdb().scheduleAdjLsaBuild();
-    }
-  }
-}
-
 } // namespace nlsr
diff --git a/src/hello-protocol.hpp b/src/hello-protocol.hpp
index 1817850..808b843 100644
--- a/src/hello-protocol.hpp
+++ b/src/hello-protocol.hpp
@@ -117,9 +117,6 @@
   onRegistrationSuccess(const ndn::nfd::ControlParameters& commandSuccessResult,
                         const ndn::Name& neighbor, const ndn::time::milliseconds& timeout);
 
-  void
-  registerPrefixes(const ndn::Name& adjName, const std::string& faceUri,
-                   double linkCost, const ndn::time::milliseconds& timeout);
 private:
   Nlsr& m_nlsr;
   ndn::Scheduler& m_scheduler;
diff --git a/src/nlsr-runner.cpp b/src/nlsr-runner.cpp
index 29e4737..827593a 100644
--- a/src/nlsr-runner.cpp
+++ b/src/nlsr-runner.cpp
@@ -75,7 +75,6 @@
     std::cerr << "ERROR: " << e.what() << std::endl;
 
     m_nlsr.getFib().clean();
-    m_nlsr.destroyFaces();
   }
 }
 
diff --git a/src/nlsr.cpp b/src/nlsr.cpp
index ca829e0..5709313 100644
--- a/src/nlsr.cpp
+++ b/src/nlsr.cpp
@@ -29,6 +29,7 @@
 #include "adjacent.hpp"
 #include "logger.hpp"
 
+#include <ndn-cxx/util/face-uri.hpp>
 
 namespace nlsr {
 
@@ -63,9 +64,12 @@
                          m_routerNameDispatcher,
                          m_nlsrFace,
                          m_keyChain)
+
   , m_helloProtocol(*this, scheduler)
   , m_certificateCache(new ndn::CertificateCacheTtl(ioService))
   , m_validator(m_nlsrFace, DEFAULT_BROADCAST_PREFIX, m_certificateCache, m_certStore)
+  , m_controller(m_nlsrFace, m_keyChain, m_validator)
+  , m_faceDatasetController(m_nlsrFace, m_keyChain)
   , m_prefixUpdateProcessor(m_nlsrFace,
                             m_namePrefixList,
                             m_nlsrLsdb,
@@ -252,6 +256,9 @@
   setInfoInterestFilter();
   setLsaInterestFilter();
 
+  initializeFaces(std::bind(&Nlsr::processFaceDataset, this, _1),
+                  std::bind(&Nlsr::onFaceDatasetFetchTimeout, this, _1, _2, 0));
+
   // Set event intervals
   setFirstHelloInterval(m_confParam.getFirstHelloInterval());
   m_nlsrLsdb.setAdjLsaBuildInterval(m_confParam.getAdjLsaBuildInterval());
@@ -377,78 +384,195 @@
 }
 
 void
-Nlsr::onDestroyFaceSuccess(const ndn::nfd::ControlParameters& commandSuccessResult)
-{
-}
-
-void
-Nlsr::onDestroyFaceFailure(const ndn::nfd::ControlResponse& response)
-{
-  std::cerr << response.getText() << " (code: " << response.getCode() << ")";
-  BOOST_THROW_EXCEPTION(Error("Error: Face destruction failed"));
-}
-
-void
-Nlsr::destroyFaces()
-{
-  std::list<Adjacent>& adjacents = m_adjacencyList.getAdjList();
-  for (std::list<Adjacent>::iterator it = adjacents.begin();
-       it != adjacents.end(); it++) {
-    m_fib.destroyFace((*it).getFaceUri().toString(),
-                      std::bind(&Nlsr::onDestroyFaceSuccess, this, _1),
-                      std::bind(&Nlsr::onDestroyFaceFailure, this, _1));
-  }
-}
-
-void
 Nlsr::onFaceEventNotification(const ndn::nfd::FaceEventNotification& faceEventNotification)
 {
   _LOG_TRACE("Nlsr::onFaceEventNotification called");
-  ndn::nfd::FaceEventKind kind = faceEventNotification.getKind();
 
-  if (kind == ndn::nfd::FACE_EVENT_DESTROYED) {
-    uint64_t faceId = faceEventNotification.getFaceId();
+  switch (faceEventNotification.getKind()) {
+    case ndn::nfd::FACE_EVENT_DESTROYED: {
+      uint64_t faceId = faceEventNotification.getFaceId();
 
-    auto adjacent = m_adjacencyList.findAdjacent(faceId);
+      auto adjacent = m_adjacencyList.findAdjacent(faceId);
 
-    if (adjacent != m_adjacencyList.end()) {
-      _LOG_DEBUG("Face to " << adjacent->getName() << " with face id: " << faceId << " destroyed");
+      if (adjacent != m_adjacencyList.end()) {
+        _LOG_DEBUG("Face to " << adjacent->getName() << " with face id: " << faceId << " destroyed");
 
-      adjacent->setFaceId(0);
+        adjacent->setFaceId(0);
 
-      // Only trigger an Adjacency LSA build if this node is changing
-      // from ACTIVE to INACTIVE since this rebuild will effectively
-      // cancel the previous Adjacency LSA refresh event and schedule
-      // a new one further in the future.
-      //
-      // Continuously scheduling the refresh in the future will block
-      // the router from refreshing its Adjacency LSA. Since other
-      // routers' Name prefixes' expiration times are updated when
-      // this router refreshes its Adjacency LSA, the other routers'
-      // prefixes will expire and be removed from the RIB.
-      //
-      // This check is required to fix Bug #2733 for now. This check
-      // would be unnecessary to fix Bug #2733 when Issue #2732 is
-      // completed, but the check also helps with optimization so it
-      // can remain even when Issue #2732 is implemented.
-      if (adjacent->getStatus() == Adjacent::STATUS_ACTIVE) {
-        adjacent->setStatus(Adjacent::STATUS_INACTIVE);
+        // Only trigger an Adjacency LSA build if this node is changing
+        // from ACTIVE to INACTIVE since this rebuild will effectively
+        // cancel the previous Adjacency LSA refresh event and schedule
+        // a new one further in the future.
+        //
+        // Continuously scheduling the refresh in the future will block
+        // the router from refreshing its Adjacency LSA. Since other
+        // routers' Name prefixes' expiration times are updated when
+        // this router refreshes its Adjacency LSA, the other routers'
+        // prefixes will expire and be removed from the RIB.
+        //
+        // This check is required to fix Bug #2733 for now. This check
+        // would be unnecessary to fix Bug #2733 when Issue #2732 is
+        // completed, but the check also helps with optimization so it
+        // can remain even when Issue #2732 is implemented.
+        if (adjacent->getStatus() == Adjacent::STATUS_ACTIVE) {
+          adjacent->setStatus(Adjacent::STATUS_INACTIVE);
 
-        // A new adjacency LSA cannot be built until the neighbor is marked INACTIVE and
-        // has met the HELLO retry threshold
-        adjacent->setInterestTimedOutNo(m_confParam.getInterestRetryNumber());
+          // A new adjacency LSA cannot be built until the neighbor is marked INACTIVE and
+          // has met the HELLO retry threshold
+          adjacent->setInterestTimedOutNo(m_confParam.getInterestRetryNumber());
 
-        if (m_confParam.getHyperbolicState() != HYPERBOLIC_STATE_OFF) {
-          getRoutingTable().scheduleRoutingTableCalculation(*this);
-        }
-        else {
-          m_nlsrLsdb.scheduleAdjLsaBuild();
+          if (m_confParam.getHyperbolicState() != HYPERBOLIC_STATE_OFF) {
+            getRoutingTable().scheduleRoutingTableCalculation(*this);
+          }
+          else {
+            m_nlsrLsdb.scheduleAdjLsaBuild();
+          }
         }
       }
+      break;
     }
+    case ndn::nfd::FACE_EVENT_CREATED: {
+      // Find the neighbor in our adjacency list
+      _LOG_DEBUG("Face created event received.");
+      auto adjacent = m_adjacencyList.findAdjacent(
+        ndn::util::FaceUri(faceEventNotification.getRemoteUri()));
+      // If we have a neighbor by that FaceUri and it has no FaceId, we
+      // have a match.
+      if (adjacent != m_adjacencyList.end()) {
+        _LOG_DEBUG("Face creation event matches neighbor: " << adjacent->getName()
+                   << ". New Face ID: " << faceEventNotification.getFaceId()
+                   << ". Registering prefixes.");
+        adjacent->setFaceId(faceEventNotification.getFaceId());
+
+        registerAdjacencyPrefixes(*adjacent, ndn::time::milliseconds::max());
+      }
+      break;
+    }
+    default:
+      break;
   }
 }
 
+void
+Nlsr::initializeFaces(const FetchDatasetCallback& onFetchSuccess,
+                      const FetchDatasetTimeoutCallback& onFetchFailure)
+{
+  _LOG_TRACE("Initializing Faces...");
+
+  m_faceDatasetController.fetch<ndn::nfd::FaceDataset>(onFetchSuccess, onFetchFailure);
+
+}
+
+void
+Nlsr::processFaceDataset(const std::vector<ndn::nfd::FaceStatus>& faces)
+{
+  // Iterate over each neighbor listed in nlsr.conf
+  bool anyFaceChanged = false;
+  for (auto&& adjacency : m_adjacencyList.getAdjList()) {
+
+    const std::string faceUriString = adjacency.getFaceUri().toString();
+    // Check the list of FaceStatus objects we got for a match
+    for (const ndn::nfd::FaceStatus& faceStatus : faces) {
+
+      // Set the adjacency FaceID if we find a URI match and it was
+      // previously unset. Change the boolean to true.
+      if (adjacency.getFaceId() == 0 && faceUriString == faceStatus.getRemoteUri()) {
+        adjacency.setFaceId(faceStatus.getFaceId());
+        anyFaceChanged = true;
+        // Register the prefixes for each neighbor
+        this->registerAdjacencyPrefixes(adjacency, ndn::time::milliseconds::max());
+      }
+    }
+    // If this adjacency has no information in this dataset, then one
+    // of two things is happening: 1. NFD is starting slowly and this
+    // Face wasn't ready yet, or 2. NFD is configured
+    // incorrectly and this Face isn't available.
+    if (adjacency.getFaceId() == 0) {
+      _LOG_WARN("The adjacency " << adjacency.getName() <<
+                " has no Face information in this dataset.");
+    }
+  }
+
+  if (anyFaceChanged) {
+    // Only do these things if something has changed.  Schedule an
+    // adjacency LSA build to update with all of the new neighbors if
+    // HR is off.
+    if (m_confParam.getHyperbolicState() != HYPERBOLIC_STATE_OFF) {
+      getRoutingTable().scheduleRoutingTableCalculation(*this);
+    }
+    else {
+      m_nlsrLsdb.scheduleAdjLsaBuild();
+    }
+
+    // Begin the Hello Protocol loop immediately, so we don't wait.
+    m_helloProtocol.sendScheduledInterest(0);
+  }
+
+  scheduleDatasetFetch();
+}
+
+void
+Nlsr::registerAdjacencyPrefixes(const Adjacent& adj,
+                                const ndn::time::milliseconds& timeout)
+{
+    std::string faceUri = adj.getFaceUri().toString();
+    double linkCost = adj.getLinkCost();
+
+    m_fib.registerPrefix(adj.getName(), faceUri, linkCost,
+                         timeout, ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
+
+    m_fib.registerPrefix(m_confParam.getChronosyncPrefix(),
+                                 faceUri, linkCost, timeout,
+                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
+
+    m_fib.registerPrefix(m_confParam.getLsaPrefix(),
+                                 faceUri, linkCost, timeout,
+                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
+
+    ndn::Name broadcastKeyPrefix = DEFAULT_BROADCAST_PREFIX;
+    broadcastKeyPrefix.append("KEYS");
+    m_fib.registerPrefix(broadcastKeyPrefix,
+                                 faceUri, linkCost, timeout,
+                                 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
+}
+
+void
+Nlsr::onFaceDatasetFetchTimeout(uint32_t code,
+                                const std::string& msg,
+                                uint32_t nRetriesSoFar)
+{
+  // If we have exceeded the maximum attempt count, do not try again.
+  if (nRetriesSoFar++ < m_confParam.getFaceDatasetFetchTries()) {
+    _LOG_DEBUG("Failed to fetch dataset: " << msg << ". Attempting retry #" << nRetriesSoFar);
+    m_faceDatasetController.fetch<ndn::nfd::FaceDataset>(std::bind(&Nlsr::processFaceDataset,
+                                                        this, _1),
+                                              std::bind(&Nlsr::onFaceDatasetFetchTimeout,
+                                                        this, _1, _2, nRetriesSoFar));
+  }
+  else {
+    _LOG_ERROR("Failed to fetch dataset: " << msg << ". Exceeded limit of " <<
+               m_confParam.getFaceDatasetFetchTries() << ", so not trying again this time.");
+    // If we fail to fetch it, just do nothing until the next
+    // interval.  Since this is a backup mechanism, we aren't as
+    // concerned with retrying.
+    scheduleDatasetFetch();
+  }
+}
+
+void
+Nlsr::scheduleDatasetFetch()
+{
+  m_scheduler.scheduleEvent(m_confParam.getFaceDatasetFetchInterval(),
+    [this] {
+      this->initializeFaces(
+        [this] (const std::vector<ndn::nfd::FaceStatus>& faces) {
+         this->processFaceDataset(faces);
+        },
+        [this] (uint32_t code, const std::string& msg) {
+         this->onFaceDatasetFetchTimeout(code, msg, 0);
+        });
+  });
+}
 
 void
 Nlsr::startEventLoop()
diff --git a/src/nlsr.hpp b/src/nlsr.hpp
index 81629ae..bb67f2d 100644
--- a/src/nlsr.hpp
+++ b/src/nlsr.hpp
@@ -33,6 +33,9 @@
 #include <ndn-cxx/mgmt/nfd/face-event-notification.hpp>
 #include <ndn-cxx/mgmt/nfd/face-monitor.hpp>
 #include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/mgmt/nfd/face-status.hpp>
+#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/encoding/block.hpp>
 
 #include "adjacency-list.hpp"
 #include "common.hpp"
@@ -57,7 +60,9 @@
 
 class Nlsr
 {
-  friend class NlsrRunner;
+public:
+  using FetchDatasetCallback = std::function<void(const std::vector<ndn::nfd::FaceStatus>&)>;
+  using FetchDatasetTimeoutCallback = std::function<void(uint32_t, const std::string&)>;
 
   class Error : public std::runtime_error
   {
@@ -69,7 +74,6 @@
     }
   };
 
-public:
   Nlsr(boost::asio::io_service& ioService, ndn::Scheduler& scheduler, ndn::Face& face, ndn::KeyChain& keyChain);
 
   void
@@ -225,6 +229,51 @@
   void
   initialize();
 
+  /*! \brief Initializes neighbors' Faces using information from NFD.
+   * \sa Nlsr::initialize()
+   * \sa Nlsr::processFaceDataset()
+   *
+   * This function serves as the entry-point for initializing the
+   * neighbors listed in nlsr.conf during Nlsr::initialize(). NLSR
+   * will attempt to fetch a dataset of Faces from NFD, and configure
+   * each of its neighbors using information from that dataset. The
+   * explicit callbacks allow for better testability.
+   */
+  void
+  initializeFaces(const FetchDatasetCallback& onFetchSuccess,
+                  const FetchDatasetTimeoutCallback& onFetchFailure);
+
+  void
+  onFaceDatasetFetchTimeout(uint32_t code,
+                            const std::string& reason,
+                            uint32_t nRetriesSoFar);
+
+  /*! \brief Consumes a Face StatusDataset to configure NLSR neighbors.
+   * \sa Nlsr::initializeFaces
+   * \param faces A Face Dataset that should conform to FaceMgmt specifications.
+   *
+   * This function processes a Face StatusDataset that should conform
+   * to the FaceMgmt specifications listed
+   * [here](https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Face-Dataset).
+   * Any newly configured neighbors will have prefixes registered with NFD
+   * and be sent Hello Interests as well.
+   */
+  void
+  processFaceDataset(const std::vector<ndn::nfd::FaceStatus>& faces);
+
+  /*! \brief Registers NLSR-specific prefixes for a neighbor (Adjacent)
+   * \sa Nlsr::initializeFaces
+   * \param adj A reference to the neighbor to register prefixes for
+   * \param timeout The amount of time to give NFD to respond to *each* registration request.
+   *
+   * Registers the prefixes in NFD that NLSR needs to route with a
+   * neighbor. The timeout given is how long to set the timeout for
+   * *each* registration request that is made.
+   */
+  void
+  registerAdjacencyPrefixes(const Adjacent& adj,
+                            const ndn::time::milliseconds& timeout);
+
   void
   initializeKey();
 
@@ -297,14 +346,6 @@
   }
 
   void
-  createFace(const std::string& faceUri,
-             const CommandSucceedCallback& onSuccess,
-             const CommandFailCallback& onFailure);
-
-  void
-  destroyFaces();
-
-  void
   setStrategies();
 
   void
@@ -316,8 +357,7 @@
     return m_firstHelloInterval;
   }
 
-  /**
-   * \brief Canonize the URI for this and all proceeding neighbors in a list.
+  /*! \brief Canonize the URI for this and all proceeding neighbors in a list.
    *
    * This function canonizes the URI of the Adjacent object pointed to
    * by currentNeighbor. It then executes the then callback, providing
@@ -365,12 +405,6 @@
   onKeyPrefixRegSuccess(const ndn::Name& name);
 
   void
-  onDestroyFaceSuccess(const ndn::nfd::ControlParameters& commandSuccessResult);
-
-  void
-  onDestroyFaceFailure(const ndn::nfd::ControlResponse& response);
-
-  void
   onFaceEventNotification(const ndn::nfd::FaceEventNotification& faceEventNotification);
 
   void
@@ -379,8 +413,7 @@
     m_firstHelloInterval = interval;
   }
 
-  /**
-   * \brief Continues canonizing neighbor URIs.
+  /*! \brief Continues canonizing neighbor URIs.
    *
    * For testability reasons, we want what each instance of
    * canonization does after completion to be controllable. The best
@@ -390,6 +423,9 @@
   void
   canonizeContinuation(std::list<Adjacent>::iterator iterator);
 
+  void
+  scheduleDatasetFetch();
+
 public:
   static const ndn::Name LOCALHOST_PREFIX;
 
@@ -421,7 +457,13 @@
 private:
   std::shared_ptr<ndn::CertificateCacheTtl> m_certificateCache;
   security::CertificateStore m_certStore;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   Validator m_validator;
+
+private:
+  ndn::nfd::Controller m_controller;
+  ndn::nfd::Controller m_faceDatasetController;
   ndn::security::SigningInfo m_signingInfo;
   ndn::Name m_defaultCertName;
   update::PrefixUpdateProcessor m_prefixUpdateProcessor;
@@ -430,6 +472,8 @@
   ndn::nfd::FaceMonitor m_faceMonitor;
 
   uint32_t m_firstHelloInterval;
+
+  friend class NlsrRunner;
 };
 
 } // namespace nlsr
diff --git a/src/route/fib.cpp b/src/route/fib.cpp
index 73cde75..49ce647 100644
--- a/src/route/fib.cpp
+++ b/src/route/fib.cpp
@@ -209,37 +209,6 @@
 }
 
 void
-Fib::createFace(const std::string& faceUri,
-                const CommandSucceedCallback& onSuccess,
-                const CommandFailCallback& onFailure)
-{
-  m_faceController.createFace(faceUri, onSuccess, onFailure);
-}
-
-void
-Fib::destroyFace(const std::string& faceUri,
-                 const CommandSucceedCallback& onSuccess,
-                 const CommandFailCallback& onFailure)
-{
-  createFace(faceUri,
-             std::bind(&Fib::destroyFaceInNfd, this, _1, onSuccess, onFailure),
-             onFailure);
-}
-
-void
-Fib::destroyFaceInNfd(const ndn::nfd::ControlParameters& faceDestroyResult,
-                        const CommandSucceedCallback& onSuccess,
-                        const CommandFailCallback& onFailure)
-{
-  ndn::nfd::ControlParameters faceParameters;
-  faceParameters
-    .setFaceId(faceDestroyResult.getFaceId());
-  m_controller.start<ndn::nfd::FaceDestroyCommand>(faceParameters,
-                                                   onSuccess,
-                                                   onFailure);
-}
-
-void
 Fib::registerPrefix(const ndn::Name& namePrefix, const std::string& faceUri,
                     uint64_t faceCost, const ndn::time::milliseconds& timeout,
                     uint64_t flags, uint8_t times)
@@ -264,35 +233,6 @@
   }
 }
 
-typedef void(Fib::*RegisterPrefixCallback)(const ndn::nfd::ControlParameters&,
-                                           const ndn::nfd::ControlParameters&, uint8_t,
-                                           const CommandSucceedCallback&,
-                                           const CommandFailCallback&);
-
-void
-Fib::registerPrefix(const ndn::Name& namePrefix,
-                    const std::string& faceUri,
-                    uint64_t faceCost,
-                    const ndn::time::milliseconds& timeout,
-                    uint64_t flags,
-                    uint8_t times,
-                    const CommandSucceedCallback& onSuccess,
-                    const CommandFailCallback& onFailure)
-
-{
-  ndn::nfd::ControlParameters parameters;
-  parameters
-    .setName(namePrefix)
-    .setFlags(flags)
-    .setCost(faceCost)
-    .setExpirationPeriod(timeout)
-    .setOrigin(ndn::nfd::ROUTE_ORIGIN_NLSR);
-  createFace(faceUri,
-             std::bind(static_cast<RegisterPrefixCallback>(&Fib::registerPrefixInNfd),
-                       this, _1, parameters, times, onSuccess, onFailure),
-             onFailure);
-}
-
 void
 Fib::registerPrefixInNfd(ndn::nfd::ControlParameters& parameters,
                          const std::string& faceUri,
@@ -304,33 +244,13 @@
                                                              "Successful in name registration",
                                                              faceUri),
                                                    std::bind(&Fib::onRegistrationFailure,
-                                                             this, _1,
+                                                            this, _1,
                                                              "Failed in name registration",
                                                              parameters,
                                                              faceUri, times));
 }
 
 void
-Fib::registerPrefixInNfd(const ndn::nfd::ControlParameters& faceCreateResult,
-                         const ndn::nfd::ControlParameters& parameters,
-                         uint8_t times,
-                         const CommandSucceedCallback& onSuccess,
-                         const CommandFailCallback& onFailure)
-{
-  ndn::nfd::ControlParameters controlParameters;
-  controlParameters
-    .setName(parameters.getName())
-    .setFaceId(faceCreateResult.getFaceId())
-    .setCost(parameters.getCost())
-    .setFlags(parameters.getFlags())
-    .setExpirationPeriod(parameters.getExpirationPeriod())
-    .setOrigin(ndn::nfd::ROUTE_ORIGIN_NLSR);
-  m_controller.start<ndn::nfd::RibRegisterCommand>(controlParameters,
-                                                   onSuccess,
-                                                   onFailure);
-}
-
-void
 Fib::unregisterPrefix(const ndn::Name& namePrefix, const std::string& faceUri)
 {
   uint32_t faceId = m_faceMap.getFaceId(faceUri);
@@ -374,6 +294,9 @@
   _LOG_DEBUG("Register successful Prefix: " << commandSuccessResult.getName() <<
              " Face Uri: " << faceUri);
 
+  // Update the adjacency list with the new Face ID
+  m_adjacencyList.findAdjacent(faceUri)->setFaceId(commandSuccessResult.getFaceId());
+  // Update the fast-access FaceMap with the new Face ID, too
   m_faceMap.update(faceUri, commandSuccessResult.getFaceId());
   m_faceMap.writeLog();
 }
diff --git a/src/route/fib.hpp b/src/route/fib.hpp
index 10aa4fa..2705e47 100644
--- a/src/route/fib.hpp
+++ b/src/route/fib.hpp
@@ -26,15 +26,12 @@
 #include "face-map.hpp"
 #include "fib-entry.hpp"
 #include "test-access-control.hpp"
-#include "utility/face-controller.hpp"
 
 #include <ndn-cxx/mgmt/nfd/controller.hpp>
 #include <ndn-cxx/util/time.hpp>
 
 namespace nlsr {
 
-typedef ndn::nfd::Controller::CommandSucceedCallback CommandSucceedCallback;
-typedef ndn::nfd::Controller::CommandFailCallback CommandFailCallback;
 typedef std::function<void(FibEntry&)> afterRefreshCallback;
 
 class AdjacencyList;
@@ -49,7 +46,6 @@
     : m_scheduler(scheduler)
     , m_refreshTime(0)
     , m_controller(face, keyChain)
-    , m_faceController(face.getIoService(), m_controller)
     , m_adjacencyList(adjacencyList)
     , m_confParameter(conf)
   {
@@ -82,26 +78,11 @@
                  uint8_t times);
 
   void
-  registerPrefix(const ndn::Name& namePrefix,
-                 const std::string& faceUri,
-                 uint64_t faceCost,
-                 const ndn::time::milliseconds& timeout,
-                 uint64_t flags,
-                 uint8_t times,
-                 const CommandSucceedCallback& onSuccess,
-                 const CommandFailCallback& onFailure);
-
-  void
   setStrategy(const ndn::Name& name, const std::string& strategy, uint32_t count);
 
   void
   writeLog();
 
-  void
-  destroyFace(const std::string& faceUri,
-              const CommandSucceedCallback& onSuccess,
-              const CommandFailCallback& onFailure);
-
 private:
   bool
   isPrefixUpdatable(const ndn::Name& name);
@@ -113,28 +94,11 @@
   getNumberOfFacesForName(NexthopList& nextHopList);
 
   void
-  createFace(const std::string& faceUri,
-             const CommandSucceedCallback& onSuccess,
-             const CommandFailCallback& onFailure);
-
-  void
   registerPrefixInNfd(ndn::nfd::ControlParameters& parameters,
                       const std::string& faceUri,
                       uint8_t times);
 
   void
-  registerPrefixInNfd(const ndn::nfd::ControlParameters& faceCreateResult,
-                      const ndn::nfd::ControlParameters& parameters,
-                      uint8_t times,
-                      const CommandSucceedCallback& onSuccess,
-                      const CommandFailCallback& onFailure);
-
-  void
-  destroyFaceInNfd(const ndn::nfd::ControlParameters& faceDestroyResult,
-                   const CommandSucceedCallback& onSuccess,
-                   const CommandFailCallback& onFailure);
-
-  void
   unregisterPrefix(const ndn::Name& namePrefix, const std::string& faceUri);
 
   void
@@ -184,7 +148,6 @@
   ndn::Scheduler& m_scheduler;
   int32_t m_refreshTime;
   ndn::nfd::Controller m_controller;
-  util::FaceController m_faceController;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   FaceMap m_faceMap;
diff --git a/src/utility/face-controller.cpp b/src/utility/face-controller.cpp
deleted file mode 100644
index e6c0a8b..0000000
--- a/src/utility/face-controller.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  The 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 "face-controller.hpp"
-
-#include "common.hpp"
-#include "logger.hpp"
-
-namespace nlsr {
-namespace util {
-
-INIT_LOGGER("FaceController");
-
-using ndn::util::FaceUri;
-
-void
-FaceController::createFace(const std::string& request,
-                           const CommandSuccessCallback& onSuccess,
-                           const CommandFailureCallback& onFailure)
-{
-  FaceUri uri(request);
-
-  _LOG_TRACE("Converting " << uri << " to canonical form");
-  uri.canonize(std::bind(&FaceController::onCanonizeSuccess, this, _1, onSuccess, onFailure, uri),
-               std::bind(&FaceController::onCanonizeFailure, this, _1, onSuccess, onFailure, uri),
-               m_ioService, TIME_ALLOWED_FOR_CANONIZATION);
-}
-
-void
-FaceController::createFaceInNfd(const FaceUri& uri,
-                                const CommandSuccessCallback& onSuccess,
-                                const CommandFailureCallback& onFailure)
-{
-  ndn::nfd::ControlParameters faceParameters;
-  faceParameters.setUri(uri.toString());
-
-  _LOG_DEBUG("Creating Face in NFD with face-uri: " << uri);
-  m_controller.start<ndn::nfd::FaceCreateCommand>(faceParameters, onSuccess,
-                                                  [onSuccess, onFailure, uri] (const ndn::nfd::ControlResponse& response) {
-                                                    ndn::nfd::ControlParameters faceParams(response.getBody());
-                                                    if (response.getCode() == 409 && faceParams.getUri() == uri.toString()) {
-                                                      _LOG_DEBUG("Got 409 - treating as success");
-                                                      onSuccess(faceParams);
-                                                    }
-                                                    else {
-                                                      onFailure(response);
-                                                    }
-                                                  }
-                                                 );
-}
-
-void
-FaceController::onCanonizeSuccess(const FaceUri& uri,
-                                  const CommandSuccessCallback& onSuccess,
-                                  const CommandFailureCallback& onFailure,
-                                  const FaceUri& request)
-{
-  _LOG_DEBUG("Converted " << request << " to canonical form: " << uri);
-
-  createFaceInNfd(uri, onSuccess, onFailure);
-}
-
-void
-FaceController::onCanonizeFailure(const std::string& reason,
-                                  const CommandSuccessCallback& onSuccess,
-                                  const CommandFailureCallback& onFailure,
-                                  const FaceUri& request)
-{
-  _LOG_WARN("Could not convert " << request << " to canonical form: " << reason);
-  onFailure(ndn::nfd::ControlResponse(CANONIZE_ERROR_CODE,
-                                      "Could not canonize face-uri: " + request.toString()));
-}
-
-} // namespace util
-} // namespace nlsr
diff --git a/src/utility/face-controller.hpp b/src/utility/face-controller.hpp
deleted file mode 100644
index d44c2ec..0000000
--- a/src/utility/face-controller.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  The University of Memphis,
- *                           Regents of the University of California,
- *                           Arizona Board of Regents.
- *
- * 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_UTIL_FACE_CONTROLLER_HPP
-#define NLSR_UTIL_FACE_CONTROLLER_HPP
-
-#include <ndn-cxx/util/face-uri.hpp>
-#include <ndn-cxx/mgmt/nfd/controller.hpp>
-
-namespace nlsr {
-namespace util {
-
-class FaceController : boost::noncopyable
-{
-public:
-  typedef ndn::nfd::Controller::CommandSucceedCallback CommandSuccessCallback;
-  typedef ndn::nfd::Controller::CommandFailCallback CommandFailureCallback;
-
-  FaceController(boost::asio::io_service& io, ndn::nfd::Controller& controller)
-    : m_ioService(io)
-    , m_controller(controller)
-  {
-  }
-
-  void
-  createFace(const std::string& request,
-             const CommandSuccessCallback& onSuccess,
-             const CommandFailureCallback& onFailure);
-
-private:
-  void
-  createFaceInNfd(const ndn::util::FaceUri& uri,
-                  const CommandSuccessCallback& onSuccess,
-                  const CommandFailureCallback& onFailure);
-
-  void
-  onCanonizeSuccess(const ndn::util::FaceUri& uri,
-                    const CommandSuccessCallback& onSuccess,
-                    const CommandFailureCallback& onFailure,
-                    const ndn::util::FaceUri& request);
-
-  void
-  onCanonizeFailure(const std::string& reason,
-                    const CommandSuccessCallback& onSuccess,
-                    const CommandFailureCallback& onFailure,
-                    const ndn::util::FaceUri& request);
-
-private:
-  boost::asio::io_service& m_ioService;
-  ndn::nfd::Controller& m_controller;
-
-  static const uint32_t CANONIZE_ERROR_CODE = 408;
-};
-
-} // namespace util
-} // namespace nlsr
-
-#endif // NLSR_UTIL_FACE_CONTROLLER_HPP
diff --git a/src/validator.cpp b/src/validator.cpp
new file mode 100644
index 0000000..a73c9ee
--- /dev/null
+++ b/src/validator.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  The 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 "validator.hpp"
+
+namespace nlsr {
+
+void
+Validator::checkPolicy(const ndn::Data& data, int nSteps, const ndn::OnDataValidated& onValidated,
+                       const ndn::OnDataValidationFailed& onValidationFailed,
+                       std::vector<shared_ptr<ndn::ValidationRequest>>& nextSteps)
+{
+  if (!m_shouldValidate) {
+    onValidated(data.shared_from_this());
+  }
+  else {
+    ValidatorConfig::checkPolicy(data, nSteps, onValidated, onValidationFailed, nextSteps);
+  }
+}
+
+void
+Validator::afterCheckPolicy(const NextSteps& nextSteps, const OnFailure& onFailure)
+{
+  if (m_face == nullptr) {
+    onFailure("Require more information to validate the packet!");
+    return;
+  }
+
+  for (const std::shared_ptr<ndn::ValidationRequest>& request : nextSteps) {
+
+    ndn::Interest& interest = request->m_interest;
+
+    // Look for certificate in permanent storage
+    std::shared_ptr<const ndn::IdentityCertificate> cert = m_certStore.find(interest.getName());
+
+    if (cert != nullptr) {
+      // If the certificate is found, no reason to express interest
+      std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>(interest.getName());
+      data->setContent(cert->wireEncode());
+
+      Validator::onData(interest, *data, request);
+    }
+    else {
+      // Prepend broadcast prefix to interest name
+      ndn::Name broadcastName = m_broadcastPrefix;
+      broadcastName.append(interest.getName());
+      interest.setName(broadcastName);
+
+      // Attempt to fetch the certificate
+      m_face->expressInterest(interest,
+                              std::bind(&Validator::onData, this, _1, _2, request),
+                              std::bind(&Validator::onTimeout, // Nack
+                                        this, _1, request->m_nRetries,
+                                        onFailure,
+                                        request),
+                              std::bind(&Validator::onTimeout,
+                                        this, _1, request->m_nRetries,
+                                        onFailure,
+                                        request));
+    }
+  }
+}
+
+std::shared_ptr<const ndn::Data>
+Validator::preCertificateValidation(const ndn::Data& data)
+{
+  std::shared_ptr<ndn::Data> internalData = std::make_shared<ndn::Data>();
+  internalData->wireDecode(data.getContent().blockFromValue());
+  return internalData;
+}
+
+} // namespace nlsr
diff --git a/src/validator.hpp b/src/validator.hpp
index 4387ea5..c4f2a19 100644
--- a/src/validator.hpp
+++ b/src/validator.hpp
@@ -49,6 +49,7 @@
             security::CertificateStore& certStore,
             const int stepLimit = 10)
     : ndn::ValidatorConfig(face, cache, ndn::ValidatorConfig::DEFAULT_GRACE_INTERVAL, stepLimit)
+    , m_shouldValidate(true)
     , m_broadcastPrefix(broadcastPrefix)
     , m_certStore(certStore)
   {
@@ -75,57 +76,22 @@
 protected:
   typedef std::vector<std::shared_ptr<ndn::ValidationRequest>> NextSteps;
 
-  virtual void
+  void
+  checkPolicy(const ndn::Data& data,
+              int nSteps,
+              const ndn::OnDataValidated& onValidated,
+              const ndn::OnDataValidationFailed& onValidationFailed,
+              std::vector<shared_ptr<ndn::ValidationRequest>>& nextSteps) override;
+
+  void
   afterCheckPolicy(const NextSteps& nextSteps,
-                   const OnFailure& onFailure)
-  {
-    if (m_face == nullptr) {
-      onFailure("Require more information to validate the packet!");
-      return;
-    }
+                   const OnFailure& onFailure) override;
 
-    for (const std::shared_ptr<ndn::ValidationRequest>& request : nextSteps) {
+  std::shared_ptr<const ndn::Data>
+  preCertificateValidation(const ndn::Data& data) override;
 
-      ndn::Interest& interest = request->m_interest;
-
-      // Look for certificate in permanent storage
-      std::shared_ptr<const ndn::IdentityCertificate> cert = m_certStore.find(interest.getName());
-
-      if (cert != nullptr) {
-        // If the certificate is found, no reason to express interest
-        std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>(interest.getName());
-        data->setContent(cert->wireEncode());
-
-        Validator::onData(interest, *data, request);
-      }
-      else {
-        // Prepend broadcast prefix to interest name
-        ndn::Name broadcastName = m_broadcastPrefix;
-        broadcastName.append(interest.getName());
-        interest.setName(broadcastName);
-
-        // Attempt to fetch the certificate
-        m_face->expressInterest(interest,
-                                std::bind(&Validator::onData, this, _1, _2, request),
-                                std::bind(&Validator::onTimeout, // Nack
-                                          this, _1, request->m_nRetries,
-                                          onFailure,
-                                          request),
-                                std::bind(&Validator::onTimeout,
-                                          this, _1, request->m_nRetries,
-                                          onFailure,
-                                          request));
-      }
-    }
-  }
-
-  virtual std::shared_ptr<const ndn::Data>
-  preCertificateValidation(const ndn::Data& data)
-  {
-    std::shared_ptr<ndn::Data> internalData = std::make_shared<ndn::Data>();
-    internalData->wireDecode(data.getContent().blockFromValue());
-    return internalData;
-  }
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  bool m_shouldValidate;
 
 private:
   ndn::Name m_broadcastPrefix;
diff --git a/tests/test-common.cpp b/tests/test-common.cpp
new file mode 100644
index 0000000..e6b5e78
--- /dev/null
+++ b/tests/test-common.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  The 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"
+
+namespace nlsr {
+namespace test {
+
+ndn::Data&
+signData(ndn::Data& data)
+{
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::encoding::makeEmptyBlock(ndn::tlv::SignatureValue));
+  data.setSignature(fakeSignature);
+  data.wireEncode();
+
+  return data;
+}
+
+MockNfdMgmtFixture::MockNfdMgmtFixture()
+  : face(std::make_shared<ndn::util::DummyClientFace>(g_ioService))
+{
+}
+
+void
+MockNfdMgmtFixture::signDatasetReply(ndn::Data& data)
+{
+  signData(data);
+}
+
+void
+UnitTestTimeFixture::advanceClocks(const ndn::time::nanoseconds& tick, size_t nTicks)
+{
+  for (size_t i = 0; i < nTicks; ++i) {
+    steadyClock->advance(tick);
+    systemClock->advance(tick);
+
+    if (g_ioService.stopped()) {
+      g_ioService.reset();
+    }
+
+    g_ioService.poll();
+  }
+}
+
+} // namespace test
+} // namespace nlsr
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index 817b77e..dcba6c0 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -17,7 +17,6 @@
  * 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_COMMON_HPP
@@ -32,9 +31,25 @@
 #include <ndn-cxx/security/key-chain.hpp>
 #include <ndn-cxx/util/time-unit-test-clock.hpp>
 
+#include <ndn-cxx/security/signature-sha256-with-rsa.hpp>
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
 namespace nlsr {
 namespace test {
 
+ndn::Data&
+signData(ndn::Data& data);
+
+/** \brief add a fake signature to Data
+ */
+inline shared_ptr<ndn::Data>
+signData(shared_ptr<ndn::Data> data)
+{
+  signData(*data);
+  return data;
+}
+
 class BaseFixture
 {
 public:
@@ -65,25 +80,89 @@
   }
 
   void
-  advanceClocks(const ndn::time::nanoseconds& tick, size_t nTicks = 1)
-  {
-    for (size_t i = 0; i < nTicks; ++i) {
-      steadyClock->advance(tick);
-      systemClock->advance(tick);
-
-      if (g_ioService.stopped()) {
-        g_ioService.reset();
-      }
-
-      g_ioService.poll();
-    }
-  }
+  advanceClocks(const ndn::time::nanoseconds& tick, size_t nTicks = 1);
 
 protected:
   std::shared_ptr<ndn::time::UnitTestSteadyClock> steadyClock;
   std::shared_ptr<ndn::time::UnitTestSystemClock> systemClock;
 };
 
+class MockNfdMgmtFixture : public UnitTestTimeFixture
+{
+public:
+  MockNfdMgmtFixture();
+
+  /** \brief send one WireEncodable in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   *  \pre Interest for dataset has been expressed, sendDataset has not been invoked
+   */
+  template<typename T>
+  void
+  sendDataset(const ndn::Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T>));
+
+    this->sendDatasetReply(prefix, payload.wireEncode());
+  }
+
+  /** \brief send two WireEncodables in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload1 first vector item
+   *  \param payload2 second vector item
+   *  \note all payloads must fit in one Data
+   *  \pre Interest for dataset has been expressed, sendDataset has not been invoked
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const ndn::Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    this->sendDatasetReply(prefix, buffer.buf(), buffer.size());
+  }
+
+  /** \brief send a payload in reply to StatusDataset request
+   *  \param name dataset prefix without version and segment
+   *  \param contentArgs passed to Data::setContent
+   */
+  template<typename ...ContentArgs>
+  void
+  sendDatasetReply(ndn::Name name, ContentArgs&&... contentArgs)
+  {
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging when nfdc does not receive StatusDataset.
+    // They usually indicate a misspelled prefix or incorrect timing in the test case.
+    if (face->sentInterests.empty()) {
+      BOOST_WARN_MESSAGE(false, "no Interest expressed");
+    }
+    else {
+      BOOST_WARN_MESSAGE(face->sentInterests.back().getName().isPrefixOf(name),
+                         "last Interest " << face->sentInterests.back().getName() <<
+                         " cannot be satisfied by this Data " << name);
+    }
+
+    auto data = make_shared<ndn::Data>(name);
+    data->setFinalBlockId(name[-1]);
+    data->setContent(std::forward<ContentArgs>(contentArgs)...);
+    this->signDatasetReply(*data);
+    face->receive(*data);
+  }
+
+  virtual void
+  signDatasetReply(ndn::Data& data);
+
+public:
+  std::shared_ptr<ndn::util::DummyClientFace> face;
+};
+
 } // namespace test
 } // namespace nlsr
 
diff --git a/tests/test-nlsr.cpp b/tests/test-nlsr.cpp
index 78e0fb9..2cfe46f 100644
--- a/tests/test-nlsr.cpp
+++ b/tests/test-nlsr.cpp
@@ -25,21 +25,21 @@
 #include "nlsr.hpp"
 
 #include <ndn-cxx/mgmt/nfd/face-event-notification.hpp>
-#include <ndn-cxx/util/dummy-client-face.hpp>
 
 namespace nlsr {
 namespace test {
 
 using std::shared_ptr;
 
-class NlsrFixture : public UnitTestTimeFixture
+class NlsrFixture : public MockNfdMgmtFixture
 {
 public:
   NlsrFixture()
-    : face(std::make_shared<ndn::util::DummyClientFace>(g_ioService))
-    , nlsr(g_ioService, g_scheduler, std::ref(*face), g_keyChain)
+    : nlsr(g_ioService, g_scheduler, std::ref(*face), g_keyChain)
     , lsdb(nlsr.getLsdb())
     , neighbors(nlsr.getAdjacencyList())
+    , nSuccessCallbacks(0)
+    , nFailureCallbacks(0)
   {
   }
 
@@ -55,10 +55,12 @@
  }
 
 public:
-  std::shared_ptr<ndn::util::DummyClientFace> face;
   Nlsr nlsr;
   Lsdb& lsdb;
   AdjacencyList& neighbors;
+  uint32_t nSuccessCallbacks;
+  uint32_t nFailureCallbacks;
+
 };
 
 BOOST_FIXTURE_TEST_SUITE(TestNlsr, NlsrFixture)
@@ -124,6 +126,92 @@
   BOOST_CHECK_EQUAL(rt.getRoutingCalcInterval(), ndn::time::seconds(9));
 }
 
+BOOST_AUTO_TEST_CASE(FaceCreateEvent)
+{
+  // Setting constants for the unit test
+  const uint32_t faceId = 1;
+  const std::string faceUri = "udp4://10.0.0.1:6363";
+  Adjacent neighbor("/ndn/neighborA", ndn::util::FaceUri(faceUri), 10, Adjacent::STATUS_INACTIVE, 0, 0);
+  BOOST_REQUIRE_EQUAL(nlsr.getAdjacencyList().insert(neighbor), 0);
+
+  this->advanceClocks(ndn::time::milliseconds(1));
+
+  // Build, sign, and send the Face Event
+  ndn::nfd::FaceEventNotification event;
+  event.setKind(ndn::nfd::FACE_EVENT_CREATED)
+    .setRemoteUri(faceUri)
+    .setFaceId(faceId);
+  std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>("/localhost/nfd/faces/events/%FE%00");
+  data->setContent(event.wireEncode());
+  nlsr.getKeyChain().sign(*data);
+  face->receive(*data);
+
+  // Move the clocks forward so that the Face processes the event.
+  this->advanceClocks(ndn::time::milliseconds(1));
+
+  // Need to explicitly provide a FaceUri object, because the
+  // conversion will attempt to create Name objects.
+  auto iterator = nlsr.getAdjacencyList().findAdjacent(ndn::util::FaceUri(faceUri));
+  BOOST_REQUIRE(iterator != nlsr.getAdjacencyList().end());
+  BOOST_CHECK_EQUAL(iterator->getFaceId(), faceId);
+}
+
+BOOST_AUTO_TEST_CASE(FaceCreateEventNoMatch)
+{
+  // Setting constants for the unit test
+  const uint32_t faceId = 1;
+  const std::string eventUri = "udp4://10.0.0.1:6363";
+  const std::string neighborUri = "udp4://10.0.0.2:6363";
+  Adjacent neighbor("/ndn/neighborA", ndn::util::FaceUri(neighborUri), 10, Adjacent::STATUS_INACTIVE, 0, 0);
+  nlsr.getAdjacencyList().insert(neighbor);
+
+  // Build, sign, and send the Face Event
+  ndn::nfd::FaceEventNotification event;
+  event.setKind(ndn::nfd::FACE_EVENT_CREATED)
+    .setRemoteUri(eventUri)
+    .setFaceId(faceId);
+  std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>("/localhost/nfd/faces/events/%FE%00");
+  data->setContent(event.wireEncode());
+  nlsr.getKeyChain().sign(*data);
+  face->receive(*data);
+
+  // Move the clocks forward so that the Face processes the event.
+  this->advanceClocks(ndn::time::milliseconds(1));
+
+  // The Face URIs did not match, so this neighbor should be unconfigured.
+  auto iterator = nlsr.getAdjacencyList().findAdjacent(ndn::util::FaceUri(neighborUri));
+  BOOST_REQUIRE(iterator != nlsr.getAdjacencyList().end());
+  BOOST_CHECK_EQUAL(iterator->getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceCreateEventAlreadyConfigured)
+{
+  // Setting constants for the unit test
+  const uint32_t eventFaceId = 1;
+  const uint32_t neighborFaceId = 2;
+  const std::string faceUri = "udp4://10.0.0.1:6363";
+  Adjacent neighbor("/ndn/neighborA", ndn::util::FaceUri(faceUri), 10, Adjacent::STATUS_ACTIVE, 0, neighborFaceId);
+  nlsr.getAdjacencyList().insert(neighbor);
+
+  // Build, sign, and send the Face Event
+  ndn::nfd::FaceEventNotification event;
+  event.setKind(ndn::nfd::FACE_EVENT_CREATED)
+    .setRemoteUri(faceUri)
+    .setFaceId(eventFaceId);
+  std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>("/localhost/nfd/faces/events/%FE%00");
+  data->setContent(event.wireEncode());
+  nlsr.getKeyChain().sign(*data);
+  face->receive(*data);
+
+  // Move the clocks forward so that the Face processes the event.
+  this->advanceClocks(ndn::time::milliseconds(1));
+
+  // Since the neighbor was already configured, this (simply erroneous) event should have no effect.
+  auto iterator = nlsr.getAdjacencyList().findAdjacent(ndn::util::FaceUri(faceUri));
+  BOOST_REQUIRE(iterator != nlsr.getAdjacencyList().end());
+  BOOST_CHECK_EQUAL(iterator->getFaceId(), neighborFaceId);
+}
+
 BOOST_FIXTURE_TEST_CASE(FaceDestroyEvent, UnitTestTimeFixture)
 {
   std::shared_ptr<ndn::util::DummyClientFace> face = std::make_shared<ndn::util::DummyClientFace>(g_ioService);
@@ -480,6 +568,134 @@
                     ndn::util::FaceUri("udp4://10.0.0.2:6363"));
 }
 
+BOOST_AUTO_TEST_CASE(FaceDatasetFetchSuccess)
+{
+  bool hasResult = false;
+  nlsr.m_validator.m_shouldValidate = false;
+
+  nlsr.initializeFaces([&hasResult] (const std::vector<ndn::nfd::FaceStatus>& faces) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(faces.size(), 2);
+      BOOST_CHECK_EQUAL(faces.front().getFaceId(), 25401);
+      BOOST_CHECK_EQUAL(faces.back().getFaceId(), 25402);
+    },
+    [] (uint32_t code, const std::string& reason) {});
+
+  this->advanceClocks(ndn::time::milliseconds(500));
+
+  ndn::nfd::FaceStatus payload1;
+  payload1.setFaceId(25401);
+  ndn::nfd::FaceStatus payload2;
+  payload2.setFaceId(25402);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+
+  this->advanceClocks(ndn::time::milliseconds(500));
+  BOOST_CHECK(hasResult);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDatasetFetchFailure)
+{
+  nlsr.m_validator.m_shouldValidate = false;
+  nlsr.initializeFaces([](const std::vector<ndn::nfd::FaceStatus>& faces) {},
+    [this](uint32_t code, const std::string& reason){
+      this->nFailureCallbacks++;
+    });
+  this->advanceClocks(ndn::time::milliseconds(500));
+
+  ndn::Name payload;
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(ndn::time::milliseconds(500));
+
+  BOOST_CHECK_EQUAL(nFailureCallbacks, 1);
+  BOOST_CHECK_EQUAL(nSuccessCallbacks, 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDatasetProcess)
+{
+  Adjacent neighborA("/ndn/neighborA", ndn::util::FaceUri("udp4://192.168.0.100:6363"), 25, Adjacent::STATUS_INACTIVE, 0, 0);
+  neighbors.insert(neighborA);
+
+  Adjacent neighborB("/ndn/neighborB", ndn::util::FaceUri("udp4://192.168.0.101:6363"), 10, Adjacent::STATUS_INACTIVE, 0, 0);
+  neighbors.insert(neighborB);
+
+  ndn::nfd::FaceStatus payload1;
+  payload1.setFaceId(1)
+    .setRemoteUri("udp4://192.168.0.100:6363");
+  ndn::nfd::FaceStatus payload2;
+  payload2.setFaceId(2)
+    .setRemoteUri("udp4://192.168.0.101:6363");
+  std::vector<ndn::nfd::FaceStatus> faceStatuses = {payload1, payload2};
+
+  nlsr.processFaceDataset(faceStatuses);
+  this->advanceClocks(ndn::time::milliseconds(100));
+
+  AdjacencyList adjList = nlsr.getAdjacencyList();
+
+  BOOST_CHECK_EQUAL(adjList.getAdjacent("/ndn/neighborA").getFaceId(), payload1.getFaceId());
+  BOOST_CHECK_EQUAL(adjList.getAdjacent("/ndn/neighborB").getFaceId(), payload2.getFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(UnconfiguredNeighbor)
+{
+  Adjacent neighborA("/ndn/neighborA", ndn::util::FaceUri("udp4://192.168.0.100:6363"), 25, Adjacent::STATUS_INACTIVE, 0, 0);
+  neighbors.insert(neighborA);
+
+  ndn::nfd::FaceStatus payload;
+  payload.setFaceId(1)
+    .setRemoteUri("udp4://192.168.0.101:6363"); // Note dissimilar Face URI.
+  std::vector<ndn::nfd::FaceStatus> faceStatuses = {payload};
+
+  nlsr.processFaceDataset(faceStatuses);
+  this->advanceClocks(ndn::time::milliseconds(100));
+
+  AdjacencyList adjList = nlsr.getAdjacencyList();
+
+  BOOST_CHECK_EQUAL(adjList.getAdjacent("/ndn/neighborA").getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDatasetPeriodicFetch)
+{
+  int nNameMatches = 0;
+  ndn::Name datasetPrefix("/localhost/nfd/faces/list");
+  ndn::nfd::CommandOptions options;
+  ndn::time::milliseconds defaultTimeout = options.getTimeout();
+
+  ndn::time::seconds fetchInterval(1);
+  ConfParameter& conf = nlsr.getConfParameter();
+  conf.setFaceDatasetFetchInterval(fetchInterval);
+  conf.setFaceDatasetFetchTries(0);
+
+  nlsr.initializeFaces(std::bind(&Nlsr::processFaceDataset, &nlsr, _1),
+                       std::bind(&Nlsr::onFaceDatasetFetchTimeout, &nlsr, _1, _2, 0));
+
+  // Elapse the default timeout time of the interest.
+  this->advanceClocks(defaultTimeout);
+
+  // Check that we have one interest for face list in the sent interests.
+  for (const ndn::Interest& interest : face->sentInterests) {
+    if (datasetPrefix.isPrefixOf(interest.getName())) {
+      nNameMatches++;
+    }
+  }
+  BOOST_CHECK_EQUAL(nNameMatches, 1);
+
+  // Elapse the clock by the reschedule time (that we set)
+  this->advanceClocks(fetchInterval);
+  // Elapse the default timeout on the interest.
+  this->advanceClocks(defaultTimeout);
+  // Plus a little more to let the events process.
+  this->advanceClocks(ndn::time::seconds(1));
+
+  // Check that we now have two interests
+  nNameMatches = 0;
+  for (const ndn::Interest& interest : face->sentInterests) {
+    if (datasetPrefix.isPrefixOf(interest.getName())) {
+      nNameMatches++;
+    }
+  }
+  BOOST_CHECK_EQUAL(nNameMatches, 2);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace test
diff --git a/tests/utility/test-face-controller.cpp b/tests/utility/test-face-controller.cpp
deleted file mode 100644
index 7684256..0000000
--- a/tests/utility/test-face-controller.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  The University of Memphis,
- *                           Regents of the University of California,
- *                           Arizona Board of Regents.
- *
- * 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 "tests/test-common.hpp"
-#include "tests/control-commands.hpp"
-
-#include "utility/face-controller.hpp"
-
-#include <ndn-cxx/security/key-chain.hpp>
-#include <ndn-cxx/mgmt/nfd/controller.hpp>
-#include <ndn-cxx/util/dummy-client-face.hpp>
-
-namespace nlsr {
-namespace test {
-
-using std::bind;
-using std::shared_ptr;
-using ndn::Interest;
-
-class FaceControllerFixture : public BaseFixture
-{
-public:
-  FaceControllerFixture()
-    : face(std::make_shared<ndn::util::DummyClientFace>())
-    , interests(face->sentInterests)
-    , controller(*face, keyChain)
-    , faceController(g_ioService, controller)
-  {
-  }
-
-  void
-  onFailure(const ndn::nfd::ControlResponse& response)
-  {
-    BOOST_CHECK_EQUAL(response.getCode(), 408);
-  }
-
-public:
-  std::shared_ptr<ndn::util::DummyClientFace> face;
-  ndn::KeyChain keyChain;
-  std::vector<Interest>& interests;
-  ndn::nfd::Controller controller;
-  util::FaceController faceController;
-};
-
-BOOST_FIXTURE_TEST_SUITE(TestFaceController, FaceControllerFixture)
-
-BOOST_AUTO_TEST_CASE(FaceCreateCanonizeSuccess)
-{
-  const std::string uri("udp4://192.0.2.1:6363");
-  faceController.createFace(uri, nullptr, nullptr);
-
-  face->processEvents(ndn::time::milliseconds(1));
-
-  BOOST_REQUIRE_EQUAL(interests.size(), 1);
-  Interest interest = interests.front();
-
-  ndn::nfd::ControlParameters extractedParameters;
-  ndn::Name::Component verb;
-
-  extractFaceCommandParameters(interest, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, ndn::Name::Component("create"));
-  BOOST_CHECK_EQUAL(uri, extractedParameters.getUri());
-}
-
-BOOST_AUTO_TEST_CASE(FaceCreateCanonizeFailure)
-{
-  faceController.createFace("invalid://256.0.0.1:6363",
-                            nullptr,
-                            std::bind(&FaceControllerFixture::onFailure, this, _1));
-
-  face->processEvents(ndn::time::milliseconds(1));
-
-  BOOST_CHECK_EQUAL(interests.size(), 0);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // namespace test
-} // namespace nlsr