mgmt refactoring: FibManager

Change-Id: Ied235d025cbe5059545c6482bfed0f38f987fdeb
Refs: #2107
diff --git a/daemon/mgmt/fib-manager.cpp b/daemon/mgmt/fib-manager.cpp
new file mode 100644
index 0000000..12374f1
--- /dev/null
+++ b/daemon/mgmt/fib-manager.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fib-manager.hpp"
+#include <ndn-cxx/management/nfd-fib-entry.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("FibManager");
+
+FibManager::FibManager(Fib& fib,
+                       function<shared_ptr<Face>(FaceId)> getFace,
+                       Dispatcher& dispatcher,
+                       CommandValidator& validator)
+  : ManagerBase(dispatcher, validator, "fib")
+  , m_fib(fib)
+  , m_getFace(getFace)
+{
+  registerCommandHandler<ndn::nfd::FibAddNextHopCommand>("add-nexthop",
+    bind(&FibManager::addNextHop, this, _2, _3, _4, _5));
+  registerCommandHandler<ndn::nfd::FibRemoveNextHopCommand>("remove-nexthop",
+    bind(&FibManager::removeNextHop, this, _2, _3, _4, _5));
+
+  registerStatusDatasetHandler("list", bind(&FibManager::listEntries, this, _1, _2, _3));
+}
+
+void
+FibManager::addNextHop(const Name& topPrefix, const Interest& interest,
+                       ControlParameters parameters,
+                       const ndn::mgmt::CommandContinuation& done)
+{
+  setFaceForSelfRegistration(interest, parameters);
+
+  const Name& prefix = parameters.getName();
+  FaceId faceId = parameters.getFaceId();
+  uint64_t cost = parameters.getCost();
+
+  NFD_LOG_TRACE("add-nexthop prefix: " << prefix
+                << " faceid: " << faceId
+                << " cost: " << cost);
+
+  auto face = m_getFace(faceId);
+  if (static_cast<bool>(face)) {
+    auto entry = m_fib.insert(prefix).first;
+
+    entry->addNextHop(face, cost);
+
+    NFD_LOG_DEBUG("add-nexthop result: OK"
+                  << " prefix:" << prefix
+                  << " faceid: " << faceId
+                  << " cost: " << cost);
+
+    return done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
+  }
+  else {
+    NFD_LOG_INFO("add-nexthop result: FAIL reason: unknown-faceid: " << faceId);
+    return done(ControlResponse(410, "Face not found"));
+  }
+}
+
+void
+FibManager::removeNextHop(const Name& topPrefix, const Interest& interest,
+                          ControlParameters parameters,
+                          const ndn::mgmt::CommandContinuation& done)
+{
+  setFaceForSelfRegistration(interest, parameters);
+
+  NFD_LOG_TRACE("remove-nexthop prefix: " << parameters.getName()
+                << " faceid: " << parameters.getFaceId());
+
+  auto face = m_getFace(parameters.getFaceId());
+  if (static_cast<bool>(face)) {
+    auto entry = m_fib.findExactMatch(parameters.getName());
+    if (static_cast<bool>(entry)) {
+      entry->removeNextHop(face);
+      NFD_LOG_DEBUG("remove-nexthop result: OK prefix: " << parameters.getName()
+                    << " faceid: " << parameters.getFaceId());
+
+      if (!entry->hasNextHops()) {
+        m_fib.erase(*entry);
+      }
+    }
+    else {
+      NFD_LOG_DEBUG("remove-nexthop result: OK");
+    }
+  }
+  else {
+    NFD_LOG_DEBUG("remove-nexthop result: OK");
+  }
+
+  done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
+}
+
+void
+FibManager::listEntries(const Name& topPrefix, const Interest& interest,
+                        ndn::mgmt::StatusDatasetContext& context)
+{
+  for (auto&& entry : m_fib) {
+    auto prefix = entry.getPrefix();
+    ndn::nfd::FibEntry record;
+    const auto& nextHops = entry.getNextHops();
+
+    for (auto&& next : nextHops) {
+      ndn::nfd::NextHopRecord nextHopRecord;
+      nextHopRecord.setFaceId(next.getFace()->getId());
+      nextHopRecord.setCost(next.getCost());
+
+      record.addNextHopRecord(nextHopRecord);
+    }
+
+    record.setPrefix(prefix);
+    context.append(record.wireEncode());
+  }
+
+  context.end();
+}
+
+void
+FibManager::setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters)
+{
+  bool isSelfRegistration = (parameters.getFaceId() == 0);
+  if (isSelfRegistration) {
+    parameters.setFaceId(request.getIncomingFaceId());
+  }
+}
+
+} // namespace
diff --git a/daemon/mgmt/fib-manager.hpp b/daemon/mgmt/fib-manager.hpp
new file mode 100644
index 0000000..7c66138
--- /dev/null
+++ b/daemon/mgmt/fib-manager.hpp
@@ -0,0 +1,82 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_MGMT_FIB_MANAGER_HPP
+#define NFD_DAEMON_MGMT_FIB_MANAGER_HPP
+
+#include "manager-base.hpp"
+#include "core/logger.hpp"
+#include "table/fib.hpp"
+#include "fw/forwarder.hpp"
+
+namespace nfd {
+
+/**
+ * @brief implement the FIB Management of NFD Management Protocol.
+ * @sa http://redmine.named-data.net/projects/nfd/wiki/FibMgmt
+ */
+class FibManager : public ManagerBase
+{
+public:
+  /**
+   * @brief construct a FibManger
+   *
+   * @param fib the managed FIB
+   * @param getFace a function used to retrive a face by FaceId from the face table
+   * @param dispatcher the management dispatcher
+   * @param validator the command validator
+   */
+  FibManager(Fib& fib,
+             function<shared_ptr<Face>(FaceId)> getFace,
+             Dispatcher& dispatcher,
+             CommandValidator& validator);
+
+private:
+  void
+  addNextHop(const Name& topPrefix, const Interest& interest,
+             ControlParameters parameters,
+             const ndn::mgmt::CommandContinuation& done);
+
+  void
+  removeNextHop(const Name& topPrefix, const Interest& interest,
+                ControlParameters parameters,
+                const ndn::mgmt::CommandContinuation& done);
+
+  void
+  listEntries(const Name& topPrefix, const Interest& interest,
+              ndn::mgmt::StatusDatasetContext& context);
+
+private:
+  void
+  setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters);
+
+private:
+  Fib& m_fib;
+  function<shared_ptr<Face>(FaceId)> m_getFace;
+};
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_MGMT_FIB_MANAGER_HPP
diff --git a/daemon/nfd.cpp b/daemon/nfd.cpp
index 14930ca..023d3d8 100644
--- a/daemon/nfd.cpp
+++ b/daemon/nfd.cpp
@@ -33,7 +33,7 @@
 #include "face/null-face.hpp"
 #include "face/internal-face.hpp"
 #include "face/internal-client-face.hpp"
-// #include "mgmt/fib-manager.hpp"
+#include "mgmt/fib-manager.hpp"
 #include "mgmt/face-manager.hpp"
 // #include "mgmt/strategy-choice-manager.hpp"
 // #include "mgmt/status-server.hpp"
@@ -138,9 +138,10 @@
 
   m_validator.reset(new CommandValidator());
 
-  // m_fibManager.reset(new FibManager(m_forwarder->getFib(),
-  //                                   bind(&Forwarder::getFace, m_forwarder.get(), _1),
-  //                                   m_internalFace, m_keyChain));
+  m_fibManager.reset(new FibManager(m_forwarder->getFib(),
+                                    bind(&Forwarder::getFace, m_forwarder.get(), _1),
+                                    *m_dispatcher,
+                                    *m_validator));
 
   m_faceManager.reset(new FaceManager(m_forwarder->getFaceTable(),
                                       *m_dispatcher,
diff --git a/daemon/nfd.hpp b/daemon/nfd.hpp
index eb7e012..b18700a 100644
--- a/daemon/nfd.hpp
+++ b/daemon/nfd.hpp
@@ -113,7 +113,7 @@
   unique_ptr<CommandValidator>      m_validator;
 
   unique_ptr<ndn::mgmt::Dispatcher> m_dispatcher;
-  // unique_ptr<FibManager>            m_fibManager;
+  unique_ptr<FibManager>            m_fibManager;
   unique_ptr<FaceManager>           m_faceManager;
   // unique_ptr<StrategyChoiceManager> m_strategyChoiceManager;
   // unique_ptr<StatusServer>          m_statusServer;
diff --git a/tests/daemon/mgmt/fib-manager.t.cpp b/tests/daemon/mgmt/fib-manager.t.cpp
new file mode 100644
index 0000000..513536c
--- /dev/null
+++ b/tests/daemon/mgmt/fib-manager.t.cpp
@@ -0,0 +1,491 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "mgmt/fib-manager.hpp"
+
+#include "manager-common-fixture.hpp"
+#include "table/fib-nexthop.hpp"
+#include "../face/dummy-face.hpp"
+#include <ndn-cxx/management/nfd-fib-entry.hpp>
+
+namespace nfd {
+namespace tests {
+
+class FibManagerFixture : public ManagerCommonFixture
+{
+public:
+  FibManagerFixture()
+    : m_fib(m_forwarder.getFib())
+    , m_faceTable(m_forwarder.getFaceTable())
+    , m_manager(m_fib, bind(&Forwarder::getFace, &m_forwarder, _1), m_dispatcher, m_validator)
+  {
+    setTopPrefixAndPrivilege("/localhost/nfd", "fib");
+  }
+
+public: // for test
+  ControlParameters
+  makeParameters(const Name& name, const FaceId& id)
+  {
+    return ControlParameters().setName(name).setFaceId(id);
+  }
+
+  ControlParameters
+  makeParameters(const Name& name, const FaceId& id, const uint32_t& cost)
+  {
+    return ControlParameters().setName(name).setFaceId(id).setCost(cost);
+  }
+
+  FaceId
+  addFace()
+  {
+    auto face = make_shared<DummyFace>();
+    m_faceTable.add(face);
+    advanceClocks(time::milliseconds(1), 10);
+    m_responses.clear(); // clear all event notifications, if any
+    return face->getId();
+  }
+
+public: // for check
+  enum class CheckNextHopResult
+  {
+    OK,
+    NO_FIB_ENTRY,
+    WRONG_N_NEXTHOPS,
+    NO_NEXTHOP,
+    WRONG_COST
+   };
+
+  /**
+   * @brief check whether the nexthop record is added / removed properly
+   *
+   * @param expectedNNextHops use -1 to skip this check
+   * @param faceId use FACEID_NULL to skip NextHopRecord checks
+   * @param expectedCost use -1 to skip this check
+   *
+   * @retval OK FIB entry is found by exact match and has the expected number of nexthops;
+   *            NextHopRe record for faceId is found and has the expected cost
+   * @retval NO_FIB_ENTRY FIB entry is not found
+   * @retval WRONG_N_NEXTHOPS FIB entry is found but has wrong number of nexthops
+   * @retval NO_NEXTHOP NextHopRecord for faceId is not found
+   * @retval WRONG_COST NextHopRecord for faceId has wrong cost
+   */
+  CheckNextHopResult
+  checkNextHop(const Name& prefix, ssize_t expectedNNextHops = -1,
+               FaceId faceId = FACEID_NULL, int32_t expectedCost = -1)
+  {
+    auto entry = m_fib.findExactMatch(prefix);
+    if (!static_cast<bool>(entry)) {
+      return CheckNextHopResult::NO_FIB_ENTRY;
+    }
+
+    auto nextHops = entry->getNextHops();
+    if (expectedNNextHops != -1 && nextHops.size() != static_cast<size_t>(expectedNNextHops)) {
+      return CheckNextHopResult::WRONG_N_NEXTHOPS;
+    }
+
+    if (faceId != FACEID_NULL) {
+      for (auto&& record : nextHops) {
+        if (record.getFace()->getId() == faceId) {
+          return expectedCost != -1 && record.getCost() != static_cast<uint32_t>(expectedCost) ?
+            CheckNextHopResult::WRONG_COST : CheckNextHopResult::OK;
+        }
+      }
+
+      return CheckNextHopResult::NO_NEXTHOP;
+    }
+
+    return CheckNextHopResult::OK;
+  }
+
+protected:
+  Fib&       m_fib;
+  FaceTable& m_faceTable;
+  FibManager m_manager;
+};
+
+std::ostream&
+operator<<(std::ostream &os, const FibManagerFixture::CheckNextHopResult& result)
+{
+  switch (result) {
+  case FibManagerFixture::CheckNextHopResult::OK:
+    os << "OK";
+    break;
+  case FibManagerFixture::CheckNextHopResult::NO_FIB_ENTRY:
+    os << "NO_FIB_ENTRY";
+    break;
+  case FibManagerFixture::CheckNextHopResult::WRONG_N_NEXTHOPS:
+    os << "WRONG_N_NEXTHOPS";
+    break;
+  case FibManagerFixture::CheckNextHopResult::NO_NEXTHOP:
+    os << "NO_NEXTHOP";
+    break;
+  case FibManagerFixture::CheckNextHopResult::WRONG_COST:
+    os << "WRONG_COST";
+    break;
+  default:
+    break;
+  };
+
+  return os;
+}
+
+BOOST_FIXTURE_TEST_SUITE(Mgmt, FibManagerFixture)
+BOOST_AUTO_TEST_SUITE(TestFibManager)
+
+BOOST_AUTO_TEST_SUITE(AddNextHop)
+
+BOOST_AUTO_TEST_CASE(UnknownFaceId)
+{
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop",
+                                           makeParameters("hello", FACEID_NULL, 101));
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+
+  // check response
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+
+  // double check that the next hop was not added
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, FACEID_NULL, 101), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitFaceId)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testAddNextHop = [&] (ControlParameters parameters, const FaceId& faceId) {
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters,
+                                             [&faceId] (shared_ptr<Interest> interest) {
+                                               interest->setIncomingFaceId(faceId);
+                                             });
+    m_responses.clear();
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters.setFaceId(faceId));
+    receiveInterest(command);
+  };
+
+  testAddNextHop(ControlParameters().setName("/hello").setCost(100).setFaceId(0), face1);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face1, 100), CheckNextHopResult::OK);
+
+  testAddNextHop(ControlParameters().setName("/hello").setCost(100), face2);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face2, 100), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(InitialAdd)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_REQUIRE(addedFaceId != INVALID_FACEID);
+
+  auto parameters = makeParameters("hello", addedFaceId, 101);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "Success", parameters)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, addedFaceId, 101), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitCost)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_REQUIRE(addedFaceId != INVALID_FACEID);
+
+  auto originalParameters = ControlParameters().setName("/hello").setFaceId(addedFaceId);
+  auto parameters = makeParameters("/hello", addedFaceId, 0);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", originalParameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "Success", parameters)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, addedFaceId, 0), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(AddToExisting)
+{
+  FaceId face = addFace();
+  BOOST_CHECK(face != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testAddNextHop = [&] (const ControlParameters& parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  // add initial, succeeds
+  testAddNextHop(makeParameters("/hello", face, 101));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+
+  // add to existing --> update cost, succeeds
+  testAddNextHop(makeParameters("/hello", face, 102));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face, 102), CheckNextHopResult::WRONG_N_NEXTHOPS);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face, 101), CheckNextHopResult::WRONG_COST);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face, 102), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // AddNextHop
+
+BOOST_AUTO_TEST_SUITE(RemoveNextHop)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testRemoveNextHop = [&] (const ControlParameters& parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  FaceId face1 = addFace();
+  FaceId face2 = addFace();
+  FaceId face3 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID && face3 != INVALID_FACEID);
+
+  shared_ptr<fib::Entry> entry = m_fib.insert("/hello").first;
+  entry->addNextHop(m_faceTable.get(face1), 101);
+  entry->addNextHop(m_faceTable.get(face2), 202);
+  entry->addNextHop(m_faceTable.get(face3), 303);
+
+  testRemoveNextHop(makeParameters("/hello", face1));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face1, 101), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face2));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face2, 202), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face3));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 0, face3, 303), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(PrefixNotFound)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_CHECK(addedFaceId != INVALID_FACEID);
+
+  auto parameters = makeParameters("hello", addedFaceId);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+  auto response = makeResponse(200, "Success", parameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), response), CheckResponseResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitFaceId)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testWithImplicitFaceId = [&] (ControlParameters parameters, FaceId face) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters,
+                                             [face] (shared_ptr<Interest> interest) {
+                                               interest->setIncomingFaceId(face);
+                                             });
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters.setFaceId(face));
+    receiveInterest(command);
+  };
+
+  shared_ptr<fib::Entry> entry = m_fib.insert("/hello").first;
+  entry->addNextHop(m_faceTable.get(face1), 101);
+  entry->addNextHop(m_faceTable.get(face2), 202);
+
+  testWithImplicitFaceId(ControlParameters().setName("/hello").setFaceId(0), face1);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face1, 101), CheckNextHopResult::NO_NEXTHOP);
+
+  testWithImplicitFaceId(ControlParameters().setName("/hello"), face2);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 0, face2, 202), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(RecordNotExist)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testRemoveNextHop = [&] (ControlParameters parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  m_fib.insert("/hello").first->addNextHop(m_faceTable.get(face1), 101);
+
+  testRemoveNextHop(makeParameters("/hello", face2 + 100));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1); // face does not exist
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, face2 + 100), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face2));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1); // record does not exist
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, face2), CheckNextHopResult::NO_NEXTHOP);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // RemoveNextHop
+
+// @todo Remove when ndn::nfd::FibEntry implements operator!= and operator<<
+class FibEntry : public ndn::nfd::FibEntry
+{
+public:
+  FibEntry() = default;
+
+  FibEntry(const ndn::nfd::FibEntry& entry)
+    : ndn::nfd::FibEntry(entry)
+  {
+  }
+};
+
+bool
+operator!=(const FibEntry& left, const FibEntry& right)
+{
+  if (left.getPrefix() != right.getPrefix()) {
+    return true;
+  }
+
+  auto leftNextHops = left.getNextHopRecords();
+  auto rightNextHops = right.getNextHopRecords();
+  if (leftNextHops.size() != rightNextHops.size()) {
+    return true;
+  }
+
+  for (auto&& nexthop : leftNextHops) {
+    auto hitEntry =
+      std::find_if(rightNextHops.begin(), rightNextHops.end(), [&] (const ndn::nfd::NextHopRecord& record) {
+          return nexthop.getCost() == record.getCost() && nexthop.getFaceId() == record.getFaceId();
+        });
+
+    if (hitEntry == rightNextHops.end()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+std::ostream&
+operator<<(std::ostream &os, const FibEntry& entry)
+{
+  const auto& nexthops = entry.getNextHopRecords();
+  os << "[" << entry.getPrefix() << ", " << nexthops.size() << ": ";
+  for (auto record : nexthops) {
+    os << "{" << record.getFaceId() << ", " << record.getCost() << "} ";
+  }
+  os << "]";
+
+  return os;
+}
+
+BOOST_AUTO_TEST_CASE(FibDataset)
+{
+  const size_t nEntries = 108;
+  std::set<Name> actualPrefixes;
+  for (size_t i = 0 ; i < nEntries ; i ++) {
+    Name prefix = Name("test").appendSegment(i);
+    actualPrefixes.insert(prefix);
+    auto fibEntry = m_fib.insert(prefix).first;
+    fibEntry->addNextHop(m_faceTable.get(addFace()), std::numeric_limits<uint8_t>::max() - 1);
+    fibEntry->addNextHop(m_faceTable.get(addFace()), std::numeric_limits<uint8_t>::max() - 2);
+  }
+
+  receiveInterest(makeInterest("/localhost/nfd/fib/list"));
+
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+  std::vector<FibEntry> receivedRecords, expectedRecords;
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    FibEntry decodedEntry;
+    BOOST_REQUIRE_NO_THROW(decodedEntry.wireDecode(content.elements()[idx]));
+    receivedRecords.push_back(decodedEntry);
+
+    actualPrefixes.erase(decodedEntry.getPrefix());
+
+    auto matchedEntry = m_fib.findExactMatch(decodedEntry.getPrefix());
+    BOOST_REQUIRE(matchedEntry != nullptr);
+
+    FibEntry record;
+    record.setPrefix(matchedEntry->getPrefix());
+    const auto& nextHops = matchedEntry->getNextHops();
+    for (auto&& next : nextHops) {
+      ndn::nfd::NextHopRecord nextHopRecord;
+      nextHopRecord.setFaceId(next.getFace()->getId());
+      nextHopRecord.setCost(next.getCost());
+      record.addNextHopRecord(nextHopRecord);
+    }
+    expectedRecords.push_back(record);
+  }
+
+  BOOST_CHECK_EQUAL(actualPrefixes.size(), 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(receivedRecords.begin(), receivedRecords.end(),
+                                expectedRecords.begin(), expectedRecords.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd