nlsr: discover Faces from NFD

refs: #2954

Change-Id: I072972d88bce0e1012e96f33577657048b7df1e1
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