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