Merge branch for issue #1138

Change-Id: Id4c2ddb3707983cc07177ebd834f8fb3a40f9606
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index d9850df..1ce3b8f 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -49,6 +49,13 @@
   m_fib.removeNextHopFromAllEntries(face);
 }
 
+shared_ptr<Face>
+Forwarder::getFace(FaceId id)
+{
+  std::map<FaceId, shared_ptr<Face> >::iterator i = m_faces.find(id);
+  return (i == m_faces.end()) ? (shared_ptr<Face>()) : (i->second);
+}
+
 void
 Forwarder::onInterest(Face& face, const Interest& interest)
 {
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index 1359737..0ea838b 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -49,6 +49,9 @@
   Cs&
   getCs();
 
+  shared_ptr<Face>
+  getFace(FaceId id);
+
 private: // pipelines
   /** \brief incoming Interest pipeline
    */
@@ -135,7 +138,6 @@
   return m_cs;
 }
 
-
 } // namespace nfd
 
 #endif // NFD_FW_FORWARDER_HPP
diff --git a/daemon/mgmt/app-face.cpp b/daemon/mgmt/app-face.cpp
new file mode 100644
index 0000000..79bf39f
--- /dev/null
+++ b/daemon/mgmt/app-face.cpp
@@ -0,0 +1,17 @@
+/* -*- 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 "app-face.hpp"
+
+namespace nfd {
+
+void
+AppFace::sign(Data& data)
+{
+  m_keyChain.sign(data);
+}
+
+} // namespace nfd
diff --git a/daemon/mgmt/app-face.hpp b/daemon/mgmt/app-face.hpp
index af58cdf..b6e2ab5 100644
--- a/daemon/mgmt/app-face.hpp
+++ b/daemon/mgmt/app-face.hpp
@@ -9,9 +9,11 @@
 
 #include "common.hpp"
 
+#include <ndn-cpp-dev/security/key-chain.hpp>
+
 namespace nfd {
 
-typedef ndn::func_lib::function<void(const Name&, const Interest&)> OnInterest;
+typedef function<void(const Name&, const Interest&)> OnInterest;
 
 class AppFace
 {
@@ -21,10 +23,16 @@
                     OnInterest onInterest) = 0;
 
   virtual void
+  sign(Data& data);
+
+  virtual void
   put(const Data& data) = 0;
 
   virtual
   ~AppFace() { }
+
+protected:
+  ndn::KeyChain m_keyChain;
 };
 
 } // namespace nfd
diff --git a/daemon/mgmt/fib-manager.cpp b/daemon/mgmt/fib-manager.cpp
new file mode 100644
index 0000000..69be19f
--- /dev/null
+++ b/daemon/mgmt/fib-manager.cpp
@@ -0,0 +1,204 @@
+/* -*- 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 "fib-manager.hpp"
+
+#include "table/fib.hpp"
+#include "fw/forwarder.hpp"
+#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 {
+
+NFD_LOG_INIT("FibManager");
+
+const Name FibManager::FIB_MANAGER_COMMAND_PREFIX = "/localhost/nfd/fib";
+
+const size_t FibManager::FIB_MANAGER_COMMAND_UNSIGNED_NCOMPS =
+  FibManager::FIB_MANAGER_COMMAND_PREFIX.size() +
+  1 + // verb
+  1;  // verb options
+
+const size_t FibManager::FIB_MANAGER_COMMAND_SIGNED_NCOMPS =
+  FibManager::FIB_MANAGER_COMMAND_UNSIGNED_NCOMPS +
+  0; // No signed Interest support in mock, otherwise 3 (timestamp, signed info tlv, signature tlv)
+
+const FibManager::VerbAndProcessor FibManager::FIB_MANAGER_COMMAND_VERBS[] =
+  {
+    // Unsupported
+
+    // VerbAndProcessor(
+    //                  "insert",
+    //                  &FibManager::fibInsert
+    //                  ),
+
+    // VerbAndProcessor(
+    //                  "delete",
+    //                  &FibManager::fibDelete
+    //                  ),
+
+    VerbAndProcessor(
+                     "add-nexthop",
+                     &FibManager::fibAddNextHop
+                     ),
+
+    // Unsupported
+
+    // VerbAndProcessor(
+    //                  "remove-nexthop",
+    //                  &FibManager::fibRemoveNextHop
+    //                  ),
+
+    // VerbAndProcessor(
+    //                  "strategy",
+    //                  &FibManager::fibStrategy
+    //                  )
+
+  };
+
+FibManager::FibManager(Fib& fib,
+                       function<shared_ptr<Face>(FaceId)> getFace,
+                       shared_ptr<AppFace> face)
+  : ManagerBase(face),
+    m_managedFib(fib),
+    m_getFace(getFace),
+    m_verbDispatch(FIB_MANAGER_COMMAND_VERBS,
+                   FIB_MANAGER_COMMAND_VERBS +
+                   (sizeof(FIB_MANAGER_COMMAND_VERBS) / sizeof(VerbAndProcessor)))
+{
+  face->setInterestFilter("/localhost/nfd/fib",
+                          bind(&FibManager::onFibRequest, this, _2));
+}
+
+void
+FibManager::onFibRequest(const Interest& request)
+{
+  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);
+      sendResponse(command, 401, "Signature required");
+
+      return;
+    }
+  else if (commandNComps < FIB_MANAGER_COMMAND_SIGNED_NCOMPS ||
+      !FIB_MANAGER_COMMAND_PREFIX.isPrefixOf(command))
+    {
+      NFD_LOG_INFO("Malformed command: " << command);
+      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);
+    }
+  else
+    {
+      NFD_LOG_INFO("Unsupported command 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)
+{
+  const Name& command = request.getName();
+  ndn::FibManagementOptions options;
+  const size_t optionCompIndex =
+    FIB_MANAGER_COMMAND_PREFIX.size() + 1;
+
+  const ndn::Buffer& optionBuffer =
+    request.getName()[optionCompIndex].getValue();
+  shared_ptr<const ndn::Buffer> tmpOptionBuffer(make_shared<ndn::Buffer>(optionBuffer));
+
+  try
+    {
+      Block rawOptions(tmpOptionBuffer);
+      options.wireDecode(rawOptions);
+    }
+  catch (const ndn::Tlv::Error& e)
+    {
+      NFD_LOG_INFO("Bad command option parse: " << command);
+      sendResponse(request.getName(), 400, "Malformed command");
+      return;
+    }
+
+  /// \todo authorize command
+  if (false)
+    {
+      NFD_LOG_INFO("Unauthorized command attempt: " << command);
+      sendResponse(request.getName(), 403, "Unauthorized command");
+      return;
+    }
+
+  NFD_LOG_INFO("add-nexthop Name: " << 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");
+    }
+  else
+    {
+      NFD_LOG_INFO("Unknown FaceId: " << command);
+      sendResponse(request.getName(), 400, "Malformed command");
+    }
+}
+
+void
+FibManager::fibRemoveNextHop(const Interest& request)
+{
+
+}
+
+void
+FibManager::fibStrategy(const Interest& request)
+{
+
+}
+
+// void
+// FibManager::onConfig(ConfigFile::Node section, bool isDryRun)
+// {
+
+// }
+
+} // namespace nfd
diff --git a/daemon/mgmt/fib-manager.hpp b/daemon/mgmt/fib-manager.hpp
new file mode 100644
index 0000000..d77a2f2
--- /dev/null
+++ b/daemon/mgmt/fib-manager.hpp
@@ -0,0 +1,84 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_MGMT_FIB_MANAGER_HPP
+#define NFD_MGMT_FIB_MANAGER_HPP
+
+#include "common.hpp"
+#include "face/face.hpp"
+#include "mgmt/app-face.hpp"
+#include "fw/strategy.hpp"
+#include "mgmt/manager-base.hpp"
+
+namespace nfd {
+
+class Forwarder;
+class Fib;
+
+class FibManager : public ManagerBase
+{
+public:
+
+  FibManager(Fib& fib,
+             function<shared_ptr<Face>(FaceId)> getFace,
+             shared_ptr<AppFace> face);
+
+  void
+  onFibRequest(const Interest& request);
+
+private:
+
+  void
+  fibInsert(const Interest& request);
+
+  void
+  fibDelete(const Interest& request);
+
+  void
+  fibAddNextHop(const Interest& request);
+
+  void
+  fibRemoveNextHop(const Interest& request);
+
+  void
+  fibStrategy(const Interest& request);
+
+  // void
+  // onConfig(ConfigFile::Node section, bool isDryRun);
+
+private:
+
+  Fib& m_managedFib;
+  function<shared_ptr<Face>(FaceId)> m_getFace;
+  std::map<Name, shared_ptr<fw::Strategy> > m_namespaceToStrategyMap;
+
+  typedef function<void(FibManager*,
+                        const Interest&)> VerbProcessor;
+
+  typedef std::map<Name::Component, VerbProcessor> VerbDispatchTable;
+
+  typedef std::pair<Name::Component, VerbProcessor> VerbAndProcessor;
+
+
+  const VerbDispatchTable m_verbDispatch;
+
+  static const Name FIB_MANAGER_COMMAND_PREFIX; // /localhost/nfd/fib
+
+  // number of components in an invalid, but not malformed, unsigned command.
+  // (/localhost/nfd/fib + verb + options) = 5
+  static const size_t FIB_MANAGER_COMMAND_UNSIGNED_NCOMPS;
+
+  // number of components in a valid signed Interest.
+  // 5 in mock (see UNSIGNED_NCOMPS), 8 with signed Interest support.
+  static const size_t FIB_MANAGER_COMMAND_SIGNED_NCOMPS;
+
+  static const VerbAndProcessor FIB_MANAGER_COMMAND_VERBS[];
+
+};
+
+} // namespace nfd
+
+#endif // NFD_MGMT_FIB_MANAGER_HPP
diff --git a/daemon/mgmt/internal-face.cpp b/daemon/mgmt/internal-face.cpp
index e3daeca..7af328f 100644
--- a/daemon/mgmt/internal-face.cpp
+++ b/daemon/mgmt/internal-face.cpp
@@ -8,6 +8,8 @@
 
 namespace nfd {
 
+NFD_LOG_INIT("InternalFace");
+
 InternalFace::InternalFace()
 {
 
@@ -16,12 +18,71 @@
 void
 InternalFace::sendInterest(const Interest& interest)
 {
-  static const Name prefixRegPrefix("/localhost/nfd/prefixreg");
-  const Name& interestName = interest.getName();
-
-  if (prefixRegPrefix.isPrefixOf(interestName))
+  if (m_interestFilters.size() == 0)
     {
-      //invoke FibManager
+      NFD_LOG_DEBUG("no Interest filters to match against");
+      return;
+    }
+
+  const Name& interestName(interest.getName());
+  NFD_LOG_DEBUG("received Interest: " << interestName);
+
+  std::map<Name, OnInterest>::const_iterator filter =
+    m_interestFilters.lower_bound(interestName);
+
+  // lower_bound gives us the first Name that is
+  // an exact match OR ordered after interestName.
+  //
+  // If we reach the end of the map, then we need
+  // only check if the before-end element is a match.
+  //
+  // If we match an element, then the current
+  // position or the previous element are potential
+  // matches.
+  //
+  // If we hit begin, the element is either an exact
+  // match or there is no matching prefix in the map.
+
+
+  if (filter == m_interestFilters.end() && filter != m_interestFilters.begin())
+    {
+      // We hit the end, check if the previous element
+      // is a match
+      --filter;
+      if (filter->first.isPrefixOf(interestName))
+        {
+          NFD_LOG_DEBUG("found Interest filter for " << filter->first << " (before end match)");
+          filter->second(interestName, interest);
+        }
+      else
+        {
+          NFD_LOG_DEBUG("no Interest filter found for " << interestName << " (before end)");
+        }
+    }
+  else if (filter->first == interestName)
+    {
+      NFD_LOG_DEBUG("found Interest filter for " << filter->first << " (exact match)");
+      filter->second(interestName, interest);
+    }
+  else if (filter != m_interestFilters.begin())
+    {
+      // the element we found is canonically
+      // ordered after interestName.
+      // Check the previous element.
+      --filter;
+      if (filter->first.isPrefixOf(interestName))
+        {
+          NFD_LOG_DEBUG("found Interest filter for " << filter->first << " (previous match)");
+          filter->second(interestName, interest);
+        }
+      else
+        {
+          NFD_LOG_DEBUG("no Interest filter found for " << interestName << " (previous)");
+        }
+    }
+  else
+    {
+      NFD_LOG_DEBUG("no Interest filter found for " << interestName << " (begin)");
     }
   //Drop Interest
 }
@@ -36,7 +97,8 @@
 InternalFace::setInterestFilter(const Name& filter,
                                 OnInterest onInterest)
 {
-
+  NFD_LOG_INFO("registering callback for " << filter);
+  m_interestFilters[filter] = onInterest;
 }
 
 void
diff --git a/daemon/mgmt/internal-face.hpp b/daemon/mgmt/internal-face.hpp
index 7798143..9539a9e 100644
--- a/daemon/mgmt/internal-face.hpp
+++ b/daemon/mgmt/internal-face.hpp
@@ -44,7 +44,6 @@
   // onConfig(ConfigFile::Node section, bool isDryRun);
 
   std::map<Name, OnInterest> m_interestFilters;
-
 };
 
 } // namespace nfd
diff --git a/daemon/mgmt/manager-base.cpp b/daemon/mgmt/manager-base.cpp
new file mode 100644
index 0000000..55787b6
--- /dev/null
+++ b/daemon/mgmt/manager-base.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "manager-base.hpp"
+#include "mgmt/app-face.hpp"
+
+#include <ndn-cpp-dev/management/control-response.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("ManagerBase");
+
+ManagerBase::ManagerBase(shared_ptr<AppFace> face)
+  : m_face(face)
+{
+
+}
+
+ManagerBase::~ManagerBase()
+{
+
+}
+
+void
+ManagerBase::sendResponse(const Name& name,
+                            uint32_t code,
+                            const std::string& text)
+{
+  ndn::ControlResponse control(code, text);
+  const Block& encodedControl = control.wireEncode();
+
+  NFD_LOG_DEBUG("sending control response"
+                << " Name: " << name
+                << " code: " << code
+                << " text: " << text);
+
+  Data response(name);
+  response.setContent(encodedControl);
+
+  m_face->sign(response);
+  m_face->put(response);
+}
+
+
+} // namespace nfd
diff --git a/daemon/mgmt/manager-base.hpp b/daemon/mgmt/manager-base.hpp
new file mode 100644
index 0000000..d62b1c0
--- /dev/null
+++ b/daemon/mgmt/manager-base.hpp
@@ -0,0 +1,40 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_MGMT_MANAGER_BASE_HPP
+#define NFD_MGMT_MANAGER_BASE_HPP
+
+#include "common.hpp"
+
+
+namespace nfd {
+
+class AppFace;
+
+class ManagerBase
+{
+public:
+  ManagerBase(shared_ptr<AppFace> face);
+
+  virtual
+  ~ManagerBase();
+
+protected:
+
+  void
+  sendResponse(const Name& name,
+                 uint32_t code,
+                 const std::string& text);
+
+protected:
+  shared_ptr<AppFace> m_face;
+};
+
+
+} // namespace nfd
+
+#endif // NFD_MGMT_MANAGER_BASE_HPP
+
diff --git a/tests/mgmt/fib-manager.cpp b/tests/mgmt/fib-manager.cpp
new file mode 100644
index 0000000..b07d11b
--- /dev/null
+++ b/tests/mgmt/fib-manager.cpp
@@ -0,0 +1,456 @@
+/* -*- 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/fib-manager.hpp"
+#include "fw/forwarder.hpp"
+#include "table/fib.hpp"
+#include "table/fib-nexthop.hpp"
+#include "face/face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "../face/dummy-face.hpp"
+
+#include <algorithm>
+
+#include <ndn-cpp-dev/management/fib-management-options.hpp>
+#include <ndn-cpp-dev/management/control-response.hpp>
+
+#include <boost/test/unit_test.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("FibManagerTest");
+
+class FibManagerFixture
+{
+public:
+
+  FibManagerFixture()
+    : m_callbackFired(false)
+  {
+
+  }
+
+  shared_ptr<Face>
+  getFace(FaceId id)
+  {
+    if (id > 0 && id <= m_faces.size())
+      {
+        return m_faces[id-1];
+      }
+    NFD_LOG_DEBUG("No face found returning NULL");
+    return shared_ptr<DummyFace>();
+  }
+
+  void
+  addFace(shared_ptr<Face> face)
+  {
+    m_faces.push_back(face);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          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(control.getCode() == expectedCode);
+    BOOST_REQUIRE(control.getText() == expectedText);
+  }
+
+  bool
+  didCallbackFire()
+  {
+    return m_callbackFired;
+  }
+
+  void
+  resetCallbackFired()
+  {
+    m_callbackFired = false;
+  }
+
+private:
+  std::vector<shared_ptr<Face> > m_faces;
+  bool m_callbackFired;
+};
+
+
+BOOST_AUTO_TEST_SUITE(MgmtFibManager)
+
+
+
+bool
+foundNextHop(FaceId id, uint32_t cost, const fib::NextHop& next)
+{
+  return id == next.getFace()->getId() && next.getCost() == cost;
+}
+
+bool
+addedNextHopWithCost(const Fib& fib, const Name& prefix, size_t oldSize, uint32_t cost)
+{
+  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch(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;
+}
+
+BOOST_AUTO_TEST_CASE(TestFireInterestFilter)
+{
+  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");
+
+  Interest command("/localhost/nfd/fib");
+  face->sendInterest(command);
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCommmand)
+{
+  FibManagerFixture fixture;
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
+
+  BOOST_REQUIRE(fixture.didCallbackFire() == false);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, &fixture, _1, 400, "Malformed command");
+
+  Interest command("/localhost/nfd/fib");
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(fixture.didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnsupportedVerb, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     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);
+  options.setCost(1);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("unsupported");
+  commandName.append(encodedOptions);
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnsignedCommand, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     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");
+  options.setFaceId(1);
+  options.setCost(101);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedOptions);
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+}
+
+BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     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");
+  options.setFaceId(1);
+  options.setCost(101);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedOptions);
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+}
+
+BOOST_FIXTURE_TEST_CASE(BadOptionParse, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     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");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnknownFaceId, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     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);
+  options.setCost(101);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedOptions);
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101) == false);
+}
+
+BOOST_FIXTURE_TEST_CASE(AddNextHopVerbInitialAdd, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          this, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+  options.setCost(101);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedOptions);
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+}
+
+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),
+                          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
+
+  for (int i = 1; i <= 2; i++)
+    {
+      ndn::FibManagementOptions options;
+      options.setName("/hello");
+      options.setFaceId(i);
+      options.setCost(100 + i);
+
+      Block encodedOptions(options.wireEncode());
+
+      Name commandName("/localhost/nfd/fib");
+      commandName.append("add-nexthop");
+      commandName.append(encodedOptions);
+
+      Interest command(commandName);
+      manager.onFibRequest(command);
+      BOOST_REQUIRE(didCallbackFire());
+      resetCallbackFired();
+
+      shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/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));
+            }
+        }
+      else
+        {
+          BOOST_FAIL("Failed to find expected fib entry");
+        }
+    }
+}
+
+BOOST_FIXTURE_TEST_CASE(AddNextHopVerbUpdateFaceCost, FibManagerFixture)
+{
+  FibManagerFixture fixture;
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          this, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+
+  {
+    options.setCost(1);
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedOptions);
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+    BOOST_REQUIRE(didCallbackFire());
+    resetCallbackFired();
+  }
+
+  {
+    options.setCost(102);
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedOptions);
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+    BOOST_REQUIRE(didCallbackFire());
+  }
+
+  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+
+  // Add faces with cost == FaceID for the name /hello
+  // This test assumes:
+  //   FaceIDs are -1 because we don't add them to a forwarder
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      BOOST_REQUIRE(hops.size() == 1);
+      BOOST_REQUIRE(std::find_if(hops.begin(),
+                                 hops.end(),
+                                 bind(&foundNextHop, -1, 102, _1)) != hops.end());
+    }
+  else
+    {
+      BOOST_FAIL("Failed to find expected fib entry");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace nfd
diff --git a/tests/mgmt/internal-face.cpp b/tests/mgmt/internal-face.cpp
index 0ece5b6..5953fc6 100644
--- a/tests/mgmt/internal-face.cpp
+++ b/tests/mgmt/internal-face.cpp
@@ -5,25 +5,241 @@
  */
 
 #include "mgmt/internal-face.hpp"
+#include "mgmt/fib-manager.hpp"
+#include "table/fib.hpp"
+#include "../face/dummy-face.hpp"
+
+#include <ndn-cpp-dev/management/fib-management-options.hpp>
+#include <ndn-cpp-dev/management/control-response.hpp>
+#include <ndn-cpp-dev/encoding/block.hpp>
 
 #include <boost/test/unit_test.hpp>
 
 namespace nfd {
 
+NFD_LOG_INIT("InternalFaceTest");
+
+class InternalFaceFixture
+{
+public:
+
+  InternalFaceFixture()
+    : m_onInterestFired(false),
+      m_noOnInterestFired(false)
+  {
+
+  }
+
+  shared_ptr<Face>
+  getFace(FaceId id)
+  {
+    if (m_faces.size() < id)
+      {
+        BOOST_FAIL("Attempted to access invalid FaceId: " << id);
+      }
+    return m_faces[id-1];
+  }
+
+  void
+  validateOnInterestCallback(const Name& name, const Interest& interest)
+  {
+    m_onInterestFired = true;
+  }
+
+  void
+  validateNoOnInterestCallback(const Name& name, const Interest& interest)
+  {
+    m_noOnInterestFired = true;
+  }
+
+  void
+  addFace(shared_ptr<Face> face)
+  {
+    m_faces.push_back(face);
+  }
+
+  bool
+  didOnInterestFire()
+  {
+    return m_onInterestFired;
+  }
+
+  bool
+  didNoOnInterestFire()
+  {
+    return m_noOnInterestFired;
+  }
+
+  void
+  resetOnInterestFired()
+  {
+    m_onInterestFired = false;
+  }
+
+  void
+  resetNoOnInterestFired()
+  {
+    m_noOnInterestFired = false;
+  }
+
+private:
+  std::vector<shared_ptr<Face> > m_faces;
+  bool m_onInterestFired;
+  bool m_noOnInterestFired;
+};
+
 BOOST_AUTO_TEST_SUITE(MgmtInternalFace)
 
-BOOST_AUTO_TEST_CASE(ValidPrefixRegistration)
+void
+validatePutData(bool& called, const Name& expectedName, const Data& data)
 {
-  InternalFace internal;
-  Interest regInterest("/localhost/nfd/prefixreg/hello/world");
-  internal.sendInterest(regInterest);
+  called = true;
+  BOOST_CHECK_EQUAL(expectedName, data.getName());
 }
 
-BOOST_AUTO_TEST_CASE(InvalidPrefixRegistration)
+BOOST_FIXTURE_TEST_CASE(PutData, InternalFaceFixture)
 {
-  InternalFace internal;
-  Interest nonRegInterest("/hello/world");
-  internal.sendInterest(nonRegInterest);
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          this, _1),
+                          face);
+
+  bool didPutData = false;
+  Name dataName("/hello");
+  face->onReceiveData += bind(&validatePutData, boost::ref(didPutData), dataName, _1);
+
+  Data testData(dataName);
+  face->sign(testData);
+  face->put(testData);
+
+  BOOST_REQUIRE(didPutData);
+}
+
+BOOST_FIXTURE_TEST_CASE(SendInterestHitEnd, InternalFaceFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          this, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          bind(&InternalFaceFixture::validateOnInterestCallback,
+                               this, _1, _2));
+
+  // generate command whose name is canonically
+  // ordered after /localhost/nfd/fib so that
+  // we hit the end of the std::map
+
+  Name commandName("/localhost/nfd/fib/end");
+  Interest command(commandName);
+  face->sendInterest(command);
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+
+
+BOOST_FIXTURE_TEST_CASE(SendInterestHitBegin, InternalFaceFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          this, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          bind(&InternalFaceFixture::validateNoOnInterestCallback,
+                               this, _1, _2));
+
+  // generate command whose name is canonically
+  // ordered before /localhost/nfd/fib so that
+  // we hit the beginning of the std::map
+
+  Name commandName("/localhost/nfd");
+  Interest command(commandName);
+  face->sendInterest(command);
+
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+
+
+BOOST_FIXTURE_TEST_CASE(SendInterestHitExact, InternalFaceFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          this, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/eib",
+                          bind(&InternalFaceFixture::validateNoOnInterestCallback,
+                               this, _1, _2));
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          bind(&InternalFaceFixture::validateOnInterestCallback,
+                           this, _1, _2));
+
+  face->setInterestFilter("/localhost/nfd/gib",
+                          bind(&InternalFaceFixture::validateNoOnInterestCallback,
+                               this, _1, _2));
+
+  // generate command whose name exactly matches
+  // /localhost/nfd/fib
+
+  Name commandName("/localhost/nfd/fib");
+  Interest command(commandName);
+  face->sendInterest(command);
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+
+
+BOOST_FIXTURE_TEST_CASE(SendInterestHitPrevious, InternalFaceFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          this, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          bind(&InternalFaceFixture::validateOnInterestCallback,
+                               this, _1, _2));
+
+  face->setInterestFilter("/localhost/nfd/fib/zzzzzzzzzzzzz/",
+                          bind(&InternalFaceFixture::validateNoOnInterestCallback,
+                               this, _1, _2));
+
+  // generate command whose name exactly matches
+  // an Interest filter
+
+  Name commandName("/localhost/nfd/fib/previous");
+  Interest command(commandName);
+  face->sendInterest(command);
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
 }
 
 BOOST_AUTO_TEST_SUITE_END()