mgmt: added FIB manager support for insert, delete, and remove-nexthop verbs

Refactor option decoding, verb authorization (placeholder),
and response sending into single location.
Added unit tests for ManagerBase.

refs: #1223

Change-Id: I731be586ee8f06defb00fcadc6010409560891a1
diff --git a/daemon/mgmt/fib-manager.cpp b/daemon/mgmt/fib-manager.cpp
index 69be19f..3bcbb1a 100644
--- a/daemon/mgmt/fib-manager.cpp
+++ b/daemon/mgmt/fib-manager.cpp
@@ -11,12 +11,9 @@
 #include "mgmt/internal-face.hpp"
 #include "mgmt/app-face.hpp"
 
-
-
 #include <ndn-cpp-dev/management/fib-management-options.hpp>
 #include <ndn-cpp-dev/encoding/tlv.hpp>
 
-#include <ndn-cpp-dev/management/fib-management-options.hpp>
 
 namespace nfd {
 
@@ -35,33 +32,32 @@
 
 const FibManager::VerbAndProcessor FibManager::FIB_MANAGER_COMMAND_VERBS[] =
   {
-    // Unsupported
+    VerbAndProcessor(
+                     "insert",
+                     &FibManager::insertEntry
+                     ),
 
-    // VerbAndProcessor(
-    //                  "insert",
-    //                  &FibManager::fibInsert
-    //                  ),
-
-    // VerbAndProcessor(
-    //                  "delete",
-    //                  &FibManager::fibDelete
-    //                  ),
+    VerbAndProcessor(
+                     "delete",
+                     &FibManager::deleteEntry
+                     ),
 
     VerbAndProcessor(
                      "add-nexthop",
-                     &FibManager::fibAddNextHop
+                     &FibManager::addNextHop
+                     ),
+
+
+
+    VerbAndProcessor(
+                     "remove-nexthop",
+                     &FibManager::removeNextHop
                      ),
 
     // Unsupported
-
-    // VerbAndProcessor(
-    //                  "remove-nexthop",
-    //                  &FibManager::fibRemoveNextHop
-    //                  ),
-
     // VerbAndProcessor(
     //                  "strategy",
-    //                  &FibManager::fibStrategy
+    //                  &FibManager::strategy
     //                  )
 
   };
@@ -86,12 +82,10 @@
   const Name& command = request.getName();
   const size_t commandNComps = command.size();
 
-  /// \todo Separate out response status codes 400 and 401
-
   if (FIB_MANAGER_COMMAND_UNSIGNED_NCOMPS <= commandNComps &&
       commandNComps < FIB_MANAGER_COMMAND_SIGNED_NCOMPS)
     {
-      NFD_LOG_INFO("Unsigned command: " << command);
+      NFD_LOG_INFO("command result: unsigned verb: " << command);
       sendResponse(command, 401, "Signature required");
 
       return;
@@ -99,45 +93,51 @@
   else if (commandNComps < FIB_MANAGER_COMMAND_SIGNED_NCOMPS ||
       !FIB_MANAGER_COMMAND_PREFIX.isPrefixOf(command))
     {
-      NFD_LOG_INFO("Malformed command: " << command);
+      NFD_LOG_INFO("command result: malformed");
       sendResponse(command, 400, "Malformed command");
       return;
     }
 
-
   const Name::Component& verb = command.get(FIB_MANAGER_COMMAND_PREFIX.size());
 
   VerbDispatchTable::const_iterator verbProcessor = m_verbDispatch.find (verb);
   if (verbProcessor != m_verbDispatch.end())
     {
-      NFD_LOG_INFO("Processing command verb: " << verb);
-      (verbProcessor->second)(this, request);
+      ndn::FibManagementOptions options;
+      if (!extractOptions(request, options))
+        {
+          sendResponse(command, 400, "Malformed command");
+          return;
+        }
+
+      /// \todo authorize command
+      if (false)
+        {
+          NFD_LOG_INFO("command result: unauthorized verb: " << command);
+          sendResponse(request.getName(), 403, "Unauthorized command");
+          return;
+        }
+
+      NFD_LOG_INFO("command result: processing verb: " << verb);
+
+      ndn::ControlResponse response;
+      (verbProcessor->second)(this, options, response);
+
+      sendResponse(command, response);
     }
   else
     {
-      NFD_LOG_INFO("Unsupported command verb: " << verb);
+      NFD_LOG_INFO("command result: unsupported verb: " << verb);
       sendResponse(request.getName(), 501, "Unsupported command");
     }
 
 }
 
-void
-FibManager::fibInsert(const Interest& request)
-{
-
-}
-
-void
-FibManager::fibDelete(const Interest& request)
-{
-
-}
-
-void
-FibManager::fibAddNextHop(const Interest& request)
+bool
+FibManager::extractOptions(const Interest& request,
+                           ndn::FibManagementOptions& extractedOptions)
 {
   const Name& command = request.getName();
-  ndn::FibManagementOptions options;
   const size_t optionCompIndex =
     FIB_MANAGER_COMMAND_PREFIX.size() + 1;
 
@@ -148,49 +148,117 @@
   try
     {
       Block rawOptions(tmpOptionBuffer);
-      options.wireDecode(rawOptions);
+      extractedOptions.wireDecode(rawOptions);
     }
   catch (const ndn::Tlv::Error& e)
     {
       NFD_LOG_INFO("Bad command option parse: " << command);
-      sendResponse(request.getName(), 400, "Malformed command");
-      return;
+      return false;
     }
+  NFD_LOG_DEBUG("Options parsed OK");
+  return true;
+}
 
-  /// \todo authorize command
-  if (false)
-    {
-      NFD_LOG_INFO("Unauthorized command attempt: " << command);
-      sendResponse(request.getName(), 403, "Unauthorized command");
-      return;
-    }
+void
+FibManager::insertEntry(const ndn::FibManagementOptions& options,
+                        ndn::ControlResponse& response)
+{
+  NFD_LOG_DEBUG("insert prefix: " << options.getName());
+  NFD_LOG_INFO("insert result: OK"
+               << " prefix: " << options.getName());
+  std::pair<shared_ptr<fib::Entry>, bool> insertResult = m_managedFib.insert(options.getName());
+  setResponse(response, 200, "OK");
+}
 
-  NFD_LOG_INFO("add-nexthop Name: " << options.getName()
-               << " FaceId: " << options.getFaceId()
-               << " Cost: " << options.getCost());
+void
+FibManager::deleteEntry(const ndn::FibManagementOptions& options,
+                        ndn::ControlResponse& response)
+{
+  NFD_LOG_DEBUG("delete prefix: " << options.getName());
+  NFD_LOG_INFO("delete result: OK"
+               << " prefix: " << options.getName());
+
+  m_managedFib.remove(options.getName());
+  setResponse(response, 200, "OK");
+}
+
+static inline bool
+nextHopEqPredicate(const fib::NextHop& target, const fib::NextHop& hop)
+{
+  return target.getFace()->getId() == hop.getFace()->getId();
+}
+
+void
+FibManager::addNextHop(const ndn::FibManagementOptions& options,
+                          ndn::ControlResponse& response)
+{
+  NFD_LOG_DEBUG("add-nexthop prefix: " << options.getName()
+                << " faceid: " << options.getFaceId()
+                << " cost: " << options.getCost());
 
   shared_ptr<Face> nextHopFace = m_getFace(options.getFaceId());
   if (static_cast<bool>(nextHopFace))
     {
-      std::pair<shared_ptr<fib::Entry>, bool> insertResult = m_managedFib.insert(options.getName());
-      insertResult.first->addNextHop(nextHopFace, options.getCost());
-      sendResponse(request.getName(), 200, "OK");
+      shared_ptr<fib::Entry> entry = m_managedFib.findExactMatch(options.getName());
+      if (static_cast<bool>(entry))
+        {
+          entry->addNextHop(nextHopFace, options.getCost());
+
+          NFD_LOG_INFO("add-nexthop result: OK"
+                       << " prefix:" << options.getName()
+                       << " faceid: " << options.getFaceId()
+                       << " cost: " << options.getCost());
+          setResponse(response, 200, "OK");
+        }
+      else
+        {
+          NFD_LOG_INFO("add-nexthop result: FAIL reason: unknown-prefix: " << options.getName());
+          setResponse(response, 404, "Prefix not found");
+        }
     }
   else
     {
-      NFD_LOG_INFO("Unknown FaceId: " << command);
-      sendResponse(request.getName(), 400, "Malformed command");
+      NFD_LOG_INFO("add-nexthop result: FAIL reason: unknown-faceid: " << options.getFaceId());
+      setResponse(response, 404, "Face not found");
     }
 }
 
 void
-FibManager::fibRemoveNextHop(const Interest& request)
+FibManager::removeNextHop(const ndn::FibManagementOptions& options,
+                             ndn::ControlResponse &response)
 {
+  NFD_LOG_DEBUG("remove-nexthop prefix: " << options.getName()
+                << " faceid: " << options.getFaceId());
 
+  shared_ptr<Face> faceToRemove = m_getFace(options.getFaceId());
+  if (static_cast<bool>(faceToRemove))
+    {
+      shared_ptr<fib::Entry> entry = m_managedFib.findExactMatch(options.getName());
+      if (static_cast<bool>(entry))
+        {
+          entry->removeNextHop(faceToRemove);
+          NFD_LOG_INFO("remove-nexthop result: OK prefix: " << options.getName()
+                       << " faceid: " << options.getFaceId());
+
+          setResponse(response, 200, "OK");
+        }
+      else
+        {
+          NFD_LOG_INFO("remove-nexthop result: FAIL reason: unknown-prefix: "
+                       << options.getName());
+          setResponse(response, 404, "Prefix not found");
+        }
+    }
+  else
+    {
+      NFD_LOG_INFO("remove-nexthop result: FAIL reason: unknown-faceid: "
+                   << options.getFaceId());
+      setResponse(response, 404, "Face not found");
+    }
 }
 
 void
-FibManager::fibStrategy(const Interest& request)
+FibManager::strategy(const ndn::FibManagementOptions& options, ndn::ControlResponse& response)
 {
 
 }
diff --git a/daemon/mgmt/fib-manager.hpp b/daemon/mgmt/fib-manager.hpp
index d77a2f2..3b36aeb 100644
--- a/daemon/mgmt/fib-manager.hpp
+++ b/daemon/mgmt/fib-manager.hpp
@@ -13,6 +13,9 @@
 #include "fw/strategy.hpp"
 #include "mgmt/manager-base.hpp"
 
+#include <ndn-cpp-dev/management/fib-management-options.hpp>
+#include <ndn-cpp-dev/management/control-response.hpp>
+
 namespace nfd {
 
 class Forwarder;
@@ -32,19 +35,28 @@
 private:
 
   void
-  fibInsert(const Interest& request);
+  insertEntry(const ndn::FibManagementOptions& options,
+                ndn::ControlResponse& response);
 
   void
-  fibDelete(const Interest& request);
+  deleteEntry(const ndn::FibManagementOptions& options,
+                ndn::ControlResponse& response);
 
   void
-  fibAddNextHop(const Interest& request);
+  addNextHop(const ndn::FibManagementOptions& options,
+               ndn::ControlResponse& response);
 
   void
-  fibRemoveNextHop(const Interest& request);
+  removeNextHop(const ndn::FibManagementOptions& options,
+                  ndn::ControlResponse& response);
 
   void
-  fibStrategy(const Interest& request);
+  strategy(const ndn::FibManagementOptions& options,
+             ndn::ControlResponse& response);
+
+  bool
+  extractOptions(const Interest& request,
+                   ndn::FibManagementOptions& extractedOptions);
 
   // void
   // onConfig(ConfigFile::Node section, bool isDryRun);
@@ -56,7 +68,8 @@
   std::map<Name, shared_ptr<fw::Strategy> > m_namespaceToStrategyMap;
 
   typedef function<void(FibManager*,
-                        const Interest&)> VerbProcessor;
+                          const ndn::FibManagementOptions&,
+                          ndn::ControlResponse&)> VerbProcessor;
 
   typedef std::map<Name::Component, VerbProcessor> VerbDispatchTable;
 
diff --git a/daemon/mgmt/manager-base.cpp b/daemon/mgmt/manager-base.cpp
index 55787b6..8ab06e8 100644
--- a/daemon/mgmt/manager-base.cpp
+++ b/daemon/mgmt/manager-base.cpp
@@ -7,8 +7,6 @@
 #include "manager-base.hpp"
 #include "mgmt/app-face.hpp"
 
-#include <ndn-cpp-dev/management/control-response.hpp>
-
 namespace nfd {
 
 NFD_LOG_INIT("ManagerBase");
@@ -26,22 +24,29 @@
 
 void
 ManagerBase::sendResponse(const Name& name,
-                            uint32_t code,
-                            const std::string& text)
+                          uint32_t code,
+                          const std::string& text)
 {
-  ndn::ControlResponse control(code, text);
-  const Block& encodedControl = control.wireEncode();
+  ndn::ControlResponse response(code, text);
+  sendResponse(name, response);
+}
 
-  NFD_LOG_DEBUG("sending control response"
-                << " Name: " << name
-                << " code: " << code
-                << " text: " << text);
+void
+ManagerBase::sendResponse(const Name& name,
+                          const ndn::ControlResponse& response)
+{
+  NFD_LOG_DEBUG("responding"
+                << " name: " << name
+                << " code: " << response.getCode()
+                << " text: " << response.getText());
 
-  Data response(name);
-  response.setContent(encodedControl);
+  const Block& encodedControl = response.wireEncode();
 
-  m_face->sign(response);
-  m_face->put(response);
+  Data responseData(name);
+  responseData.setContent(encodedControl);
+
+  m_face->sign(responseData);
+  m_face->put(responseData);
 }
 
 
diff --git a/daemon/mgmt/manager-base.hpp b/daemon/mgmt/manager-base.hpp
index d62b1c0..e8da7c9 100644
--- a/daemon/mgmt/manager-base.hpp
+++ b/daemon/mgmt/manager-base.hpp
@@ -8,7 +8,7 @@
 #define NFD_MGMT_MANAGER_BASE_HPP
 
 #include "common.hpp"
-
+#include <ndn-cpp-dev/management/control-response.hpp>
 
 namespace nfd {
 
@@ -25,14 +25,32 @@
 protected:
 
   void
+  setResponse(ndn::ControlResponse& response,
+              uint32_t code,
+              const std::string& text);
+
+  void
   sendResponse(const Name& name,
-                 uint32_t code,
-                 const std::string& text);
+               const ndn::ControlResponse& response);
+
+  void
+  sendResponse(const Name& name,
+               uint32_t code,
+               const std::string& text);
 
 protected:
   shared_ptr<AppFace> m_face;
 };
 
+inline void
+ManagerBase::setResponse(ndn::ControlResponse& response,
+                         uint32_t code,
+                         const std::string& text)
+{
+  response.setCode(code);
+  response.setText(text);
+}
+
 
 } // namespace nfd
 
diff --git a/tests/mgmt/fib-manager.cpp b/tests/mgmt/fib-manager.cpp
index b07d11b..25c38b3 100644
--- a/tests/mgmt/fib-manager.cpp
+++ b/tests/mgmt/fib-manager.cpp
@@ -5,7 +5,6 @@
  */
 
 #include "mgmt/fib-manager.hpp"
-#include "fw/forwarder.hpp"
 #include "table/fib.hpp"
 #include "table/fib-nexthop.hpp"
 #include "face/face.hpp"
@@ -52,6 +51,7 @@
 
   void
   validateControlResponse(const Data& response,
+                          const Name& expectedName,
                           uint32_t expectedCode,
                           const std::string& expectedText)
   {
@@ -66,8 +66,9 @@
                   << " code: " << control.getCode()
                   << " text: " << control.getText());
 
-    BOOST_REQUIRE(control.getCode() == expectedCode);
-    BOOST_REQUIRE(control.getText() == expectedText);
+    BOOST_CHECK_EQUAL(response.getName(), expectedName);
+    BOOST_CHECK_EQUAL(control.getCode(), expectedCode);
+    BOOST_CHECK_EQUAL(control.getText(), expectedText);
   }
 
   bool
@@ -101,7 +102,7 @@
 bool
 addedNextHopWithCost(const Fib& fib, const Name& prefix, size_t oldSize, uint32_t cost)
 {
-  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch(prefix);
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(prefix);
 
   if (static_cast<bool>(entry))
     {
@@ -112,42 +113,44 @@
   return false;
 }
 
-BOOST_AUTO_TEST_CASE(TestFireInterestFilter)
+BOOST_FIXTURE_TEST_CASE(TestFireInterestFilter, FibManagerFixture)
 {
-  FibManagerFixture fixture;
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          &fixture, _1),
-                          face);
-
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, &fixture,  _1, 400, "Malformed command");
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
 
   Interest command("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this,  _1,
+         command.getName(), 400, "Malformed command");
+
   face->sendInterest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
 }
 
-BOOST_AUTO_TEST_CASE(MalformedCommmand)
+BOOST_FIXTURE_TEST_CASE(MalformedCommmand, FibManagerFixture)
 {
-  FibManagerFixture fixture;
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          &fixture, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
 
-  BOOST_REQUIRE(fixture.didCallbackFire() == false);
-
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, &fixture, _1, 400, "Malformed command");
+  BOOST_REQUIRE(didCallbackFire() == false);
 
   Interest command("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command.getName(), 400, "Malformed command");
+
   manager.onFibRequest(command);
 
-  BOOST_REQUIRE(fixture.didCallbackFire());
+  BOOST_REQUIRE(didCallbackFire());
 }
 
 BOOST_FIXTURE_TEST_CASE(UnsupportedVerb, FibManagerFixture)
@@ -155,13 +158,9 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 501, "Unsupported command");
-
   ndn::FibManagementOptions options;
   options.setName("/hello");
   options.setFaceId(1);
@@ -173,6 +172,10 @@
   commandName.append("unsupported");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 501, "Unsupported command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -186,13 +189,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
-  /// \todo enable once sig checking implement
-    // bind(&FibManagerFixture::validateControlResponse, this, _1, 401, "SIGREG");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -205,11 +203,17 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+  /// \todo enable once sig checking implemented
+    // bind(&FibManagerFixture::validateControlResponse, this, _1, 401, "Signature required");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+  BOOST_REQUIRE(!addedNextHopWithCost(fib, "/hello", 0, 101));
 }
 
 BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, FibManagerFixture)
@@ -219,13 +223,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
-  /// \todo enable once sig checking implement
-    // bind(&FibManagerFixture::validateControlResponse, this, _1, 403, "Unauthorized command");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -238,11 +237,17 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+  /// \todo enable once sig checking implemented
+    // bind(&FibManagerFixture::validateControlResponse, this, _1, 403, "Unauthorized command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+  BOOST_REQUIRE(!addedNextHopWithCost(fib, "/hello", 0, 101));
 }
 
 BOOST_FIXTURE_TEST_CASE(BadOptionParse, FibManagerFixture)
@@ -252,17 +257,17 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 400, "Malformed command");
-
   Name commandName("/localhost/nfd/fib");
   commandName.append("add-nexthop");
   commandName.append("NotReallyOptions");
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 400, "Malformed command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -276,13 +281,9 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 400, "Malformed command");
-
   ndn::FibManagementOptions options;
   options.setName("/hello");
   options.setFaceId(1000);
@@ -294,6 +295,10 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Face not found");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -308,11 +313,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -325,6 +327,12 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 200, "OK");
+
+  fib.insert("/hello");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -335,26 +343,21 @@
 BOOST_FIXTURE_TEST_CASE(AddNextHopVerbAddToExisting, FibManagerFixture)
 {
   addFace(make_shared<DummyFace>());
-  addFace(make_shared<DummyFace>());
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
 
-  // Add faces with cost == FaceID for the name /hello
-  // This test assumes:
-  //   FaceIDs are -1 because we don't add them to a forwarder
+  fib.insert("/hello");
 
   for (int i = 1; i <= 2; i++)
     {
+
       ndn::FibManagementOptions options;
       options.setName("/hello");
-      options.setFaceId(i);
+      options.setFaceId(1);
       options.setCost(100 + i);
 
       Block encodedOptions(options.wireEncode());
@@ -363,25 +366,31 @@
       commandName.append("add-nexthop");
       commandName.append(encodedOptions);
 
+      face->onReceiveData +=
+        bind(&FibManagerFixture::validateControlResponse, this, _1,
+             commandName, 200, "OK");
+
       Interest command(commandName);
       manager.onFibRequest(command);
       BOOST_REQUIRE(didCallbackFire());
       resetCallbackFired();
 
-      shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+      shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
 
       if (static_cast<bool>(entry))
         {
           const fib::NextHopList& hops = entry->getNextHops();
-          for (int j = 1; j <= i; j++)
-            {
-              BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", i - 1, 100 + j));
-            }
+          BOOST_REQUIRE(hops.size() == 1);
+          BOOST_REQUIRE(std::find_if(hops.begin(), hops.end(),
+                                     bind(&foundNextHop, -1, 100 + i, _1)) != hops.end());
+
         }
       else
         {
           BOOST_FAIL("Failed to find expected fib entry");
         }
+
+      face->onReceiveData.clear();
     }
 }
 
@@ -396,8 +405,8 @@
                      bind(&FibManagerFixture::getFace,
                           this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
+
+  fib.insert("/hello");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -412,12 +421,19 @@
     commandName.append("add-nexthop");
     commandName.append(encodedOptions);
 
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
     Interest command(commandName);
     manager.onFibRequest(command);
+
     BOOST_REQUIRE(didCallbackFire());
-    resetCallbackFired();
   }
 
+  resetCallbackFired();
+  face->onReceiveData.clear();
+
   {
     options.setCost(102);
 
@@ -427,12 +443,17 @@
     commandName.append("add-nexthop");
     commandName.append(encodedOptions);
 
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
     Interest command(commandName);
     manager.onFibRequest(command);
+
     BOOST_REQUIRE(didCallbackFire());
   }
 
-  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+  shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
 
   // Add faces with cost == FaceID for the name /hello
   // This test assumes:
@@ -451,6 +472,286 @@
     }
 }
 
+BOOST_FIXTURE_TEST_CASE(Insert, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  {
+    ndn::FibManagementOptions options;
+    options.setName("/hello");
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("insert");
+    commandName.append(encodedOptions);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      BOOST_CHECK_EQUAL(hops.size(), 0);
+    }
+
+  resetCallbackFired();
+
+  {
+    ndn::FibManagementOptions options;
+    options.setName("/hello");
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("insert");
+    commandName.append(encodedOptions);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  entry = fib.findExactMatch("/hello");
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      BOOST_CHECK_EQUAL(hops.size(), 0);
+    }
+
+}
+
+void
+testRemove(FibManagerFixture* fixture,
+           FibManager& manager,
+           Fib& fib,
+           shared_ptr<Face> face,
+           const Name& target)
+{
+  ndn::FibManagementOptions options;
+  options.setName(target);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("delete");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, fixture, _1,
+         commandName, 200, "OK");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(fixture->didCallbackFire());
+
+  if (static_cast<bool>(fib.findExactMatch(target)))
+    {
+      BOOST_FAIL("Found \"removed\" prefix");
+    }
+  face->onReceiveData.clear();
+}
+
+BOOST_FIXTURE_TEST_CASE(Delete, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  fib.insert("/a");
+  fib.insert("/a/b");
+  fib.insert("/a/b/c");
+
+  testRemove(this, manager, fib, face, "/");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b/c")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a/b");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b/c")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a/b/c");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a");
+
+  testRemove(this, manager, fib, face, "/does/not/exist");
+}
+
+bool
+removedNextHopWithCost(const Fib& fib, const Name& prefix, size_t oldSize, uint32_t cost)
+{
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(prefix);
+
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      return hops.size() == oldSize - 1 &&
+        std::find_if(hops.begin(), hops.end(), bind(&foundNextHop, -1, cost, _1)) == hops.end();
+    }
+  return false;
+}
+
+void
+testRemoveNextHop(FibManagerFixture* fixture,
+                  FibManager& manager,
+                  Fib& fib,
+                  shared_ptr<Face> face,
+                  const Name& targetName,
+                  FaceId targetFace)
+{
+  ndn::FibManagementOptions options;
+  options.setName(targetName);
+  options.setFaceId(targetFace);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, fixture, _1,
+         commandName, 200, "OK");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(fixture->didCallbackFire());
+
+  fixture->resetCallbackFired();
+  face->onReceiveData.clear();
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNextHop, FibManagerFixture)
+{
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  shared_ptr<Face> face3 = make_shared<DummyFace>();
+
+  addFace(face1);
+  addFace(face2);
+  addFace(face3);
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                          face);
+
+  shared_ptr<fib::Entry> entry = fib.insert("/hello").first;
+
+  entry->addNextHop(face1, 101);
+  entry->addNextHop(face2, 202);
+  entry->addNextHop(face3, 303);
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 2);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 3, 202));
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 3);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 2, 303));
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 1);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 1, 101));
+
+  if (!static_cast<bool>(fib.findExactMatch("/hello")))
+    {
+      BOOST_FAIL("removed entry after removing all next hops");
+    }
+
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNoFace, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                          face);
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Face not found");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNoPrefix, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace nfd
diff --git a/tests/mgmt/manager-base.cpp b/tests/mgmt/manager-base.cpp
new file mode 100644
index 0000000..6105699
--- /dev/null
+++ b/tests/mgmt/manager-base.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "mgmt/manager-base.hpp"
+#include "mgmt/internal-face.hpp"
+
+#include <ndn-cpp-dev/management/control-response.hpp>
+
+#include <boost/test/unit_test.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("ManagerBaseTest");
+
+BOOST_AUTO_TEST_SUITE(MgmtManagerBase)
+
+class ManagerBaseTest : public ManagerBase
+{
+
+public:
+
+  ManagerBaseTest()
+    : ManagerBase(make_shared<InternalFace>()),
+      m_callbackFired(false)
+  {
+
+  }
+
+  void
+  testSetResponse(ndn::ControlResponse& response,
+                  uint32_t code,
+                  const std::string& text)
+  {
+    setResponse(response, code, text);
+  }
+
+  void
+  testSendResponse(const Name& name,
+                   uint32_t code,
+                   const std::string& text)
+  {
+    sendResponse(name, code, text);
+  }
+
+  void
+  testSendResponse(const Name& name,
+                   const ndn::ControlResponse& response)
+  {
+    sendResponse(name, response);
+  }
+
+  shared_ptr<InternalFace>
+  getInternalFace()
+  {
+    return ndn::ptr_lib::static_pointer_cast<InternalFace>(m_face);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText)
+  {
+    m_callbackFired = true;
+    Block controlRaw = response.getContent().blockFromValue();
+
+    ndn::ControlResponse control;
+    control.wireDecode(controlRaw);
+
+    NFD_LOG_DEBUG("received control response"
+                  << " name: " << response.getName()
+                  << " code: " << control.getCode()
+                  << " text: " << control.getText());
+
+    BOOST_REQUIRE(response.getName() == expectedName);
+    BOOST_REQUIRE(control.getCode() == expectedCode);
+    BOOST_REQUIRE(control.getText() == expectedText);
+  }
+
+  bool
+  didCallbackFire()
+  {
+    return m_callbackFired;
+  }
+
+private:
+
+  bool m_callbackFired;
+
+};
+
+BOOST_FIXTURE_TEST_CASE(SetResponse, ManagerBaseTest)
+{
+  ndn::ControlResponse response(200, "OK");
+
+  BOOST_CHECK_EQUAL(response.getCode(), 200);
+  BOOST_CHECK_EQUAL(response.getText(), "OK");
+
+  testSetResponse(response, 100, "test");
+
+  BOOST_CHECK_EQUAL(response.getCode(), 100);
+  BOOST_CHECK_EQUAL(response.getText(), "test");
+}
+
+
+BOOST_FIXTURE_TEST_CASE(SendResponse3Arg, ManagerBaseTest)
+{
+  getInternalFace()->onReceiveData +=
+    bind(&ManagerBaseTest::validateControlResponse, this, _1,
+         "/response", 100, "test");
+
+  testSendResponse("/response", 100, "test");
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+
+BOOST_FIXTURE_TEST_CASE(SendResponse2Arg, ManagerBaseTest)
+{
+  getInternalFace()->onReceiveData +=
+    bind(&ManagerBaseTest::validateControlResponse, this, _1,
+         "/response", 100, "test");
+
+  ndn::ControlResponse response(100, "test");
+
+  testSendResponse("/response", 100, "test");
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace nfd
+
+
+
+
+
+
+