mgmt: improved interest filter matching and bug fixes

tests/mgmt: added test fixtures to simplify unit testing

Added control response Data signing
Fixed control response payload parsing
Simplified fib-manager unit test assumptions
Expanded internal-face unit tests

refs: #1138

Change-Id: Ibe5d95ab9c42f890c0691c9040e4ae792e598974
diff --git a/daemon/mgmt/fib-manager.hpp b/daemon/mgmt/fib-manager.hpp
index 59a242b..a0de2fc 100644
--- a/daemon/mgmt/fib-manager.hpp
+++ b/daemon/mgmt/fib-manager.hpp
@@ -9,13 +9,12 @@
 
 #include "common.hpp"
 #include "face/face.hpp"
+#include "mgmt/app-face.hpp"
+#include "fw/strategy.hpp"
 #include "mgmt/manager-base.hpp"
 
 namespace nfd {
 
-class AppFace;
-class Face;
-class Strategy;
 class Forwarder;
 class Fib;
 
@@ -30,9 +29,6 @@
   void
   onFibRequest(const Interest& request);
 
-  const Name&
-  getRequestPrefix() const { return FIB_MANAGER_REQUEST_PREFIX; }
-
 private:
 
   void
@@ -57,7 +53,7 @@
 
   Fib& m_managedFib;
   function<shared_ptr<Face>(FaceId)> m_getFace;
-  std::map<Name, shared_ptr<Strategy> > m_namespaceToStrategyMap;
+  std::map<Name, shared_ptr<fw::Strategy> > m_namespaceToStrategyMap;
 
 
 
diff --git a/daemon/mgmt/internal-face.cpp b/daemon/mgmt/internal-face.cpp
index 0c3fe3b..ec1baab 100644
--- a/daemon/mgmt/internal-face.cpp
+++ b/daemon/mgmt/internal-face.cpp
@@ -18,26 +18,72 @@
 void
 InternalFace::sendInterest(const Interest& interest)
 {
+  if (m_interestFilters.size() == 0)
+    {
+      NFD_LOG_DEBUG("no Interest filters to match against");
+      return;
+    }
+
   const Name& interestName(interest.getName());
   NFD_LOG_DEBUG("received Interest: " << interestName);
 
-  size_t nComps = interestName.size();
-  for (size_t i = 0; i < nComps; i++)
-    {
-      Name prefix(interestName.getPrefix(nComps - i));
-      std::map<Name, OnInterest>::const_iterator filter =
-        m_interestFilters.find(prefix);
+  std::map<Name, OnInterest>::const_iterator filter =
+    m_interestFilters.lower_bound(interestName);
 
-      if (filter != m_interestFilters.end())
+  // 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())
+    {
+      // 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 " << prefix);
+          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 " << prefix);
+          NFD_LOG_DEBUG("no Interest filter found for " << interestName << " (before end)");
         }
     }
+  else if (filter->first.isPrefixOf(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
 }
 
diff --git a/daemon/mgmt/internal-face.hpp b/daemon/mgmt/internal-face.hpp
index 217d631..9539a9e 100644
--- a/daemon/mgmt/internal-face.hpp
+++ b/daemon/mgmt/internal-face.hpp
@@ -12,8 +12,6 @@
 
 namespace nfd {
 
-class FibManager;
-
 class InternalFace : public Face, public AppFace
 {
 public:
diff --git a/daemon/mgmt/manager-base.cpp b/daemon/mgmt/manager-base.cpp
index 5e561ff..2b27f85 100644
--- a/daemon/mgmt/manager-base.cpp
+++ b/daemon/mgmt/manager-base.cpp
@@ -29,47 +29,19 @@
                             uint32_t code,
                             const std::string& text)
 {
-  // Path 1 - runtime exception on receive's wireDecode.
-  // Set Data response's content payload to the
-  // encoded Block.
-  {
-    ndn::ControlResponse control(code, text);
-    const Block& encodedControl = control.wireEncode();
+  ndn::ControlResponse control(code, text);
+  const Block& encodedControl = control.wireEncode();
 
-    NFD_LOG_DEBUG("sending control response (Path 1)"
-                  << " Name: " << name
-                  << " code: " << code
-                  << " text: " << text);
+  NFD_LOG_DEBUG("sending control response"
+                << " Name: " << name
+                << " code: " << code
+                << " text: " << text);
 
-    NFD_LOG_DEBUG("sending raw control block size = " << encodedControl.size());
+  Data response(name);
+  response.setContent(encodedControl);
 
-    Data response(name);
-    response.setContent(encodedControl);
-
-    m_face->put(response);
-  }
-
-  // Path 2 - works, but not conformant to protocol.
-  // Encode ControlResponse and append Block as
-  // the last component of the name.
-  // {
-  //   ndn::ControlResponse control(code, text);
-  //   const Block& encodedControl = control.wireEncode();
-
-  //   NFD_LOG_DEBUG("sending control response (Path 2)"
-  //                 << " Name: " << name
-  //                 << " code: " << code
-  //                 << " text: " << text);
-
-  //   NFD_LOG_DEBUG("sending raw control block size = " << encodedControl.size());
-
-  //   Name responseName(name);
-  //   responseName.append(encodedControl);
-
-  //   Data response(responseName);
-  //   m_face->put(response);
-  // }
-
+  m_keyChain.sign(response);
+  m_face->put(response);
 }
 
 
diff --git a/daemon/mgmt/manager-base.hpp b/daemon/mgmt/manager-base.hpp
index 7308b3e..78ee38e 100644
--- a/daemon/mgmt/manager-base.hpp
+++ b/daemon/mgmt/manager-base.hpp
@@ -8,6 +8,7 @@
 #define NFD_MGMT_MANAGER_BASE_HPP
 
 #include "common.hpp"
+#include <ndn-cpp-dev/security/key-chain.hpp>
 
 namespace nfd {
 
@@ -30,6 +31,7 @@
 
 protected:
   shared_ptr<AppFace> m_face;
+   ndn::KeyChain m_keyChain;
 };
 
 
diff --git a/tests/mgmt/fib-manager.cpp b/tests/mgmt/fib-manager.cpp
index 6ac4a7f..4e4f587 100644
--- a/tests/mgmt/fib-manager.cpp
+++ b/tests/mgmt/fib-manager.cpp
@@ -7,214 +7,284 @@
 #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>
 
-static nfd::FaceId g_faceCount = 1;
-static std::vector<nfd::shared_ptr<nfd::Face> > g_faces;
+namespace nfd {
 
-static nfd::shared_ptr<nfd::Face>
-getFace(nfd::FaceId id)
+NFD_LOG_INIT("FibManagerTest");
+
+class FibManagerFixture
 {
-  if (g_faces.size() < id)
-    {
-      BOOST_FAIL("Attempted to access invalid FaceId: " << id);
-    }
-  return g_faces[id-1];
+public:
+
+  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
+  addFace(shared_ptr<Face> face)
+  {
+    m_faces.push_back(face);
+  }
+
+private:
+  std::vector<shared_ptr<Face> > m_faces;
+};
+
+
+BOOST_AUTO_TEST_SUITE(MgmtFibManager)
+
+void
+validateControlResponse(const Data& response,
+                        uint32_t expectedCode,
+                        const std::string& expectedText)
+{
+  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);
 }
 
-// namespace nfd {
+BOOST_AUTO_TEST_CASE(MalformedCommmand)
+{
+  FibManagerFixture fixture;
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
 
-// NFD_LOG_INIT("FibManagerTest");
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 404, "MALFORMED");
 
-// BOOST_AUTO_TEST_SUITE(MgmtFibManager)
+  Interest command("/localhost/nfd/fib");
+  manager.onFibRequest(command);
+}
 
-// BOOST_AUTO_TEST_CASE(MalformedCommmand)
-// {
-//   shared_ptr<InternalFace> face(new InternalFace);
-//   Fib fib;
-//   FibManager manager(fib, &getFace, face);
+BOOST_AUTO_TEST_CASE(UnsupportedVerb)
+{
+  FibManagerFixture fixture;
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 404, "UNSUPPORTED");
 
-//   Interest command(manager.getRequestPrefix());
-//   manager.onFibRequest(command);
-// }
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+  options.setCost(1);
 
-// BOOST_AUTO_TEST_CASE(UnsupportedVerb)
-// {
-//   shared_ptr<InternalFace> face(new InternalFace);
-//   Fib fib;
-//   FibManager manager(fib, &getFace, face);
+  Block encodedOptions(options.wireEncode());
 
-//   ndn::FibManagementOptions options;
-//   options.setName("/hello");
-//   options.setFaceId(1);
-//   options.setCost(1);
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("unsupported");
+  commandName.append(encodedOptions);
 
-//   Block encodedOptions(options.wireEncode());
+  Interest command(commandName);
+  manager.onFibRequest(command);
+}
 
-//   Name commandName(manager.getRequestPrefix());
-//   commandName.append("unsupported");
-//   commandName.append(encodedOptions);
+bool
+foundNextHop(FaceId id, uint32_t cost, const fib::NextHop& next)
+{
+  return id == next.getFace()->getId() && next.getCost() == cost;
+}
 
-//   Interest command(commandName);
-//   manager.onFibRequest(command);
-// }
+BOOST_AUTO_TEST_CASE(AddNextHopVerbInitialAdd)
+{
+  FibManagerFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
 
-// BOOST_AUTO_TEST_CASE(AddNextHopVerbInitialAdd)
-// {
-//   g_faceCount = 1;
-//   g_faces.clear();
-//   g_faces.push_back(make_shared<DummyFace>());
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 200, "OK");
 
-//   shared_ptr<InternalFace> face(new InternalFace);
-//   Fib fib;
-//   FibManager manager(fib, &getFace, face);
 
-//   ndn::FibManagementOptions options;
-//   options.setName("/hello");
-//   options.setFaceId(1);
-//   options.setCost(1);
 
-//   Block encodedOptions(options.wireEncode());
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+  options.setCost(101);
 
-//   Name commandName(manager.getRequestPrefix());
-//   commandName.append("add-nexthop");
-//   commandName.append(encodedOptions);
+  Block encodedOptions(options.wireEncode());
 
-//   Interest command(commandName);
-//   manager.onFibRequest(command);
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedOptions);
 
-//   shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+  Interest command(commandName);
+  manager.onFibRequest(command);
 
-//   if (entry)
-//     {
-//       const fib::NextHopList& hops = entry->getNextHops();
-//       BOOST_REQUIRE(hops.size() == 1);
-//       //      BOOST_CHECK(hops[0].getFace()->getFaceId() == 1);
-//       BOOST_CHECK(hops[0].getCost() == 1);
-//     }
-//   else
-//     {
-//       BOOST_FAIL("Failed to find expected fib entry");
-//     }
-// }
+  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
 
-// BOOST_AUTO_TEST_CASE(AddNextHopVerbAddToExisting)
-// {
-//   g_faceCount = 1;
-//   g_faces.clear();
-//   g_faces.push_back(make_shared<DummyFace>());
-//   g_faces.push_back(make_shared<DummyFace>());
+  if (entry)
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      NFD_LOG_DEBUG("FaceId: " << hops[0].getFace()->getId());
+      BOOST_REQUIRE(hops.size() == 1);
+      BOOST_REQUIRE(std::find_if(hops.begin(),
+                                 hops.end(),
+                                 bind(&foundNextHop, -1, 101, _1)) != hops.end());
+    }
+  else
+    {
+      BOOST_FAIL("Failed to find expected fib entry");
+    }
+}
 
-//   shared_ptr<InternalFace> face(new InternalFace);
-//   Fib fib;
-//   FibManager manager(fib, &getFace, face);
+BOOST_AUTO_TEST_CASE(AddNextHopVerbAddToExisting)
+{
+  FibManagerFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
+  fixture.addFace(make_shared<DummyFace>());
 
-//   // Add faces with cost == FaceID for the name /hello
-//   // This test assumes:
-//   //   FaceIDs are assigned from 1 to N
-//   //   Faces are store sequentially in the NextHopList
-//   //   NextHopList supports random access
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 200, "OK");
 
-//   for (int i = 1; i <= 2; i++)
-//     {
-//       ndn::FibManagementOptions options;
-//       options.setName("/hello");
-//       options.setFaceId(i);
-//       options.setCost(i);
+  // Add faces with cost == FaceID for the name /hello
+  // This test assumes:
+  //   FaceIDs are -1 because we don't add them to a forwarder
 
-//       Block encodedOptions(options.wireEncode());
+  for (int i = 1; i <= 2; i++)
+    {
+      ndn::FibManagementOptions options;
+      options.setName("/hello");
+      options.setFaceId(i);
+      options.setCost(100 + i);
 
-//       Name commandName(manager.getRequestPrefix());
-//       commandName.append("add-nexthop");
-//       commandName.append(encodedOptions);
+      Block encodedOptions(options.wireEncode());
 
-//       Interest command(commandName);
-//       manager.onFibRequest(command);
+      Name commandName("/localhost/nfd/fib");
+      commandName.append("add-nexthop");
+      commandName.append(encodedOptions);
 
-//       shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+      Interest command(commandName);
+      manager.onFibRequest(command);
 
-//       if (entry)
-//         {
-//           const fib::NextHopList& hops = entry->getNextHops();
-//           for (int j = 1; j <= i; j++)
-//             {
-//               BOOST_REQUIRE(hops.size() == i);
-//               BOOST_CHECK(hops[j-1].getCost() == j);
-//             }
-//         }
-//       else
-//         {
-//           BOOST_FAIL("Failed to find expected fib entry");
-//         }
-//     }
-// }
+      shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
 
-// BOOST_AUTO_TEST_CASE(AddNextHopVerbUpdateFaceCost)
-// {
-//   g_faceCount = 1;
-//   g_faces.clear();
-//   g_faces.push_back(make_shared<DummyFace>());
+      if (entry)
+        {
+          const fib::NextHopList& hops = entry->getNextHops();
+          for (int j = 1; j <= i; j++)
+            {
+              BOOST_REQUIRE(hops.size() == i);
+              // BOOST_REQUIRE(hops[j-1].getCost() == j);
+              BOOST_REQUIRE(std::find_if(hops.begin(),
+                                         hops.end(),
+                                         bind(&foundNextHop, -1, 100 + j, _1)) != hops.end());
+            }
+        }
+      else
+        {
+          BOOST_FAIL("Failed to find expected fib entry");
+        }
+    }
+}
 
-//   shared_ptr<InternalFace> face(new InternalFace);
-//   Fib fib;
-//   FibManager manager(fib, &getFace, face);
+BOOST_AUTO_TEST_CASE(AddNextHopVerbUpdateFaceCost)
+{
+  FibManagerFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
 
-//   ndn::FibManagementOptions options;
-//   options.setName("/hello");
-//   options.setFaceId(1);
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace,
+                          &fixture, _1),
+                          face);
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 200, "OK");
 
-//   {
-//     options.setCost(1);
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
 
-//     Block encodedOptions(options.wireEncode());
+  {
+    options.setCost(1);
 
-//     Name commandName(manager.getRequestPrefix());
-//     commandName.append("add-nexthop");
-//     commandName.append(encodedOptions);
+    Block encodedOptions(options.wireEncode());
 
-//     Interest command(commandName);
-//     manager.onFibRequest(command);
-//   }
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedOptions);
 
-//   {
-//     options.setCost(2);
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
 
-//     Block encodedOptions(options.wireEncode());
+  {
+    options.setCost(102);
 
-//     Name commandName(manager.getRequestPrefix());
-//     commandName.append("add-nexthop");
-//     commandName.append(encodedOptions);
+    Block encodedOptions(options.wireEncode());
 
-//     Interest command(commandName);
-//     manager.onFibRequest(command);
-//   }
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedOptions);
 
-//   shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
 
-//   // Add faces with cost == FaceID for the name /hello
-//   // This test assumes:
-//   //   FaceIDs are assigned from 1 to N
-//   //   Faces are store sequentially in the NextHopList
-//   //   NextHopList supports random access
-//   if (entry)
-//     {
-//       const fib::NextHopList& hops = entry->getNextHops();
-//       BOOST_REQUIRE(hops.size() == 1);
-//       // BOOST_CHECK(hops[0].getFace()->getFaceId() == 1);
-//       BOOST_CHECK(hops[0].getCost() == 2);
-//     }
-//   else
-//     {
-//       BOOST_FAIL("Failed to find expected fib entry");
-//     }
-// }
+  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
 
-// BOOST_AUTO_TEST_SUITE_END()
+  // 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 (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");
+    }
+}
 
-// } // namespace nfd
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace nfd
diff --git a/tests/mgmt/internal-face.cpp b/tests/mgmt/internal-face.cpp
index 72ce74a..2fce7f1 100644
--- a/tests/mgmt/internal-face.cpp
+++ b/tests/mgmt/internal-face.cpp
@@ -11,128 +11,78 @@
 
 #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>
 
-static nfd::FaceId g_faceCount = 1;
-static std::vector<nfd::shared_ptr<nfd::Face> > g_faces;
-
-static nfd::shared_ptr<nfd::Face>
-getFace(nfd::FaceId id)
-{
-  if (g_faces.size() < id)
-    {
-      BOOST_FAIL("Attempted to access invalid FaceId: " << id);
-    }
-  return g_faces[id-1];
-}
-
-
-
 namespace nfd {
 
 NFD_LOG_INIT("InternalFaceTest");
 
-void
-receiveValidNextHopControlResponse(const ndn::Data& response)
+class InternalFaceFixture
 {
-  // Path 1 - runtime exception on wireDecode.
-  // Extract Block from response's payload and attempt
-  // to decode.
-  // {
-  //   ndn::ControlResponse control;
-  //   Block controlRaw(response.getContent());
+public:
 
-  //   NFD_LOG_DEBUG("received raw control block size = " << controlRaw.size());
-
-  //   control.wireDecode(controlRaw);
-
-  //   NFD_LOG_DEBUG("received control response (Path 1)"
-  //                 << " Name: " << response.getName()
-  //                 << " code: " << control.getCode()
-  //                 << " text: " << control.getText());
-
-  //   BOOST_REQUIRE(control.getCode() == 200);
-  //   BOOST_REQUIRE(control.getText() == "OK");
-  // }
-
-  // Path 1.5 - same as Path 1, but offset the payload's
-  // encoded block by 2 bytes before decoding. 2 bytes
-  // is the measured Block size difference between
-  // ManagerBase's sendResponse and above Path 1's
-  // received size.
+  shared_ptr<Face>
+  getFace(FaceId id)
   {
-    ndn::ControlResponse control;
-    Block controlRaw(response.getContent());
-
-    NFD_LOG_DEBUG("received raw control block size = " << controlRaw.size());
-    // controlRaw is currently 2 bytes larger than what was sent
-    // try to offset it manually and create a new block
-
-    BOOST_REQUIRE(controlRaw.hasWire());
-    const uint8_t* buf = controlRaw.wire() + 2;
-    size_t bufSize = controlRaw.size() - 2;
-
-    Block alt(buf, bufSize);
-    control.wireDecode(alt);
-
-    NFD_LOG_DEBUG("received control response (Path 1)"
-                  << " Name: " << response.getName()
-                  << " code: " << control.getCode()
-                  << " text: " << control.getText());
-
-    BOOST_REQUIRE(control.getCode() == 200);
-    BOOST_REQUIRE(control.getText() == "OK");
+    if (m_faces.size() < id)
+      {
+        BOOST_FAIL("Attempted to access invalid FaceId: " << id);
+      }
+    return m_faces[id-1];
   }
 
-  // Path 2 - works, but not conformant to protocol.
-  // Extract decode and ControlResponse from last
-  // component of response's name.
-  // {
-  //   const Name& responseName = response.getName();
-  //   const ndn::Buffer& controlBuffer =
-  //     responseName[responseName.size()-1].getValue();
+  void
+  addFace(shared_ptr<Face> face)
+  {
+    m_faces.push_back(face);
+  }
 
-  //   shared_ptr<const ndn::Buffer> tmpBuffer(new ndn::Buffer(controlBuffer));
-  //   Block controlRaw(tmpBuffer);
-
-  //   NFD_LOG_DEBUG("received raw control block size = " << controlRaw.size());
-
-  //   ndn::ControlResponse control;
-  //   control.wireDecode(controlRaw);
-
-  //   NFD_LOG_DEBUG("received control response (Path 2)"
-  //                 << " Name: " << response.getName()
-  //                 << " code: " << control.getCode()
-  //                 << " text: " << control.getText());
-
-  //   BOOST_REQUIRE(control.getCode() == 200);
-  //   BOOST_REQUIRE(control.getText() == "OK");
-  // }
-
-
-}
+private:
+  std::vector<shared_ptr<Face> > m_faces;
+};
 
 BOOST_AUTO_TEST_SUITE(MgmtInternalFace)
 
+void
+validateControlResponse(const Data& response,
+                        uint32_t expectedCode,
+                        const std::string& expectedText)
+{
+  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);
+}
+
 BOOST_AUTO_TEST_CASE(ValidAddNextHop)
 {
-  g_faceCount = 1;
-  g_faces.clear();
-  g_faces.push_back(make_shared<DummyFace>());
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
 
   shared_ptr<InternalFace> face(new InternalFace);
   Fib fib;
-  FibManager manager(fib, &getFace, face);
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                          face);
 
-  face->setInterestFilter(manager.getRequestPrefix(),
+  face->setInterestFilter("/localhost/nfd/fib",
                           bind(&FibManager::onFibRequest,
                                &manager, _2));
 
   face->onReceiveData +=
-    bind(&receiveValidNextHopControlResponse, _1);
+    bind(&validateControlResponse, _1, 200, "OK");
 
-  Name regName(manager.getRequestPrefix());
   ndn::FibManagementOptions options;
   options.setName("/hello");
   options.setFaceId(1);
@@ -140,7 +90,7 @@
 
   Block encodedOptions(options.wireEncode());
 
-  Name commandName(manager.getRequestPrefix());
+  Name commandName("/localhost/nfd/fib");
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
@@ -163,18 +113,23 @@
 
 BOOST_AUTO_TEST_CASE(InvalidPrefixRegistration)
 {
-  g_faceCount = 1;
-  g_faces.clear();
-  g_faces.push_back(make_shared<DummyFace>());
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
 
   shared_ptr<InternalFace> face(new InternalFace);
   Fib fib;
-  FibManager manager(fib, &getFace, face);
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                     face);
 
-  face->setInterestFilter(manager.getRequestPrefix(),
+  face->setInterestFilter("/localhost/nfd/fib",
                           bind(&FibManager::onFibRequest,
                                &manager, _2));
 
+  face->onReceiveData +=
+    bind(&validateControlResponse, _1, 404, "MALFORMED");
+
   Interest nonRegInterest("/hello");
   face->sendInterest(nonRegInterest);
 
@@ -187,6 +142,127 @@
     }
 }
 
+void
+validateOnInterestCallback(const Name& name, const Interest& interest)
+{
+  NFD_LOG_DEBUG("Reached correct callback");
+}
+
+void
+validateNoOnInterestCallback(const Name& name, const Interest& interest)
+{
+  BOOST_FAIL("Reached wrong callback");
+}
+
+BOOST_AUTO_TEST_CASE(SendInterestHitEnd)
+{
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          &validateOnInterestCallback);
+
+  // 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_AUTO_TEST_CASE(SendInterestHitBegin)
+{
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          &validateNoOnInterestCallback);
+
+  // 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_AUTO_TEST_CASE(SendInterestHitExact)
+{
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/eib",
+                          &validateNoOnInterestCallback);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          &validateOnInterestCallback);
+
+  face->setInterestFilter("/localhost/nfd/gib",
+                          &validateNoOnInterestCallback);
+
+  // generate command whose name exactly matches
+  // /localhost/nfd/fib
+
+  Name commandName("/localhost/nfd/fib");
+  Interest command(commandName);
+  face->sendInterest(command);
+}
+
+
+
+BOOST_AUTO_TEST_CASE(SendInterestHitPrevious)
+{
+  InternalFaceFixture fixture;
+  fixture.addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&InternalFaceFixture::getFace,
+                          &fixture, _1),
+                          face);
+
+  face->setInterestFilter("/localhost/nfd/fib",
+                          &validateOnInterestCallback);
+
+  face->setInterestFilter("/localhost/nfd/fib/zzzzzzzzzzzzz/",
+                          &validateNoOnInterestCallback);
+
+  // generate command whose name exactly matches
+  // an Interest filter
+
+  Name commandName("/localhost/nfd/fib/previous");
+  Interest command(commandName);
+  face->sendInterest(command);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace nfd