src: Reorganizing source code in preparation to merge NRD code

Note that as of this commit, there are several changes in location of
compiled binaries in `build/` folder:

* `nfd` has been moved to `build/bin/nfd`
* `unit-tests` has been split into `unit-tests-core` and `unit-tests-daemon`

Change-Id: I2c830c117879edbaa5457d6423c13f0273285919
Refs: #1486
diff --git a/tests/daemon/face/dummy-face.hpp b/tests/daemon/face/dummy-face.hpp
new file mode 100644
index 0000000..7039286
--- /dev/null
+++ b/tests/daemon/face/dummy-face.hpp
@@ -0,0 +1,93 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_FACE_DUMMY_FACE_HPP
+#define NFD_TESTS_NFD_FACE_DUMMY_FACE_HPP
+
+#include "face/face.hpp"
+#include "face/local-face.hpp"
+
+namespace nfd {
+namespace tests {
+
+/** \class DummyFace
+ *  \brief a Face for unit testing
+ */
+template<class FaceBase>
+class DummyFaceImpl : public FaceBase
+{
+public:
+  DummyFaceImpl()
+    : FaceBase(FaceUri("dummy://"), FaceUri("dummy://"))
+  {
+  }
+
+  virtual void
+  sendInterest(const Interest& interest)
+  {
+    this->onSendInterest(interest);
+    m_sentInterests.push_back(interest);
+    this->afterSend();
+  }
+
+  virtual void
+  sendData(const Data& data)
+  {
+    this->onSendData(data);
+    m_sentDatas.push_back(data);
+    this->afterSend();
+  }
+
+  virtual void
+  close()
+  {
+    this->onFail("close");
+  }
+
+  void
+  receiveInterest(const Interest& interest)
+  {
+    this->onReceiveInterest(interest);
+  }
+
+  void
+  receiveData(const Data& data)
+  {
+    this->onReceiveData(data);
+  }
+
+  EventEmitter<> afterSend;
+
+public:
+  std::vector<Interest> m_sentInterests;
+  std::vector<Data> m_sentDatas;
+};
+
+typedef DummyFaceImpl<Face> DummyFace;
+typedef DummyFaceImpl<LocalFace> DummyLocalFace;
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_FACE_DUMMY_FACE_HPP
diff --git a/tests/daemon/face/ethernet.cpp b/tests/daemon/face/ethernet.cpp
new file mode 100644
index 0000000..d27fda4
--- /dev/null
+++ b/tests/daemon/face/ethernet.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/ethernet-factory.hpp"
+#include "core/network-interface.hpp"
+#include "tests/test-common.hpp"
+
+#include <ndn-cpp-dev/security/key-chain.hpp>
+#include <pcap/pcap.h>
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceEthernet, BaseFixture)
+
+class InterfacesFixture : protected BaseFixture
+{
+protected:
+  InterfacesFixture()
+  {
+    std::list< shared_ptr<NetworkInterfaceInfo> > ifs = listNetworkInterfaces();
+    for (std::list< shared_ptr<NetworkInterfaceInfo> >::const_iterator i = ifs.begin();
+         i != ifs.end();
+         ++i)
+      {
+        if (!(*i)->isLoopback() && (*i)->isUp())
+          {
+            pcap_t* p = pcap_create((*i)->name.c_str(), 0);
+            if (!p)
+              continue;
+
+            if (pcap_activate(p) == 0)
+              m_interfaces.push_back(*i);
+
+            pcap_close(p);
+          }
+      }
+  }
+
+protected:
+  std::list< shared_ptr<NetworkInterfaceInfo> > m_interfaces;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(MulticastFacesMap, InterfacesFixture)
+{
+  EthernetFactory factory;
+
+  if (m_interfaces.empty())
+    {
+      BOOST_WARN_MESSAGE(false, "No interfaces available, cannot perform MulticastFacesMap test");
+      return;
+    }
+
+  shared_ptr<EthernetFace> face1;
+  BOOST_REQUIRE_NO_THROW(face1 = factory.createMulticastFace(m_interfaces.front(),
+                                                             ethernet::getBroadcastAddress()));
+  shared_ptr<EthernetFace> face1bis;
+  BOOST_REQUIRE_NO_THROW(face1bis = factory.createMulticastFace(m_interfaces.front(),
+                                                                ethernet::getBroadcastAddress()));
+  BOOST_CHECK_EQUAL(face1, face1bis);
+
+  if (m_interfaces.size() > 1)
+    {
+      shared_ptr<EthernetFace> face2;
+      BOOST_REQUIRE_NO_THROW(face2 = factory.createMulticastFace(m_interfaces.back(),
+                                                                 ethernet::getBroadcastAddress()));
+      BOOST_CHECK_NE(face1, face2);
+    }
+  else
+    {
+      BOOST_WARN_MESSAGE(false, "Cannot test second EthernetFace creation, "
+                         "only one interface available");
+    }
+
+  shared_ptr<EthernetFace> face3;
+  BOOST_REQUIRE_NO_THROW(face3 = factory.createMulticastFace(m_interfaces.front(),
+                                                             ethernet::getDefaultMulticastAddress()));
+  BOOST_CHECK_NE(face1, face3);
+}
+
+BOOST_FIXTURE_TEST_CASE(SendPacket, InterfacesFixture)
+{
+  EthernetFactory factory;
+
+  if (m_interfaces.empty())
+    {
+      BOOST_WARN_MESSAGE(false, "No interfaces available for pcap, cannot perform SendPacket test");
+      return;
+    }
+
+  shared_ptr<EthernetFace> face = factory.createMulticastFace(m_interfaces.front(),
+                                                              ethernet::getDefaultMulticastAddress());
+
+  BOOST_REQUIRE(static_cast<bool>(face));
+
+  BOOST_CHECK(!face->isOnDemand());
+  BOOST_CHECK_EQUAL(face->isLocal(), false);
+  BOOST_CHECK_EQUAL(face->getRemoteUri().toString(),
+                    "ether://" + ethernet::getDefaultMulticastAddress().toString());
+  BOOST_CHECK_EQUAL(face->getLocalUri().toString(),
+                    "dev://" + m_interfaces.front()->name);
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  BOOST_CHECK_NO_THROW(face->sendInterest(interest1));
+  BOOST_CHECK_NO_THROW(face->sendData    (data1    ));
+  BOOST_CHECK_NO_THROW(face->sendInterest(interest2));
+  BOOST_CHECK_NO_THROW(face->sendData    (data2    ));
+
+//  m_ioRemaining = 4;
+//  m_ioService.run();
+//  m_ioService.reset();
+
+//  BOOST_REQUIRE_EQUAL(m_face1_receivedInterests.size(), 1);
+//  BOOST_REQUIRE_EQUAL(m_face1_receivedDatas    .size(), 1);
+//  BOOST_REQUIRE_EQUAL(m_face2_receivedInterests.size(), 1);
+//  BOOST_REQUIRE_EQUAL(m_face2_receivedDatas    .size(), 1);
+
+//  BOOST_CHECK_EQUAL(m_face1_receivedInterests[0].getName(), interest2.getName());
+//  BOOST_CHECK_EQUAL(m_face1_receivedDatas    [0].getName(), data2.getName());
+//  BOOST_CHECK_EQUAL(m_face2_receivedInterests[0].getName(), interest1.getName());
+//  BOOST_CHECK_EQUAL(m_face2_receivedDatas    [0].getName(), data1.getName());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/face/face.cpp b/tests/daemon/face/face.cpp
new file mode 100644
index 0000000..46c0345
--- /dev/null
+++ b/tests/daemon/face/face.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/face.hpp"
+#include "face/local-face.hpp"
+#include "dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceFace, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Description)
+{
+  DummyFace face;
+  face.setDescription("3pFsKrvWr");
+  BOOST_CHECK_EQUAL(face.getDescription(), "3pFsKrvWr");
+}
+
+BOOST_AUTO_TEST_CASE(LocalControlHeaderEnabled)
+{
+  DummyLocalFace face;
+
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(), false);
+
+  face.setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID, true);
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(), true);
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID), true);
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(
+                         LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID), false);
+
+  face.setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID, false);
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(), false);
+  BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(
+                         LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID), false);
+}
+
+BOOST_AUTO_TEST_CASE(Counters)
+{
+  DummyFace face;
+  const FaceCounters& counters = face.getCounters();
+  BOOST_CHECK_EQUAL(counters.getNInInterests() , 0);
+  BOOST_CHECK_EQUAL(counters.getNInDatas()     , 0);
+  BOOST_CHECK_EQUAL(counters.getNOutInterests(), 0);
+  BOOST_CHECK_EQUAL(counters.getNOutDatas()    , 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/face/ndnlp.cpp b/tests/daemon/face/ndnlp.cpp
new file mode 100644
index 0000000..f5ba8e1
--- /dev/null
+++ b/tests/daemon/face/ndnlp.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/ndnlp-sequence-generator.hpp"
+#include "face/ndnlp-slicer.hpp"
+#include "face/ndnlp-partial-message-store.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <boost/scoped_array.hpp>
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceNdnlp, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(SequenceBlock)
+{
+  ndnlp::SequenceBlock sb(0x8000, 2);
+  BOOST_CHECK_EQUAL(sb.count(), 2);
+  BOOST_CHECK_EQUAL(sb[0], 0x8000);
+  BOOST_CHECK_EQUAL(sb[1], 0x8001);
+  BOOST_CHECK_THROW(sb[2], std::out_of_range);
+}
+
+// sequence number can safely wrap around
+BOOST_AUTO_TEST_CASE(SequenceBlockWrap)
+{
+  ndnlp::SequenceBlock sb(std::numeric_limits<uint64_t>::max(), 2);
+  BOOST_CHECK_EQUAL(sb[0], std::numeric_limits<uint64_t>::max());
+  BOOST_CHECK_EQUAL(sb[1], std::numeric_limits<uint64_t>::min());
+  BOOST_CHECK_EQUAL(sb[1] - sb[0], 1);
+}
+
+BOOST_AUTO_TEST_CASE(SequenceGenerator)
+{
+  ndnlp::SequenceGenerator seqgen;
+
+  ndnlp::SequenceBlock sb1 = seqgen.nextBlock(2);
+  BOOST_CHECK_EQUAL(sb1.count(), 2);
+
+  ndnlp::SequenceBlock sb2 = seqgen.nextBlock(1);
+  BOOST_CHECK_NE(sb1[0], sb2[0]);
+  BOOST_CHECK_NE(sb1[1], sb2[0]);
+}
+
+// slice a Block to one NDNLP packet
+BOOST_AUTO_TEST_CASE(Slice1)
+{
+  uint8_t blockValue[60];
+  memset(blockValue, 0xcc, sizeof(blockValue));
+  Block block = ndn::dataBlock(0x01, blockValue, sizeof(blockValue));
+
+  ndnlp::Slicer slicer(9000);
+  ndnlp::PacketArray pa = slicer.slice(block);
+
+  BOOST_REQUIRE_EQUAL(pa->size(), 1);
+
+  const Block& pkt = pa->at(0);
+  BOOST_CHECK_EQUAL(pkt.type(), static_cast<uint32_t>(tlv::NdnlpData));
+  pkt.parse();
+
+  const Block::element_container& elements = pkt.elements();
+  BOOST_REQUIRE_EQUAL(elements.size(), 2);
+
+  const Block& sequenceElement = elements[0];
+  BOOST_CHECK_EQUAL(sequenceElement.type(), static_cast<uint32_t>(tlv::NdnlpSequence));
+  BOOST_REQUIRE_EQUAL(sequenceElement.value_size(), sizeof(uint64_t));
+
+  const Block& payloadElement = elements[1];
+  BOOST_CHECK_EQUAL(payloadElement.type(), static_cast<uint32_t>(tlv::NdnlpPayload));
+  size_t payloadSize = payloadElement.value_size();
+  BOOST_CHECK_EQUAL(payloadSize, block.size());
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(payloadElement.value_begin(), payloadElement.value_end(),
+                                block.begin(),                block.end());
+}
+
+// slice a Block to four NDNLP packets
+BOOST_AUTO_TEST_CASE(Slice4)
+{
+  uint8_t blockValue[5050];
+  memset(blockValue, 0xcc, sizeof(blockValue));
+  Block block = ndn::dataBlock(0x01, blockValue, sizeof(blockValue));
+
+  ndnlp::Slicer slicer(1500);
+  ndnlp::PacketArray pa = slicer.slice(block);
+
+  BOOST_REQUIRE_EQUAL(pa->size(), 4);
+
+  uint64_t seq0 = 0xdddd;
+
+  size_t totalPayloadSize = 0;
+
+  for (size_t i = 0; i < 4; ++i) {
+    const Block& pkt = pa->at(i);
+    BOOST_CHECK_EQUAL(pkt.type(), static_cast<uint32_t>(tlv::NdnlpData));
+    pkt.parse();
+
+    const Block::element_container& elements = pkt.elements();
+    BOOST_REQUIRE_EQUAL(elements.size(), 4);
+
+    const Block& sequenceElement = elements[0];
+    BOOST_CHECK_EQUAL(sequenceElement.type(), static_cast<uint32_t>(tlv::NdnlpSequence));
+    BOOST_REQUIRE_EQUAL(sequenceElement.value_size(), sizeof(uint64_t));
+    uint64_t seq = be64toh(*reinterpret_cast<const uint64_t*>(
+                             &*sequenceElement.value_begin()));
+    if (i == 0) {
+      seq0 = seq;
+    }
+    BOOST_CHECK_EQUAL(seq, seq0 + i);
+
+    const Block& fragIndexElement = elements[1];
+    BOOST_CHECK_EQUAL(fragIndexElement.type(), static_cast<uint32_t>(tlv::NdnlpFragIndex));
+    uint64_t fragIndex = ndn::readNonNegativeInteger(fragIndexElement);
+    BOOST_CHECK_EQUAL(fragIndex, i);
+
+    const Block& fragCountElement = elements[2];
+    BOOST_CHECK_EQUAL(fragCountElement.type(), static_cast<uint32_t>(tlv::NdnlpFragCount));
+    uint64_t fragCount = ndn::readNonNegativeInteger(fragCountElement);
+    BOOST_CHECK_EQUAL(fragCount, 4);
+
+    const Block& payloadElement = elements[3];
+    BOOST_CHECK_EQUAL(payloadElement.type(), static_cast<uint32_t>(tlv::NdnlpPayload));
+    size_t payloadSize = payloadElement.value_size();
+    totalPayloadSize += payloadSize;
+  }
+
+  BOOST_CHECK_EQUAL(totalPayloadSize, block.size());
+}
+
+class ReassembleFixture : protected BaseFixture
+{
+protected:
+  ReassembleFixture()
+    : m_slicer(1500)
+  {
+    m_partialMessageStore.onReceive +=
+      // push_back in C++11 has 2 overloads, and specific version needs to be selected
+      bind(static_cast<void (std::vector<Block>::*)(const Block&)>(&std::vector<Block>::push_back),
+           &m_received, _1);
+  }
+
+  Block
+  makeBlock(size_t valueLength)
+  {
+    boost::scoped_array<uint8_t> blockValue(new uint8_t[valueLength]);
+    memset(blockValue.get(), 0xcc, valueLength);
+    return ndn::dataBlock(0x01, blockValue.get(), valueLength);
+  }
+
+protected:
+  ndnlp::Slicer m_slicer;
+  ndnlp::PartialMessageStore m_partialMessageStore;
+
+  // received network layer packets
+  std::vector<Block> m_received;
+};
+
+// reassemble one NDNLP packets into one Block
+BOOST_FIXTURE_TEST_CASE(Reassemble1, ReassembleFixture)
+{
+  Block block = makeBlock(60);
+  ndnlp::PacketArray pa = m_slicer.slice(block);
+  BOOST_REQUIRE_EQUAL(pa->size(), 1);
+
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa->at(0));
+
+  BOOST_REQUIRE_EQUAL(m_received.size(), 1);
+  BOOST_CHECK_EQUAL_COLLECTIONS(m_received.at(0).begin(), m_received.at(0).end(),
+                                block.begin(),            block.end());
+}
+
+// reassemble four and two NDNLP packets into two Blocks
+BOOST_FIXTURE_TEST_CASE(Reassemble4and2, ReassembleFixture)
+{
+  Block block = makeBlock(5050);
+  ndnlp::PacketArray pa = m_slicer.slice(block);
+  BOOST_REQUIRE_EQUAL(pa->size(), 4);
+
+  Block block2 = makeBlock(2000);
+  ndnlp::PacketArray pa2 = m_slicer.slice(block2);
+  BOOST_REQUIRE_EQUAL(pa2->size(), 2);
+
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa->at(0));
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa->at(1));
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa2->at(1));
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa->at(1));
+  BOOST_CHECK_EQUAL(m_received.size(), 0);
+  m_partialMessageStore.receiveNdnlpData(pa2->at(0));
+  BOOST_CHECK_EQUAL(m_received.size(), 1);
+  m_partialMessageStore.receiveNdnlpData(pa->at(3));
+  BOOST_CHECK_EQUAL(m_received.size(), 1);
+  m_partialMessageStore.receiveNdnlpData(pa->at(2));
+
+  BOOST_REQUIRE_EQUAL(m_received.size(), 2);
+  BOOST_CHECK_EQUAL_COLLECTIONS(m_received.at(1).begin(), m_received.at(1).end(),
+                                block.begin(),            block.end());
+  BOOST_CHECK_EQUAL_COLLECTIONS(m_received.at(0).begin(), m_received.at(0).end(),
+                                block2.begin(),           block2.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/face/tcp.cpp b/tests/daemon/face/tcp.cpp
new file mode 100644
index 0000000..07e6ec5
--- /dev/null
+++ b/tests/daemon/face/tcp.cpp
@@ -0,0 +1,440 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/tcp-factory.hpp"
+#include <ndn-cpp-dev/security/key-chain.hpp>
+
+#include "tests/test-common.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceTcp, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(ChannelMap)
+{
+  TcpFactory factory;
+
+  shared_ptr<TcpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<TcpChannel> channel1a = factory.createChannel("127.0.0.1", "20070");
+  BOOST_CHECK_EQUAL(channel1, channel1a);
+  BOOST_CHECK_EQUAL(channel1->getUri().toString(), "tcp4://127.0.0.1:20070");
+
+  shared_ptr<TcpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+  BOOST_CHECK_NE(channel1, channel2);
+
+  shared_ptr<TcpChannel> channel3 = factory.createChannel("::1", "20071");
+  BOOST_CHECK_NE(channel2, channel3);
+  BOOST_CHECK_EQUAL(channel3->getUri().toString(), "tcp6://[::1]:20071");
+}
+
+class EndToEndFixture : protected BaseFixture
+{
+public:
+  void
+  channel1_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face1));
+    face1 = newFace;
+    face1->onReceiveInterest +=
+      bind(&EndToEndFixture::face1_onReceiveInterest, this, _1);
+    face1->onReceiveData +=
+      bind(&EndToEndFixture::face1_onReceiveData, this, _1);
+    face1->onFail +=
+      bind(&EndToEndFixture::face1_onFail, this);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel1_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveInterest(const Interest& interest)
+  {
+    face1_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveData(const Data& data)
+  {
+    face1_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onFail()
+  {
+    face1.reset();
+    limitedIo.afterOp();
+  }
+
+  void
+  channel2_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face2));
+    face2 = newFace;
+    face2->onReceiveInterest +=
+      bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+    face2->onReceiveData +=
+      bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+    face2->onFail +=
+      bind(&EndToEndFixture::face2_onFail, this);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel2_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveInterest(const Interest& interest)
+  {
+    face2_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveData(const Data& data)
+  {
+    face2_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onFail()
+  {
+    face2.reset();
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    faces.push_back(newFace);
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  checkFaceList(size_t shouldBe)
+  {
+    BOOST_CHECK_EQUAL(faces.size(), shouldBe);
+  }
+
+public:
+  LimitedIo limitedIo;
+
+  shared_ptr<Face> face1;
+  std::vector<Interest> face1_receivedInterests;
+  std::vector<Data> face1_receivedDatas;
+  shared_ptr<Face> face2;
+  std::vector<Interest> face2_receivedInterests;
+  std::vector<Data> face2_receivedDatas;
+
+  std::list< shared_ptr<Face> > faces;
+};
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd4, EndToEndFixture)
+{
+  TcpFactory factory1;
+
+  shared_ptr<TcpChannel> channel1 = factory1.createChannel("127.0.0.1", "20070");
+  factory1.createChannel("127.0.0.1", "20071");
+
+  BOOST_CHECK_EQUAL(channel1->isListening(), false);
+
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  BOOST_CHECK_EQUAL(channel1->isListening(), true);
+
+  TcpFactory factory2;
+
+  shared_ptr<TcpChannel> channel2 = factory2.createChannel("127.0.0.2", "20070");
+  factory2.createChannel("127.0.0.2", "20071");
+
+  factory2.createFace(FaceUri("tcp://127.0.0.1:20070"),
+                      bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                      bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_REQUIRE(static_cast<bool>(face2));
+
+  BOOST_CHECK(face1->isOnDemand());
+  BOOST_CHECK(!face2->isOnDemand());
+
+  BOOST_CHECK_EQUAL(face2->getRemoteUri().toString(), "tcp4://127.0.0.1:20070");
+  BOOST_CHECK_EQUAL(face1->getLocalUri().toString(), "tcp4://127.0.0.1:20070");
+  // face1 has an unknown remoteUri, since the source port is automatically chosen by OS
+
+  BOOST_CHECK_EQUAL(face1->isLocal(), true);
+  BOOST_CHECK_EQUAL(face2->isLocal(), true);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(face1)), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(face2)), true);
+
+  // integrated tests needs to check that TcpFace for non-loopback fails these tests...
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(8, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot send or receive Interest/Data packets");
+
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+
+  const FaceCounters& counters1 = face1->getCounters();
+  BOOST_CHECK_EQUAL(counters1.getNInInterests() , 1);
+  BOOST_CHECK_EQUAL(counters1.getNInDatas()     , 3);
+  BOOST_CHECK_EQUAL(counters1.getNOutInterests(), 3);
+  BOOST_CHECK_EQUAL(counters1.getNOutDatas()    , 1);
+
+  const FaceCounters& counters2 = face2->getCounters();
+  BOOST_CHECK_EQUAL(counters2.getNInInterests() , 3);
+  BOOST_CHECK_EQUAL(counters2.getNInDatas()     , 1);
+  BOOST_CHECK_EQUAL(counters2.getNOutInterests(), 1);
+  BOOST_CHECK_EQUAL(counters2.getNOutDatas()    , 3);
+}
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd6, EndToEndFixture)
+{
+  TcpFactory factory1;
+
+  shared_ptr<TcpChannel> channel1 = factory1.createChannel("::1", "20070");
+  shared_ptr<TcpChannel> channel2 = factory1.createChannel("::1", "20071");
+
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  TcpFactory factory2;
+
+  factory2.createChannel("::2", "20070");
+
+  factory2.createFace(FaceUri("tcp://[::1]:20070"),
+                      bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                      bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_REQUIRE(static_cast<bool>(face2));
+
+  BOOST_CHECK_EQUAL(face2->getRemoteUri().toString(), "tcp6://[::1]:20070");
+  BOOST_CHECK_EQUAL(face1->getLocalUri().toString(), "tcp6://[::1]:20070");
+  // face1 has an unknown remoteUri, since the source port is automatically chosen by OS
+
+  BOOST_CHECK_EQUAL(face1->isLocal(), true);
+  BOOST_CHECK_EQUAL(face2->isLocal(), true);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(face1)), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(face2)), true);
+
+  // integrated tests needs to check that TcpFace for non-loopback fails these tests...
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(4, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot send or receive Interest/Data packets");
+
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+}
+
+BOOST_FIXTURE_TEST_CASE(MultipleAccepts, EndToEndFixture)
+{
+  TcpFactory factory;
+
+  shared_ptr<TcpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<TcpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+
+  channel1->listen(bind(&EndToEndFixture::channel_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+
+  channel2->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1),
+                    time::seconds(4)); // very short timeout
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot connect or cannot accept connection");
+
+
+  BOOST_CHECK_EQUAL(faces.size(), 2);
+
+  shared_ptr<TcpChannel> channel3 = factory.createChannel("127.0.0.1", "20072");
+  channel3->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1),
+                    time::seconds(4)); // very short timeout
+
+
+  shared_ptr<TcpChannel> channel4 = factory.createChannel("127.0.0.1", "20073");
+
+  BOOST_CHECK_NE(channel3, channel4);
+
+  scheduler::schedule(time::seconds(1),
+      bind(&TcpChannel::connect, channel4, "127.0.0.1", "20070",
+          // does not work without static_cast
+           static_cast<TcpChannel::FaceCreatedCallback>(
+               bind(&EndToEndFixture::channel_onFaceCreated, this, _1)),
+           static_cast<TcpChannel::ConnectFailedCallback>(
+               bind(&EndToEndFixture::channel_onConnectFailed, this, _1)),
+           time::seconds(4)));
+
+  scheduler::schedule(time::milliseconds(500),
+                      bind(&EndToEndFixture::checkFaceList, this, 4));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(4,// 2 connects and 2 accepts
+                      time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot connect or cannot accept multiple connections");
+
+  BOOST_CHECK_EQUAL(faces.size(), 6);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(FaceClosing, EndToEndFixture)
+{
+  TcpFactory factory;
+
+  shared_ptr<TcpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<TcpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  channel2->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel2_onConnectFailed, this, _1),
+                    time::seconds(4)); // very short timeout
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "TcpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_CHECK_EQUAL(channel1->size(), 1);
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_CHECK(static_cast<bool>(face2));
+
+  // Face::close must be invoked during io run to be counted as an op
+  scheduler::schedule(time::milliseconds(100), bind(&Face::close, face1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
+                      "FaceClosing error: cannot properly close faces");
+
+  // both faces should get closed
+  BOOST_CHECK(!static_cast<bool>(face1));
+  BOOST_CHECK(!static_cast<bool>(face2));
+
+  BOOST_CHECK_EQUAL(channel1->size(), 0);
+  BOOST_CHECK_EQUAL(channel2->size(), 0);
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/face/udp.cpp b/tests/daemon/face/udp.cpp
new file mode 100644
index 0000000..2dfca3a
--- /dev/null
+++ b/tests/daemon/face/udp.cpp
@@ -0,0 +1,915 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/udp-factory.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceUdp, BaseFixture)
+
+class FactoryErrorCheck : protected BaseFixture
+{
+public:
+  bool isTheSameMulticastEndpoint(const UdpFactory::Error& e) {
+    return strcmp(e.what(),
+                  "Cannot create the requested UDP unicast channel, local "
+                  "endpoint is already allocated for a UDP multicast face") == 0;
+  }
+
+  bool isNotMulticastAddress(const UdpFactory::Error& e) {
+    return strcmp(e.what(),
+                  "Cannot create the requested UDP multicast face, "
+                  "the multicast group given as input is not a multicast address") == 0;
+  }
+
+  bool isTheSameUnicastEndpoint(const UdpFactory::Error& e) {
+    return strcmp(e.what(),
+                  "Cannot create the requested UDP multicast face, local "
+                  "endpoint is already allocated for a UDP unicast channel") == 0;
+  }
+
+  bool isLocalEndpointOnDifferentGroup(const UdpFactory::Error& e) {
+    return strcmp(e.what(),
+                  "Cannot create the requested UDP multicast face, local "
+                  "endpoint is already allocated for a UDP multicast face "
+                  "on a different multicast group") == 0;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(ChannelMapUdp, FactoryErrorCheck)
+{
+  using boost::asio::ip::udp;
+
+  UdpFactory factory = UdpFactory();
+
+  //to instantiate multicast face on a specific ip address, change interfaceIp
+  std::string interfaceIp = "0.0.0.0";
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<UdpChannel> channel1a = factory.createChannel("127.0.0.1", "20070");
+  BOOST_CHECK_EQUAL(channel1, channel1a);
+  BOOST_CHECK_EQUAL(channel1->getUri().toString(), "udp4://127.0.0.1:20070");
+
+  shared_ptr<UdpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+  BOOST_CHECK_NE(channel1, channel2);
+
+  shared_ptr<UdpChannel> channel3 = factory.createChannel(interfaceIp, "20070");
+
+  shared_ptr<UdpChannel> channel4 = factory.createChannel("::1", "20071");
+  BOOST_CHECK_NE(channel2, channel4);
+  BOOST_CHECK_EQUAL(channel4->getUri().toString(), "udp6://[::1]:20071");
+
+  //same endpoint of a unicast channel
+  BOOST_CHECK_EXCEPTION(factory.createMulticastFace(interfaceIp,
+                                                    "224.0.0.1",
+                                                    "20070"),
+                        UdpFactory::Error,
+                        isTheSameUnicastEndpoint);
+
+
+  shared_ptr<MulticastUdpFace> multicastFace1 = factory.createMulticastFace(interfaceIp,
+                                                                            "224.0.0.1",
+                                                                            "20072");
+  shared_ptr<MulticastUdpFace> multicastFace1a = factory.createMulticastFace(interfaceIp,
+                                                                            "224.0.0.1",
+                                                                            "20072");
+  BOOST_CHECK_EQUAL(multicastFace1, multicastFace1a);
+
+
+  //same endpoint of a multicast face
+  BOOST_CHECK_EXCEPTION(factory.createChannel(interfaceIp, "20072"),
+                        UdpFactory::Error,
+                        isTheSameMulticastEndpoint);
+
+  //same multicast endpoint, different group
+  BOOST_CHECK_EXCEPTION(factory.createMulticastFace(interfaceIp,
+                                                    "224.0.0.42",
+                                                    "20072"),
+                        UdpFactory::Error,
+                        isLocalEndpointOnDifferentGroup);
+
+  BOOST_CHECK_EXCEPTION(factory.createMulticastFace(interfaceIp,
+                                                    "192.168.10.15",
+                                                    "20025"),
+                        UdpFactory::Error,
+                        isNotMulticastAddress);
+
+
+//  //Test commented because it required to be run in a machine that can resolve ipv6 query
+//  shared_ptr<UdpChannel> channel1v6 = factory.createChannel(//"::1",
+//                                                     "fe80::5e96:9dff:fe7d:9c8d%en1",
+//                                                     //"fe80::aa54:b2ff:fe08:27b8%wlan0",
+//                                                     "20070");
+//
+//  //the creation of multicastFace2 works properly. It has been disable because it needs an IP address of
+//  //an available network interface (different from the first one used)
+//  shared_ptr<MulticastUdpFace> multicastFace2 = factory.createMulticastFace("192.168.1.17",
+//                                                                            "224.0.0.1",
+//                                                                            "20073");
+//  BOOST_CHECK_NE(multicastFace1, multicastFace2);
+//
+//
+//  //ipv6 - work in progress
+//  shared_ptr<MulticastUdpFace> multicastFace3 = factory.createMulticastFace("fe80::5e96:9dff:fe7d:9c8d%en1",
+//                                                                            "FF01:0:0:0:0:0:0:2",
+//                                                                            "20073");
+//
+//  shared_ptr<MulticastUdpFace> multicastFace4 = factory.createMulticastFace("fe80::aa54:b2ff:fe08:27b8%wlan0",
+//                                                                            "FF01:0:0:0:0:0:0:2",
+//                                                                            "20073");
+//
+//  BOOST_CHECK_EQUAL(multicastFace3, multicastFace4);
+//
+//  shared_ptr<MulticastUdpFace> multicastFace5 = factory.createMulticastFace("::1",
+//                                                                            "FF01:0:0:0:0:0:0:2",
+//                                                                            "20073");
+//
+//  BOOST_CHECK_NE(multicastFace3, multicastFace5);
+//
+//  //same local ipv6 endpoint for a different multicast group
+//  BOOST_CHECK_THROW(factory.createMulticastFace("fe80::aa54:b2ff:fe08:27b8%wlan0",
+//                                                "FE01:0:0:0:0:0:0:2",
+//                                                "20073"),
+//                    UdpFactory::Error);
+//
+//  //same local ipv6 (expect for th port number) endpoint for a different multicast group
+//  BOOST_CHECK_THROW(factory.createMulticastFace("fe80::aa54:b2ff:fe08:27b8%wlan0",
+//                                                "FE01:0:0:0:0:0:0:2",
+//                                                "20075"),
+//                    UdpFactory::Error);
+//
+//  BOOST_CHECK_THROW(factory.createMulticastFace("fa80::20a:9dff:fef6:12ff",
+//                                                "FE12:0:0:0:0:0:0:2",
+//                                                "20075"),
+//                    UdpFactory::Error);
+//
+//  //not a multicast ipv6
+//  BOOST_CHECK_THROW(factory.createMulticastFace("fa80::20a:9dff:fef6:12ff",
+//                                                "A112:0:0:0:0:0:0:2",
+//                                                "20075"),
+//                    UdpFactory::Error);
+}
+
+class EndToEndFixture : protected BaseFixture
+{
+public:
+  void
+  channel1_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face1));
+    channel1_onFaceCreatedNoCheck(newFace);
+  }
+
+  void
+  channel1_onFaceCreatedNoCheck(const shared_ptr<Face>& newFace)
+  {
+    face1 = newFace;
+    face1->onReceiveInterest +=
+      bind(&EndToEndFixture::face1_onReceiveInterest, this, _1);
+    face1->onReceiveData +=
+      bind(&EndToEndFixture::face1_onReceiveData, this, _1);
+    face1->onFail +=
+      bind(&EndToEndFixture::face1_onFail, this);
+    BOOST_CHECK_MESSAGE(true, "channel 1 face created");
+
+    faces.push_back(face1);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel1_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveInterest(const Interest& interest)
+  {
+    face1_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveData(const Data& data)
+  {
+    face1_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onFail()
+  {
+    face1.reset();
+    limitedIo.afterOp();
+  }
+
+  void
+  channel2_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face2));
+    face2 = newFace;
+    face2->onReceiveInterest +=
+      bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+    face2->onReceiveData +=
+      bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+    face2->onFail +=
+      bind(&EndToEndFixture::face2_onFail, this);
+
+    faces.push_back(face2);
+
+    BOOST_CHECK_MESSAGE(true, "channel 2 face created");
+    limitedIo.afterOp();
+  }
+
+  void
+  channel2_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveInterest(const Interest& interest)
+  {
+    face2_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveData(const Data& data)
+  {
+    face2_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onFail()
+  {
+    face2.reset();
+    limitedIo.afterOp();
+  }
+
+  void
+  channel3_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face1));
+    face3 = newFace;
+    faces.push_back(newFace);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    faces.push_back(newFace);
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onConnectFailedOk(const std::string& reason)
+  {
+    //it's ok, it was supposed to fail
+    limitedIo.afterOp();
+  }
+
+  void
+  checkFaceList(size_t shouldBe)
+  {
+    BOOST_CHECK_EQUAL(faces.size(), shouldBe);
+  }
+
+public:
+  LimitedIo limitedIo;
+
+  shared_ptr<Face> face1;
+  std::vector<Interest> face1_receivedInterests;
+  std::vector<Data> face1_receivedDatas;
+  shared_ptr<Face> face2;
+  std::vector<Interest> face2_receivedInterests;
+  std::vector<Data> face2_receivedDatas;
+  shared_ptr<Face> face3;
+
+  std::list< shared_ptr<Face> > faces;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd4, EndToEndFixture)
+{
+  UdpFactory factory;
+
+  factory.createChannel("127.0.0.1", "20071");
+
+  factory.createFace(FaceUri("udp4://127.0.0.1:20070"),
+                     bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                     bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face2));
+  BOOST_CHECK_EQUAL(face2->getRemoteUri().toString(), "udp4://127.0.0.1:20070");
+  BOOST_CHECK_EQUAL(face2->getLocalUri().toString(), "udp4://127.0.0.1:20071");
+  BOOST_CHECK_EQUAL(face2->isLocal(), false);
+  // face1 is not created yet
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+  Interest interest3("ndn:/QWiIhjgkj5sL");
+  Data     data3    ("ndn:/XNBV794f");
+  data3.setContent(0, 0);
+
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue,
+                                        reinterpret_cast<const uint8_t*>(0),
+                                        0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+  data3.setSignature(fakeSignature);
+
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(5,//4 send + 1 listen return
+                      time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_CHECK_EQUAL(face1->getRemoteUri().toString(), "udp4://127.0.0.1:20071");
+  BOOST_CHECK_EQUAL(face1->getLocalUri().toString(), "udp4://127.0.0.1:20070");
+  BOOST_CHECK_EQUAL(face1->isLocal(), false);
+
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(4, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+
+
+
+  //checking if the connection accepting mechanism works properly.
+
+  face2->sendData    (data3    );
+  face2->sendInterest(interest3);
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 2);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 4);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[1].getName(), interest3.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [3].getName(), data3.getName());
+
+  const FaceCounters& counters1 = face1->getCounters();
+  BOOST_CHECK_EQUAL(counters1.getNInInterests() , 2);
+  BOOST_CHECK_EQUAL(counters1.getNInDatas()     , 4);
+  BOOST_CHECK_EQUAL(counters1.getNOutInterests(), 3);
+  BOOST_CHECK_EQUAL(counters1.getNOutDatas()    , 1);
+
+  const FaceCounters& counters2 = face2->getCounters();
+  BOOST_CHECK_EQUAL(counters2.getNInInterests() , 3);
+  BOOST_CHECK_EQUAL(counters2.getNInDatas()     , 1);
+  BOOST_CHECK_EQUAL(counters2.getNOutInterests(), 2);
+  BOOST_CHECK_EQUAL(counters2.getNOutDatas()    , 4);
+}
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd6, EndToEndFixture)
+{
+  UdpFactory factory;
+
+  factory.createChannel("::1", "20071");
+
+  factory.createFace(FaceUri("udp://[::1]:20070"),
+                     bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                     bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face2));
+  BOOST_CHECK_EQUAL(face2->getRemoteUri().toString(), "udp6://[::1]:20070");
+  BOOST_CHECK_EQUAL(face2->getLocalUri().toString(), "udp6://[::1]:20071");
+  BOOST_CHECK_EQUAL(face2->isLocal(), false);
+  // face1 is not created yet
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("::1", "20070");
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+  Interest interest3("ndn:/QWiIhjgkj5sL");
+  Data     data3    ("ndn:/XNBV794f");
+  data3.setContent(0, 0);
+
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue,
+                                        reinterpret_cast<const uint8_t*>(0),
+                                        0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+  data3.setSignature(fakeSignature);
+
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(3,//2 send + 1 listen return
+                      time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_CHECK_EQUAL(face1->getRemoteUri().toString(), "udp6://[::1]:20071");
+  BOOST_CHECK_EQUAL(face1->getLocalUri().toString(), "udp6://[::1]:20070");
+  BOOST_CHECK_EQUAL(face1->isLocal(), false);
+
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+
+
+
+  //checking if the connection accepting mechanism works properly.
+
+  face2->sendData    (data3    );
+  face2->sendInterest(interest3);
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 2);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 2);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[1].getName(), interest3.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [1].getName(), data3.getName());
+}
+
+BOOST_FIXTURE_TEST_CASE(MultipleAccepts, EndToEndFixture)
+{
+  Interest interest1("ndn:/TpnzGvW9R");
+  Interest interest2("ndn:/QWiIMfj5sL");
+
+  UdpFactory factory;
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<UdpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+
+  channel1->listen(bind(&EndToEndFixture::channel_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+
+  channel2->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_CHECK_EQUAL(faces.size(), 1);
+
+  shared_ptr<UdpChannel> channel3 = factory.createChannel("127.0.0.1", "20072");
+  channel3->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel3_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+
+  shared_ptr<UdpChannel> channel4 = factory.createChannel("127.0.0.1", "20073");
+
+  BOOST_CHECK_NE(channel3, channel4);
+
+  scheduler::schedule(time::milliseconds(500),
+           bind(&UdpChannel::connect, channel4, "127.0.0.1", "20070",
+                // does not work without static_cast
+                static_cast<UdpChannel::FaceCreatedCallback>(
+                    bind(&EndToEndFixture::channel_onFaceCreated, this, _1)),
+                static_cast<UdpChannel::ConnectFailedCallback>(
+                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1))));
+
+  scheduler::schedule(time::milliseconds(400), bind(&EndToEndFixture::checkFaceList, this, 2));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2,// 2 connects
+                      time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept multiple connections");
+
+  BOOST_CHECK_EQUAL(faces.size(), 3);
+
+
+  face2->sendInterest(interest1);
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_CHECK_EQUAL(faces.size(), 4);
+
+  face3->sendInterest(interest2);
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  //channel1 should have created 2 faces, one when face2 sent an interest, one when face3 sent an interest
+  BOOST_CHECK_EQUAL(faces.size(), 5);
+  BOOST_CHECK_THROW(channel1->listen(bind(&EndToEndFixture::channel_onFaceCreated,     this, _1),
+                                     bind(&EndToEndFixture::channel_onConnectFailedOk, this, _1)),
+                    UdpChannel::Error);
+}
+
+//Test commented because it required to be run in a machine that can resolve ipv6 query
+//BOOST_FIXTURE_TEST_CASE(EndToEndIpv6, EndToEndFixture)
+//{
+//  UdpFactory factory = UdpFactory();
+//  Scheduler scheduler(g_io); // to limit the amount of time the test may take
+//
+//  EventId abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                              "UdpChannel error: cannot connect or cannot accept connection"));
+//
+//  limitedIoRemaining = 1;
+//
+//  shared_ptr<UdpChannel> channel1 = factory.createChannel("::1", "20070");
+//  shared_ptr<UdpChannel> channel2 = factory.createChannel("::1", "20071");
+//
+//  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+//                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+//
+//  channel2->connect("::1", "20070",
+//                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+//                    bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_REQUIRE(static_cast<bool>(face2));
+//
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot send or receive Interest/Data packets"));
+//
+//  Interest interest1("ndn:/TpnzGvW9R");
+//  Data     data1    ("ndn:/KfczhUqVix");
+//  data1.setContent(0, 0);
+//  Interest interest2("ndn:/QWiIMfj5sL");
+//  Data     data2    ("ndn:/XNBV796f");
+//  data2.setContent(0, 0);
+//  Interest interest3("ndn:/QWiIhjgkj5sL");
+//  Data     data3    ("ndn:/XNBV794f");
+//  data3.setContent(0, 0);
+//
+//  ndn::SignatureSha256WithRsa fakeSignature;
+//  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+//
+//  // set fake signature on data1 and data2
+//  data1.setSignature(fakeSignature);
+//  data2.setSignature(fakeSignature);
+//  data3.setSignature(fakeSignature);
+//
+//  face2->sendInterest(interest2);
+//  face2->sendData    (data2    );
+//
+//  limitedIoRemaining = 3; //2 send + 1 listen return
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_REQUIRE(static_cast<bool>(face1));
+//
+//  face1->sendInterest(interest1);
+//  face1->sendData    (data1    );
+//
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot send or receive Interest/Data packets"));
+//  limitedIoRemaining = 2;
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+//  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 1);
+//  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 1);
+//  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+//
+//  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+//  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+//  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+//  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+//
+//  //checking if the connection accepting mechanism works properly.
+//
+//  face2->sendData    (data3    );
+//  face2->sendInterest(interest3);
+//
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot send or receive Interest/Data packets"));
+//  limitedIoRemaining = 2;
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 2);
+//  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 2);
+//
+//  BOOST_CHECK_EQUAL(face1_receivedInterests[1].getName(), interest3.getName());
+//  BOOST_CHECK_EQUAL(face1_receivedDatas    [1].getName(), data3.getName());
+//
+//}
+
+
+//Test commented because it required to be run in a machine that can resolve ipv6 query
+//BOOST_FIXTURE_TEST_CASE(MultipleAcceptsIpv6, EndToEndFixture)
+//{
+//  Interest interest1("ndn:/TpnzGvW9R");
+//  Interest interest2("ndn:/QWiIMfj5sL");
+//  Interest interest3("ndn:/QWiIhjgkj5sL");
+//
+//  UdpFactory factory = UdpFactory();
+//  Scheduler scheduler(g_io); // to limit the amount of time the test may take
+//
+//  EventId abortEvent =
+//  scheduler.scheduleEvent(time::seconds(4),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot connect or cannot accept connection"));
+//
+//  shared_ptr<UdpChannel> channel1 = factory.createChannel("::1", "20070");
+//  shared_ptr<UdpChannel> channel2 = factory.createChannel("::1", "20071");
+//
+//  channel1->listen(bind(&EndToEndFixture::channel_onFaceCreated,   this, _1),
+//                   bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+//
+//  channel2->connect("::1", "20070",
+//                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+//                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+//
+//  limitedIoRemaining = 1;
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_CHECK_EQUAL(faces.size(), 1);
+//
+//  shared_ptr<UdpChannel> channel3 = factory.createChannel("::1", "20072");
+//  channel3->connect("::1", "20070",
+//                    bind(&EndToEndFixture::channel3_onFaceCreated, this, _1),
+//                    bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+//
+//  shared_ptr<UdpChannel> channel4 = factory.createChannel("::1", "20073");
+//
+//  BOOST_CHECK_NE(channel3, channel4);
+//
+//  scheduler
+//  .scheduleEvent(time::milliseconds(500),
+//                 bind(&UdpChannel::connect, channel4,
+//                      "::1", "20070",
+//                      static_cast<UdpChannel::FaceCreatedCallback>(bind(&EndToEndFixture::
+//                                                                        channel_onFaceCreated, this, _1)),
+//                      static_cast<UdpChannel::ConnectFailedCallback>(bind(&EndToEndFixture::
+//                                                                          channel_onConnectFailed, this, _1))));
+//
+//  limitedIoRemaining = 2; // 2 connects
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(4),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot connect or cannot accept multiple connections"));
+//
+//  scheduler.scheduleEvent(time::seconds(0.4),
+//                          bind(&EndToEndFixture::checkFaceList, this, 2));
+//
+//  g_io.run();
+//  g_io.reset();
+//
+//  BOOST_CHECK_EQUAL(faces.size(), 3);
+//
+//
+//  face2->sendInterest(interest1);
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot send or receive Interest/Data packets"));
+//  limitedIoRemaining = 1;
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  BOOST_CHECK_EQUAL(faces.size(), 4);
+//
+//  face3->sendInterest(interest2);
+//  abortEvent =
+//  scheduler.scheduleEvent(time::seconds(1),
+//                          bind(&EndToEndFixture::abortTestCase, this,
+//                               "UdpChannel error: cannot send or receive Interest/Data packets"));
+//  limitedIoRemaining = 1;
+//  g_io.run();
+//  g_io.reset();
+//  scheduler.cancelEvent(abortEvent);
+//
+//  //channel1 should have created 2 faces, one when face2 sent an interest, one when face3 sent an interest
+//  BOOST_CHECK_EQUAL(faces.size(), 5);
+//  BOOST_CHECK_THROW(channel1->listen(bind(&EndToEndFixture::channel_onFaceCreated,
+//                                          this,
+//                                          _1),
+//                                      bind(&EndToEndFixture::channel_onConnectFailedOk,
+//                                          this,
+//                                          _1)),
+//                    UdpChannel::Error);
+//}
+
+
+BOOST_FIXTURE_TEST_CASE(FaceClosing, EndToEndFixture)
+{
+  UdpFactory factory = UdpFactory();
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("127.0.0.1", "20070");
+  shared_ptr<UdpChannel> channel2 = factory.createChannel("127.0.0.1", "20071");
+
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  channel2->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept connection");
+
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+
+  BOOST_CHECK(static_cast<bool>(face2));
+
+  // Face::close must be invoked during io run to be counted as an op
+  scheduler::schedule(time::milliseconds(100), bind(&Face::close, face2));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "FaceClosing error: cannot properly close faces");
+
+  BOOST_CHECK(!static_cast<bool>(face2));
+
+  BOOST_CHECK_EQUAL(channel2->size(), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(ClosingIdleFace, EndToEndFixture)
+{
+  Interest interest1("ndn:/TpnzGvW9R");
+  Interest interest2("ndn:/QWiIMfj5sL");
+
+  UdpFactory factory;
+
+  shared_ptr<UdpChannel> channel1 = factory.createChannel("127.0.0.1", "20070",
+                                                          time::seconds(2));
+  shared_ptr<UdpChannel> channel2 = factory.createChannel("127.0.0.1", "20071",
+                                                          time::seconds(2));
+
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  channel2->connect("127.0.0.1", "20070",
+                    bind(&EndToEndFixture::channel2_onFaceCreated, this, _1),
+                    bind(&EndToEndFixture::channel2_onConnectFailed, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect or cannot accept connection");
+
+  face2->sendInterest(interest1);
+  BOOST_CHECK(!face2->isOnDemand());
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2,//1 send + 1 listen return
+                                      time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_CHECK_EQUAL(faces.size(), 2);
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(2)) == LimitedIo::EXCEED_TIME,
+                      "Idle face should be still open because has been used recently");
+  BOOST_CHECK_EQUAL(faces.size(), 2);
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_OPS,
+                      "Closing idle face error: face should be closed by now");
+
+  //the face on listen should be closed now
+  BOOST_CHECK_EQUAL(channel1->size(), 0);
+  //checking that face2 has not been closed
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+  BOOST_REQUIRE(static_cast<bool>(face2));
+
+  face2->sendInterest(interest2);
+  BOOST_CHECK_MESSAGE(limitedIo.run(2,//1 send + 1 listen return
+                                      time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot send or receive Interest/Data packets");
+  //channel1 should have created a new face by now
+  BOOST_CHECK_EQUAL(channel1->size(), 1);
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+  BOOST_REQUIRE(static_cast<bool>(face1));
+  BOOST_CHECK(face1->isOnDemand());
+
+  channel1->connect("127.0.0.1", "20071",
+                    bind(&EndToEndFixture::channel1_onFaceCreatedNoCheck, this, _1),
+                    bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(1,//1 connect
+                                      time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UdpChannel error: cannot connect");
+
+  BOOST_CHECK(!face1->isOnDemand());
+
+  //the connect should have set face1 as permanent face,
+  //but it shouln't have created any additional faces
+  BOOST_CHECK_EQUAL(channel1->size(), 1);
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+  BOOST_CHECK_MESSAGE(limitedIo.run(1, time::seconds(4)) == LimitedIo::EXCEED_TIME,
+                      "Idle face should be still open because it's permanent now");
+  //both faces are permanent, nothing should have changed
+  BOOST_CHECK_EQUAL(channel1->size(), 1);
+  BOOST_CHECK_EQUAL(channel2->size(), 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/face/unix-stream.cpp b/tests/daemon/face/unix-stream.cpp
new file mode 100644
index 0000000..4181aa1
--- /dev/null
+++ b/tests/daemon/face/unix-stream.cpp
@@ -0,0 +1,423 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face/unix-stream-factory.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace tests {
+
+using namespace boost::asio::local;
+
+#define CHANNEL_PATH1 "unix-stream-test.1.sock"
+#define CHANNEL_PATH2 "unix-stream-test.2.sock"
+
+BOOST_FIXTURE_TEST_SUITE(FaceUnixStream, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(ChannelMap)
+{
+  UnixStreamFactory factory;
+
+  shared_ptr<UnixStreamChannel> channel1 = factory.createChannel(CHANNEL_PATH1);
+  shared_ptr<UnixStreamChannel> channel1a = factory.createChannel(CHANNEL_PATH1);
+  BOOST_CHECK_EQUAL(channel1, channel1a);
+  std::string channel1uri = channel1->getUri().toString();
+  BOOST_CHECK_EQUAL(channel1uri.find("unix:///"), 0); // third '/' is the path separator
+  BOOST_CHECK_EQUAL(channel1uri.rfind(CHANNEL_PATH1),
+                    channel1uri.size() - std::string(CHANNEL_PATH1).size());
+
+  shared_ptr<UnixStreamChannel> channel2 = factory.createChannel(CHANNEL_PATH2);
+  BOOST_CHECK_NE(channel1, channel2);
+}
+
+class EndToEndFixture : protected BaseFixture
+{
+public:
+  void
+  client_onConnect(const boost::system::error_code& error)
+  {
+    BOOST_CHECK_MESSAGE(!error, error.message());
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel1_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    BOOST_CHECK(!static_cast<bool>(face1));
+    face1 = static_pointer_cast<UnixStreamFace>(newFace);
+    face1->onReceiveInterest +=
+      bind(&EndToEndFixture::face1_onReceiveInterest, this, _1);
+    face1->onReceiveData +=
+      bind(&EndToEndFixture::face1_onReceiveData, this, _1);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel1_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveInterest(const Interest& interest)
+  {
+    face1_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face1_onReceiveData(const Data& data)
+  {
+    face1_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveInterest(const Interest& interest)
+  {
+    face2_receivedInterests.push_back(interest);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  face2_onReceiveData(const Data& data)
+  {
+    face2_receivedDatas.push_back(data);
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onFaceCreated(const shared_ptr<Face>& newFace)
+  {
+    faces.push_back(static_pointer_cast<UnixStreamFace>(newFace));
+
+    limitedIo.afterOp();
+  }
+
+  void
+  channel_onConnectFailed(const std::string& reason)
+  {
+    BOOST_CHECK_MESSAGE(false, reason);
+
+    limitedIo.afterOp();
+  }
+
+protected:
+  LimitedIo limitedIo;
+
+  shared_ptr<UnixStreamFace> face1;
+  std::vector<Interest> face1_receivedInterests;
+  std::vector<Data> face1_receivedDatas;
+  shared_ptr<UnixStreamFace> face2;
+  std::vector<Interest> face2_receivedInterests;
+  std::vector<Data> face2_receivedDatas;
+
+  std::list< shared_ptr<UnixStreamFace> > faces;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd, EndToEndFixture)
+{
+  UnixStreamFactory factory;
+
+  shared_ptr<UnixStreamChannel> channel1 = factory.createChannel(CHANNEL_PATH1);
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  shared_ptr<stream_protocol::socket> client =
+      make_shared<stream_protocol::socket>(boost::ref(g_io));
+  client->async_connect(stream_protocol::endpoint(CHANNEL_PATH1),
+                        bind(&EndToEndFixture::client_onConnect, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+
+  BOOST_CHECK_EQUAL(face1->getRemoteUri().getScheme(), "fd");
+  BOOST_CHECK_NO_THROW(boost::lexical_cast<int>(face1->getRemoteUri().getHost()));
+  std::string face1localUri = face1->getLocalUri().toString();
+  BOOST_CHECK_EQUAL(face1localUri.find("unix:///"), 0); // third '/' is the path separator
+  BOOST_CHECK_EQUAL(face1localUri.rfind(CHANNEL_PATH1),
+                    face1localUri.size() - std::string(CHANNEL_PATH1).size());
+
+  face2 = make_shared<UnixStreamFace>(client);
+  face2->onReceiveInterest +=
+    bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+  face2->onReceiveData +=
+    bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(8, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 3);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+
+  const FaceCounters& counters1 = face1->getCounters();
+  BOOST_CHECK_EQUAL(counters1.getNInInterests() , 1);
+  BOOST_CHECK_EQUAL(counters1.getNInDatas()     , 3);
+  BOOST_CHECK_EQUAL(counters1.getNOutInterests(), 3);
+  BOOST_CHECK_EQUAL(counters1.getNOutDatas()    , 1);
+
+  const FaceCounters& counters2 = face2->getCounters();
+  BOOST_CHECK_EQUAL(counters2.getNInInterests() , 3);
+  BOOST_CHECK_EQUAL(counters2.getNInDatas()     , 1);
+  BOOST_CHECK_EQUAL(counters2.getNOutInterests(), 1);
+  BOOST_CHECK_EQUAL(counters2.getNOutDatas()    , 3);
+}
+
+BOOST_FIXTURE_TEST_CASE(MultipleAccepts, EndToEndFixture)
+{
+  UnixStreamFactory factory;
+
+  shared_ptr<UnixStreamChannel> channel = factory.createChannel(CHANNEL_PATH1);
+  channel->listen(bind(&EndToEndFixture::channel_onFaceCreated,   this, _1),
+                  bind(&EndToEndFixture::channel_onConnectFailed, this, _1));
+
+  shared_ptr<stream_protocol::socket> client1 =
+      make_shared<stream_protocol::socket>(boost::ref(g_io));
+  client1->async_connect(stream_protocol::endpoint(CHANNEL_PATH1),
+                         bind(&EndToEndFixture::client_onConnect, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot connect or cannot accept connection");
+
+  BOOST_CHECK_EQUAL(faces.size(), 1);
+
+  shared_ptr<stream_protocol::socket> client2 =
+      make_shared<stream_protocol::socket>(boost::ref(g_io));
+  client2->async_connect(stream_protocol::endpoint(CHANNEL_PATH1),
+                         bind(&EndToEndFixture::client_onConnect, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot accept multiple connections");
+
+  BOOST_CHECK_EQUAL(faces.size(), 2);
+
+  // now close one of the faces
+  faces.front()->close();
+
+  // we should still be able to send/receive with the other one
+  face1 = faces.back();
+  face1->onReceiveInterest += bind(&EndToEndFixture::face1_onReceiveInterest, this, _1);
+  face1->onReceiveData += bind(&EndToEndFixture::face1_onReceiveData, this, _1);
+
+  face2 = make_shared<UnixStreamFace>(client2);
+  face2->onReceiveInterest += bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+  face2->onReceiveData += bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  face1->sendInterest(interest1);
+  face1->sendData    (data1    );
+  face2->sendInterest(interest2);
+  face2->sendData    (data2    );
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(4, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getName(), interest2.getName());
+  BOOST_CHECK_EQUAL(face1_receivedDatas    [0].getName(), data2.getName());
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getName(), interest1.getName());
+  BOOST_CHECK_EQUAL(face2_receivedDatas    [0].getName(), data1.getName());
+}
+
+static inline void
+noOp()
+{
+}
+
+BOOST_FIXTURE_TEST_CASE(UnixStreamFaceLocalControlHeader, EndToEndFixture)
+{
+  UnixStreamFactory factory;
+
+  shared_ptr<UnixStreamChannel> channel1 = factory.createChannel(CHANNEL_PATH1);
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  shared_ptr<stream_protocol::socket> client =
+      make_shared<stream_protocol::socket>(boost::ref(g_io));
+  client->async_connect(stream_protocol::endpoint(CHANNEL_PATH1),
+                        bind(&EndToEndFixture::client_onConnect, this, _1));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot connect or cannot accept connection");
+
+  BOOST_REQUIRE(static_cast<bool>(face1));
+
+  face2 = make_shared<UnixStreamFace>(client);
+  face2->onReceiveInterest +=
+    bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+  face2->onReceiveData +=
+    bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  face1->setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+  face1->setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  BOOST_CHECK(face1->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(face1->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  face2->setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+  face2->setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  BOOST_CHECK(face2->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(face2->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  ////////////////////////////////////////////////////////
+
+  interest1.setIncomingFaceId(11);
+  interest1.setNextHopFaceId(111);
+
+  face1->sendInterest(interest1);
+
+  data1.setIncomingFaceId(22);
+  data1.getLocalControlHeader().setNextHopFaceId(222);
+
+  face1->sendData    (data1);
+
+  //
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face2_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face2_receivedDatas    .size(), 1);
+
+  // sending allows only IncomingFaceId, receiving allows only NextHopFaceId
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(face2_receivedInterests[0].getLocalControlHeader().hasNextHopFaceId(), false);
+
+  BOOST_CHECK_EQUAL(face2_receivedDatas[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(face2_receivedDatas[0].getLocalControlHeader().hasNextHopFaceId(), false);
+
+  ////////////////////////////////////////////////////////
+
+  using namespace boost::asio;
+
+  std::vector<const_buffer> interestWithHeader;
+  Block iHeader  = interest1.getLocalControlHeader().wireEncode(interest1, true, true);
+  Block iPayload = interest1.wireEncode();
+  interestWithHeader.push_back(buffer(iHeader.wire(),  iHeader.size()));
+  interestWithHeader.push_back(buffer(iPayload.wire(), iPayload.size()));
+
+  std::vector<const_buffer> dataWithHeader;
+  Block dHeader  = data1.getLocalControlHeader().wireEncode(data1, true, true);
+  Block dPayload = data1.wireEncode();
+  dataWithHeader.push_back(buffer(dHeader.wire(),  dHeader.size()));
+  dataWithHeader.push_back(buffer(dPayload.wire(), dPayload.size()));
+
+  //
+
+  client->async_send(interestWithHeader, bind(&noOp));
+  client->async_send(dataWithHeader, bind(&noOp));
+
+  BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(1)) == LimitedIo::EXCEED_OPS,
+                      "UnixStreamChannel error: cannot send or receive Interest/Data packets");
+
+  BOOST_REQUIRE_EQUAL(face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(face1_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getLocalControlHeader().hasNextHopFaceId(), true);
+  BOOST_CHECK_EQUAL(face1_receivedInterests[0].getNextHopFaceId(), 111);
+
+  BOOST_CHECK_EQUAL(face1_receivedDatas[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(face1_receivedDatas[0].getLocalControlHeader().hasNextHopFaceId(), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/broadcast-strategy.cpp b/tests/daemon/fw/broadcast-strategy.cpp
new file mode 100644
index 0000000..cde9b44
--- /dev/null
+++ b/tests/daemon/fw/broadcast-strategy.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "fw/broadcast-strategy.hpp"
+#include "strategy-tester.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FwBroadcastStrategy, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Forward2)
+{
+  Forwarder forwarder;
+  typedef StrategyTester<fw::BroadcastStrategy> BroadcastStrategyTester;
+  BroadcastStrategyTester strategy(forwarder);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face3 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert(Name()).first;
+  fibEntry->addNextHop(face1, 0);
+  fibEntry->addNextHop(face2, 0);
+  fibEntry->addNextHop(face3, 0);
+
+  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
+  Pit& pit = forwarder.getPit();
+  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(face3, *interest);
+
+  strategy.afterReceiveInterest(*face3, *interest, fibEntry, pitEntry);
+  BOOST_CHECK_EQUAL(strategy.m_rejectPendingInterestHistory.size(), 0);
+  BOOST_CHECK_EQUAL(strategy.m_sendInterestHistory.size(), 2);
+  bool hasFace1 = false;
+  bool hasFace2 = false;
+  for (std::vector<BroadcastStrategyTester::SendInterestArgs>::iterator it =
+       strategy.m_sendInterestHistory.begin();
+       it != strategy.m_sendInterestHistory.end(); ++it) {
+    if (it->get<1>() == face1) {
+      hasFace1 = true;
+    }
+    if (it->get<1>() == face2) {
+      hasFace2 = true;
+    }
+  }
+  BOOST_CHECK(hasFace1 && hasFace2);
+}
+
+BOOST_AUTO_TEST_CASE(RejectScope)
+{
+  Forwarder forwarder;
+  typedef StrategyTester<fw::BroadcastStrategy> BroadcastStrategyTester;
+  BroadcastStrategyTester strategy(forwarder);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert("ndn:/localhop/uS09bub6tm").first;
+  fibEntry->addNextHop(face2, 0);
+
+  shared_ptr<Interest> interest = makeInterest("ndn:/localhop/uS09bub6tm/eG3MMoP6z");
+  Pit& pit = forwarder.getPit();
+  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(face1, *interest);
+
+  strategy.afterReceiveInterest(*face1, *interest, fibEntry, pitEntry);
+  BOOST_CHECK_EQUAL(strategy.m_rejectPendingInterestHistory.size(), 1);
+  BOOST_CHECK_EQUAL(strategy.m_sendInterestHistory.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RejectLoopback)
+{
+  Forwarder forwarder;
+  typedef StrategyTester<fw::BroadcastStrategy> BroadcastStrategyTester;
+  BroadcastStrategyTester strategy(forwarder);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert(Name()).first;
+  fibEntry->addNextHop(face1, 0);
+
+  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
+  Pit& pit = forwarder.getPit();
+  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(face1, *interest);
+
+  strategy.afterReceiveInterest(*face1, *interest, fibEntry, pitEntry);
+  BOOST_CHECK_EQUAL(strategy.m_rejectPendingInterestHistory.size(), 1);
+  BOOST_CHECK_EQUAL(strategy.m_sendInterestHistory.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/client-control-strategy.cpp b/tests/daemon/fw/client-control-strategy.cpp
new file mode 100644
index 0000000..c76fb52
--- /dev/null
+++ b/tests/daemon/fw/client-control-strategy.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "fw/client-control-strategy.hpp"
+#include "strategy-tester.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FwClientControlStrategy, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Forward3)
+{
+  Forwarder forwarder;
+  typedef StrategyTester<fw::ClientControlStrategy> ClientControlStrategyTester;
+  ClientControlStrategyTester strategy(forwarder);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face3 = make_shared<DummyFace>();
+  shared_ptr<DummyLocalFace> face4 = make_shared<DummyLocalFace>();
+  face4->setLocalControlHeaderFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+  forwarder.addFace(face4);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert(Name()).first;
+  fibEntry->addNextHop(face2, 0);
+
+  Pit& pit = forwarder.getPit();
+
+  // Interest with valid NextHopFaceId
+  shared_ptr<Interest> interest1 = makeInterest("ndn:/0z8r6yDDe");
+  interest1->setNextHopFaceId(face1->getId());
+  shared_ptr<pit::Entry> pitEntry1 = pit.insert(*interest1).first;
+  pitEntry1->insertOrUpdateInRecord(face4, *interest1);
+
+  strategy.m_sendInterestHistory.clear();
+  strategy.afterReceiveInterest(*face4, *interest1, fibEntry, pitEntry1);
+  BOOST_REQUIRE_EQUAL(strategy.m_sendInterestHistory.size(), 1);
+  BOOST_CHECK_EQUAL(strategy.m_sendInterestHistory[0].get<1>(), face1);
+
+  // Interest without NextHopFaceId
+  shared_ptr<Interest> interest2 = makeInterest("ndn:/y6JQADGVz");
+  shared_ptr<pit::Entry> pitEntry2 = pit.insert(*interest2).first;
+  pitEntry2->insertOrUpdateInRecord(face4, *interest2);
+
+  strategy.m_sendInterestHistory.clear();
+  strategy.afterReceiveInterest(*face4, *interest2, fibEntry, pitEntry2);
+  BOOST_REQUIRE_EQUAL(strategy.m_sendInterestHistory.size(), 1);
+  BOOST_CHECK_EQUAL(strategy.m_sendInterestHistory[0].get<1>(), face2);
+
+  // Interest with invalid NextHopFaceId
+  shared_ptr<Interest> interest3 = makeInterest("ndn:/0z8r6yDDe");
+  interest3->setNextHopFaceId(face3->getId());
+  shared_ptr<pit::Entry> pitEntry3 = pit.insert(*interest3).first;
+  pitEntry3->insertOrUpdateInRecord(face4, *interest3);
+
+  face3->close(); // face3 is closed and its FaceId becomes invalid
+  strategy.m_sendInterestHistory.clear();
+  strategy.m_rejectPendingInterestHistory.clear();
+  strategy.afterReceiveInterest(*face4, *interest3, fibEntry, pitEntry3);
+  BOOST_REQUIRE_EQUAL(strategy.m_sendInterestHistory.size(), 0);
+  BOOST_REQUIRE_EQUAL(strategy.m_rejectPendingInterestHistory.size(), 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/dummy-strategy.hpp b/tests/daemon/fw/dummy-strategy.hpp
new file mode 100644
index 0000000..67784fc
--- /dev/null
+++ b/tests/daemon/fw/dummy-strategy.hpp
@@ -0,0 +1,86 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_FW_DUMMY_STRATEGY_HPP
+#define NFD_TESTS_NFD_FW_DUMMY_STRATEGY_HPP
+
+#include "fw/strategy.hpp"
+
+namespace nfd {
+namespace tests {
+
+/** \brief strategy for unit testing
+ *
+ *  Triggers on DummyStrategy are recorded but does nothing
+ */
+class DummyStrategy : public fw::Strategy
+{
+public:
+  DummyStrategy(Forwarder& forwarder, const Name& name)
+    : Strategy(forwarder, name)
+  {
+  }
+
+  virtual void
+  afterReceiveInterest(const Face& inFace,
+                       const Interest& interest,
+                       shared_ptr<fib::Entry> fibEntry,
+                       shared_ptr<pit::Entry> pitEntry)
+  {
+    ++m_afterReceiveInterest_count;
+
+    if (static_cast<bool>(m_interestOutFace)) {
+      this->sendInterest(pitEntry, m_interestOutFace);
+    }
+    else {
+      this->rejectPendingInterest(pitEntry);
+    }
+  }
+
+  virtual void
+  beforeSatisfyPendingInterest(shared_ptr<pit::Entry> pitEntry,
+                               const Face& inFace, const Data& data)
+  {
+    ++m_beforeSatisfyPendingInterest_count;
+  }
+
+  virtual void
+  beforeExpirePendingInterest(shared_ptr<pit::Entry> pitEntry)
+  {
+    ++m_beforeExpirePendingInterest_count;
+  }
+
+public:
+  int m_afterReceiveInterest_count;
+  int m_beforeSatisfyPendingInterest_count;
+  int m_beforeExpirePendingInterest_count;
+
+  /// outFace to use in afterReceiveInterest, nullptr to reject
+  shared_ptr<Face> m_interestOutFace;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_FW_DUMMY_STRATEGY_HPP
diff --git a/tests/daemon/fw/face-table.cpp b/tests/daemon/fw/face-table.cpp
new file mode 100644
index 0000000..daf9256
--- /dev/null
+++ b/tests/daemon/fw/face-table.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "fw/face-table.hpp"
+#include "fw/forwarder.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FwFaceTable, BaseFixture)
+
+static inline void
+saveFaceId(std::vector<FaceId>& faceIds, shared_ptr<Face> face)
+{
+  faceIds.push_back(face->getId());
+}
+
+BOOST_AUTO_TEST_CASE(AddRemove)
+{
+  Forwarder forwarder;
+
+  FaceTable& faceTable = forwarder.getFaceTable();
+  std::vector<FaceId> onAddHistory;
+  std::vector<FaceId> onRemoveHistory;
+  faceTable.onAdd    += bind(&saveFaceId, boost::ref(onAddHistory   ), _1);
+  faceTable.onRemove += bind(&saveFaceId, boost::ref(onRemoveHistory), _1);
+
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+
+  BOOST_CHECK_EQUAL(face1->getId(), INVALID_FACEID);
+  BOOST_CHECK_EQUAL(face2->getId(), INVALID_FACEID);
+
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  BOOST_CHECK_NE(face1->getId(), INVALID_FACEID);
+  BOOST_CHECK_NE(face2->getId(), INVALID_FACEID);
+  BOOST_CHECK_NE(face1->getId(), face2->getId());
+
+  FaceId oldId1 = face1->getId();
+  faceTable.add(face1);
+  BOOST_CHECK_EQUAL(face1->getId(), oldId1);
+  BOOST_CHECK_EQUAL(faceTable.size(), 2);
+
+  BOOST_REQUIRE_EQUAL(onAddHistory.size(), 2);
+  BOOST_CHECK_EQUAL(onAddHistory[0], face1->getId());
+  BOOST_CHECK_EQUAL(onAddHistory[1], face2->getId());
+
+  face1->close();
+
+  BOOST_CHECK_EQUAL(face1->getId(), INVALID_FACEID);
+
+  BOOST_REQUIRE_EQUAL(onRemoveHistory.size(), 1);
+  BOOST_CHECK_EQUAL(onRemoveHistory[0], onAddHistory[0]);
+}
+
+BOOST_AUTO_TEST_CASE(Enumerate)
+{
+  Forwarder forwarder;
+  FaceTable& faceTable = forwarder.getFaceTable();
+
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  bool hasFace1 = false, hasFace2 = false;
+
+  BOOST_CHECK_EQUAL(faceTable.size(), 0);
+  BOOST_CHECK_EQUAL(std::distance(faceTable.begin(), faceTable.end()), faceTable.size());
+
+  faceTable.add(face1);
+  BOOST_CHECK_EQUAL(faceTable.size(), 1);
+  BOOST_CHECK_EQUAL(std::distance(faceTable.begin(), faceTable.end()), faceTable.size());
+  hasFace1 = hasFace2 = false;
+  for (FaceTable::const_iterator it = faceTable.begin(); it != faceTable.end(); ++it) {
+    if (*it == face1) {
+      hasFace1 = true;
+    }
+  }
+  BOOST_CHECK(hasFace1);
+
+  faceTable.add(face2);
+  BOOST_CHECK_EQUAL(faceTable.size(), 2);
+  BOOST_CHECK_EQUAL(std::distance(faceTable.begin(), faceTable.end()), faceTable.size());
+  hasFace1 = hasFace2 = false;
+  for (FaceTable::const_iterator it = faceTable.begin(); it != faceTable.end(); ++it) {
+    if (*it == face1) {
+      hasFace1 = true;
+    }
+    if (*it == face2) {
+      hasFace2 = true;
+    }
+  }
+  BOOST_CHECK(hasFace1);
+  BOOST_CHECK(hasFace2);
+
+  face1->close();
+  BOOST_CHECK_EQUAL(faceTable.size(), 1);
+  BOOST_CHECK_EQUAL(std::distance(faceTable.begin(), faceTable.end()), faceTable.size());
+  hasFace1 = hasFace2 = false;
+  for (FaceTable::const_iterator it = faceTable.begin(); it != faceTable.end(); ++it) {
+    if (*it == face1) {
+      hasFace1 = true;
+    }
+    if (*it == face2) {
+      hasFace2 = true;
+    }
+  }
+  BOOST_CHECK(!hasFace1);
+  BOOST_CHECK(hasFace2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/forwarder.cpp b/tests/daemon/fw/forwarder.cpp
new file mode 100644
index 0000000..0151460
--- /dev/null
+++ b/tests/daemon/fw/forwarder.cpp
@@ -0,0 +1,352 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "fw/forwarder.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+#include "dummy-strategy.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FwForwarder, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(SimpleExchange)
+{
+  Forwarder forwarder;
+
+  Name nameA  ("ndn:/A");
+  Name nameAB ("ndn:/A/B");
+  Name nameABC("ndn:/A/B/C");
+  shared_ptr<Interest> interestAB = makeInterest(nameAB);
+  interestAB->setInterestLifetime(time::seconds(4));
+  shared_ptr<Data> dataABC = makeData(nameABC);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+  face1->afterSend += bind(&boost::asio::io_service::stop, &g_io);
+  face2->afterSend += bind(&boost::asio::io_service::stop, &g_io);
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert(Name("ndn:/A")).first;
+  fibEntry->addNextHop(face2, 0);
+
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNInInterests (), 0);
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNOutInterests(), 0);
+  face1->receiveInterest(*interestAB);
+  g_io.run();
+  g_io.reset();
+  BOOST_REQUIRE_EQUAL(face2->m_sentInterests.size(), 1);
+  BOOST_CHECK(face2->m_sentInterests[0].getName().equals(nameAB));
+  BOOST_CHECK_EQUAL(face2->m_sentInterests[0].getIncomingFaceId(), face1->getId());
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNInInterests (), 1);
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNOutInterests(), 1);
+
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNInDatas (), 0);
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNOutDatas(), 0);
+  face2->receiveData(*dataABC);
+  g_io.run();
+  g_io.reset();
+  BOOST_REQUIRE_EQUAL(face1->m_sentDatas.size(), 1);
+  BOOST_CHECK(face1->m_sentDatas[0].getName().equals(nameABC));
+  BOOST_CHECK_EQUAL(face1->m_sentDatas[0].getIncomingFaceId(), face2->getId());
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNInDatas (), 1);
+  BOOST_CHECK_EQUAL(forwarder.getCounters().getNOutDatas(), 1);
+}
+
+class ScopeLocalhostIncomingTestForwarder : public Forwarder
+{
+public:
+  ScopeLocalhostIncomingTestForwarder()
+  {
+  }
+
+  virtual void
+  onDataUnsolicited(Face& inFace, const Data& data)
+  {
+    ++m_onDataUnsolicited_count;
+  }
+
+protected:
+  virtual void
+  dispatchToStrategy(shared_ptr<pit::Entry> pitEntry, function<void(fw::Strategy*)> f)
+  {
+    ++m_dispatchToStrategy_count;
+  }
+
+public:
+  int m_dispatchToStrategy_count;
+  int m_onDataUnsolicited_count;
+};
+
+BOOST_AUTO_TEST_CASE(ScopeLocalhostIncoming)
+{
+  ScopeLocalhostIncomingTestForwarder forwarder;
+  shared_ptr<Face> face1 = make_shared<DummyLocalFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  // local face, /localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  shared_ptr<Interest> i1 = makeInterest("/localhost/A1");
+  forwarder.onIncomingInterest(*face1, *i1);
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+
+  // non-local face, /localhost: violate
+  forwarder.m_dispatchToStrategy_count = 0;
+  shared_ptr<Interest> i2 = makeInterest("/localhost/A2");
+  forwarder.onIncomingInterest(*face2, *i2);
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 0);
+
+  // local face, non-/localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  shared_ptr<Interest> i3 = makeInterest("/A3");
+  forwarder.onIncomingInterest(*face1, *i3);
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+
+  // non-local face, non-/localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  shared_ptr<Interest> i4 = makeInterest("/A4");
+  forwarder.onIncomingInterest(*face2, *i4);
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+
+  // local face, /localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  shared_ptr<Data> d1 = makeData("/localhost/B1");
+  forwarder.onIncomingData(*face1, *d1);
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+
+  // non-local face, /localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  shared_ptr<Data> d2 = makeData("/localhost/B2");
+  forwarder.onIncomingData(*face2, *d2);
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 0);
+
+  // local face, non-/localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  shared_ptr<Data> d3 = makeData("/B3");
+  forwarder.onIncomingData(*face1, *d3);
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+
+  // non-local face, non-/localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  shared_ptr<Data> d4 = makeData("/B4");
+  forwarder.onIncomingData(*face2, *d4);
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+}
+
+BOOST_AUTO_TEST_CASE(ScopeLocalhostOutgoing)
+{
+  Forwarder forwarder;
+  shared_ptr<DummyLocalFace> face1 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face2 = make_shared<DummyFace>();
+  shared_ptr<Face>           face3 = make_shared<DummyLocalFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+  Pit& pit = forwarder.getPit();
+
+  // local face, /localhost: OK
+  shared_ptr<Interest> interestA1 = makeInterest("/localhost/A1");
+  shared_ptr<pit::Entry> pitA1 = pit.insert(*interestA1).first;
+  pitA1->insertOrUpdateInRecord(face3, *interestA1);
+  face1->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pitA1, *face1);
+  BOOST_CHECK_EQUAL(face1->m_sentInterests.size(), 1);
+
+  // non-local face, /localhost: violate
+  shared_ptr<Interest> interestA2 = makeInterest("/localhost/A2");
+  shared_ptr<pit::Entry> pitA2 = pit.insert(*interestA2).first;
+  pitA2->insertOrUpdateInRecord(face3, *interestA2);
+  face2->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pitA2, *face2);
+  BOOST_CHECK_EQUAL(face2->m_sentInterests.size(), 0);
+
+  // local face, non-/localhost: OK
+  shared_ptr<Interest> interestA3 = makeInterest("/A3");
+  shared_ptr<pit::Entry> pitA3 = pit.insert(*interestA3).first;
+  pitA3->insertOrUpdateInRecord(face3, *interestA3);
+  face1->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pitA3, *face1);
+  BOOST_CHECK_EQUAL(face1->m_sentInterests.size(), 1);
+
+  // non-local face, non-/localhost: OK
+  shared_ptr<Interest> interestA4 = makeInterest("/A4");
+  shared_ptr<pit::Entry> pitA4 = pit.insert(*interestA4).first;
+  pitA4->insertOrUpdateInRecord(face3, *interestA4);
+  face2->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pitA4, *face2);
+  BOOST_CHECK_EQUAL(face2->m_sentInterests.size(), 1);
+
+  // local face, /localhost: OK
+  face1->m_sentDatas.clear();
+  forwarder.onOutgoingData(Data("/localhost/B1"), *face1);
+  BOOST_CHECK_EQUAL(face1->m_sentDatas.size(), 1);
+
+  // non-local face, /localhost: OK
+  face2->m_sentDatas.clear();
+  forwarder.onOutgoingData(Data("/localhost/B2"), *face2);
+  BOOST_CHECK_EQUAL(face2->m_sentDatas.size(), 0);
+
+  // local face, non-/localhost: OK
+  face1->m_sentDatas.clear();
+  forwarder.onOutgoingData(Data("/B3"), *face1);
+  BOOST_CHECK_EQUAL(face1->m_sentDatas.size(), 1);
+
+  // non-local face, non-/localhost: OK
+  face2->m_sentDatas.clear();
+  forwarder.onOutgoingData(Data("/B4"), *face2);
+  BOOST_CHECK_EQUAL(face2->m_sentDatas.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ScopeLocalhopOutgoing)
+{
+  Forwarder forwarder;
+  shared_ptr<DummyLocalFace> face1 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face2 = make_shared<DummyFace>();
+  shared_ptr<DummyLocalFace> face3 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face4 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+  forwarder.addFace(face4);
+  Pit& pit = forwarder.getPit();
+
+  // from local face, to local face: OK
+  shared_ptr<Interest> interest1 = makeInterest("/localhop/1");
+  shared_ptr<pit::Entry> pit1 = pit.insert(*interest1).first;
+  pit1->insertOrUpdateInRecord(face1, *interest1);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit1, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from non-local face, to local face: OK
+  shared_ptr<Interest> interest2 = makeInterest("/localhop/2");
+  shared_ptr<pit::Entry> pit2 = pit.insert(*interest2).first;
+  pit2->insertOrUpdateInRecord(face2, *interest2);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit2, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from local face, to non-local face: OK
+  shared_ptr<Interest> interest3 = makeInterest("/localhop/3");
+  shared_ptr<pit::Entry> pit3 = pit.insert(*interest3).first;
+  pit3->insertOrUpdateInRecord(face1, *interest3);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit3, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 1);
+
+  // from non-local face, to non-local face: violate
+  shared_ptr<Interest> interest4 = makeInterest("/localhop/4");
+  shared_ptr<pit::Entry> pit4 = pit.insert(*interest4).first;
+  pit4->insertOrUpdateInRecord(face2, *interest4);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit4, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 0);
+
+  // from local face and non-local face, to local face: OK
+  shared_ptr<Interest> interest5 = makeInterest("/localhop/5");
+  shared_ptr<pit::Entry> pit5 = pit.insert(*interest5).first;
+  pit5->insertOrUpdateInRecord(face1, *interest5);
+  pit5->insertOrUpdateInRecord(face2, *interest5);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit5, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from local face and non-local face, to non-local face: OK
+  shared_ptr<Interest> interest6 = makeInterest("/localhop/6");
+  shared_ptr<pit::Entry> pit6 = pit.insert(*interest6).first;
+  pit6->insertOrUpdateInRecord(face1, *interest6);
+  pit6->insertOrUpdateInRecord(face2, *interest6);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit6, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyDispatch)
+{
+  LimitedIo limitedIo;
+  Forwarder forwarder;
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  StrategyChoice& strategyChoice = forwarder.getStrategyChoice();
+  shared_ptr<DummyStrategy> strategyP = make_shared<DummyStrategy>(
+                                        boost::ref(forwarder), "ndn:/strategyP");
+  shared_ptr<DummyStrategy> strategyQ = make_shared<DummyStrategy>(
+                                        boost::ref(forwarder), "ndn:/strategyQ");
+  strategyChoice.install(strategyP);
+  strategyChoice.install(strategyQ);
+  strategyChoice.insert("ndn:/" , strategyP->getName());
+  strategyChoice.insert("ndn:/B", strategyQ->getName());
+
+  shared_ptr<Interest> interest1 = makeInterest("ndn:/A/1");
+  strategyP->m_afterReceiveInterest_count = 0;
+  strategyP->m_interestOutFace = face2;
+  forwarder.onInterest(*face1, *interest1);
+  BOOST_CHECK_EQUAL(strategyP->m_afterReceiveInterest_count, 1);
+
+  shared_ptr<Interest> interest2 = makeInterest("ndn:/B/2");
+  strategyQ->m_afterReceiveInterest_count = 0;
+  strategyQ->m_interestOutFace = face2;
+  forwarder.onInterest(*face1, *interest2);
+  BOOST_CHECK_EQUAL(strategyQ->m_afterReceiveInterest_count, 1);
+
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(5));
+
+  shared_ptr<Data> data1 = makeData("ndn:/A/1/a");
+  strategyP->m_beforeSatisfyPendingInterest_count = 0;
+  forwarder.onData(*face2, *data1);
+  BOOST_CHECK_EQUAL(strategyP->m_beforeSatisfyPendingInterest_count, 1);
+
+  shared_ptr<Data> data2 = makeData("ndn:/B/2/b");
+  strategyQ->m_beforeSatisfyPendingInterest_count = 0;
+  forwarder.onData(*face2, *data2);
+  BOOST_CHECK_EQUAL(strategyQ->m_beforeSatisfyPendingInterest_count, 1);
+
+  shared_ptr<Interest> interest3 = makeInterest("ndn:/A/3");
+  interest3->setInterestLifetime(time::milliseconds(30));
+  forwarder.onInterest(*face1, *interest3);
+  shared_ptr<Interest> interest4 = makeInterest("ndn:/B/4");
+  interest4->setInterestLifetime(time::milliseconds(5000));
+  forwarder.onInterest(*face1, *interest4);
+
+  strategyP->m_beforeExpirePendingInterest_count = 0;
+  strategyQ->m_beforeExpirePendingInterest_count = 0;
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(100));
+  BOOST_CHECK_EQUAL(strategyP->m_beforeExpirePendingInterest_count, 1);
+  BOOST_CHECK_EQUAL(strategyQ->m_beforeExpirePendingInterest_count, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/ncc-strategy.cpp b/tests/daemon/fw/ncc-strategy.cpp
new file mode 100644
index 0000000..29db3b3
--- /dev/null
+++ b/tests/daemon/fw/ncc-strategy.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "fw/ncc-strategy.hpp"
+#include "strategy-tester.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+#include "tests/limited-io.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FwNccStrategy, BaseFixture)
+
+// NccStrategy is fairly complex.
+// The most important property is:
+// it remembers which upstream is the fastest to return Data,
+// and favors this upstream in subsequent Interests.
+BOOST_AUTO_TEST_CASE(FavorRespondingUpstream)
+{
+  LimitedIo limitedIo;
+  Forwarder forwarder;
+  typedef StrategyTester<fw::NccStrategy> NccStrategyTester;
+  shared_ptr<NccStrategyTester> strategy = make_shared<NccStrategyTester>(boost::ref(forwarder));
+  strategy->onAction += bind(&LimitedIo::afterOp, &limitedIo);
+
+  shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+  shared_ptr<DummyFace> face3 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+
+  Fib& fib = forwarder.getFib();
+  shared_ptr<fib::Entry> fibEntry = fib.insert(Name()).first;
+  fibEntry->addNextHop(face1, 10);
+  fibEntry->addNextHop(face2, 20);
+
+  StrategyChoice& strategyChoice = forwarder.getStrategyChoice();
+  strategyChoice.install(strategy);
+  strategyChoice.insert(Name(), strategy->getName());
+
+  Pit& pit = forwarder.getPit();
+
+  // first Interest: strategy knows nothing and follows routing
+  shared_ptr<Interest> interest1p = makeInterest("ndn:/0Jm1ajrW/%00");
+  Interest& interest1 = *interest1p;
+  interest1.setInterestLifetime(time::milliseconds(8000));
+  shared_ptr<pit::Entry> pitEntry1 = pit.insert(interest1).first;
+
+  pitEntry1->insertOrUpdateInRecord(face3, interest1);
+  strategy->afterReceiveInterest(*face3, interest1, fibEntry, pitEntry1);
+
+  // forwards to face1 because routing says it's best
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(1));
+  BOOST_REQUIRE_EQUAL(strategy->m_sendInterestHistory.size(), 1);
+  BOOST_CHECK_EQUAL(strategy->m_sendInterestHistory[0].get<1>(), face1);
+
+  // forwards to face2 because face1 doesn't respond
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(500));
+  BOOST_REQUIRE_EQUAL(strategy->m_sendInterestHistory.size(), 2);
+  BOOST_CHECK_EQUAL(strategy->m_sendInterestHistory[1].get<1>(), face2);
+
+  // face2 responds
+  shared_ptr<Data> data1p = makeData("ndn:/0Jm1ajrW/%00");
+  Data& data1 = *data1p;
+  strategy->beforeSatisfyPendingInterest(pitEntry1, *face2, data1);
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(500));
+
+  // second Interest: strategy knows face2 is best
+  shared_ptr<Interest> interest2p = makeInterest("ndn:/0Jm1ajrW/%00%01");
+  Interest& interest2 = *interest2p;
+  interest2.setInterestLifetime(time::milliseconds(8000));
+  shared_ptr<pit::Entry> pitEntry2 = pit.insert(interest2).first;
+
+  pitEntry2->insertOrUpdateInRecord(face3, interest2);
+  strategy->afterReceiveInterest(*face3, interest2, fibEntry, pitEntry2);
+
+  // forwards to face2 because it responds previously
+  limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(1));
+  BOOST_REQUIRE_GE(strategy->m_sendInterestHistory.size(), 3);
+  BOOST_CHECK_EQUAL(strategy->m_sendInterestHistory[2].get<1>(), face2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/strategy-tester.hpp b/tests/daemon/fw/strategy-tester.hpp
new file mode 100644
index 0000000..e3debef
--- /dev/null
+++ b/tests/daemon/fw/strategy-tester.hpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_FW_STRATEGY_TESTER_HPP
+#define NFD_TESTS_NFD_FW_STRATEGY_TESTER_HPP
+
+#include <boost/tuple/tuple_comparison.hpp>
+#include "fw/strategy.hpp"
+
+namespace nfd {
+namespace tests {
+
+/** \class StrategyTester
+ *  \brief extends strategy S for unit testing
+ *
+ *  Actions invoked by S are recorded but not passed to forwarder
+ */
+template<typename S>
+class StrategyTester : public S
+{
+public:
+  explicit
+  StrategyTester(Forwarder& forwarder)
+    : S(forwarder, Name(S::STRATEGY_NAME).append("tester"))
+  {
+  }
+
+  /// fires after each Action
+  EventEmitter<> onAction;
+
+protected:
+  virtual void
+  sendInterest(shared_ptr<pit::Entry> pitEntry,shared_ptr<Face> outFace);
+
+  virtual void
+  rejectPendingInterest(shared_ptr<pit::Entry> pitEntry);
+
+public:
+  typedef boost::tuple<shared_ptr<pit::Entry>, shared_ptr<Face> > SendInterestArgs;
+  std::vector<SendInterestArgs> m_sendInterestHistory;
+
+  typedef boost::tuple<shared_ptr<pit::Entry> > RejectPendingInterestArgs;
+  std::vector<RejectPendingInterestArgs> m_rejectPendingInterestHistory;
+};
+
+
+template<typename S>
+inline void
+StrategyTester<S>::sendInterest(shared_ptr<pit::Entry> pitEntry,
+                                shared_ptr<Face> outFace)
+{
+  m_sendInterestHistory.push_back(SendInterestArgs(pitEntry, outFace));
+  pitEntry->insertOrUpdateOutRecord(outFace, pitEntry->getInterest());
+  onAction();
+}
+
+template<typename S>
+inline void
+StrategyTester<S>::rejectPendingInterest(shared_ptr<pit::Entry> pitEntry)
+{
+  m_rejectPendingInterestHistory.push_back(RejectPendingInterestArgs(pitEntry));
+  onAction();
+}
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_FW_STRATEGY_TESTER_HPP
diff --git a/tests/daemon/mgmt/command-validator.cpp b/tests/daemon/mgmt/command-validator.cpp
new file mode 100644
index 0000000..cb8031a
--- /dev/null
+++ b/tests/daemon/mgmt/command-validator.cpp
@@ -0,0 +1,600 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/command-validator.hpp"
+#include "core/config-file.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <ndn-cpp-dev/util/command-interest-generator.hpp>
+#include <ndn-cpp-dev/util/io.hpp>
+#include <boost/filesystem.hpp>
+#include <fstream>
+
+namespace nfd {
+
+namespace tests {
+
+NFD_LOG_INIT("CommandValidatorTest");
+
+BOOST_FIXTURE_TEST_SUITE(MgmtCommandValidator, BaseFixture)
+
+// authorizations
+// {
+//   authorize
+//   {
+//     certfile "tests/daemon/mgmt/cert1.ndncert"
+//     privileges
+//     {
+//       fib
+//       stats
+//     }
+//   }
+
+//   authorize
+//   {
+//     certfile "tests/daemon/mgmt/cert2.ndncert"
+//     privileges
+//     {
+//       faces
+//     }
+//   }
+// }
+
+const std::string CONFIG =
+"authorizations\n"
+"{\n"
+"  authorize\n"
+"  {\n"
+"    certfile \"tests/daemon/mgmt/cert1.ndncert\"\n"
+"    privileges\n"
+"    {\n"
+"      fib\n"
+"      stats\n"
+"    }\n"
+"  }\n"
+"  authorize\n"
+"  {\n"
+"    certfile \"tests/daemon/mgmt/cert2.ndncert\"\n"
+"    privileges\n"
+"    {\n"
+"      faces\n"
+"    }\n"
+"  }\n"
+  "}\n";
+
+const boost::filesystem::path CONFIG_PATH =
+  boost::filesystem::current_path() /= std::string("unit-test-nfd.conf");
+
+class CommandValidatorTester
+{
+public:
+
+  CommandValidatorTester()
+    : m_validated(false),
+      m_validationFailed(false)
+  {
+
+  }
+
+  void
+  generateIdentity(const Name& prefix)
+  {
+    m_identityName = prefix;
+    m_identityName.appendVersion();
+
+    const Name certName = m_keys.createIdentity(m_identityName);
+
+    m_certificate = m_keys.getCertificate(certName);
+  }
+
+  void
+  saveIdentityToFile(const char* filename)
+  {
+    std::ofstream out;
+    out.open(filename);
+
+    BOOST_REQUIRE(out.is_open());
+    BOOST_REQUIRE(static_cast<bool>(m_certificate));
+
+    ndn::io::save<ndn::IdentityCertificate>(*m_certificate, out);
+
+    out.close();
+  }
+
+  const Name&
+  getIdentityName() const
+  {
+    BOOST_REQUIRE_NE(m_identityName, Name());
+    return m_identityName;
+  }
+
+  const Name&
+  getPublicKeyName() const
+  {
+    BOOST_REQUIRE(static_cast<bool>(m_certificate));
+    return m_certificate->getPublicKeyName();
+  }
+
+  void
+  onValidated(const shared_ptr<const Interest>& interest)
+  {
+    // NFD_LOG_DEBUG("validated command");
+    m_validated = true;
+  }
+
+  void
+  onValidationFailed(const shared_ptr<const Interest>& interest, const std::string& info)
+  {
+    NFD_LOG_DEBUG("validation failed: " << info);
+    m_validationFailed = true;
+  }
+
+  bool
+  commandValidated() const
+  {
+    return m_validated;
+  }
+
+  bool
+  commandValidationFailed() const
+  {
+    return m_validationFailed;
+  }
+
+  void
+  resetValidation()
+  {
+    m_validated = false;
+    m_validationFailed = false;
+  }
+
+  ~CommandValidatorTester()
+  {
+    m_keys.deleteIdentity(m_identityName);
+  }
+
+private:
+  bool m_validated;
+  bool m_validationFailed;
+
+  ndn::KeyChain m_keys;
+  Name m_identityName;
+  shared_ptr<ndn::IdentityCertificate> m_certificate;
+};
+
+class TwoValidatorFixture : public BaseFixture
+{
+public:
+  TwoValidatorFixture()
+  {
+    m_tester1.generateIdentity("/test/CommandValidator/TwoKeys/id1");
+    m_tester1.saveIdentityToFile("tests/daemon/mgmt/cert1.ndncert");
+
+    m_tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2");
+    m_tester2.saveIdentityToFile("tests/daemon/mgmt/cert2.ndncert");
+  }
+
+  ~TwoValidatorFixture()
+  {
+    boost::system::error_code error;
+    boost::filesystem::remove("tests/daemon/mgmt/cert1.ndncert", error);
+    boost::filesystem::remove("tests/daemon/mgmt/cert2.ndncert", error);
+  }
+
+protected:
+  CommandValidatorTester m_tester1;
+  CommandValidatorTester m_tester2;
+};
+
+BOOST_FIXTURE_TEST_CASE(TwoKeys, TwoValidatorFixture)
+{
+  shared_ptr<Interest> fibCommand = make_shared<Interest>("/localhost/nfd/fib/insert");
+  shared_ptr<Interest> statsCommand = make_shared<Interest>("/localhost/nfd/stats/dosomething");
+  shared_ptr<Interest> facesCommand = make_shared<Interest>("/localhost/nfd/faces/create");
+
+  ndn::CommandInterestGenerator generator;
+  generator.generateWithIdentity(*fibCommand, m_tester1.getIdentityName());
+  generator.generateWithIdentity(*statsCommand, m_tester1.getIdentityName());
+  generator.generateWithIdentity(*facesCommand, m_tester2.getIdentityName());
+
+  ConfigFile config;
+  CommandValidator validator;
+  validator.addSupportedPrivilege("faces");
+  validator.addSupportedPrivilege("fib");
+  validator.addSupportedPrivilege("stats");
+
+  validator.setConfigFile(config);
+
+  config.parse(CONFIG, false, CONFIG_PATH.native());
+
+  validator.validate(*fibCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2));
+
+  BOOST_REQUIRE(m_tester1.commandValidated());
+  m_tester1.resetValidation();
+
+  validator.validate(*statsCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2));
+
+  BOOST_REQUIRE(m_tester1.commandValidated());
+
+  validator.validate(*facesCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2));
+
+  BOOST_REQUIRE(m_tester2.commandValidated());
+  m_tester2.resetValidation();
+
+  // use cert2 for fib command (authorized for cert1 only)
+  shared_ptr<Interest> unauthorizedFibCommand = make_shared<Interest>("/localhost/nfd/fib/insert");
+  generator.generateWithIdentity(*unauthorizedFibCommand, m_tester2.getIdentityName());
+
+  validator.validate(*unauthorizedFibCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2));
+
+  BOOST_REQUIRE(m_tester2.commandValidationFailed());
+}
+
+BOOST_FIXTURE_TEST_CASE(TwoKeysDryRun, TwoValidatorFixture)
+{
+  CommandValidatorTester tester1;
+  tester1.generateIdentity("/test/CommandValidator/TwoKeys/id1");
+  tester1.saveIdentityToFile("tests/daemon/mgmt/cert1.ndncert");
+
+  CommandValidatorTester tester2;
+  tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2");
+  tester2.saveIdentityToFile("tests/daemon/mgmt/cert2.ndncert");
+
+  shared_ptr<Interest> fibCommand = make_shared<Interest>("/localhost/nfd/fib/insert");
+  shared_ptr<Interest> statsCommand = make_shared<Interest>("/localhost/nfd/stats/dosomething");
+  shared_ptr<Interest> facesCommand = make_shared<Interest>("/localhost/nfd/faces/create");
+
+  ndn::CommandInterestGenerator generator;
+  generator.generateWithIdentity(*fibCommand, m_tester1.getIdentityName());
+  generator.generateWithIdentity(*statsCommand, m_tester1.getIdentityName());
+  generator.generateWithIdentity(*facesCommand, m_tester2.getIdentityName());
+
+  ConfigFile config;
+  CommandValidator validator;
+  validator.addSupportedPrivilege("faces");
+  validator.addSupportedPrivilege("fib");
+  validator.addSupportedPrivilege("stats");
+
+  validator.setConfigFile(config);
+
+  config.parse(CONFIG, true, CONFIG_PATH.native());
+
+  validator.validate(*fibCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2));
+
+  BOOST_REQUIRE(m_tester1.commandValidationFailed());
+  m_tester1.resetValidation();
+
+  validator.validate(*statsCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2));
+
+  BOOST_REQUIRE(m_tester1.commandValidationFailed());
+
+  validator.validate(*facesCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2));
+
+  BOOST_REQUIRE(m_tester2.commandValidationFailed());
+  m_tester2.resetValidation();
+
+  // use cert2 for fib command (authorized for cert1 only)
+  shared_ptr<Interest> unauthorizedFibCommand = make_shared<Interest>("/localhost/nfd/fib/insert");
+  generator.generateWithIdentity(*unauthorizedFibCommand, m_tester2.getIdentityName());
+
+  validator.validate(*unauthorizedFibCommand,
+                     bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1),
+                     bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2));
+
+  BOOST_REQUIRE(m_tester2.commandValidationFailed());
+}
+
+BOOST_AUTO_TEST_CASE(NoAuthorizeSections)
+{
+  const std::string NO_AUTHORIZE_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_THROW(config.parse(NO_AUTHORIZE_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NoPrivilegesSections)
+{
+  const std::string NO_PRIVILEGES_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/cert1.ndncert\"\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+
+  BOOST_CHECK_THROW(config.parse(NO_PRIVILEGES_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidCertfile)
+{
+  const std::string INVALID_CERT_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/notacertfile.ndncert\"\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_THROW(config.parse(INVALID_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NoCertfile)
+{
+  const std::string NO_CERT_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "}\n";
+
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_THROW(config.parse(NO_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCert)
+{
+    const std::string MALFORMED_CERT_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/malformed.ndncert\"\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "}\n";
+
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_THROW(config.parse(MALFORMED_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error);
+}
+
+bool
+validateErrorMessage(const std::string& expectedMessage, const ConfigFile::Error& error)
+{
+  bool gotExpected = error.what() == expectedMessage;
+  if (!gotExpected)
+    {
+      NFD_LOG_WARN("\ncaught exception: " << error.what()
+                    << "\n\nexpected exception: " << expectedMessage);
+    }
+  return gotExpected;
+}
+
+BOOST_AUTO_TEST_CASE(NoAuthorizeSectionsDryRun)
+{
+  const std::string NO_AUTHORIZE_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_EXCEPTION(config.parse(NO_AUTHORIZE_CONFIG, true, CONFIG_PATH.native()),
+                        ConfigFile::Error,
+                        bind(&validateErrorMessage,
+                             "No authorize sections found", _1));
+}
+
+BOOST_FIXTURE_TEST_CASE(NoPrivilegesSectionsDryRun, TwoValidatorFixture)
+{
+  const std::string NO_PRIVILEGES_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/cert1.ndncert\"\n"
+    "  }\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/cert2.ndncert\"\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+
+  std::stringstream expectedError;
+  expectedError << "No privileges section found for certificate file tests/daemon/mgmt/cert1.ndncert "
+                << "(" << m_tester1.getPublicKeyName().toUri() << ")\n"
+                << "No privileges section found for certificate file tests/daemon/mgmt/cert2.ndncert "
+                << "(" << m_tester2.getPublicKeyName().toUri() << ")";
+
+  BOOST_CHECK_EXCEPTION(config.parse(NO_PRIVILEGES_CONFIG, true, CONFIG_PATH.native()),
+                        ConfigFile::Error,
+                        bind(&validateErrorMessage, expectedError.str(), _1));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidCertfileDryRun)
+{
+  using namespace boost::filesystem;
+
+  const std::string INVALID_KEY_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/notacertfile.ndncert\"\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/stillnotacertfile.ndncert\"\n"
+    "    privileges\n"
+    "    {\n"
+    "    }\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+
+  std::stringstream error;
+  error << "Unable to open certificate file "
+        << absolute("tests/daemon/mgmt/notacertfile.ndncert").native() << "\n"
+        << "Unable to open certificate file "
+        << absolute("tests/daemon/mgmt/stillnotacertfile.ndncert").native();
+
+  BOOST_CHECK_EXCEPTION(config.parse(INVALID_KEY_CONFIG, true, CONFIG_PATH.native()),
+                        ConfigFile::Error,
+                        bind(&validateErrorMessage, error.str(), _1));
+}
+
+BOOST_AUTO_TEST_CASE(NoCertfileDryRun)
+{
+  const std::string NO_CERT_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "  authorize\n"
+    "  {\n"
+    "  }\n"
+    "}\n";
+
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+  BOOST_CHECK_EXCEPTION(config.parse(NO_CERT_CONFIG, true, CONFIG_PATH.native()),
+                        ConfigFile::Error,
+                        bind(&validateErrorMessage,
+                             "No certfile specified\n"
+                             "No certfile specified", _1));
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCertDryRun)
+{
+  using namespace boost::filesystem;
+
+  const std::string MALFORMED_CERT_CONFIG =
+    "authorizations\n"
+    "{\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/malformed.ndncert\"\n"
+    "    privileges\n"
+    "    {\n"
+    "      fib\n"
+    "      stats\n"
+    "    }\n"
+    "  }\n"
+    "  authorize\n"
+    "  {\n"
+    "    certfile \"tests/daemon/mgmt/malformed.ndncert\"\n"
+    "  }\n"
+    "}\n";
+
+
+  ConfigFile config;
+  CommandValidator validator;
+
+  validator.setConfigFile(config);
+
+  std::stringstream error;
+  error << "Malformed certificate file "
+        << absolute("tests/daemon/mgmt/malformed.ndncert").native() << "\n"
+        << "Malformed certificate file "
+        << absolute("tests/daemon/mgmt/malformed.ndncert").native();
+
+  BOOST_CHECK_EXCEPTION(config.parse(MALFORMED_CERT_CONFIG, true, CONFIG_PATH.native()),
+                        ConfigFile::Error,
+                        bind(&validateErrorMessage, error.str(), _1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+
+} // namespace nfd
diff --git a/tests/daemon/mgmt/face-flags.cpp b/tests/daemon/mgmt/face-flags.cpp
new file mode 100644
index 0000000..0aa6c09
--- /dev/null
+++ b/tests/daemon/mgmt/face-flags.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/face-flags.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFaceFlags, BaseFixture)
+
+template<typename DummyFaceBase>
+class DummyOnDemandFace : public DummyFaceBase
+{
+public:
+  DummyOnDemandFace()
+  {
+    this->setOnDemand(true);
+  }
+};
+
+BOOST_AUTO_TEST_CASE(Get)
+{
+  DummyFace face1;
+  BOOST_CHECK_EQUAL(getFaceFlags(face1), 0);
+
+  DummyLocalFace face2;
+  BOOST_CHECK_EQUAL(getFaceFlags(face2), static_cast<uint64_t>(ndn::nfd::FACE_IS_LOCAL));
+
+  DummyOnDemandFace<DummyFace> face3;
+  BOOST_CHECK_EQUAL(getFaceFlags(face3), static_cast<uint64_t>(ndn::nfd::FACE_IS_ON_DEMAND));
+
+  DummyOnDemandFace<DummyLocalFace> face4;
+  BOOST_CHECK_EQUAL(getFaceFlags(face4), static_cast<uint64_t>(ndn::nfd::FACE_IS_LOCAL |
+                                         ndn::nfd::FACE_IS_ON_DEMAND));
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/face-manager.cpp b/tests/daemon/mgmt/face-manager.cpp
new file mode 100644
index 0000000..e799944
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager.cpp
@@ -0,0 +1,1666 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/face-manager.hpp"
+#include "mgmt/internal-face.hpp"
+#include "mgmt/face-status-publisher.hpp"
+
+#include "face/face.hpp"
+#include "../face/dummy-face.hpp"
+#include "fw/face-table.hpp"
+#include "fw/forwarder.hpp"
+
+#include "common.hpp"
+#include "tests/test-common.hpp"
+#include "validation-common.hpp"
+#include "face-status-publisher-common.hpp"
+
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+#include <ndn-cpp-dev/management/nfd-face-event-notification.hpp>
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("FaceManagerTest");
+
+class FaceManagerTestFace : public DummyFace
+{
+public:
+
+  FaceManagerTestFace()
+    : m_closeFired(false)
+  {
+
+  }
+
+  virtual
+  ~FaceManagerTestFace()
+  {
+
+  }
+
+  virtual void
+  close()
+  {
+    m_closeFired = true;
+  }
+
+  bool
+  didCloseFire() const
+  {
+    return m_closeFired;
+  }
+
+private:
+  bool m_closeFired;
+};
+
+class TestFaceTable : public FaceTable
+{
+public:
+  TestFaceTable(Forwarder& forwarder)
+    : FaceTable(forwarder),
+      m_addFired(false),
+      m_getFired(false),
+      m_dummy(make_shared<FaceManagerTestFace>())
+  {
+
+  }
+
+  virtual
+  ~TestFaceTable()
+  {
+
+  }
+
+  virtual void
+  add(shared_ptr<Face> face)
+  {
+    m_addFired = true;
+  }
+
+  virtual shared_ptr<Face>
+  get(FaceId id) const
+  {
+    m_getFired = true;
+    return m_dummy;
+  }
+
+  bool
+  didAddFire() const
+  {
+    return m_addFired;
+  }
+
+  bool
+  didGetFire() const
+  {
+    return m_getFired;
+  }
+
+  void
+  reset()
+  {
+    m_addFired = false;
+    m_getFired = false;
+  }
+
+  shared_ptr<FaceManagerTestFace>&
+  getDummyFace()
+  {
+    return m_dummy;
+  }
+
+private:
+  bool m_addFired;
+  mutable bool m_getFired;
+  shared_ptr<FaceManagerTestFace> m_dummy;
+};
+
+
+class TestFaceTableFixture : public BaseFixture
+{
+public:
+  TestFaceTableFixture()
+    : m_faceTable(m_forwarder)
+  {
+
+  }
+
+  virtual
+  ~TestFaceTableFixture()
+  {
+
+  }
+
+protected:
+  Forwarder m_forwarder;
+  TestFaceTable m_faceTable;
+};
+
+class TestFaceManagerCommon
+{
+public:
+  TestFaceManagerCommon()
+    : m_face(make_shared<InternalFace>()),
+      m_callbackFired(false)
+  {
+
+  }
+
+  virtual
+  ~TestFaceManagerCommon()
+  {
+
+  }
+
+  shared_ptr<InternalFace>&
+  getFace()
+  {
+    return m_face;
+  }
+
+  void
+  validateControlResponseCommon(const Data& response,
+                                const Name& expectedName,
+                                uint32_t expectedCode,
+                                const std::string& expectedText,
+                                ControlResponse& control)
+  {
+    m_callbackFired = true;
+    Block controlRaw = response.getContent().blockFromValue();
+
+    control.wireDecode(controlRaw);
+
+    // NFD_LOG_DEBUG("received control response"
+    //               << " Name: " << response.getName()
+    //               << " code: " << control.getCode()
+    //               << " text: " << control.getText());
+
+    BOOST_CHECK_EQUAL(response.getName(), expectedName);
+    BOOST_CHECK_EQUAL(control.getCode(), expectedCode);
+    BOOST_CHECK_EQUAL(control.getText(), expectedText);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    if (!control.getBody().empty())
+      {
+        BOOST_FAIL("found unexpected control response body");
+      }
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText,
+                          const Block& expectedBody)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    BOOST_REQUIRE(!control.getBody().empty());
+    BOOST_REQUIRE(control.getBody().value_size() == expectedBody.value_size());
+
+    BOOST_CHECK(memcmp(control.getBody().value(), expectedBody.value(),
+                       expectedBody.value_size()) == 0);
+
+  }
+
+  bool
+  didCallbackFire() const
+  {
+    return m_callbackFired;
+  }
+
+  void
+  resetCallbackFired()
+  {
+    m_callbackFired = false;
+  }
+
+protected:
+  shared_ptr<InternalFace> m_face;
+
+private:
+  bool m_callbackFired;
+};
+
+class FaceManagerFixture : public TestFaceTableFixture, public TestFaceManagerCommon
+{
+public:
+  FaceManagerFixture()
+    : m_manager(m_faceTable, m_face)
+  {
+    m_manager.setConfigFile(m_config);
+  }
+
+  virtual
+  ~FaceManagerFixture()
+  {
+
+  }
+
+  void
+  parseConfig(const std::string configuration, bool isDryRun)
+  {
+    m_config.parse(configuration, isDryRun, "dummy-config");
+  }
+
+  FaceManager&
+  getManager()
+  {
+    return m_manager;
+  }
+
+  void
+  addInterestRule(const std::string& regex,
+                  ndn::IdentityCertificate& certificate)
+  {
+    m_manager.addInterestRule(regex, certificate);
+  }
+
+  bool
+  didFaceTableAddFire() const
+  {
+    return m_faceTable.didAddFire();
+  }
+
+  bool
+  didFaceTableGetFire() const
+  {
+    return m_faceTable.didGetFire();
+  }
+
+  void
+  resetFaceTable()
+  {
+    m_faceTable.reset();
+  }
+
+private:
+  FaceManager m_manager;
+  ConfigFile m_config;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFaceManager, FaceManagerFixture)
+
+bool
+isExpectedException(const ConfigFile::Error& error, const std::string& expectedMessage)
+{
+  if (error.what() != expectedMessage)
+    {
+      NFD_LOG_ERROR("expected: " << expectedMessage << "\tgot: " << error.what());
+    }
+  return error.what() == expectedMessage;
+}
+
+#ifdef HAVE_UNIX_SOCKETS
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUnix)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    listen yes\n"
+    "    path /tmp/nfd.sock\n"
+    "  }\n"
+    "}\n";
+  BOOST_TEST_CHECKPOINT("Calling parse");
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUnixDryRun)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    listen yes\n"
+    "    path /var/run/nfd.sock\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUnixBadListen)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    listen hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"listen\" in \"unix\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUnixUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Unrecognized option \"hello\" in \"unix\" section"));
+}
+
+#endif // HAVE_UNIX_SOCKETS
+
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcp)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    listen yes\n"
+    "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
+    "  }\n"
+    "}\n";
+  try
+    {
+      parseConfig(CONFIG, false);
+    }
+  catch (const std::runtime_error& e)
+    {
+      const std::string reason = e.what();
+      if (reason.find("Address in use") != std::string::npos)
+        {
+          BOOST_FAIL(reason);
+        }
+    }
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcpDryRun)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    listen yes\n"
+    "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcpBadListen)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    listen hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"listen\" in \"tcp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcpChannelsDisabled)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 no\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 and IPv6 channels have been disabled."
+                             " Remove \"tcp\" section to disable TCP channels or"
+                             " re-enable at least one channel type."));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcpUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Unrecognized option \"hello\" in \"tcp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdp)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpDryRun)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpBadIdleTimeout)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    idle_timeout hello\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"idle_timeout\" in \"udp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpBadMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast hello\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"mcast\" in \"udp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpBadMcastGroup)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast no\n"
+    "    mcast_port 50\n"
+    "    mcast_group hello\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"mcast_group\" in \"udp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpBadMcastGroupV6)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast no\n"
+    "    mcast_port 50\n"
+    "    mcast_group ::1\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"mcast_group\" in \"udp\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpChannelsDisabled)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 no\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 and IPv6 channels have been disabled."
+                             " Remove \"udp\" section to disable UDP channels or"
+                             " re-enable at least one channel type."));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpConflictingMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 yes\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 multicast requested, but IPv4 channels"
+                             " have been disabled (conflicting configuration options set)"));
+}
+
+
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Unrecognized option \"hello\" in \"udp\" section"));
+}
+
+#ifdef HAVE_LIBPCAP
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionEther)
+{
+
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast yes\n"
+    "    mcast_group 01:00:5E:00:17:AA\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionEtherDryRun)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast yes\n"
+    "    mcast_group 01:00:5E:00:17:AA\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionEtherBadMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast hello\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"mcast\" in \"ether\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionEtherBadMcastGroup)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast yes\n"
+    "    mcast_group\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Invalid value for option \"mcast_group\" in \"ether\" section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionEtherUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "Unrecognized option \"hello\" in \"ether\" section"));
+}
+
+#endif
+
+BOOST_AUTO_TEST_CASE(TestFireInterestFilter)
+{
+  shared_ptr<Interest> command(make_shared<Interest>("/localhost/nfd/faces"));
+
+  getFace()->onReceiveData +=
+    bind(&FaceManagerFixture::validateControlResponse, this,  _1,
+         command->getName(), 400, "Malformed command");
+
+  getFace()->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCommmand)
+{
+  shared_ptr<Interest> command(make_shared<Interest>("/localhost/nfd/faces"));
+
+  getFace()->onReceiveData +=
+    bind(&FaceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onFaceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(UnsignedCommand)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  getFace()->onReceiveData +=
+    bind(&FaceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 401, "Signature required");
+
+  getManager().onFaceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, UnauthorizedCommandFixture<FaceManagerFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 403, "Unauthorized command");
+
+  getManager().onFaceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+template <typename T> class AuthorizedCommandFixture : public CommandFixture<T>
+{
+public:
+  AuthorizedCommandFixture()
+  {
+    const std::string regex = "^<localhost><nfd><faces>";
+    T::addInterestRule(regex, *CommandFixture<T>::m_certificate);
+  }
+
+  virtual
+  ~AuthorizedCommandFixture()
+  {
+
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(UnsupportedCommand, AuthorizedCommandFixture<FaceManagerFixture>)
+{
+  ControlParameters parameters;
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("unsupported");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 501, "Unsupported command");
+
+  getManager().onFaceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+class ValidatedFaceRequestFixture : public TestFaceTableFixture,
+                                    public TestFaceManagerCommon,
+                                    public FaceManager
+{
+public:
+
+  ValidatedFaceRequestFixture()
+    : FaceManager(TestFaceTableFixture::m_faceTable, TestFaceManagerCommon::m_face),
+      m_createFaceFired(false),
+      m_destroyFaceFired(false)
+  {
+
+  }
+
+  virtual void
+  createFace(const Interest& request,
+             ControlParameters& parameters)
+  {
+    m_createFaceFired = true;
+  }
+
+  virtual void
+  destroyFace(const Interest& request,
+              ControlParameters& parameters)
+  {
+    m_destroyFaceFired = true;
+  }
+
+  virtual
+  ~ValidatedFaceRequestFixture()
+  {
+
+  }
+
+  bool
+  didCreateFaceFire() const
+  {
+    return m_createFaceFired;
+  }
+
+  bool
+  didDestroyFaceFire() const
+  {
+    return m_destroyFaceFired;
+  }
+
+private:
+  bool m_createFaceFired;
+  bool m_destroyFaceFired;
+};
+
+BOOST_FIXTURE_TEST_CASE(ValidatedFaceRequestBadOptionParse,
+                        AuthorizedCommandFixture<ValidatedFaceRequestFixture>)
+{
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append("NotReallyParameters");
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&ValidatedFaceRequestFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  onValidatedFaceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(ValidatedFaceRequestCreateFace,
+                        AuthorizedCommandFixture<ValidatedFaceRequestFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  onValidatedFaceRequest(command);
+  BOOST_CHECK(didCreateFaceFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(ValidatedFaceRequestDestroyFace,
+                        AuthorizedCommandFixture<ValidatedFaceRequestFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("destroy");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  onValidatedFaceRequest(command);
+  BOOST_CHECK(didDestroyFaceFire());
+}
+
+class FaceTableFixture
+{
+public:
+  FaceTableFixture()
+    : m_faceTable(m_forwarder)
+  {
+  }
+
+  virtual
+  ~FaceTableFixture()
+  {
+  }
+
+protected:
+  Forwarder m_forwarder;
+  FaceTable m_faceTable;
+};
+
+class LocalControlFixture : public FaceTableFixture,
+                            public TestFaceManagerCommon,
+                            public FaceManager
+{
+public:
+  LocalControlFixture()
+    : FaceManager(FaceTableFixture::m_faceTable, TestFaceManagerCommon::m_face)
+  {
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(LocalControlInFaceId,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
+  BOOST_REQUIRE(dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 200, "Success", encodedParameters);
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 200, "Success", encodedParameters);
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlInFaceIdFaceNotFound,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
+  BOOST_REQUIRE(dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId() + 100);
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 410, "Face not found");
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId() + 100);
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 410, "Face not found");
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlMissingFeature,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
+  BOOST_REQUIRE(dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 400, "Malformed command");
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 400, "Malformed command");
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlInFaceIdNonLocal,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<DummyFace> dummy = make_shared<DummyFace>();
+  BOOST_REQUIRE(!dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 412, "Face is non-local");
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(enable));
+  disableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 412, "Face is non-local");
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlNextHopFaceId,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
+  BOOST_REQUIRE(dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 200, "Success", encodedParameters);
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 200, "Success", encodedParameters);
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlNextHopFaceIdFaceNotFound,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
+  BOOST_REQUIRE(dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId() + 100);
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 410, "Face not found");
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId() + 100);
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 410, "Face not found");
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+  BOOST_CHECK(!dummy->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+}
+
+BOOST_FIXTURE_TEST_CASE(LocalControlNextHopFaceIdNonLocal,
+                        AuthorizedCommandFixture<LocalControlFixture>)
+{
+  shared_ptr<DummyFace> dummy = make_shared<DummyFace>();
+  BOOST_REQUIRE(!dummy->isLocal());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name enable("/localhost/nfd/faces/enable-local-control");
+  enable.append(encodedParameters);
+
+  shared_ptr<Interest> enableCommand(make_shared<Interest>(enable));
+  enableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*enableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         enableCommand->getName(), 412, "Face is non-local");
+
+  onValidatedFaceRequest(enableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  TestFaceManagerCommon::m_face->onReceiveData.clear();
+  resetCallbackFired();
+
+  Name disable("/localhost/nfd/faces/disable-local-control");
+  disable.append(encodedParameters);
+
+  shared_ptr<Interest> disableCommand(make_shared<Interest>(disable));
+  disableCommand->setIncomingFaceId(dummy->getId());
+
+  generateCommand(*disableCommand);
+
+  TestFaceManagerCommon::m_face->onReceiveData +=
+    bind(&LocalControlFixture::validateControlResponse, this, _1,
+         disableCommand->getName(), 412, "Face is non-local");
+
+  onValidatedFaceRequest(disableCommand);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+class FaceFixture : public FaceTableFixture,
+                    public TestFaceManagerCommon,
+                    public FaceManager
+{
+public:
+  FaceFixture()
+    : FaceManager(FaceTableFixture::m_faceTable,
+                  TestFaceManagerCommon::m_face)
+    , m_receivedNotification(false)
+  {
+
+  }
+
+  virtual
+  ~FaceFixture()
+  {
+
+  }
+
+  void
+  callbackDispatch(const Data& response,
+                   const Name& expectedName,
+                   uint32_t expectedCode,
+                   const std::string& expectedText,
+                   const Block& expectedBody,
+                   const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+    Block payload = response.getContent().blockFromValue();
+    if (payload.type() == ndn::tlv::nfd::ControlResponse)
+      {
+        validateControlResponse(response, expectedName, expectedCode,
+                                expectedText, expectedBody);
+      }
+    else if (payload.type() == ndn::tlv::nfd::FaceEventNotification)
+      {
+        validateFaceEvent(payload, expectedFaceEvent);
+      }
+    else
+      {
+        BOOST_FAIL("Received unknown message type: #" << payload.type());
+      }
+  }
+
+  void
+  callbackDispatch(const Data& response,
+                   const Name& expectedName,
+                   uint32_t expectedCode,
+                   const std::string& expectedText,
+                   const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+    Block payload = response.getContent().blockFromValue();
+    if (payload.type() == ndn::tlv::nfd::ControlResponse)
+      {
+        validateControlResponse(response, expectedName,
+                                expectedCode, expectedText);
+      }
+    else if (payload.type() == ndn::tlv::nfd::FaceEventNotification)
+      {
+        validateFaceEvent(payload, expectedFaceEvent);
+      }
+    else
+      {
+        BOOST_FAIL("Received unknown message type: #" << payload.type());
+      }
+  }
+
+  void
+  validateFaceEvent(const Block& wire,
+                    const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+
+    m_receivedNotification = true;
+
+    ndn::nfd::FaceEventNotification notification(wire);
+
+    BOOST_CHECK_EQUAL(notification.getKind(), expectedFaceEvent.getKind());
+    BOOST_CHECK_EQUAL(notification.getFaceId(), expectedFaceEvent.getFaceId());
+    BOOST_CHECK_EQUAL(notification.getRemoteUri(), expectedFaceEvent.getRemoteUri());
+    BOOST_CHECK_EQUAL(notification.getLocalUri(), expectedFaceEvent.getLocalUri());
+  }
+
+  bool
+  didReceiveNotication() const
+  {
+    return m_receivedNotification;
+  }
+
+protected:
+  bool m_receivedNotification;
+};
+
+BOOST_FIXTURE_TEST_CASE(CreateFaceBadUri, AuthorizedCommandFixture<FaceFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp:/127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  createFace(*command, parameters);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(CreateFaceMissingUri, AuthorizedCommandFixture<FaceFixture>)
+{
+  ControlParameters parameters;
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  createFace(*command, parameters);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(CreateFaceUnknownScheme, AuthorizedCommandFixture<FaceFixture>)
+{
+  ControlParameters parameters;
+  // this will be an unsupported protocol because no factories have been
+  // added to the face manager
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::validateControlResponse, this, _1,
+         command->getName(), 501, "Unsupported protocol");
+
+  createFace(*command, parameters);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(OnCreated, AuthorizedCommandFixture<FaceFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  ControlParameters resultParameters;
+  resultParameters.setUri("tcp://127.0.0.1");
+  resultParameters.setFaceId(1);
+
+  shared_ptr<DummyFace> dummy(make_shared<DummyFace>());
+
+  ndn::nfd::FaceEventNotification expectedFaceEvent;
+  expectedFaceEvent.setKind(ndn::nfd::FACE_EVENT_CREATED)
+                   .setFaceId(1)
+                   .setRemoteUri(dummy->getRemoteUri().toString())
+                   .setLocalUri(dummy->getLocalUri().toString())
+                   .setFlags(0);
+
+  Block encodedResultParameters(resultParameters.wireEncode());
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::callbackDispatch, this, _1,
+         command->getName(), 200, "Success",
+         encodedResultParameters, expectedFaceEvent);
+
+  onCreated(command->getName(), parameters, dummy);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(didReceiveNotication());
+}
+
+BOOST_FIXTURE_TEST_CASE(OnConnectFailed, AuthorizedCommandFixture<FaceFixture>)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://127.0.0.1");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::validateControlResponse, this, _1,
+         command->getName(), 408, "unit-test-reason");
+
+  onConnectFailed(command->getName(), "unit-test-reason");
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_CHECK_EQUAL(didReceiveNotication(), false);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(DestroyFace, AuthorizedCommandFixture<FaceFixture>)
+{
+  shared_ptr<DummyFace> dummy(make_shared<DummyFace>());
+  FaceTableFixture::m_faceTable.add(dummy);
+
+  ControlParameters parameters;
+  parameters.setFaceId(dummy->getId());
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("destroy");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  ndn::nfd::FaceEventNotification expectedFaceEvent;
+  expectedFaceEvent.setKind(ndn::nfd::FACE_EVENT_DESTROYED)
+                   .setFaceId(dummy->getId())
+                   .setRemoteUri(dummy->getRemoteUri().toString())
+                   .setLocalUri(dummy->getLocalUri().toString())
+                   .setFlags(0);
+
+  getFace()->onReceiveData +=
+    bind(&FaceFixture::callbackDispatch, this, _1,
+         command->getName(), 200, "Success", boost::ref(encodedParameters), expectedFaceEvent);
+
+  destroyFace(*command, parameters);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(didReceiveNotication());
+}
+
+class FaceListFixture : public FaceStatusPublisherFixture
+{
+public:
+  FaceListFixture()
+    : m_manager(m_table, m_face)
+  {
+
+  }
+
+  virtual
+  ~FaceListFixture()
+  {
+
+  }
+
+protected:
+  FaceManager m_manager;
+};
+
+BOOST_FIXTURE_TEST_CASE(TestFaceList, FaceListFixture)
+
+{
+  Name commandName("/localhost/nfd/faces/list");
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  // MAX_SEGMENT_SIZE == 4400, FaceStatus size with filler counters is 55
+  // 55 divides 4400 (== 80), so only use 79 FaceStatuses and then two smaller ones
+  // to force a FaceStatus to span Data packets
+  for (int i = 0; i < 79; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+
+      uint64_t filler = std::numeric_limits<uint64_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_back(dummy);
+
+      add(dummy);
+    }
+
+  for (int i = 0; i < 2; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+      uint64_t filler = std::numeric_limits<uint32_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_back(dummy);
+
+      add(dummy);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FaceStatusPublisherFixture::decodeFaceStatusBlock, this, _1);
+
+  m_manager.listFaces(*command);
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/face-status-publisher-common.hpp b/tests/daemon/mgmt/face-status-publisher-common.hpp
new file mode 100644
index 0000000..11ed191
--- /dev/null
+++ b/tests/daemon/mgmt/face-status-publisher-common.hpp
@@ -0,0 +1,192 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
+#define NFD_TESTS_NFD_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
+
+#include "mgmt/face-status-publisher.hpp"
+#include "mgmt/app-face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "mgmt/face-flags.hpp"
+#include "fw/forwarder.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include <ndn-cpp-dev/management/nfd-face-status.hpp>
+
+namespace nfd {
+namespace tests {
+
+class TestCountersFace : public DummyFace
+{
+public:
+
+  TestCountersFace()
+  {
+  }
+
+  virtual
+  ~TestCountersFace()
+  {
+  }
+
+  void
+  setCounters(FaceCounter nInInterests,
+              FaceCounter nInDatas,
+              FaceCounter nOutInterests,
+              FaceCounter nOutDatas)
+  {
+    FaceCounters& counters = getMutableCounters();
+    counters.getNInInterests()  = nInInterests;
+    counters.getNInDatas()      = nInDatas;
+    counters.getNOutInterests() = nOutInterests;
+    counters.getNOutDatas()     = nOutDatas;
+  }
+
+
+};
+
+static inline uint64_t
+readNonNegativeIntegerType(const Block& block,
+                           uint32_t type)
+{
+  if (block.type() == type)
+    {
+      return readNonNegativeInteger(block);
+    }
+  std::stringstream error;
+  error << "expected type " << type << " got " << block.type();
+  throw ndn::Tlv::Error(error.str());
+}
+
+static inline uint64_t
+checkedReadNonNegativeIntegerType(Block::element_const_iterator& i,
+                                  Block::element_const_iterator end,
+                                  uint32_t type)
+{
+  if (i != end)
+    {
+      const Block& block = *i;
+      ++i;
+      return readNonNegativeIntegerType(block, type);
+    }
+  throw ndn::Tlv::Error("Unexpected end of FaceStatus");
+}
+
+class FaceStatusPublisherFixture : public BaseFixture
+{
+public:
+
+  FaceStatusPublisherFixture()
+    : m_table(m_forwarder)
+    , m_face(make_shared<InternalFace>())
+    , m_publisher(m_table, m_face, "/localhost/nfd/FaceStatusPublisherFixture")
+    , m_finished(false)
+  {
+
+  }
+
+  virtual
+  ~FaceStatusPublisherFixture()
+  {
+
+  }
+
+  void
+  add(shared_ptr<Face> face)
+  {
+    m_table.add(face);
+  }
+
+  void
+  validateFaceStatus(const Block& statusBlock, const shared_ptr<Face>& reference)
+  {
+    ndn::nfd::FaceStatus status;
+    BOOST_REQUIRE_NO_THROW(status.wireDecode(statusBlock));
+    const FaceCounters& counters = reference->getCounters();
+
+    BOOST_CHECK_EQUAL(status.getFaceId(), reference->getId());
+    BOOST_CHECK_EQUAL(status.getRemoteUri(), reference->getRemoteUri().toString());
+    BOOST_CHECK_EQUAL(status.getLocalUri(), reference->getLocalUri().toString());
+    BOOST_CHECK_EQUAL(status.getFlags(), getFaceFlags(*reference));
+    BOOST_CHECK_EQUAL(status.getNInInterests(), counters.getNInInterests());
+    BOOST_CHECK_EQUAL(status.getNInDatas(), counters.getNInDatas());
+    BOOST_CHECK_EQUAL(status.getNOutInterests(), counters.getNOutInterests());
+    BOOST_CHECK_EQUAL(status.getNOutDatas(), counters.getNOutDatas());
+  }
+
+  void
+  decodeFaceStatusBlock(const Data& data)
+  {
+    Block payload = data.getContent();
+
+    m_buffer.appendByteArray(payload.value(), payload.value_size());
+
+    BOOST_CHECK_NO_THROW(data.getName()[-1].toSegment());
+    if (data.getFinalBlockId() != data.getName()[-1])
+      {
+        return;
+      }
+
+    // wrap the Face Statuses in a single Content TLV for easy parsing
+    m_buffer.prependVarNumber(m_buffer.size());
+    m_buffer.prependVarNumber(ndn::Tlv::Content);
+
+    ndn::Block parser(m_buffer.buf(), m_buffer.size());
+    parser.parse();
+
+    BOOST_REQUIRE_EQUAL(parser.elements_size(), m_referenceFaces.size());
+
+    std::list<shared_ptr<Face> >::const_iterator iReference = m_referenceFaces.begin();
+    for (Block::element_const_iterator i = parser.elements_begin();
+         i != parser.elements_end();
+         ++i)
+      {
+        if (i->type() != ndn::tlv::nfd::FaceStatus)
+          {
+            BOOST_FAIL("expected face status, got type #" << i->type());
+          }
+        validateFaceStatus(*i, *iReference);
+        ++iReference;
+      }
+    m_finished = true;
+  }
+
+protected:
+  Forwarder m_forwarder;
+  FaceTable m_table;
+  shared_ptr<InternalFace> m_face;
+  FaceStatusPublisher m_publisher;
+  ndn::EncodingBuffer m_buffer;
+  std::list<shared_ptr<Face> > m_referenceFaces;
+
+protected:
+  bool m_finished;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
diff --git a/tests/daemon/mgmt/face-status-publisher.cpp b/tests/daemon/mgmt/face-status-publisher.cpp
new file mode 100644
index 0000000..836ea2d
--- /dev/null
+++ b/tests/daemon/mgmt/face-status-publisher.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "face-status-publisher-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("FaceStatusPublisherTest");
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFaceSatusPublisher, FaceStatusPublisherFixture)
+
+BOOST_AUTO_TEST_CASE(EncodingDecoding)
+{
+  Name commandName("/localhost/nfd/faces/list");
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  // MAX_SEGMENT_SIZE == 4400, FaceStatus size with filler counters is 55
+  // 55 divides 4400 (== 80), so only use 79 FaceStatuses and then two smaller ones
+  // to force a FaceStatus to span Data packets
+  for (int i = 0; i < 79; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+
+      uint64_t filler = std::numeric_limits<uint64_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_back(dummy);
+
+      add(dummy);
+    }
+
+  for (int i = 0; i < 2; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+      uint64_t filler = std::numeric_limits<uint32_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_back(dummy);
+
+      add(dummy);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FaceStatusPublisherFixture::decodeFaceStatusBlock, this, _1);
+
+  m_publisher.publish();
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/fib-enumeration-publisher-common.hpp b/tests/daemon/mgmt/fib-enumeration-publisher-common.hpp
new file mode 100644
index 0000000..6befe54
--- /dev/null
+++ b/tests/daemon/mgmt/fib-enumeration-publisher-common.hpp
@@ -0,0 +1,219 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_MGMT_FIB_ENUMERATION_PUBLISHER_COMMON_HPP
+#define NFD_TESTS_NFD_MGMT_FIB_ENUMERATION_PUBLISHER_COMMON_HPP
+
+#include "mgmt/fib-enumeration-publisher.hpp"
+
+#include "mgmt/app-face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "table/fib.hpp"
+#include "table/name-tree.hpp"
+
+#include "tests/test-common.hpp"
+#include "../face/dummy-face.hpp"
+
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+
+namespace nfd {
+namespace tests {
+
+static inline uint64_t
+readNonNegativeIntegerType(const Block& block,
+                           uint32_t type)
+{
+  if (block.type() == type)
+    {
+      return readNonNegativeInteger(block);
+    }
+  std::stringstream error;
+  error << "Expected type " << type << " got " << block.type();
+  throw ndn::Tlv::Error(error.str());
+}
+
+static inline uint64_t
+checkedReadNonNegativeIntegerType(Block::element_const_iterator& i,
+                                  Block::element_const_iterator end,
+                                  uint32_t type)
+{
+  if (i != end)
+    {
+      const Block& block = *i;
+      ++i;
+      return readNonNegativeIntegerType(block, type);
+    }
+  std::stringstream error;
+  error << "Unexpected end of Block while attempting to read type #"
+        << type;
+  throw ndn::Tlv::Error(error.str());
+}
+
+class FibEnumerationPublisherFixture : public BaseFixture
+{
+public:
+
+  FibEnumerationPublisherFixture()
+    : m_fib(m_nameTree)
+    , m_face(make_shared<InternalFace>())
+    , m_publisher(m_fib, m_face, "/localhost/nfd/FibEnumerationPublisherFixture")
+    , m_finished(false)
+  {
+  }
+
+  virtual
+  ~FibEnumerationPublisherFixture()
+  {
+  }
+
+  bool
+  hasNextHopWithCost(const fib::NextHopList& nextHops,
+                     FaceId faceId,
+                     uint64_t cost)
+  {
+    for (fib::NextHopList::const_iterator i = nextHops.begin();
+         i != nextHops.end();
+         ++i)
+      {
+        if (i->getFace()->getId() == faceId && i->getCost() == cost)
+          {
+            return true;
+          }
+      }
+    return false;
+  }
+
+  bool
+  entryHasPrefix(const shared_ptr<fib::Entry> entry, const Name& prefix)
+  {
+    return entry->getPrefix() == prefix;
+  }
+
+  void
+  validateFibEntry(const Block& entry)
+  {
+    entry.parse();
+
+    Block::element_const_iterator i = entry.elements_begin();
+    BOOST_REQUIRE(i != entry.elements_end());
+
+
+    BOOST_REQUIRE(i->type() == ndn::Tlv::Name);
+    Name prefix(*i);
+    ++i;
+
+    std::set<shared_ptr<fib::Entry> >::const_iterator referenceIter =
+      std::find_if(m_referenceEntries.begin(), m_referenceEntries.end(),
+                   boost::bind(&FibEnumerationPublisherFixture::entryHasPrefix,
+                               this, _1, prefix));
+
+    BOOST_REQUIRE(referenceIter != m_referenceEntries.end());
+
+    const shared_ptr<fib::Entry>& reference = *referenceIter;
+    BOOST_REQUIRE_EQUAL(prefix, reference->getPrefix());
+
+    // 0 or more next hop records
+    size_t nRecords = 0;
+    const fib::NextHopList& referenceNextHops = reference->getNextHops();
+    for (; i != entry.elements_end(); ++i)
+      {
+        const ndn::Block& nextHopRecord = *i;
+        BOOST_REQUIRE(nextHopRecord.type() == ndn::tlv::nfd::NextHopRecord);
+        nextHopRecord.parse();
+
+        Block::element_const_iterator j = nextHopRecord.elements_begin();
+
+        FaceId faceId =
+          checkedReadNonNegativeIntegerType(j,
+                                            entry.elements_end(),
+                                            ndn::tlv::nfd::FaceId);
+
+        uint64_t cost =
+          checkedReadNonNegativeIntegerType(j,
+                                            entry.elements_end(),
+                                            ndn::tlv::nfd::Cost);
+
+        BOOST_REQUIRE(hasNextHopWithCost(referenceNextHops, faceId, cost));
+
+        BOOST_REQUIRE(j == nextHopRecord.elements_end());
+        nRecords++;
+      }
+    BOOST_REQUIRE_EQUAL(nRecords, referenceNextHops.size());
+
+    BOOST_REQUIRE(i == entry.elements_end());
+    m_referenceEntries.erase(referenceIter);
+  }
+
+  void
+  decodeFibEntryBlock(const Data& data)
+  {
+    Block payload = data.getContent();
+
+    m_buffer.appendByteArray(payload.value(), payload.value_size());
+
+    BOOST_CHECK_NO_THROW(data.getName()[-1].toSegment());
+    if (data.getFinalBlockId() != data.getName()[-1])
+      {
+        return;
+      }
+
+    // wrap the FIB Entry blocks in a single Content TLV for easy parsing
+    m_buffer.prependVarNumber(m_buffer.size());
+    m_buffer.prependVarNumber(ndn::Tlv::Content);
+
+    ndn::Block parser(m_buffer.buf(), m_buffer.size());
+    parser.parse();
+
+    BOOST_REQUIRE_EQUAL(parser.elements_size(), m_referenceEntries.size());
+
+    for (Block::element_const_iterator i = parser.elements_begin();
+         i != parser.elements_end();
+         ++i)
+      {
+        if (i->type() != ndn::tlv::nfd::FibEntry)
+          {
+            BOOST_FAIL("expected fib entry, got type #" << i->type());
+          }
+
+        validateFibEntry(*i);
+      }
+    m_finished = true;
+  }
+
+protected:
+  NameTree m_nameTree;
+  Fib m_fib;
+  shared_ptr<InternalFace> m_face;
+  FibEnumerationPublisher m_publisher;
+  ndn::EncodingBuffer m_buffer;
+  std::set<shared_ptr<fib::Entry> > m_referenceEntries;
+
+protected:
+  bool m_finished;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_MGMT_FIB_ENUMERATION_PUBLISHER_COMMON_HPP
diff --git a/tests/daemon/mgmt/fib-enumeration-publisher.cpp b/tests/daemon/mgmt/fib-enumeration-publisher.cpp
new file mode 100644
index 0000000..725d51e
--- /dev/null
+++ b/tests/daemon/mgmt/fib-enumeration-publisher.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/fib-enumeration-publisher.hpp"
+
+#include "mgmt/app-face.hpp"
+#include "mgmt/internal-face.hpp"
+
+#include "tests/test-common.hpp"
+#include "../face/dummy-face.hpp"
+
+#include "fib-enumeration-publisher-common.hpp"
+
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("TestFibEnumerationPublisher");
+
+
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFibEnumeration, FibEnumerationPublisherFixture)
+
+BOOST_AUTO_TEST_CASE(TestFibEnumerationPublisher)
+{
+  for (int i = 0; i < 87; i++)
+    {
+      Name prefix("/test");
+      prefix.appendSegment(i);
+
+      shared_ptr<DummyFace> dummy1(make_shared<DummyFace>());
+      shared_ptr<DummyFace> dummy2(make_shared<DummyFace>());
+
+      shared_ptr<fib::Entry> entry = m_fib.insert(prefix).first;
+      entry->addNextHop(dummy1, std::numeric_limits<uint64_t>::max() - 1);
+      entry->addNextHop(dummy2, std::numeric_limits<uint64_t>::max() - 2);
+
+      m_referenceEntries.insert(entry);
+    }
+  for (int i = 0; i < 2; i++)
+    {
+      Name prefix("/test2");
+      prefix.appendSegment(i);
+
+      shared_ptr<DummyFace> dummy1(make_shared<DummyFace>());
+      shared_ptr<DummyFace> dummy2(make_shared<DummyFace>());
+
+      shared_ptr<fib::Entry> entry = m_fib.insert(prefix).first;
+      entry->addNextHop(dummy1, std::numeric_limits<uint8_t>::max() - 1);
+      entry->addNextHop(dummy2, std::numeric_limits<uint8_t>::max() - 2);
+
+      m_referenceEntries.insert(entry);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FibEnumerationPublisherFixture::decodeFibEntryBlock, this, _1);
+
+  m_publisher.publish();
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/fib-manager.cpp b/tests/daemon/mgmt/fib-manager.cpp
new file mode 100644
index 0000000..b38c3b8
--- /dev/null
+++ b/tests/daemon/mgmt/fib-manager.cpp
@@ -0,0 +1,920 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/fib-manager.hpp"
+#include "table/fib.hpp"
+#include "table/fib-nexthop.hpp"
+#include "face/face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "validation-common.hpp"
+#include "tests/test-common.hpp"
+
+#include "fib-enumeration-publisher-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("FibManagerTest");
+
+class FibManagerFixture : public FibEnumerationPublisherFixture
+{
+public:
+
+  virtual
+  ~FibManagerFixture()
+  {
+  }
+
+  shared_ptr<Face>
+  getFace(FaceId id)
+  {
+    if (id > 0 && static_cast<size_t>(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
+  validateControlResponseCommon(const Data& response,
+                                const Name& expectedName,
+                                uint32_t expectedCode,
+                                const std::string& expectedText,
+                                ControlResponse& control)
+  {
+    m_callbackFired = true;
+    Block controlRaw = response.getContent().blockFromValue();
+
+    control.wireDecode(controlRaw);
+
+    // NFD_LOG_DEBUG("received control response"
+    //               << " Name: " << response.getName()
+    //               << " code: " << control.getCode()
+    //               << " text: " << control.getText());
+
+    BOOST_CHECK_EQUAL(response.getName(), expectedName);
+    BOOST_CHECK_EQUAL(control.getCode(), expectedCode);
+    BOOST_CHECK_EQUAL(control.getText(), expectedText);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    if (!control.getBody().empty())
+      {
+        BOOST_FAIL("found unexpected control response body");
+      }
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText,
+                          const Block& expectedBody)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    BOOST_REQUIRE(!control.getBody().empty());
+    BOOST_REQUIRE_EQUAL(control.getBody().value_size(), expectedBody.value_size());
+
+    BOOST_CHECK(memcmp(control.getBody().value(), expectedBody.value(),
+                       expectedBody.value_size()) == 0);
+
+  }
+
+  bool
+  didCallbackFire()
+  {
+    return m_callbackFired;
+  }
+
+  void
+  resetCallbackFired()
+  {
+    m_callbackFired = false;
+  }
+
+  shared_ptr<InternalFace>
+  getInternalFace()
+  {
+    return m_face;
+  }
+
+  FibManager&
+  getFibManager()
+  {
+    return m_manager;
+  }
+
+  Fib&
+  getFib()
+  {
+    return m_fib;
+  }
+
+  void
+  addInterestRule(const std::string& regex,
+                  ndn::IdentityCertificate& certificate)
+  {
+    m_manager.addInterestRule(regex, certificate);
+  }
+
+protected:
+    FibManagerFixture()
+      : m_manager(boost::ref(m_fib),
+                  bind(&FibManagerFixture::getFace, this, _1),
+                  m_face)
+    , m_callbackFired(false)
+  {
+  }
+
+protected:
+  FibManager m_manager;
+
+  std::vector<shared_ptr<Face> > m_faces;
+  bool m_callbackFired;
+};
+
+template <typename T> class AuthorizedCommandFixture:
+    public CommandFixture<T>
+{
+public:
+  AuthorizedCommandFixture()
+  {
+    const std::string regex = "^<localhost><nfd><fib>";
+    T::addInterestRule(regex, *CommandFixture<T>::m_certificate);
+  }
+
+  virtual
+  ~AuthorizedCommandFixture()
+  {
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFibManager, AuthorizedCommandFixture<FibManagerFixture>)
+
+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.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;
+}
+
+bool
+foundNextHopWithFace(FaceId id, uint32_t cost,
+                     shared_ptr<Face> face, const fib::NextHop& next)
+{
+  return id == next.getFace()->getId() && next.getCost() == cost && face == next.getFace();
+}
+
+bool
+addedNextHopWithFace(const Fib& fib, const Name& prefix, size_t oldSize,
+                     uint32_t cost, shared_ptr<Face> face)
+{
+  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;
+}
+
+BOOST_AUTO_TEST_CASE(TestFireInterestFilter)
+{
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  shared_ptr<Interest> command = makeInterest("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this,  _1,
+         command->getName(), 400, "Malformed command");
+
+  face->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCommmand)
+{
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  BOOST_REQUIRE(didCallbackFire() == false);
+
+  Interest command("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command.getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(UnsupportedVerb)
+{
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+  parameters.setCost(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("unsupported");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 501, "Unsupported command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(UnsignedCommand)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+  parameters.setCost(101);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  Interest command(commandName);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse,
+         this, _1, command.getName(), 401, "Signature required");
+
+
+  getFibManager().onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!addedNextHopWithCost(getFib(), "/hello", 0, 101));
+}
+
+BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, UnauthorizedCommandFixture<FibManagerFixture>)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+  parameters.setCost(101);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse,
+         this, _1, command->getName(), 403, "Unauthorized command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(!addedNextHopWithCost(getFib(), "/hello", 0, 101));
+}
+
+BOOST_AUTO_TEST_CASE(BadOptionParse)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append("NotReallyParameters");
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(UnknownFaceId)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1000);
+  parameters.setCost(101);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 410, "Face not found");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(getFib(), "/hello", 0, 101) == false);
+}
+
+BOOST_AUTO_TEST_CASE(TestImplicitFaceId)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(0);
+  parameters.setCost(101);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  ControlParameters expectedParameters;
+  expectedParameters.setName("/hello");
+  expectedParameters.setFaceId(1);
+  expectedParameters.setCost(101);
+
+  Block encodedExpectedParameters(expectedParameters.wireEncode());
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  command->setIncomingFaceId(1);
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedExpectedParameters);
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithFace(getFib(), "/hello", 0, 101, getFace(1)));
+}
+
+BOOST_AUTO_TEST_CASE(AddNextHopVerbInitialAdd)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+  parameters.setCost(101);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(getFib(), "/hello", 0, 101));
+}
+
+BOOST_AUTO_TEST_CASE(AddNextHopVerbImplicitCost)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  ControlParameters resultParameters;
+  resultParameters.setName("/hello");
+  resultParameters.setFaceId(1);
+  resultParameters.setCost(0);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", resultParameters.wireEncode());
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  BOOST_REQUIRE(addedNextHopWithCost(getFib(), "/hello", 0, 0));
+}
+
+BOOST_AUTO_TEST_CASE(AddNextHopVerbAddToExisting)
+{
+  addFace(make_shared<DummyFace>());
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  for (int i = 1; i <= 2; i++)
+    {
+
+      ControlParameters parameters;
+      parameters.setName("/hello");
+      parameters.setFaceId(1);
+      parameters.setCost(100 + i);
+
+      Block encodedParameters(parameters.wireEncode());
+
+      Name commandName("/localhost/nfd/fib");
+      commandName.append("add-nexthop");
+      commandName.append(encodedParameters);
+
+      shared_ptr<Interest> command(make_shared<Interest>(commandName));
+      generateCommand(*command);
+
+      face->onReceiveData +=
+        bind(&FibManagerFixture::validateControlResponse, this, _1,
+             command->getName(), 200, "Success", encodedParameters);
+
+      getFibManager().onFibRequest(*command);
+      BOOST_REQUIRE(didCallbackFire());
+      resetCallbackFired();
+
+      shared_ptr<fib::Entry> entry = getFib().findExactMatch("/hello");
+
+      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, 100 + i, _1)) != hops.end());
+
+        }
+      else
+        {
+          BOOST_FAIL("Failed to find expected fib entry");
+        }
+
+      face->onReceiveData.clear();
+    }
+}
+
+BOOST_AUTO_TEST_CASE(AddNextHopVerbUpdateFaceCost)
+{
+  addFace(make_shared<DummyFace>());
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+
+  {
+    parameters.setCost(1);
+
+    Block encodedParameters(parameters.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedParameters);
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    generateCommand(*command);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           command->getName(), 200, "Success", encodedParameters);
+
+    getFibManager().onFibRequest(*command);
+
+    BOOST_REQUIRE(didCallbackFire());
+  }
+
+  resetCallbackFired();
+  face->onReceiveData.clear();
+
+  {
+    parameters.setCost(102);
+
+    Block encodedParameters(parameters.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("add-nexthop");
+    commandName.append(encodedParameters);
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    generateCommand(*command);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           command->getName(), 200, "Success", encodedParameters);
+
+    getFibManager().onFibRequest(*command);
+
+    BOOST_REQUIRE(didCallbackFire());
+  }
+
+  shared_ptr<fib::Entry> entry = getFib().findExactMatch("/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_CASE(AddNextHopVerbMissingPrefix)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setFaceId(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(AddNextHopVerbMissingFaceId)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("add-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+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(CommandFixture<FibManagerFixture>* fixture,
+                  FibManager& manager,
+                  Fib& fib,
+                  shared_ptr<Face> face,
+                  const Name& targetName,
+                  FaceId targetFace)
+{
+  ControlParameters parameters;
+  parameters.setName(targetName);
+  parameters.setFaceId(targetFace);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  fixture->generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, fixture, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  manager.onFibRequest(*command);
+
+  BOOST_REQUIRE(fixture->didCallbackFire());
+
+  fixture->resetCallbackFired();
+  face->onReceiveData.clear();
+}
+
+BOOST_AUTO_TEST_CASE(RemoveNextHop)
+{
+  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 = getInternalFace();
+  FibManager& manager = getFibManager();
+  Fib& fib = getFib();
+
+  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));
+
+  BOOST_CHECK(!static_cast<bool>(getFib().findExactMatch("/hello")));
+}
+
+BOOST_AUTO_TEST_CASE(RemoveFaceNotFound)
+{
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(RemovePrefixNotFound)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+  parameters.setFaceId(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(RemoveMissingPrefix)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setFaceId(1);
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(RemoveMissingFaceId)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face = getInternalFace();
+
+  ControlParameters parameters;
+  parameters.setName("/hello");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getFibManager().onFibRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(TestFibEnumerationRequest, FibManagerFixture)
+{
+  for (int i = 0; i < 87; i++)
+    {
+      Name prefix("/test");
+      prefix.appendSegment(i);
+
+      shared_ptr<DummyFace> dummy1(make_shared<DummyFace>());
+      shared_ptr<DummyFace> dummy2(make_shared<DummyFace>());
+
+      shared_ptr<fib::Entry> entry = m_fib.insert(prefix).first;
+      entry->addNextHop(dummy1, std::numeric_limits<uint64_t>::max() - 1);
+      entry->addNextHop(dummy2, std::numeric_limits<uint64_t>::max() - 2);
+
+      m_referenceEntries.insert(entry);
+    }
+  for (int i = 0; i < 2; i++)
+    {
+      Name prefix("/test2");
+      prefix.appendSegment(i);
+
+      shared_ptr<DummyFace> dummy1(make_shared<DummyFace>());
+      shared_ptr<DummyFace> dummy2(make_shared<DummyFace>());
+
+      shared_ptr<fib::Entry> entry = m_fib.insert(prefix).first;
+      entry->addNextHop(dummy1, std::numeric_limits<uint8_t>::max() - 1);
+      entry->addNextHop(dummy2, std::numeric_limits<uint8_t>::max() - 2);
+
+      m_referenceEntries.insert(entry);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FibEnumerationPublisherFixture::decodeFibEntryBlock, this, _1);
+
+  shared_ptr<Interest> command(make_shared<Interest>("/localhost/nfd/fib/list"));
+
+  m_manager.onFibRequest(*command);
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/internal-face.cpp b/tests/daemon/mgmt/internal-face.cpp
new file mode 100644
index 0000000..04ef4a2
--- /dev/null
+++ b/tests/daemon/mgmt/internal-face.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/internal-face.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("InternalFaceTest");
+
+class InternalFaceFixture : protected BaseFixture
+{
+public:
+
+  InternalFaceFixture()
+    : m_onInterestFired(false),
+      m_noOnInterestFired(false)
+  {
+
+  }
+
+  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_FIXTURE_TEST_SUITE(MgmtInternalFace, InternalFaceFixture)
+
+void
+validatePutData(bool& called, const Name& expectedName, const Data& data)
+{
+  called = true;
+  BOOST_CHECK_EQUAL(expectedName, data.getName());
+}
+
+BOOST_AUTO_TEST_CASE(PutData)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+
+  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_CHECK_THROW(face->close(), InternalFace::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SendInterestHitEnd)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+
+  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");
+  shared_ptr<Interest> command = makeInterest(commandName);
+  face->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+BOOST_AUTO_TEST_CASE(SendInterestHitBegin)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+
+  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");
+  shared_ptr<Interest> command = makeInterest(commandName);
+  face->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+BOOST_AUTO_TEST_CASE(SendInterestHitExact)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+
+  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");
+  shared_ptr<Interest> command = makeInterest(commandName);
+  face->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+BOOST_AUTO_TEST_CASE(SendInterestHitPrevious)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(new InternalFace);
+
+  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");
+  shared_ptr<Interest> command = makeInterest(commandName);
+  face->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didOnInterestFire());
+  BOOST_REQUIRE(didNoOnInterestFire() == false);
+}
+
+BOOST_AUTO_TEST_CASE(InterestGone)
+{
+  shared_ptr<InternalFace> face = make_shared<InternalFace>();
+  shared_ptr<Interest> interest = makeInterest("ndn:/localhost/nfd/gone");
+  face->sendInterest(*interest);
+
+  interest.reset();
+  BOOST_CHECK_NO_THROW(g_io.poll());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/malformed.ndncert b/tests/daemon/mgmt/malformed.ndncert
new file mode 100644
index 0000000..38b2fbb
--- /dev/null
+++ b/tests/daemon/mgmt/malformed.ndncert
@@ -0,0 +1 @@
+definitely not a key
\ No newline at end of file
diff --git a/tests/daemon/mgmt/manager-base.cpp b/tests/daemon/mgmt/manager-base.cpp
new file mode 100644
index 0000000..74fcb6e
--- /dev/null
+++ b/tests/daemon/mgmt/manager-base.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/manager-base.hpp"
+#include "mgmt/internal-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("ManagerBaseTest");
+
+class ManagerBaseTest : public ManagerBase, protected BaseFixture
+{
+
+public:
+
+  ManagerBaseTest()
+    : ManagerBase(make_shared<InternalFace>(), "TEST-PRIVILEGE"),
+      m_callbackFired(false)
+  {
+
+  }
+
+  void
+  testSetResponse(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,
+                   const Block& body)
+  {
+    sendResponse(name, code, text, body);
+  }
+
+  void
+  testSendResponse(const Name& name,
+                   uint32_t code,
+                   const std::string& text)
+  {
+    sendResponse(name, code, text);
+  }
+
+  void
+  testSendResponse(const Name& name,
+                   const 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();
+
+    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);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText,
+                          const Block& expectedBody)
+  {
+    m_callbackFired = true;
+    Block controlRaw = response.getContent().blockFromValue();
+
+    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);
+
+    BOOST_REQUIRE(control.getBody().value_size() == expectedBody.value_size());
+
+    BOOST_CHECK(memcmp(control.getBody().value(), expectedBody.value(),
+                       expectedBody.value_size()) == 0);
+  }
+
+  bool
+  didCallbackFire()
+  {
+    return m_callbackFired;
+  }
+
+private:
+
+  bool m_callbackFired;
+
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtManagerBase, ManagerBaseTest)
+
+BOOST_AUTO_TEST_CASE(SetResponse)
+{
+  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_AUTO_TEST_CASE(SendResponse4Arg)
+{
+  ndn::nfd::ControlParameters parameters;
+  parameters.setName("/test/body");
+
+  getInternalFace()->onReceiveData +=
+    bind(&ManagerBaseTest::validateControlResponse, this, _1,
+         "/response", 100, "test", parameters.wireEncode());
+
+  testSendResponse("/response", 100, "test", parameters.wireEncode());
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+
+BOOST_AUTO_TEST_CASE(SendResponse3Arg)
+{
+  getInternalFace()->onReceiveData +=
+    bind(&ManagerBaseTest::validateControlResponse, this, _1,
+         "/response", 100, "test");
+
+  testSendResponse("/response", 100, "test");
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(SendResponse2Arg)
+{
+  getInternalFace()->onReceiveData +=
+    bind(&ManagerBaseTest::validateControlResponse, this, _1,
+         "/response", 100, "test");
+
+  ControlResponse response(100, "test");
+
+  testSendResponse("/response", 100, "test");
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/notification-stream.cpp b/tests/daemon/mgmt/notification-stream.cpp
new file mode 100644
index 0000000..32f5a20
--- /dev/null
+++ b/tests/daemon/mgmt/notification-stream.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/notification-stream.hpp"
+#include "mgmt/internal-face.hpp"
+
+#include "tests/test-common.hpp"
+
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("NotificationStreamTest");
+
+
+
+class NotificationStreamFixture : public BaseFixture
+{
+public:
+  NotificationStreamFixture()
+    : m_callbackFired(false)
+    , m_prefix("/localhost/nfd/NotificationStreamTest")
+    , m_message("TestNotificationMessage")
+    , m_sequenceNo(0)
+  {
+  }
+
+  virtual
+  ~NotificationStreamFixture()
+  {
+  }
+
+  void
+  validateCallback(const Data& data)
+  {
+    Name expectedName(m_prefix);
+    expectedName.appendSegment(m_sequenceNo);
+    BOOST_REQUIRE_EQUAL(data.getName(), expectedName);
+
+    ndn::Block payload = data.getContent();
+    std::string message;
+
+    message.append(reinterpret_cast<const char*>(payload.value()), payload.value_size());
+
+    BOOST_REQUIRE_EQUAL(message, m_message);
+
+    m_callbackFired = true;
+    ++m_sequenceNo;
+  }
+
+  void
+  resetCallbackFired()
+  {
+    m_callbackFired = false;
+  }
+
+protected:
+  bool m_callbackFired;
+  const std::string m_prefix;
+  const std::string m_message;
+  uint64_t m_sequenceNo;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtNotificationStream, NotificationStreamFixture)
+
+class TestNotification
+{
+public:
+  TestNotification(const std::string& message)
+    : m_message(message)
+  {
+  }
+
+  ~TestNotification()
+  {
+  }
+
+  Block
+  wireEncode() const
+  {
+    ndn::EncodingBuffer buffer;
+
+    prependByteArrayBlock(buffer,
+                          ndn::Tlv::Content,
+                          reinterpret_cast<const uint8_t*>(m_message.c_str()),
+                          m_message.size());
+    return buffer.block();
+  }
+
+private:
+  const std::string m_message;
+};
+
+BOOST_AUTO_TEST_CASE(TestPostEvent)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  NotificationStream notificationStream(face, "/localhost/nfd/NotificationStreamTest");
+
+  face->onReceiveData += bind(&NotificationStreamFixture::validateCallback, this, _1);
+
+  TestNotification event1(m_message);
+  notificationStream.postNotification(event1);
+
+  BOOST_REQUIRE(m_callbackFired);
+
+  resetCallbackFired();
+
+  TestNotification event2(m_message);
+  notificationStream.postNotification(event2);
+
+  BOOST_REQUIRE(m_callbackFired);
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/segment-publisher.cpp b/tests/daemon/mgmt/segment-publisher.cpp
new file mode 100644
index 0000000..e40f372
--- /dev/null
+++ b/tests/daemon/mgmt/segment-publisher.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/segment-publisher.hpp"
+#include "mgmt/internal-face.hpp"
+#include "mgmt/app-face.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("SegmentPublisherTest");
+
+class TestSegmentPublisher : public SegmentPublisher
+{
+public:
+  TestSegmentPublisher(shared_ptr<AppFace> face,
+                       const Name& prefix,
+                       const uint64_t limit=10000)
+    : SegmentPublisher(face, prefix)
+    , m_limit((limit == 0)?(1):(limit))
+  {
+
+  }
+
+  virtual
+  ~TestSegmentPublisher()
+  {
+
+  }
+
+  uint16_t
+  getLimit() const
+  {
+    return m_limit;
+  }
+
+protected:
+
+  virtual size_t
+  generate(ndn::EncodingBuffer& outBuffer)
+  {
+    size_t totalLength = 0;
+    for (uint64_t i = 0; i < m_limit; i++)
+      {
+        totalLength += prependNonNegativeIntegerBlock(outBuffer, ndn::Tlv::Content, i);
+      }
+    return totalLength;
+  }
+
+protected:
+  const uint64_t m_limit;
+};
+
+class SegmentPublisherFixture : public BaseFixture
+{
+public:
+  SegmentPublisherFixture()
+    : m_face(make_shared<InternalFace>())
+    , m_publisher(m_face, "/localhost/nfd/SegmentPublisherFixture")
+    , m_finished(false)
+  {
+
+  }
+
+  void
+  validate(const Data& data)
+  {
+    Block payload = data.getContent();
+    NFD_LOG_DEBUG("payload size (w/o Content TLV): " << payload.value_size());
+
+    m_buffer.appendByteArray(payload.value(), payload.value_size());
+
+    uint64_t segmentNo = data.getName()[-1].toSegment();
+    if (data.getFinalBlockId() != data.getName()[-1])
+      {
+        return;
+      }
+
+    NFD_LOG_DEBUG("got final block: #" << segmentNo);
+
+    // wrap data in a single Content TLV for easy parsing
+    m_buffer.prependVarNumber(m_buffer.size());
+    m_buffer.prependVarNumber(ndn::Tlv::Content);
+
+    BOOST_TEST_CHECKPOINT("creating parser");
+    ndn::Block parser(m_buffer.buf(), m_buffer.size());
+    BOOST_TEST_CHECKPOINT("parsing aggregated response");
+    parser.parse();
+
+    BOOST_REQUIRE_EQUAL(parser.elements_size(), m_publisher.getLimit());
+
+    uint64_t expectedNo = m_publisher.getLimit() - 1;
+    for (Block::element_const_iterator i = parser.elements_begin();
+         i != parser.elements_end();
+         ++i)
+      {
+        uint64_t number = readNonNegativeInteger(*i);
+        BOOST_REQUIRE_EQUAL(number, expectedNo);
+        --expectedNo;
+      }
+    m_finished = true;
+  }
+
+protected:
+  shared_ptr<InternalFace> m_face;
+  TestSegmentPublisher m_publisher;
+  ndn::EncodingBuffer m_buffer;
+
+protected:
+  bool m_finished;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtSegmentPublisher, SegmentPublisherFixture)
+
+BOOST_AUTO_TEST_CASE(Generate)
+{
+  m_face->onReceiveData +=
+    bind(&SegmentPublisherFixture::validate, this, _1);
+
+  m_publisher.publish();
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/status-server.cpp b/tests/daemon/mgmt/status-server.cpp
new file mode 100644
index 0000000..75b4114
--- /dev/null
+++ b/tests/daemon/mgmt/status-server.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/status-server.hpp"
+#include "fw/forwarder.hpp"
+#include "version.hpp"
+#include "mgmt/internal-face.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(MgmtStatusServer, BaseFixture)
+
+shared_ptr<const Data> g_response;
+
+void
+interceptResponse(const Data& data)
+{
+  g_response = data.shared_from_this();
+}
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  // initialize
+  time::system_clock::TimePoint t1 = time::system_clock::now();
+  Forwarder forwarder;
+  shared_ptr<InternalFace> internalFace = make_shared<InternalFace>();
+  internalFace->onReceiveData += &interceptResponse;
+  StatusServer statusServer(internalFace, boost::ref(forwarder));
+  time::system_clock::TimePoint t2 = time::system_clock::now();
+
+  // populate tables
+  forwarder.getFib().insert("ndn:/fib1");
+  forwarder.getPit().insert(*makeInterest("ndn:/pit1"));
+  forwarder.getPit().insert(*makeInterest("ndn:/pit2"));
+  forwarder.getPit().insert(*makeInterest("ndn:/pit3"));
+  forwarder.getPit().insert(*makeInterest("ndn:/pit4"));
+  forwarder.getMeasurements().get("ndn:/measurements1");
+  forwarder.getMeasurements().get("ndn:/measurements2");
+  forwarder.getMeasurements().get("ndn:/measurements3");
+  BOOST_CHECK_GE(forwarder.getFib().size(), 1);
+  BOOST_CHECK_GE(forwarder.getPit().size(), 4);
+  BOOST_CHECK_GE(forwarder.getMeasurements().size(), 3);
+
+  // request
+  shared_ptr<Interest> request = makeInterest("ndn:/localhost/nfd/status");
+  request->setMustBeFresh(true);
+  request->setChildSelector(1);
+
+  g_response.reset();
+  time::system_clock::TimePoint t3 = time::system_clock::now();
+  internalFace->sendInterest(*request);
+  g_io.run_one();
+  time::system_clock::TimePoint t4 = time::system_clock::now();
+  BOOST_REQUIRE(static_cast<bool>(g_response));
+
+  // verify
+  ndn::nfd::ForwarderStatus status;
+  BOOST_REQUIRE_NO_THROW(status.wireDecode(g_response->getContent()));
+
+  BOOST_CHECK_EQUAL(status.getNfdVersion(), NFD_VERSION);
+  BOOST_CHECK_GE(time::toUnixTimestamp(status.getStartTimestamp()), time::toUnixTimestamp(t1));
+  BOOST_CHECK_LE(time::toUnixTimestamp(status.getStartTimestamp()), time::toUnixTimestamp(t2));
+  BOOST_CHECK_GE(time::toUnixTimestamp(status.getCurrentTimestamp()), time::toUnixTimestamp(t3));
+  BOOST_CHECK_LE(time::toUnixTimestamp(status.getCurrentTimestamp()), time::toUnixTimestamp(t4));
+
+  // StatusServer under test isn't added to Forwarder,
+  // so request and response won't affect table size
+  BOOST_CHECK_EQUAL(status.getNNameTreeEntries(), forwarder.getNameTree().size());
+  BOOST_CHECK_EQUAL(status.getNFibEntries(), forwarder.getFib().size());
+  BOOST_CHECK_EQUAL(status.getNPitEntries(), forwarder.getPit().size());
+  BOOST_CHECK_EQUAL(status.getNMeasurementsEntries(), forwarder.getMeasurements().size());
+  BOOST_CHECK_EQUAL(status.getNCsEntries(), forwarder.getCs().size());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/strategy-choice-manager.cpp b/tests/daemon/mgmt/strategy-choice-manager.cpp
new file mode 100644
index 0000000..99d5636
--- /dev/null
+++ b/tests/daemon/mgmt/strategy-choice-manager.cpp
@@ -0,0 +1,548 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/strategy-choice-manager.hpp"
+#include "face/face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "table/name-tree.hpp"
+#include "table/strategy-choice.hpp"
+#include "fw/forwarder.hpp"
+#include "fw/strategy.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+#include "tests/daemon/fw/dummy-strategy.hpp"
+
+#include "tests/test-common.hpp"
+#include "validation-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("StrategyChoiceManagerTest");
+
+class StrategyChoiceManagerFixture : protected BaseFixture
+{
+public:
+
+  StrategyChoiceManagerFixture()
+    : m_strategyChoice(m_forwarder.getStrategyChoice())
+    , m_face(make_shared<InternalFace>())
+    , m_manager(m_strategyChoice, m_face)
+    , m_callbackFired(false)
+  {
+    m_strategyChoice.install(make_shared<DummyStrategy>(boost::ref(m_forwarder), "/localhost/nfd/strategy/test-strategy-a"));
+    m_strategyChoice.insert("ndn:/", "/localhost/nfd/strategy/test-strategy-a");
+  }
+
+  virtual
+  ~StrategyChoiceManagerFixture()
+  {
+
+  }
+
+  void
+  validateControlResponseCommon(const Data& response,
+                                const Name& expectedName,
+                                uint32_t expectedCode,
+                                const std::string& expectedText,
+                                ControlResponse& control)
+  {
+    m_callbackFired = true;
+    Block controlRaw = response.getContent().blockFromValue();
+
+    control.wireDecode(controlRaw);
+
+    NFD_LOG_DEBUG("received control response"
+                  << " Name: " << response.getName()
+                  << " code: " << control.getCode()
+                  << " text: " << control.getText());
+
+    BOOST_CHECK_EQUAL(response.getName(), expectedName);
+    BOOST_CHECK_EQUAL(control.getCode(), expectedCode);
+    BOOST_CHECK_EQUAL(control.getText(), expectedText);
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    if (!control.getBody().empty())
+      {
+        BOOST_FAIL("found unexpected control response body");
+      }
+  }
+
+  void
+  validateControlResponse(const Data& response,
+                          const Name& expectedName,
+                          uint32_t expectedCode,
+                          const std::string& expectedText,
+                          const Block& expectedBody)
+  {
+    ControlResponse control;
+    validateControlResponseCommon(response, expectedName,
+                                  expectedCode, expectedText, control);
+
+    BOOST_REQUIRE(!control.getBody().empty());
+    BOOST_REQUIRE(control.getBody().value_size() == expectedBody.value_size());
+
+    BOOST_CHECK(memcmp(control.getBody().value(), expectedBody.value(),
+                       expectedBody.value_size()) == 0);
+
+  }
+
+  bool
+  didCallbackFire()
+  {
+    return m_callbackFired;
+  }
+
+  void
+  resetCallbackFired()
+  {
+    m_callbackFired = false;
+  }
+
+  shared_ptr<InternalFace>&
+  getFace()
+  {
+    return m_face;
+  }
+
+  StrategyChoiceManager&
+  getManager()
+  {
+    return m_manager;
+  }
+
+  StrategyChoice&
+  getStrategyChoice()
+  {
+    return m_strategyChoice;
+  }
+
+  void
+  addInterestRule(const std::string& regex,
+                  ndn::IdentityCertificate& certificate)
+  {
+    m_manager.addInterestRule(regex, certificate);
+  }
+
+protected:
+  Forwarder m_forwarder;
+  StrategyChoice& m_strategyChoice;
+  shared_ptr<InternalFace> m_face;
+  StrategyChoiceManager m_manager;
+
+private:
+  bool m_callbackFired;
+};
+
+class AllStrategiesFixture : public StrategyChoiceManagerFixture
+{
+public:
+  AllStrategiesFixture()
+  {
+    m_strategyChoice.install(make_shared<DummyStrategy>(boost::ref(m_forwarder), "/localhost/nfd/strategy/test-strategy-b"));
+  }
+
+  virtual
+  ~AllStrategiesFixture()
+  {
+
+  }
+};
+
+template <typename T> class AuthorizedCommandFixture : public CommandFixture<T>
+{
+public:
+  AuthorizedCommandFixture()
+  {
+    const std::string regex = "^<localhost><nfd><strategy-choice>";
+    T::addInterestRule(regex, *CommandFixture<T>::m_certificate);
+  }
+
+  virtual
+  ~AuthorizedCommandFixture()
+  {
+
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtStrategyChoiceManager,
+                         AuthorizedCommandFixture<AllStrategiesFixture>)
+
+BOOST_FIXTURE_TEST_CASE(TestFireInterestFilter, AllStrategiesFixture)
+{
+  shared_ptr<Interest> command(make_shared<Interest>("/localhost/nfd/strategy-choice"));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this,  _1,
+         command->getName(), 400, "Malformed command");
+
+  getFace()->sendInterest(*command);
+  g_io.run_one();
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(MalformedCommmand, AllStrategiesFixture)
+{
+  shared_ptr<Interest> command(make_shared<Interest>("/localhost/nfd/strategy-choice"));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onStrategyChoiceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnsignedCommand, AllStrategiesFixture)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+  parameters.setStrategy("/localhost/nfd/strategy/best-route");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 401, "Signature required");
+
+  getManager().onStrategyChoiceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand,
+                        UnauthorizedCommandFixture<StrategyChoiceManagerFixture>)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+  parameters.setStrategy("/localhost/nfd/strategy/best-route");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 403, "Unauthorized command");
+
+  getManager().onStrategyChoiceRequest(*command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(UnsupportedVerb)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+  parameters.setStrategy("/localhost/nfd/strategy/test-strategy-b");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("unsupported");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 501, "Unsupported command");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(BadOptionParse)
+{
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append("NotReallyParameters");
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(SetStrategies)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+  parameters.setStrategy("/localhost/nfd/strategy/test-strategy-b");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  fw::Strategy& strategy = getStrategyChoice().findEffectiveStrategy("/test");
+  BOOST_REQUIRE_EQUAL(strategy.getName(), "/localhost/nfd/strategy/test-strategy-b");
+}
+
+BOOST_AUTO_TEST_CASE(SetStrategiesMissingName)
+{
+  ControlParameters parameters;
+  parameters.setStrategy("/localhost/nfd/strategy/test-strategy-b");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_AUTO_TEST_CASE(SetStrategiesMissingStrategy)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  fw::Strategy& strategy = getStrategyChoice().findEffectiveStrategy("/test");
+  BOOST_REQUIRE_EQUAL(strategy.getName(), "/localhost/nfd/strategy/test-strategy-a");
+}
+
+BOOST_AUTO_TEST_CASE(SetUnsupportedStrategy)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+  parameters.setStrategy("/localhost/nfd/strategy/unit-test-doesnotexist");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("set");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 504, "Unsupported strategy");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+  fw::Strategy& strategy = getStrategyChoice().findEffectiveStrategy("/test");
+  BOOST_CHECK_EQUAL(strategy.getName(), "/localhost/nfd/strategy/test-strategy-a");
+}
+
+class DefaultStrategyOnlyFixture : public StrategyChoiceManagerFixture
+{
+public:
+  DefaultStrategyOnlyFixture()
+    : StrategyChoiceManagerFixture()
+  {
+
+  }
+
+  virtual
+  ~DefaultStrategyOnlyFixture()
+  {
+
+  }
+};
+
+
+/// \todo I'm not sure this code branch (code 405) can happen. The manager tests for the strategy first and will return 504.
+// BOOST_FIXTURE_TEST_CASE(SetNotInstalled, DefaultStrategyOnlyFixture)
+// {
+//   BOOST_REQUIRE(!getStrategyChoice().hasStrategy("/localhost/nfd/strategy/test-strategy-b"));
+//   ControlParameters parameters;
+//   parameters.setName("/test");
+//   parameters.setStrategy("/localhost/nfd/strategy/test-strategy-b");
+
+//   Block encodedParameters(parameters.wireEncode());
+
+//   Name commandName("/localhost/nfd/strategy-choice");
+//   commandName.append("set");
+//   commandName.append(encodedParameters);
+
+//   shared_ptr<Interest> command(make_shared<Interest>(commandName));
+//   generateCommand(*command);
+
+//   getFace()->onReceiveData +=
+//     bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+//          command->getName(), 405, "Strategy not installed");
+
+//   getManager().onValidatedStrategyChoiceRequest(command);
+
+//   BOOST_REQUIRE(didCallbackFire());
+//   fw::Strategy& strategy = getStrategyChoice().findEffectiveStrategy("/test");
+//   BOOST_CHECK_EQUAL(strategy.getName(), "/localhost/nfd/strategy/test-strategy-a");
+// }
+
+BOOST_AUTO_TEST_CASE(Unset)
+{
+  ControlParameters parameters;
+  parameters.setName("/test");
+
+  BOOST_REQUIRE(m_strategyChoice.insert("/test", "/localhost/nfd/strategy/test-strategy-b"));
+  BOOST_REQUIRE_EQUAL(m_strategyChoice.findEffectiveStrategy("/test").getName(),
+                      "/localhost/nfd/strategy/test-strategy-b");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("unset");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 200, "Success", encodedParameters);
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  BOOST_CHECK_EQUAL(m_strategyChoice.findEffectiveStrategy("/test").getName(),
+                    "/localhost/nfd/strategy/test-strategy-a");
+}
+
+BOOST_AUTO_TEST_CASE(UnsetRoot)
+{
+  ControlParameters parameters;
+  parameters.setName("/");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("unset");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 403, "Cannot unset root prefix strategy");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  BOOST_CHECK_EQUAL(m_strategyChoice.findEffectiveStrategy("/test").getName(),
+                    "/localhost/nfd/strategy/test-strategy-a");
+}
+
+BOOST_AUTO_TEST_CASE(UnsetMissingName)
+{
+  ControlParameters parameters;
+
+  BOOST_REQUIRE(m_strategyChoice.insert("/test", "/localhost/nfd/strategy/test-strategy-b"));
+  BOOST_REQUIRE_EQUAL(m_strategyChoice.findEffectiveStrategy("/test").getName(),
+                      "/localhost/nfd/strategy/test-strategy-b");
+
+  Block encodedParameters(parameters.wireEncode());
+
+  Name commandName("/localhost/nfd/strategy-choice");
+  commandName.append("unset");
+  commandName.append(encodedParameters);
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  generateCommand(*command);
+
+  getFace()->onReceiveData +=
+    bind(&StrategyChoiceManagerFixture::validateControlResponse, this, _1,
+         command->getName(), 400, "Malformed command");
+
+  getManager().onValidatedStrategyChoiceRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  BOOST_CHECK_EQUAL(m_strategyChoice.findEffectiveStrategy("/test").getName(),
+                    "/localhost/nfd/strategy/test-strategy-b");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/validation-common.cpp b/tests/daemon/mgmt/validation-common.cpp
new file mode 100644
index 0000000..6065c27
--- /dev/null
+++ b/tests/daemon/mgmt/validation-common.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "validation-common.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+namespace nfd {
+namespace tests {
+
+const Name CommandIdentityGlobalFixture::s_identityName("/unit-test/CommandFixture/id");
+shared_ptr<ndn::IdentityCertificate> CommandIdentityGlobalFixture::s_certificate;
+
+CommandIdentityGlobalFixture::CommandIdentityGlobalFixture()
+{
+  BOOST_ASSERT(!static_cast<bool>(s_certificate));
+  s_certificate = m_keys.getCertificate(m_keys.createIdentity(s_identityName));
+}
+
+CommandIdentityGlobalFixture::~CommandIdentityGlobalFixture()
+{
+  s_certificate.reset();
+  m_keys.deleteIdentity(s_identityName);
+}
+
+BOOST_GLOBAL_FIXTURE(CommandIdentityGlobalFixture)
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/validation-common.hpp b/tests/daemon/mgmt/validation-common.hpp
new file mode 100644
index 0000000..9aa2e28
--- /dev/null
+++ b/tests/daemon/mgmt/validation-common.hpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP
+#define NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP
+
+#include "common.hpp"
+#include <ndn-cpp-dev/util/command-interest-generator.hpp>
+
+namespace nfd {
+namespace tests {
+
+// class ValidatedManagementFixture
+// {
+// public:
+//   ValidatedManagementFixture()
+//     : m_validator(make_shared<ndn::CommandInterestValidator>())
+//   {
+//   }
+
+//   virtual
+//   ~ValidatedManagementFixture()
+//   {
+//   }
+
+// protected:
+//   shared_ptr<ndn::CommandInterestValidator> m_validator;
+// };
+
+/// a global fixture that holds the identity for CommandFixture
+class CommandIdentityGlobalFixture
+{
+public:
+  CommandIdentityGlobalFixture();
+
+  ~CommandIdentityGlobalFixture();
+
+  static const Name& getIdentityName()
+  {
+    return s_identityName;
+  }
+
+  static shared_ptr<ndn::IdentityCertificate> getCertificate()
+  {
+    BOOST_ASSERT(static_cast<bool>(s_certificate));
+    return s_certificate;
+  }
+
+private:
+  ndn::KeyChain m_keys;
+  static const Name s_identityName;
+  static shared_ptr<ndn::IdentityCertificate> s_certificate;
+};
+
+template<typename T>
+class CommandFixture : public T
+{
+public:
+  virtual
+  ~CommandFixture()
+  {
+  }
+
+  void
+  generateCommand(Interest& interest)
+  {
+    m_generator.generateWithIdentity(interest, getIdentityName());
+  }
+
+  const Name&
+  getIdentityName() const
+  {
+    return CommandIdentityGlobalFixture::getIdentityName();
+  }
+
+protected:
+  CommandFixture()
+    : m_certificate(CommandIdentityGlobalFixture::getCertificate())
+  {
+  }
+
+protected:
+  shared_ptr<ndn::IdentityCertificate> m_certificate;
+  ndn::CommandInterestGenerator m_generator;
+};
+
+template <typename T>
+class UnauthorizedCommandFixture : public CommandFixture<T>
+{
+public:
+  UnauthorizedCommandFixture()
+  {
+  }
+
+  virtual
+  ~UnauthorizedCommandFixture()
+  {
+  }
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP
diff --git a/tests/daemon/table/cs.cpp b/tests/daemon/table/cs.cpp
new file mode 100644
index 0000000..cdcc0db
--- /dev/null
+++ b/tests/daemon/table/cs.cpp
@@ -0,0 +1,919 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * \author Ilya Moiseenko <iliamo@ucla.edu>
+ **/
+
+#include "table/cs.hpp"
+#include "table/cs-entry.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+class CsAccessor : public Cs
+{
+public:
+  bool
+  evictItem_accessor()
+  {
+    return evictItem();
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TableCs, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Insertion)
+{
+  Cs cs;
+
+  BOOST_CHECK_EQUAL(cs.insert(*makeData("/insertion")), true);
+}
+
+BOOST_AUTO_TEST_CASE(Insertion2)
+{
+  Cs cs;
+
+  cs.insert(*makeData("/a"));
+  cs.insert(*makeData("/b"));
+  cs.insert(*makeData("/c"));
+  cs.insert(*makeData("/d"));
+
+  BOOST_CHECK_EQUAL(cs.size(), 4);
+}
+
+
+BOOST_AUTO_TEST_CASE(DuplicateInsertion)
+{
+  Cs cs;
+
+  shared_ptr<Data> data0 = makeData("/insert/smth");
+  BOOST_CHECK_EQUAL(cs.insert(*data0), true);
+
+  shared_ptr<Data> data = makeData("/insert/duplicate");
+  BOOST_CHECK_EQUAL(cs.insert(*data), true);
+
+  cs.insert(*data);
+  BOOST_CHECK_EQUAL(cs.size(), 2);
+}
+
+
+BOOST_AUTO_TEST_CASE(DuplicateInsertion2)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/insert/duplicate");
+  BOOST_CHECK_EQUAL(cs.insert(*data), true);
+
+  cs.insert(*data);
+  BOOST_CHECK_EQUAL(cs.size(), 1);
+
+  shared_ptr<Data> data2 = makeData("/insert/original");
+  BOOST_CHECK_EQUAL(cs.insert(*data2), true);
+  BOOST_CHECK_EQUAL(cs.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(InsertAndFind)
+{
+  Cs cs;
+
+  Name name("/insert/and/find");
+
+  shared_ptr<Data> data = makeData(name);
+  BOOST_CHECK_EQUAL(cs.insert(*data), true);
+
+  shared_ptr<Interest> interest = make_shared<Interest>(name);
+
+  const Data* found = cs.find(*interest);
+
+  BOOST_REQUIRE(found != 0);
+  BOOST_CHECK_EQUAL(data->getName(), found->getName());
+}
+
+BOOST_AUTO_TEST_CASE(InsertAndNotFind)
+{
+  Cs cs;
+
+  Name name("/insert/and/find");
+  shared_ptr<Data> data = makeData(name);
+  BOOST_CHECK_EQUAL(cs.insert(*data), true);
+
+  Name name2("/not/find");
+  shared_ptr<Interest> interest = make_shared<Interest>(name2);
+
+  const Data* found = cs.find(*interest);
+
+  BOOST_CHECK_EQUAL(found, static_cast<const Data*>(0));
+}
+
+
+BOOST_AUTO_TEST_CASE(InsertAndErase)
+{
+  CsAccessor cs;
+
+  shared_ptr<Data> data = makeData("/insertandelete");
+  cs.insert(*data);
+  BOOST_CHECK_EQUAL(cs.size(), 1);
+
+  cs.evictItem_accessor();
+  BOOST_CHECK_EQUAL(cs.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(StalenessQueue)
+{
+  CsAccessor cs;
+
+  Name name2("/insert/fresh");
+  shared_ptr<Data> data2 = makeData(name2);
+  data2->setFreshnessPeriod(time::milliseconds(5000));
+  cs.insert(*data2);
+
+  Name name("/insert/expire");
+  shared_ptr<Data> data = makeData(name);
+  data->setFreshnessPeriod(time::milliseconds(500));
+  cs.insert(*data);
+
+  BOOST_CHECK_EQUAL(cs.size(), 2);
+
+  sleep(3);
+
+  cs.evictItem_accessor();
+  BOOST_CHECK_EQUAL(cs.size(), 1);
+
+  shared_ptr<Interest> interest = make_shared<Interest>(name2);
+  const Data* found = cs.find(*interest);
+  BOOST_REQUIRE(found != 0);
+  BOOST_CHECK_EQUAL(found->getName(), name2);
+}
+
+//even max size
+BOOST_AUTO_TEST_CASE(ReplacementEvenSize)
+{
+  Cs cs(4);
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/e");
+  cs.insert(*data5);
+
+  BOOST_CHECK_EQUAL(cs.size(), 4);
+}
+
+//odd max size
+BOOST_AUTO_TEST_CASE(Replacement2)
+{
+  Cs cs(3);
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  BOOST_CHECK_EQUAL(cs.size(), 3);
+}
+
+BOOST_AUTO_TEST_CASE(InsertAndEraseByName)
+{
+  CsAccessor cs;
+
+  Name name("/insertandremovebyname");
+  shared_ptr<Data> data = makeData(name);
+  cs.insert(*data);
+
+  ndn::ConstBufferPtr digest1 = ndn::crypto::sha256(data->wireEncode().wire(),
+                                                    data->wireEncode().size());
+
+  shared_ptr<Data> data2 = makeData("/a");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/z");
+  cs.insert(*data3);
+
+  BOOST_CHECK_EQUAL(cs.size(), 3);
+
+  name.append(digest1->buf(), digest1->size());
+  cs.erase(name);
+  BOOST_CHECK_EQUAL(cs.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(DigestCalculation)
+{
+  shared_ptr<Data> data = makeData("/digest/compute");
+
+  ndn::ConstBufferPtr digest1 = ndn::crypto::sha256(data->wireEncode().wire(),
+                                                    data->wireEncode().size());
+  BOOST_CHECK_EQUAL(digest1->size(), 32);
+
+  cs::Entry* entry = new cs::Entry();
+  entry->setData(*data, false);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest1->begin(), digest1->end(),
+                                entry->getDigest()->begin(), entry->getDigest()->end());
+}
+
+BOOST_AUTO_TEST_CASE(InsertCanonical)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  cs.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/1/2/3");
+  cs.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1");
+  cs.insert(*data7);
+}
+
+BOOST_AUTO_TEST_CASE(EraseCanonical)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  cs.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/1/2/3");
+  cs.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1");
+  cs.insert(*data7);
+
+  ndn::ConstBufferPtr digest1 = ndn::crypto::sha256(data->wireEncode().wire(),
+                                                    data->wireEncode().size());
+
+  Name name("/a");
+  name.append(digest1->buf(), digest1->size());
+  cs.erase(name);
+  BOOST_CHECK_EQUAL(cs.size(), 6);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitDigestSelector)
+{
+  CsAccessor cs;
+
+  Name name("/digest/works");
+  shared_ptr<Data> data = makeData(name);
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/a");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/z/z/z");
+  cs.insert(*data3);
+
+  ndn::ConstBufferPtr digest1 = ndn::crypto::sha256(data->wireEncode().wire(),
+                                                    data->wireEncode().size());
+  uint8_t digest2[32] = {0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1};
+
+  shared_ptr<Interest> interest = make_shared<Interest>();
+  interest->setName(Name(name).append(digest1->buf(), digest1->size()));
+  interest->setMinSuffixComponents(0);
+  interest->setMaxSuffixComponents(0);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_NE(found, static_cast<const Data*>(0));
+  BOOST_CHECK_EQUAL(found->getName(), name);
+
+  shared_ptr<Interest> interest2 = make_shared<Interest>();
+  interest2->setName(Name(name).append(digest2, 32));
+  interest2->setMinSuffixComponents(0);
+  interest2->setMaxSuffixComponents(0);
+
+  const Data* notfound = cs.find(*interest2);
+  BOOST_CHECK_EQUAL(notfound, static_cast<const Data*>(0));
+}
+
+BOOST_AUTO_TEST_CASE(ChildSelector)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c");
+  cs.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/f");
+  cs.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/n");
+  cs.insert(*data7);
+
+  shared_ptr<Interest> interest = make_shared<Interest>("/c");
+  interest->setChildSelector(1);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/n");
+
+  shared_ptr<Interest> interest2 = make_shared<Interest>("/c");
+  interest2->setChildSelector(0);
+
+  const Data* found2 = cs.find(*interest2);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c");
+}
+
+BOOST_AUTO_TEST_CASE(ChildSelector2)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a/b/1");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/a/b/2");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/a/z/1");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/a/z/2");
+  cs.insert(*data4);
+
+  shared_ptr<Interest> interest = make_shared<Interest>("/a");
+  interest->setChildSelector(1);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found->getName(), "/a/z/1");
+}
+
+BOOST_AUTO_TEST_CASE(MustBeFreshSelector)
+{
+  Cs cs;
+
+  Name name("/insert/nonfresh");
+  shared_ptr<Data> data = makeData(name);
+  data->setFreshnessPeriod(time::milliseconds(500));
+  cs.insert(*data);
+
+  sleep(1);
+
+  shared_ptr<Interest> interest = make_shared<Interest>(name);
+  interest->setMustBeFresh(true);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found, static_cast<const Data*>(0));
+}
+
+BOOST_AUTO_TEST_CASE(PublisherKeySelector)
+{
+  Cs cs;
+
+  Name name("/insert/withkey");
+  shared_ptr<Data> data = makeData(name);
+  cs.insert(*data);
+
+  shared_ptr<Interest> interest = make_shared<Interest>(name);
+  Name keyName("/somewhere/key");
+
+  ndn::KeyLocator locator(keyName);
+  interest->setPublisherPublicKeyLocator(locator);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found, static_cast<const Data*>(0));
+}
+
+BOOST_AUTO_TEST_CASE(PublisherKeySelector2)
+{
+  Cs cs;
+  Name name("/insert/withkey");
+  shared_ptr<Data> data = makeData(name);
+  cs.insert(*data);
+
+  Name name2("/insert/withkey2");
+  shared_ptr<Data> data2 = make_shared<Data>(name2);
+
+  Name keyName("/somewhere/key");
+  const ndn::KeyLocator locator(keyName);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue,
+                                        reinterpret_cast<const uint8_t*>(0), 0));
+
+  fakeSignature.setKeyLocator(locator);
+  data2->setSignature(fakeSignature);
+
+  cs.insert(*data2);
+
+  shared_ptr<Interest> interest = make_shared<Interest>(name2);
+  interest->setPublisherPublicKeyLocator(locator);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_NE(found, static_cast<const Data*>(0));
+  BOOST_CHECK_EQUAL(found->getName(), data2->getName());
+}
+
+
+BOOST_AUTO_TEST_CASE(MinMaxComponentsSelector)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  cs.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/6/7/8/9");
+  cs.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1/2/3");
+  cs.insert(*data7);
+
+  shared_ptr<Data> data8 = makeData("/c/c/1");
+  cs.insert(*data8);
+
+  shared_ptr<Interest> interest = make_shared<Interest>("/c/c");
+  interest->setMinSuffixComponents(3);
+  interest->setChildSelector(0);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/c/1/2/3/4/5/6");
+
+  shared_ptr<Interest> interest2 = make_shared<Interest>("/c/c");
+  interest2->setMinSuffixComponents(4);
+  interest2->setChildSelector(1);
+
+  const Data* found2 = cs.find(*interest2);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c/6/7/8/9");
+
+  shared_ptr<Interest> interest3 = make_shared<Interest>("/c/c");
+  interest3->setMaxSuffixComponents(2);
+  interest3->setChildSelector(1);
+
+  const Data* found3 = cs.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), "/c/c/1");
+}
+
+BOOST_AUTO_TEST_CASE(ExcludeSelector)
+{
+  Cs cs;
+
+  shared_ptr<Data> data = makeData("/a");
+  cs.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  cs.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c/a");
+  cs.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  cs.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c");
+  cs.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/f");
+  cs.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/n");
+  cs.insert(*data7);
+
+  shared_ptr<Interest> interest = make_shared<Interest>("/c");
+  interest->setChildSelector(1);
+  Exclude e;
+  e.excludeOne (Name::Component("n"));
+  interest->setExclude(e);
+
+  const Data* found = cs.find(*interest);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/f");
+
+  shared_ptr<Interest> interest2 = make_shared<Interest>("/c");
+  interest2->setChildSelector(0);
+
+  Exclude e2;
+  e2.excludeOne (Name::Component("a"));
+  interest2->setExclude(e2);
+
+  const Data* found2 = cs.find(*interest2);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c");
+
+  shared_ptr<Interest> interest3 = make_shared<Interest>("/c");
+  interest3->setChildSelector(0);
+
+  Exclude e3;
+  e3.excludeOne (Name::Component("c"));
+  interest3->setExclude(e3);
+
+  const Data* found3 = cs.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), "/c/a");
+}
+
+//
+
+class FindFixture : protected BaseFixture
+{
+protected:
+  void
+  insert(uint32_t id, const Name& name)
+  {
+    shared_ptr<Data> data = makeData(name);
+    data->setFreshnessPeriod(time::milliseconds(99999));
+    data->setContent(reinterpret_cast<const uint8_t*>(&id), sizeof(id));
+
+    m_cs.insert(*data);
+  }
+
+  Interest&
+  startInterest(const Name& name)
+  {
+    m_interest = make_shared<Interest>(name);
+    return *m_interest;
+  }
+
+  uint32_t
+  find()
+  {
+    const Data* found = m_cs.find(*m_interest);
+    if (found == 0) {
+      return 0;
+    }
+    const Block& content = found->getContent();
+    if (content.value_size() != sizeof(uint32_t)) {
+      return 0;
+    }
+    return *reinterpret_cast<const uint32_t*>(content.value());
+  }
+
+protected:
+  Cs m_cs;
+  shared_ptr<Interest> m_interest;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Find, FindFixture)
+
+BOOST_AUTO_TEST_CASE(EmptyDataName)
+{
+  insert(1, "ndn:/");
+
+  startInterest("ndn:/");
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInterestName)
+{
+  insert(1, "ndn:/A");
+
+  startInterest("ndn:/");
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Leftmost)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/B/p/1");
+  insert(3, "ndn:/B/p/2");
+  insert(4, "ndn:/B/q/1");
+  insert(5, "ndn:/B/q/2");
+  insert(6, "ndn:/C");
+
+  startInterest("ndn:/B");
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Rightmost)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/B/p/1");
+  insert(3, "ndn:/B/p/2");
+  insert(4, "ndn:/B/q/1");
+  insert(5, "ndn:/B/q/2");
+  insert(6, "ndn:/C");
+
+  startInterest("ndn:/B")
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(Leftmost_ExactName1)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A/B");
+  insert(3, "ndn:/A/C");
+  insert(4, "ndn:/A");
+  insert(5, "ndn:/D");
+
+  // Intuitively you would think Data 4 should be between Data 1 and 2,
+  // but Data 4 has full Name ndn:/A/<32-octet hash>.
+  startInterest("ndn:/A");
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Leftmost_ExactName33)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/A/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); // 33 'B's
+  insert(4, "ndn:/A/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"); // 33 'C's
+  insert(5, "ndn:/D");
+
+  // Data 2 is returned, because <32-octet hash> is less than Data 3.
+  startInterest("ndn:/A");
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(MinSuffixComponents)
+{
+  insert(1, "ndn:/A/1/2/3/4");
+  insert(2, "ndn:/B/1/2/3");
+  insert(3, "ndn:/C/1/2");
+  insert(4, "ndn:/D/1");
+  insert(5, "ndn:/E");
+  insert(6, "ndn:/");
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMinSuffixComponents(7);
+  BOOST_CHECK_EQUAL(find(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(MaxSuffixComponents)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/A/B");
+  insert(4, "ndn:/A/B/C");
+  insert(5, "ndn:/A/B/C/D");
+  insert(6, "ndn:/A/B/C/D/E");
+  // Order is 6,5,4,3,2,1, because <32-octet hash> is greater than a 1-octet component.
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 0);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/")
+    .setMaxSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 6);
+}
+
+BOOST_AUTO_TEST_CASE(DigestOrder)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/A");
+  // We don't know which comes first, but there must be some order
+
+  startInterest("ndn:/A")
+    .setChildSelector(0);
+  uint32_t leftmost = find();
+
+  startInterest("ndn:/A")
+    .setChildSelector(1);
+  uint32_t rightmost = find();
+
+  BOOST_CHECK_NE(leftmost, rightmost);
+}
+
+BOOST_AUTO_TEST_CASE(DigestExclude)
+{
+  insert(1, "ndn:/A/B");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/A/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"); // 33 'C's
+
+  startInterest("ndn:/A")
+    .setExclude(Exclude().excludeBefore(name::Component(reinterpret_cast<const uint8_t*>(
+        "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+        "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"), 31))); // 31 0xFF's
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/A")
+    .setChildSelector(1)
+    .setExclude(Exclude().excludeAfter(name::Component(reinterpret_cast<const uint8_t*>(
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00"), 33))); // 33 0x00's
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(ExactName32)
+{
+  insert(1, "ndn:/A/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); // 32 'B's
+  insert(2, "ndn:/A/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"); // 32 'C's
+
+  startInterest("ndn:/A/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(MinSuffixComponents32)
+{
+  insert(1, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A/1/2/3/4"); // 32 'x's
+  insert(2, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/B/1/2/3");
+  insert(3, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/C/1/2");
+  insert(4, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/D/1");
+  insert(5, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/E");
+  insert(6, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setChildSelector(1)
+    .setMinSuffixComponents(7);
+  BOOST_CHECK_EQUAL(find(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(MaxSuffixComponents32)
+{
+  insert(1, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/"); // 32 'x's
+  insert(2, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A");
+  insert(3, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A/B");
+  insert(4, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A/B/C");
+  insert(5, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A/B/C/D");
+  insert(6, "ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/A/B/C/D/E");
+  // Order is 6,5,4,3,2,1, because <32-octet hash> is greater than a 1-octet component.
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 0);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+    .setMaxSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 6);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Find
+
+BOOST_AUTO_TEST_SUITE_END() // TableCs
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/fib.cpp b/tests/daemon/table/fib.cpp
new file mode 100644
index 0000000..bdf2fca
--- /dev/null
+++ b/tests/daemon/table/fib.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/fib.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TableFib, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Entry)
+{
+  Name prefix("ndn:/pxWhfFza");
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+
+  fib::Entry entry(prefix);
+  BOOST_CHECK_EQUAL(entry.getPrefix(), prefix);
+
+  const fib::NextHopList& nexthops1 = entry.getNextHops();
+  // []
+  BOOST_CHECK_EQUAL(nexthops1.size(), 0);
+
+  entry.addNextHop(face1, 20);
+  const fib::NextHopList& nexthops2 = entry.getNextHops();
+  // [(face1,20)]
+  BOOST_CHECK_EQUAL(nexthops2.size(), 1);
+  BOOST_CHECK_EQUAL(nexthops2.begin()->getFace(), face1);
+  BOOST_CHECK_EQUAL(nexthops2.begin()->getCost(), 20);
+
+  entry.addNextHop(face1, 30);
+  const fib::NextHopList& nexthops3 = entry.getNextHops();
+  // [(face1,30)]
+  BOOST_CHECK_EQUAL(nexthops3.size(), 1);
+  BOOST_CHECK_EQUAL(nexthops3.begin()->getFace(), face1);
+  BOOST_CHECK_EQUAL(nexthops3.begin()->getCost(), 30);
+
+  entry.addNextHop(face2, 40);
+  const fib::NextHopList& nexthops4 = entry.getNextHops();
+  // [(face1,30), (face2,40)]
+  BOOST_CHECK_EQUAL(nexthops4.size(), 2);
+  int i = -1;
+  for (fib::NextHopList::const_iterator it = nexthops4.begin();
+    it != nexthops4.end(); ++it) {
+    ++i;
+    switch (i) {
+      case 0 :
+        BOOST_CHECK_EQUAL(it->getFace(), face1);
+        BOOST_CHECK_EQUAL(it->getCost(), 30);
+        break;
+      case 1 :
+        BOOST_CHECK_EQUAL(it->getFace(), face2);
+        BOOST_CHECK_EQUAL(it->getCost(), 40);
+        break;
+    }
+  }
+
+  entry.addNextHop(face2, 10);
+  const fib::NextHopList& nexthops5 = entry.getNextHops();
+  // [(face2,10), (face1,30)]
+  BOOST_CHECK_EQUAL(nexthops5.size(), 2);
+  i = -1;
+  for (fib::NextHopList::const_iterator it = nexthops5.begin();
+    it != nexthops5.end(); ++it) {
+    ++i;
+    switch (i) {
+      case 0 :
+        BOOST_CHECK_EQUAL(it->getFace(), face2);
+        BOOST_CHECK_EQUAL(it->getCost(), 10);
+        break;
+      case 1 :
+        BOOST_CHECK_EQUAL(it->getFace(), face1);
+        BOOST_CHECK_EQUAL(it->getCost(), 30);
+        break;
+    }
+  }
+
+  entry.removeNextHop(face1);
+  const fib::NextHopList& nexthops6 = entry.getNextHops();
+  // [(face2,10)]
+  BOOST_CHECK_EQUAL(nexthops6.size(), 1);
+  BOOST_CHECK_EQUAL(nexthops6.begin()->getFace(), face2);
+  BOOST_CHECK_EQUAL(nexthops6.begin()->getCost(), 10);
+
+  entry.removeNextHop(face1);
+  const fib::NextHopList& nexthops7 = entry.getNextHops();
+  // [(face2,10)]
+  BOOST_CHECK_EQUAL(nexthops7.size(), 1);
+  BOOST_CHECK_EQUAL(nexthops7.begin()->getFace(), face2);
+  BOOST_CHECK_EQUAL(nexthops7.begin()->getCost(), 10);
+
+  entry.removeNextHop(face2);
+  const fib::NextHopList& nexthops8 = entry.getNextHops();
+  // []
+  BOOST_CHECK_EQUAL(nexthops8.size(), 0);
+
+  entry.removeNextHop(face2);
+  const fib::NextHopList& nexthops9 = entry.getNextHops();
+  // []
+  BOOST_CHECK_EQUAL(nexthops9.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Insert_LongestPrefixMatch)
+{
+  Name nameEmpty;
+  Name nameA   ("ndn:/A");
+  Name nameAB  ("ndn:/A/B");
+  Name nameABC ("ndn:/A/B/C");
+  Name nameABCD("ndn:/A/B/C/D");
+  Name nameE   ("ndn:/E");
+
+  std::pair<shared_ptr<fib::Entry>, bool> insertRes;
+  shared_ptr<fib::Entry> entry;
+
+  NameTree nameTree;
+  Fib fib(nameTree);
+  // []
+  BOOST_CHECK_EQUAL(fib.size(), 0);
+
+  entry = fib.findLongestPrefixMatch(nameA);
+  BOOST_REQUIRE(static_cast<bool>(entry)); // the empty entry
+
+  insertRes = fib.insert(nameEmpty);
+  BOOST_CHECK_EQUAL(insertRes.second, true);
+  BOOST_CHECK_EQUAL(insertRes.first->getPrefix(), nameEmpty);
+  // ['/']
+  BOOST_CHECK_EQUAL(fib.size(), 1);
+
+  entry = fib.findLongestPrefixMatch(nameA);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameEmpty);
+
+  insertRes = fib.insert(nameA);
+  BOOST_CHECK_EQUAL(insertRes.second, true);
+  BOOST_CHECK_EQUAL(insertRes.first->getPrefix(), nameA);
+  // ['/', '/A']
+  BOOST_CHECK_EQUAL(fib.size(), 2);
+
+  insertRes = fib.insert(nameA);
+  BOOST_CHECK_EQUAL(insertRes.second, false);
+  BOOST_CHECK_EQUAL(insertRes.first->getPrefix(), nameA);
+  // ['/', '/A']
+  BOOST_CHECK_EQUAL(fib.size(), 2);
+
+  entry = fib.findLongestPrefixMatch(nameA);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameA);
+
+  entry = fib.findLongestPrefixMatch(nameABCD);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameA);
+
+  insertRes = fib.insert(nameABC);
+  BOOST_CHECK_EQUAL(insertRes.second, true);
+  BOOST_CHECK_EQUAL(insertRes.first->getPrefix(), nameABC);
+  // ['/', '/A', '/A/B/C']
+  BOOST_CHECK_EQUAL(fib.size(), 3);
+
+  entry = fib.findLongestPrefixMatch(nameA);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameA);
+
+  entry = fib.findLongestPrefixMatch(nameAB);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameA);
+
+  entry = fib.findLongestPrefixMatch(nameABCD);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameABC);
+
+  entry = fib.findLongestPrefixMatch(nameE);
+  BOOST_REQUIRE(static_cast<bool>(entry));
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameEmpty);
+}
+
+BOOST_AUTO_TEST_CASE(RemoveNextHopFromAllEntries)
+{
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  Name nameEmpty("ndn:/");
+  Name nameA("ndn:/A");
+  Name nameB("ndn:/B");
+
+  std::pair<shared_ptr<fib::Entry>, bool> insertRes;
+  shared_ptr<fib::Entry> entry;
+
+  NameTree nameTree;
+  Fib fib(nameTree);
+  // {}
+
+  insertRes = fib.insert(nameA);
+  insertRes.first->addNextHop(face1, 0);
+  insertRes.first->addNextHop(face2, 0);
+  // {'/A':[1,2]}
+
+  insertRes = fib.insert(nameB);
+  insertRes.first->addNextHop(face1, 0);
+  // {'/A':[1,2], '/B':[1]}
+  BOOST_CHECK_EQUAL(fib.size(), 2);
+
+  fib.removeNextHopFromAllEntries(face1);
+  // {'/A':[2]}
+  BOOST_CHECK_EQUAL(fib.size(), 1);
+
+  entry = fib.findLongestPrefixMatch(nameA);
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameA);
+  const fib::NextHopList& nexthopsA = entry->getNextHops();
+  BOOST_CHECK_EQUAL(nexthopsA.size(), 1);
+  BOOST_CHECK_EQUAL(nexthopsA.begin()->getFace(), face2);
+
+  entry = fib.findLongestPrefixMatch(nameB);
+  BOOST_CHECK_EQUAL(entry->getPrefix(), nameEmpty);
+}
+
+void
+validateFindExactMatch(const Fib& fib, const Name& target)
+{
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(target);
+  if (static_cast<bool>(entry))
+    {
+      BOOST_CHECK_EQUAL(entry->getPrefix(), target);
+    }
+  else
+    {
+      BOOST_FAIL("No entry found for " << target);
+    }
+}
+
+void
+validateNoExactMatch(const Fib& fib, const Name& target)
+{
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(target);
+  if (static_cast<bool>(entry))
+    {
+      BOOST_FAIL("Found unexpected entry for " << target);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(FindExactMatch)
+{
+  NameTree nameTree;
+  Fib fib(nameTree);
+  fib.insert("/A");
+  fib.insert("/A/B");
+  fib.insert("/A/B/C");
+
+  validateFindExactMatch(fib, "/A");
+  validateFindExactMatch(fib, "/A/B");
+  validateFindExactMatch(fib, "/A/B/C");
+  validateNoExactMatch(fib, "/");
+
+  validateNoExactMatch(fib, "/does/not/exist");
+
+  NameTree gapNameTree;
+  Fib gapFib(nameTree);
+  fib.insert("/X");
+  fib.insert("/X/Y/Z");
+
+  validateNoExactMatch(gapFib, "/X/Y");
+
+  NameTree emptyNameTree;
+  Fib emptyFib(emptyNameTree);
+  validateNoExactMatch(emptyFib, "/nothing/here");
+}
+
+void
+validateRemove(Fib& fib, const Name& target)
+{
+  fib.erase(target);
+
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(target);
+  if (static_cast<bool>(entry))
+    {
+      BOOST_FAIL("Found \"removed\" entry for " << target);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(Remove)
+{
+  NameTree emptyNameTree;
+  Fib emptyFib(emptyNameTree);
+
+  emptyFib.erase("/does/not/exist"); // crash test
+
+  validateRemove(emptyFib, "/");
+
+  emptyFib.erase("/still/does/not/exist"); // crash test
+
+  NameTree nameTree;
+  Fib fib(nameTree);
+  fib.insert("/");
+  fib.insert("/A");
+  fib.insert("/A/B");
+  fib.insert("/A/B/C");
+
+  // check if we remove the right thing and leave
+  // everything else alone
+
+  validateRemove(fib, "/A/B");
+  validateFindExactMatch(fib, "/A");
+  validateFindExactMatch(fib, "/A/B/C");
+  validateFindExactMatch(fib, "/");
+
+  validateRemove(fib, "/A/B/C");
+  validateFindExactMatch(fib, "/A");
+  validateFindExactMatch(fib, "/");
+
+  validateRemove(fib, "/A");
+  validateFindExactMatch(fib, "/");
+
+  validateRemove(fib, "/");
+  validateNoExactMatch(fib, "/");
+
+  NameTree gapNameTree;
+  Fib gapFib(gapNameTree);
+  gapFib.insert("/X");
+  gapFib.insert("/X/Y/Z");
+
+  gapFib.erase("/X/Y"); //should do nothing
+  validateFindExactMatch(gapFib, "/X");
+  validateFindExactMatch(gapFib, "/X/Y/Z");
+}
+
+BOOST_AUTO_TEST_CASE(Iterator)
+{
+  NameTree nameTree;
+  Fib fib(nameTree);
+  Name nameA("/A");
+  Name nameAB("/A/B");
+  Name nameABC("/A/B/C");
+  Name nameRoot("/");
+
+  fib.insert(nameA);
+  fib.insert(nameAB);
+  fib.insert(nameABC);
+  fib.insert(nameRoot);
+
+  std::set<Name> expected;
+  expected.insert(nameA);
+  expected.insert(nameAB);
+  expected.insert(nameABC);
+  expected.insert(nameRoot);
+
+  for (Fib::const_iterator it = fib.begin(); it != fib.end(); it++)
+  {
+    bool isInSet = expected.find(it->getPrefix()) != expected.end();
+    BOOST_CHECK(isInSet);
+    expected.erase(it->getPrefix());
+  }
+
+  BOOST_CHECK_EQUAL(expected.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/measurements-accessor.cpp b/tests/daemon/table/measurements-accessor.cpp
new file mode 100644
index 0000000..6dc8ff3
--- /dev/null
+++ b/tests/daemon/table/measurements-accessor.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/measurements-accessor.hpp"
+#include "fw/strategy.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TableMeasurementsAccessor, BaseFixture)
+
+class MeasurementsAccessorTestStrategy : public fw::Strategy
+{
+public:
+  MeasurementsAccessorTestStrategy(Forwarder& forwarder, const Name& name)
+    : Strategy(forwarder, name)
+  {
+  }
+
+  virtual
+  ~MeasurementsAccessorTestStrategy()
+
+  {
+  }
+
+  virtual void
+  afterReceiveInterest(const Face& inFace,
+                       const Interest& interest,
+                       shared_ptr<fib::Entry> fibEntry,
+                       shared_ptr<pit::Entry> pitEntry)
+  {
+    BOOST_ASSERT(false);
+  }
+
+public: // accessors
+  MeasurementsAccessor&
+  getMeasurements_accessor()
+  {
+    return this->getMeasurements();
+  }
+};
+
+BOOST_AUTO_TEST_CASE(Access)
+{
+  Forwarder forwarder;
+
+  shared_ptr<MeasurementsAccessorTestStrategy> strategy1 =
+    make_shared<MeasurementsAccessorTestStrategy>(boost::ref(forwarder), "ndn:/strategy1");
+  shared_ptr<MeasurementsAccessorTestStrategy> strategy2 =
+    make_shared<MeasurementsAccessorTestStrategy>(boost::ref(forwarder), "ndn:/strategy2");
+
+  Name nameRoot("ndn:/");
+  Name nameA   ("ndn:/A");
+  Name nameAB  ("ndn:/A/B");
+  Name nameABC ("ndn:/A/B/C");
+  Name nameAD  ("ndn:/A/D");
+
+  StrategyChoice& strategyChoice = forwarder.getStrategyChoice();
+  strategyChoice.install(strategy1);
+  strategyChoice.install(strategy2);
+  strategyChoice.insert(nameRoot, strategy1->getName());
+  strategyChoice.insert(nameA   , strategy2->getName());
+  strategyChoice.insert(nameAB  , strategy1->getName());
+
+  MeasurementsAccessor& accessor1 = strategy1->getMeasurements_accessor();
+  MeasurementsAccessor& accessor2 = strategy2->getMeasurements_accessor();
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor1.get(nameRoot)), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor1.get(nameA   )), false);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor1.get(nameAB  )), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor1.get(nameABC )), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor1.get(nameAD  )), false);
+
+  shared_ptr<measurements::Entry> entryRoot = forwarder.getMeasurements().get(nameRoot);
+  BOOST_CHECK_NO_THROW(accessor1.getParent(entryRoot));
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor2.get(nameRoot)), false);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor2.get(nameA   )), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor2.get(nameAB  )), false);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor2.get(nameABC )), false);
+  BOOST_CHECK_EQUAL(static_cast<bool>(accessor2.get(nameAD  )), true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/measurements.cpp b/tests/daemon/table/measurements.cpp
new file mode 100644
index 0000000..98676b5
--- /dev/null
+++ b/tests/daemon/table/measurements.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/measurements.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TableMeasurements, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Get_Parent)
+{
+  NameTree nameTree;
+  Measurements measurements(nameTree);
+
+  Name name0;
+  Name nameA ("ndn:/A");
+  Name nameAB("ndn:/A/B");
+
+  shared_ptr<measurements::Entry> entryAB = measurements.get(nameAB);
+  BOOST_REQUIRE(static_cast<bool>(entryAB));
+  BOOST_CHECK_EQUAL(entryAB->getName(), nameAB);
+
+  shared_ptr<measurements::Entry> entry0 = measurements.get(name0);
+  BOOST_REQUIRE(static_cast<bool>(entry0));
+
+  shared_ptr<measurements::Entry> entryA = measurements.getParent(entryAB);
+  BOOST_REQUIRE(static_cast<bool>(entryA));
+  BOOST_CHECK_EQUAL(entryA->getName(), nameA);
+
+  shared_ptr<measurements::Entry> entry0c = measurements.getParent(entryA);
+  BOOST_CHECK_EQUAL(entry0, entry0c);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/name-tree.cpp b/tests/daemon/table/name-tree.cpp
new file mode 100644
index 0000000..e5e9089
--- /dev/null
+++ b/tests/daemon/table/name-tree.cpp
@@ -0,0 +1,801 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/name-tree.hpp"
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+using name_tree::Entry;
+
+BOOST_FIXTURE_TEST_SUITE(TableNameTree, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Hash)
+{
+  Name root("/");
+  root.wireEncode();
+  size_t hashValue = name_tree::computeHash(root);
+  BOOST_CHECK_EQUAL(hashValue, static_cast<size_t>(0));
+
+  Name prefix("/nohello/world/ndn/research");
+  prefix.wireEncode();
+  std::vector<size_t> hashSet = name_tree::computeHashSet(prefix);
+  BOOST_CHECK_EQUAL(hashSet.size(), prefix.size() + 1);
+}
+
+BOOST_AUTO_TEST_CASE(Entry)
+{
+  Name prefix("ndn:/named-data/research/abc/def/ghi");
+
+  shared_ptr<name_tree::Entry> npe = make_shared<name_tree::Entry>(prefix);
+  BOOST_CHECK_EQUAL(npe->getPrefix(), prefix);
+
+  // examine all the get methods
+
+  size_t hash = npe->getHash();
+  BOOST_CHECK_EQUAL(hash, static_cast<size_t>(0));
+
+  shared_ptr<name_tree::Entry> parent = npe->getParent();
+  BOOST_CHECK(!static_cast<bool>(parent));
+
+  std::vector<shared_ptr<name_tree::Entry> >& childList = npe->getChildren();
+  BOOST_CHECK_EQUAL(childList.size(), static_cast<size_t>(0));
+
+  shared_ptr<fib::Entry> fib = npe->getFibEntry();
+  BOOST_CHECK(!static_cast<bool>(fib));
+
+  const std::vector< shared_ptr<pit::Entry> >& pitList = npe->getPitEntries();
+  BOOST_CHECK_EQUAL(pitList.size(), static_cast<size_t>(0));
+
+  // examine all the set method
+
+  npe->setHash(static_cast<size_t>(12345));
+  BOOST_CHECK_EQUAL(npe->getHash(), static_cast<size_t>(12345));
+
+  Name parentName("ndn:/named-data/research/abc/def");
+  parent = make_shared<name_tree::Entry>(parentName);
+  npe->setParent(parent);
+  BOOST_CHECK_EQUAL(npe->getParent(), parent);
+
+  // Insert FIB
+
+  shared_ptr<fib::Entry> fibEntry(new fib::Entry(prefix));
+  shared_ptr<fib::Entry> fibEntryParent(new fib::Entry(parentName));
+
+  npe->setFibEntry(fibEntry);
+  BOOST_CHECK_EQUAL(npe->getFibEntry(), fibEntry);
+
+  npe->setFibEntry(shared_ptr<fib::Entry>());
+  BOOST_CHECK(!static_cast<bool>(npe->getFibEntry()));
+
+  // Insert a PIT
+
+  shared_ptr<pit::Entry> PitEntry(make_shared<pit::Entry>(prefix));
+  shared_ptr<pit::Entry> PitEntry2(make_shared<pit::Entry>(parentName));
+
+  npe->insertPitEntry(PitEntry);
+  BOOST_CHECK_EQUAL(npe->getPitEntries().size(), 1);
+
+  npe->insertPitEntry(PitEntry2);
+  BOOST_CHECK_EQUAL(npe->getPitEntries().size(), 2);
+
+  npe->erasePitEntry(PitEntry);
+  BOOST_CHECK_EQUAL(npe->getPitEntries().size(), 1);
+
+  npe->erasePitEntry(PitEntry2);
+  BOOST_CHECK_EQUAL(npe->getPitEntries().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(NameTreeBasic)
+{
+  size_t nBuckets = 16;
+  NameTree nt(nBuckets);
+
+  BOOST_CHECK_EQUAL(nt.size(), 0);
+  BOOST_CHECK_EQUAL(nt.getNBuckets(), nBuckets);
+
+  Name nameABC("ndn:/a/b/c");
+  shared_ptr<name_tree::Entry> npeABC = nt.lookup(nameABC);
+  BOOST_CHECK_EQUAL(nt.size(), 4);
+
+  Name nameABD("/a/b/d");
+  shared_ptr<name_tree::Entry> npeABD = nt.lookup(nameABD);
+  BOOST_CHECK_EQUAL(nt.size(), 5);
+
+  Name nameAE("/a/e/");
+  shared_ptr<name_tree::Entry> npeAE = nt.lookup(nameAE);
+  BOOST_CHECK_EQUAL(nt.size(), 6);
+
+  Name nameF("/f");
+  shared_ptr<name_tree::Entry> npeF = nt.lookup(nameF);
+  BOOST_CHECK_EQUAL(nt.size(), 7);
+
+  // validate lookup() and findExactMatch()
+
+  Name nameAB ("/a/b");
+  BOOST_CHECK_EQUAL(npeABC->getParent(), nt.findExactMatch(nameAB));
+  BOOST_CHECK_EQUAL(npeABD->getParent(), nt.findExactMatch(nameAB));
+
+  Name nameA ("/a");
+  BOOST_CHECK_EQUAL(npeAE->getParent(), nt.findExactMatch(nameA));
+
+  Name nameRoot ("/");
+  BOOST_CHECK_EQUAL(npeF->getParent(), nt.findExactMatch(nameRoot));
+  BOOST_CHECK_EQUAL(nt.size(), 7);
+
+  Name name0("/does/not/exist");
+  shared_ptr<name_tree::Entry> npe0 = nt.findExactMatch(name0);
+  BOOST_CHECK(!static_cast<bool>(npe0));
+
+
+  // Longest Prefix Matching
+  shared_ptr<name_tree::Entry> temp;
+  Name nameABCLPM("/a/b/c/def/asdf/nlf");
+  temp = nt.findLongestPrefixMatch(nameABCLPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameABC));
+
+  Name nameABDLPM("/a/b/d/def/asdf/nlf");
+  temp = nt.findLongestPrefixMatch(nameABDLPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameABD));
+
+  Name nameABLPM("/a/b/hello/world");
+  temp = nt.findLongestPrefixMatch(nameABLPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameAB));
+
+  Name nameAELPM("/a/e/hello/world");
+  temp = nt.findLongestPrefixMatch(nameAELPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameAE));
+
+  Name nameALPM("/a/hello/world");
+  temp = nt.findLongestPrefixMatch(nameALPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameA));
+
+  Name nameFLPM("/f/hello/world");
+  temp = nt.findLongestPrefixMatch(nameFLPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameF));
+
+  Name nameRootLPM("/does_not_exist");
+  temp = nt.findLongestPrefixMatch(nameRootLPM);
+  BOOST_CHECK_EQUAL(temp, nt.findExactMatch(nameRoot));
+
+  bool eraseResult = false;
+  temp = nt.findExactMatch(nameABC);
+  if (static_cast<bool>(temp))
+    eraseResult = nt.
+  eraseEntryIfEmpty(temp);
+  BOOST_CHECK_EQUAL(nt.size(), 6);
+  BOOST_CHECK(!static_cast<bool>(nt.findExactMatch(nameABC)));
+  BOOST_CHECK_EQUAL(eraseResult, true);
+
+  eraseResult = false;
+  temp = nt.findExactMatch(nameABCLPM);
+  if (static_cast<bool>(temp))
+    eraseResult = nt.
+  eraseEntryIfEmpty(temp);
+  BOOST_CHECK(!static_cast<bool>(temp));
+  BOOST_CHECK_EQUAL(nt.size(), 6);
+  BOOST_CHECK_EQUAL(eraseResult, false);
+
+  // nt.dump(std::cout);
+
+  nt.lookup(nameABC);
+  BOOST_CHECK_EQUAL(nt.size(), 7);
+
+  eraseResult = false;
+  temp = nt.findExactMatch(nameABC);
+  if (static_cast<bool>(temp))
+    eraseResult = nt.
+  eraseEntryIfEmpty(temp);
+  BOOST_CHECK_EQUAL(nt.size(), 6);
+  BOOST_CHECK_EQUAL(eraseResult, true);
+  BOOST_CHECK(!static_cast<bool>(nt.findExactMatch(nameABC)));
+
+  BOOST_CHECK_EQUAL(nt.getNBuckets(), 16);
+
+  // should resize now
+  Name nameABCD("a/b/c/d");
+  nt.lookup(nameABCD);
+  Name nameABCDE("a/b/c/d/e");
+  nt.lookup(nameABCDE);
+  BOOST_CHECK_EQUAL(nt.size(), 9);
+  BOOST_CHECK_EQUAL(nt.getNBuckets(), 32);
+
+  // try to erase /a/b/c, should return false
+  temp = nt.findExactMatch(nameABC);
+  BOOST_CHECK_EQUAL(temp->getPrefix(), nameABC);
+  eraseResult = nt.
+  eraseEntryIfEmpty(temp);
+  BOOST_CHECK_EQUAL(eraseResult, false);
+  temp = nt.findExactMatch(nameABC);
+  BOOST_CHECK_EQUAL(temp->getPrefix(), nameABC);
+
+  temp = nt.findExactMatch(nameABD);
+  if (static_cast<bool>(temp))
+    nt.
+  eraseEntryIfEmpty(temp);
+  BOOST_CHECK_EQUAL(nt.size(), 8);
+}
+
+BOOST_AUTO_TEST_CASE(NameTreeIteratorFullEnumerate)
+{
+  size_t nBuckets = 1024;
+  NameTree nt(nBuckets);
+
+  BOOST_CHECK_EQUAL(nt.size(), 0);
+  BOOST_CHECK_EQUAL(nt.getNBuckets(), nBuckets);
+
+  Name nameABC("ndn:/a/b/c");
+  shared_ptr<name_tree::Entry> npeABC = nt.lookup(nameABC);
+  BOOST_CHECK_EQUAL(nt.size(), 4);
+
+  Name nameABD("/a/b/d");
+  shared_ptr<name_tree::Entry> npeABD = nt.lookup(nameABD);
+  BOOST_CHECK_EQUAL(nt.size(), 5);
+
+  Name nameAE("/a/e/");
+  shared_ptr<name_tree::Entry> npeAE = nt.lookup(nameAE);
+  BOOST_CHECK_EQUAL(nt.size(), 6);
+
+  Name nameF("/f");
+  shared_ptr<name_tree::Entry> npeF = nt.lookup(nameF);
+  BOOST_CHECK_EQUAL(nt.size(), 7);
+
+  Name nameRoot("/");
+  shared_ptr<name_tree::Entry> npeRoot = nt.lookup(nameRoot);
+  BOOST_CHECK_EQUAL(nt.size(), 7);
+
+  Name nameA("/a");
+  Name nameAB("/a/b");
+
+  bool hasRoot  = false;
+  bool hasA     = false;
+  bool hasAB    = false;
+  bool hasABC   = false;
+  bool hasABD   = false;
+  bool hasAE    = false;
+  bool hasF     = false;
+
+  int counter = 0;
+  NameTree::const_iterator it = nt.fullEnumerate();
+
+  for(; it != nt.end(); it++)
+  {
+    counter++;
+
+    if (it->getPrefix() == nameRoot)
+      hasRoot = true;
+    if (it->getPrefix() == nameA)
+      hasA    = true;
+    if (it->getPrefix() == nameAB)
+      hasAB   = true;
+    if (it->getPrefix() == nameABC)
+      hasABC  = true;
+    if (it->getPrefix() == nameABD)
+      hasABD  = true;
+    if (it->getPrefix() == nameAE)
+      hasAE   = true;
+    if (it->getPrefix() == nameF)
+      hasF    = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasRoot , true);
+  BOOST_CHECK_EQUAL(hasA    , true);
+  BOOST_CHECK_EQUAL(hasAB   , true);
+  BOOST_CHECK_EQUAL(hasABC  , true);
+  BOOST_CHECK_EQUAL(hasABD  , true);
+  BOOST_CHECK_EQUAL(hasAE   , true);
+  BOOST_CHECK_EQUAL(hasF    , true);
+
+  BOOST_CHECK_EQUAL(counter , 7);
+}
+
+// Predicates for testing the partial enumerate function
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_NameA_Only(const name_tree::Entry& entry)
+{
+  Name nameA("/a");
+  bool first = nameA.equals(entry.getPrefix());
+  return std::make_pair(first, true);
+}
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_Except_NameA(const name_tree::Entry& entry)
+{
+  Name nameA("/a");
+  bool first = !(nameA.equals(entry.getPrefix()));
+  return std::make_pair(first, true);
+}
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_NoSubNameA(const name_tree::Entry& entry)
+{
+  Name nameA("/a");
+  bool second = !(nameA.equals(entry.getPrefix()));
+  return std::make_pair(true, second);
+}
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_NoNameA_NoSubNameAB(const name_tree::Entry& entry)
+{
+  Name nameA("/a");
+  Name nameAB("/a/b");
+  bool first = !(nameA.equals(entry.getPrefix()));
+  bool second = !(nameAB.equals(entry.getPrefix()));
+  return std::make_pair(first, second);
+}
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_NoNameA_NoSubNameAC(const name_tree::Entry& entry)
+{
+  Name nameA("/a");
+  Name nameAC("/a/c");
+  bool first = !(nameA.equals(entry.getPrefix()));
+  bool second = !(nameAC.equals(entry.getPrefix()));
+  return std::make_pair(first, second);
+}
+
+static inline std::pair<bool, bool>
+predicate_NameTreeEntry_Example(const name_tree::Entry& entry)
+{
+  Name nameRoot("/");
+  Name nameA("/a");
+  Name nameAB("/a/b");
+  Name nameABC("/a/b/c");
+  Name nameAD("/a/d");
+  Name nameE("/e");
+  Name nameF("/f");
+
+  bool first = false;
+  bool second = false;
+
+  Name name = entry.getPrefix();
+
+  if (name == nameRoot || name == nameAB || name == nameABC || name == nameAD)
+  {
+    first = true;
+  }
+
+  if(name == nameRoot || name == nameA || name == nameF)
+  {
+    second = true;
+  }
+
+  return std::make_pair(first, second);
+}
+
+BOOST_AUTO_TEST_CASE(NameTreeIteratorPartialEnumerate)
+{
+  typedef NameTree::const_iterator const_iterator;
+
+  size_t nBuckets = 16;
+  NameTree nameTree(nBuckets);
+  int counter = 0;
+
+  // empty nameTree, should return end();
+  Name nameA("/a");
+  bool hasA = false;
+  const_iterator itA = nameTree.partialEnumerate(nameA);
+  BOOST_CHECK(itA == nameTree.end());
+
+  // We have "/", "/a", "/a/b", "a/c" now.
+  Name nameAB("/a/b");
+  bool hasAB = false;
+  nameTree.lookup(nameAB);
+
+  Name nameAC("/a/c");
+  bool hasAC = false;
+  nameTree.lookup(nameAC);
+  BOOST_CHECK_EQUAL(nameTree.size(), 4);
+
+  // Enumerate on some name that is not in nameTree
+  Name name0="/0";
+  const_iterator it0 = nameTree.partialEnumerate(name0);
+  BOOST_CHECK(it0 == nameTree.end());
+
+  // Accept "root" nameA only
+  const_iterator itAOnly = nameTree.partialEnumerate(nameA, &predicate_NameTreeEntry_NameA_Only);
+  BOOST_CHECK_EQUAL(itAOnly->getPrefix(), nameA);
+  BOOST_CHECK(++itAOnly == nameTree.end());
+
+  // Accept anything except "root" nameA
+  const_iterator itExceptA = nameTree.partialEnumerate(nameA, &predicate_NameTreeEntry_Except_NameA);
+  hasA = false;
+  hasAB = false;
+  hasAC = false;
+
+  counter = 0;
+  for (; itExceptA != nameTree.end(); itExceptA++)
+  {
+    counter++;
+
+    if (itExceptA->getPrefix() == nameA)
+      hasA  = true;
+
+    if (itExceptA->getPrefix() == nameAB)
+      hasAB = true;
+
+    if (itExceptA->getPrefix() == nameAC)
+      hasAC = true;
+  }
+  BOOST_CHECK_EQUAL(hasA,  false);
+  BOOST_CHECK_EQUAL(hasAB, true);
+  BOOST_CHECK_EQUAL(hasAC, true);
+
+  BOOST_CHECK_EQUAL(counter, 2);
+
+  Name nameAB1("a/b/1");
+  bool hasAB1 = false;
+  nameTree.lookup(nameAB1);
+
+  Name nameAB2("a/b/2");
+  bool hasAB2 = false;
+  nameTree.lookup(nameAB2);
+
+  Name nameAC1("a/c/1");
+  bool hasAC1 = false;
+  nameTree.lookup(nameAC1);
+
+  Name nameAC2("a/c/2");
+  bool hasAC2 = false;
+  nameTree.lookup(nameAC2);
+
+  BOOST_CHECK_EQUAL(nameTree.size(), 8);
+
+  // No NameA
+  // No SubTree from NameAB
+  const_iterator itNoNameANoSubNameAB = nameTree.partialEnumerate(nameA, &predicate_NameTreeEntry_NoNameA_NoSubNameAB);
+  hasA   = false;
+  hasAB  = false;
+  hasAB1 = false;
+  hasAB2 = false;
+  hasAC  = false;
+  hasAC1 = false;
+  hasAC2 = false;
+
+  counter = 0;
+
+  for (; itNoNameANoSubNameAB != nameTree.end(); itNoNameANoSubNameAB++)
+  {
+    counter++;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameA)
+      hasA   = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAB)
+      hasAB  = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAB1)
+      hasAB1 = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAB2)
+      hasAB2 = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAC)
+      hasAC  = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAC1)
+      hasAC1 = true;
+
+    if (itNoNameANoSubNameAB->getPrefix() == nameAC2)
+      hasAC2 = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasA,   false);
+  BOOST_CHECK_EQUAL(hasAB,  true);
+  BOOST_CHECK_EQUAL(hasAB1, false);
+  BOOST_CHECK_EQUAL(hasAB2, false);
+  BOOST_CHECK_EQUAL(hasAC,  true);
+  BOOST_CHECK_EQUAL(hasAC1, true);
+  BOOST_CHECK_EQUAL(hasAC2, true);
+
+  BOOST_CHECK_EQUAL(counter, 4);
+
+  // No NameA
+  // No SubTree from NameAC
+  const_iterator itNoNameANoSubNameAC = nameTree.partialEnumerate(nameA, &predicate_NameTreeEntry_NoNameA_NoSubNameAC);
+  hasA   = false;
+  hasAB  = false;
+  hasAB1 = false;
+  hasAB2 = false;
+  hasAC  = false;
+  hasAC1 = false;
+  hasAC2 = false;
+
+  counter = 0;
+
+  for (; itNoNameANoSubNameAC != nameTree.end(); itNoNameANoSubNameAC++)
+  {
+    counter++;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameA)
+      hasA   = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAB)
+      hasAB  = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAB1)
+      hasAB1 = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAB2)
+      hasAB2 = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAC)
+      hasAC  = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAC1)
+      hasAC1 = true;
+
+    if (itNoNameANoSubNameAC->getPrefix() == nameAC2)
+      hasAC2 = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasA,   false);
+  BOOST_CHECK_EQUAL(hasAB,  true);
+  BOOST_CHECK_EQUAL(hasAB1, true);
+  BOOST_CHECK_EQUAL(hasAB2, true);
+  BOOST_CHECK_EQUAL(hasAC,  true);
+  BOOST_CHECK_EQUAL(hasAC1, false);
+  BOOST_CHECK_EQUAL(hasAC2, false);
+
+  BOOST_CHECK_EQUAL(counter, 4);
+
+  // No Subtree from NameA
+  const_iterator itNoANoSubNameA = nameTree.partialEnumerate(nameA, &predicate_NameTreeEntry_NoSubNameA);
+
+  hasA   = false;
+  hasAB  = false;
+  hasAB1 = false;
+  hasAB2 = false;
+  hasAC  = false;
+  hasAC1 = false;
+  hasAC2 = false;
+
+  counter = 0;
+
+  for (; itNoANoSubNameA != nameTree.end(); itNoANoSubNameA++)
+  {
+    counter++;
+
+    if (itNoANoSubNameA->getPrefix() == nameA)
+      hasA   = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAB)
+      hasAB  = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAB1)
+      hasAB1 = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAB2)
+      hasAB2 = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAC)
+      hasAC  = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAC1)
+      hasAC1 = true;
+
+    if (itNoANoSubNameA->getPrefix() == nameAC2)
+      hasAC2 = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasA,   true);
+  BOOST_CHECK_EQUAL(hasAB,  false);
+  BOOST_CHECK_EQUAL(hasAB1, false);
+  BOOST_CHECK_EQUAL(hasAB2, false);
+  BOOST_CHECK_EQUAL(hasAC,  false);
+  BOOST_CHECK_EQUAL(hasAC1, false);
+  BOOST_CHECK_EQUAL(hasAC2, false);
+
+  BOOST_CHECK_EQUAL(counter, 1);
+
+  // Example
+  // /
+  // /A
+  // /A/B x
+  // /A/B/C
+  // /A/D x
+  // /E
+  // /F
+
+  NameTree nameTreeExample(nBuckets);
+
+  Name nameRoot("/");
+  bool hasRoot = false;
+
+  nameTreeExample.lookup(nameA);
+  hasA = false;
+  nameTreeExample.lookup(nameAB);
+  hasAB = false;
+
+  Name nameABC("a/b/c");
+  bool hasABC = false;
+  nameTreeExample.lookup(nameABC);
+
+  Name nameAD("/a/d");
+  nameTreeExample.lookup(nameAD);
+  bool hasAD = false;
+
+  Name nameE("/e");
+  nameTreeExample.lookup(nameE);
+  bool hasE = false;
+
+  Name nameF("/f");
+  nameTreeExample.lookup(nameF);
+  bool hasF = false;
+
+  counter = 0;
+  const_iterator itExample = nameTreeExample.partialEnumerate(nameA, &predicate_NameTreeEntry_Example);
+
+  for (; itExample != nameTreeExample.end(); itExample++)
+  {
+    counter++;
+
+    if (itExample->getPrefix() == nameRoot)
+      hasRoot = true;
+
+    if (itExample->getPrefix() == nameA)
+     hasA     = true;
+
+    if (itExample->getPrefix() == nameAB)
+     hasAB    = true;
+
+    if (itExample->getPrefix() == nameABC)
+     hasABC   = true;
+
+    if (itExample->getPrefix() == nameAD)
+     hasAD    = true;
+
+    if (itExample->getPrefix() == nameE)
+     hasE     = true;
+
+    if (itExample->getPrefix() == nameF)
+      hasF    = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasRoot, false);
+  BOOST_CHECK_EQUAL(hasA,    false);
+  BOOST_CHECK_EQUAL(hasAB,   true);
+  BOOST_CHECK_EQUAL(hasABC,  false);
+  BOOST_CHECK_EQUAL(hasAD,   true);
+  BOOST_CHECK_EQUAL(hasE,    false);
+  BOOST_CHECK_EQUAL(hasF,    false);
+
+  BOOST_CHECK_EQUAL(counter, 2);
+}
+
+
+BOOST_AUTO_TEST_CASE(NameTreeIteratorFindAllMatches)
+{
+  size_t nBuckets = 16;
+  NameTree nt(nBuckets);
+  int counter = 0;
+
+  Name nameABCDEF("a/b/c/d/e/f");
+  shared_ptr<name_tree::Entry> npeABCDEF = nt.lookup(nameABCDEF);
+
+  Name nameAAC("a/a/c");
+  shared_ptr<name_tree::Entry> npeAAC = nt.lookup(nameAAC);
+
+  Name nameAAD1("a/a/d/1");
+  shared_ptr<name_tree::Entry> npeAAD1 = nt.lookup(nameAAD1);
+
+  Name nameAAD2("a/a/d/2");
+  shared_ptr<name_tree::Entry> npeAAD2 = nt.lookup(nameAAD2);
+
+
+  BOOST_CHECK_EQUAL(nt.size(), 12);
+
+  Name nameRoot ("/");
+  Name nameA    ("/a");
+  Name nameAB   ("/a/b");
+  Name nameABC  ("/a/b/c");
+  Name nameABCD ("/a/b/c/d");
+  Name nameABCDE("/a/b/c/d/e");
+  Name nameAA   ("/a/a");
+  Name nameAAD  ("/a/a/d");
+
+  bool hasRoot    = false;
+  bool hasA       = false;
+  bool hasAB      = false;
+  bool hasABC     = false;
+  bool hasABCD    = false;
+  bool hasABCDE   = false;
+  bool hasABCDEF  = false;
+  bool hasAA      = false;
+  bool hasAAC     = false;
+  bool hasAAD     = false;
+  bool hasAAD1    = false;
+  bool hasAAD2    = false;
+
+  counter = 0;
+
+  for (NameTree::const_iterator it = nt.findAllMatches(nameABCDEF);
+      it != nt.end(); it++)
+  {
+    counter++;
+
+    if (it->getPrefix() == nameRoot)
+      hasRoot   = true;
+    if (it->getPrefix() == nameA)
+      hasA      = true;
+    if (it->getPrefix() == nameAB)
+      hasAB     = true;
+    if (it->getPrefix() == nameABC)
+      hasABC    = true;
+    if (it->getPrefix() == nameABCD)
+      hasABCD   = true;
+    if (it->getPrefix() == nameABCDE)
+      hasABCDE  = true;
+    if (it->getPrefix() == nameABCDEF)
+      hasABCDEF = true;
+    if (it->getPrefix() == nameAA)
+      hasAA     = true;
+    if (it->getPrefix() == nameAAC)
+      hasAAC    = true;
+    if (it->getPrefix() == nameAAD)
+      hasAAD    = true;
+    if (it->getPrefix() == nameAAD1)
+      hasAAD1   = true;
+    if (it->getPrefix() == nameAAD2)
+      hasAAD2   = true;
+  }
+
+  BOOST_CHECK_EQUAL(hasRoot   , true);
+  BOOST_CHECK_EQUAL(hasA      , true);
+  BOOST_CHECK_EQUAL(hasAB     , true);
+  BOOST_CHECK_EQUAL(hasABC    , true);
+  BOOST_CHECK_EQUAL(hasABCD   , true);
+  BOOST_CHECK_EQUAL(hasABCDE  , true);
+  BOOST_CHECK_EQUAL(hasABCDEF , true);
+  BOOST_CHECK_EQUAL(hasAA     , false);
+  BOOST_CHECK_EQUAL(hasAAC    , false);
+  BOOST_CHECK_EQUAL(hasAAD    , false);
+  BOOST_CHECK_EQUAL(hasAAD1   , false);
+  BOOST_CHECK_EQUAL(hasAAD2   , false);
+
+  BOOST_CHECK_EQUAL(counter, 7);
+}
+
+BOOST_AUTO_TEST_CASE(NameTreeHashTableResizeShrink)
+{
+  size_t nBuckets = 16;
+  NameTree nameTree(nBuckets);
+
+  Name prefix("/a/b/c/d/e/f/g/h"); // requires 9 buckets
+
+  shared_ptr<name_tree::Entry> entry = nameTree.lookup(prefix);
+  BOOST_CHECK_EQUAL(nameTree.size(), 9);
+  BOOST_CHECK_EQUAL(nameTree.getNBuckets(), 32);
+
+  nameTree.eraseEntryIfEmpty(entry);
+  BOOST_CHECK_EQUAL(nameTree.size(), 0);
+  BOOST_CHECK_EQUAL(nameTree.getNBuckets(), 16);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/pit.cpp b/tests/daemon/table/pit.cpp
new file mode 100644
index 0000000..c77c909
--- /dev/null
+++ b/tests/daemon/table/pit.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/pit.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TablePit, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(EntryInOutRecords)
+{
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  Name name("ndn:/KuYfjtRq");
+  shared_ptr<Interest> interest  = makeInterest(name);
+  shared_ptr<Interest> interest1 = makeInterest(name);
+  interest1->setInterestLifetime(time::milliseconds(2528));
+  interest1->setNonce(25559);
+  shared_ptr<Interest> interest2 = makeInterest(name);
+  interest2->setInterestLifetime(time::milliseconds(6464));
+  interest2->setNonce(19004);
+  shared_ptr<Interest> interest3 = makeInterest(name);
+  interest3->setInterestLifetime(time::milliseconds(3585));
+  interest3->setNonce(24216);
+  shared_ptr<Interest> interest4 = makeInterest(name);
+  interest4->setInterestLifetime(time::milliseconds(8795));
+  interest4->setNonce(17365);
+
+  pit::Entry entry(*interest);
+
+  BOOST_CHECK_EQUAL(entry.getInterest().getName(), name);
+  BOOST_CHECK_EQUAL(entry.getName(), name);
+
+  const pit::InRecordCollection& inRecords1 = entry.getInRecords();
+  BOOST_CHECK_EQUAL(inRecords1.size(), 0);
+  const pit::OutRecordCollection& outRecords1 = entry.getOutRecords();
+  BOOST_CHECK_EQUAL(outRecords1.size(), 0);
+
+  // insert InRecord
+  time::steady_clock::TimePoint before1 = time::steady_clock::now();
+  pit::InRecordCollection::iterator in1 =
+    entry.insertOrUpdateInRecord(face1, *interest1);
+  time::steady_clock::TimePoint after1 = time::steady_clock::now();
+  const pit::InRecordCollection& inRecords2 = entry.getInRecords();
+  BOOST_CHECK_EQUAL(inRecords2.size(), 1);
+  BOOST_CHECK(in1 == inRecords2.begin());
+  BOOST_CHECK_EQUAL(in1->getFace(), face1);
+  BOOST_CHECK_EQUAL(in1->getLastNonce(), interest1->getNonce());
+  BOOST_CHECK_GE(in1->getLastRenewed(), before1);
+  BOOST_CHECK_LE(in1->getLastRenewed(), after1);
+  BOOST_CHECK_LE(in1->getExpiry() - in1->getLastRenewed()
+                 - interest1->getInterestLifetime(),
+                 (after1 - before1));
+
+  // insert OutRecord
+  time::steady_clock::TimePoint before2 = time::steady_clock::now();
+  pit::OutRecordCollection::iterator out1 =
+    entry.insertOrUpdateOutRecord(face1, *interest1);
+  time::steady_clock::TimePoint after2 = time::steady_clock::now();
+  const pit::OutRecordCollection& outRecords2 = entry.getOutRecords();
+  BOOST_CHECK_EQUAL(outRecords2.size(), 1);
+  BOOST_CHECK(out1 == outRecords2.begin());
+  BOOST_CHECK_EQUAL(out1->getFace(), face1);
+  BOOST_CHECK_EQUAL(out1->getLastNonce(), interest1->getNonce());
+  BOOST_CHECK_GE(out1->getLastRenewed(), before2);
+  BOOST_CHECK_LE(out1->getLastRenewed(), after2);
+  BOOST_CHECK_LE(out1->getExpiry() - out1->getLastRenewed()
+                 - interest1->getInterestLifetime(),
+                 (after2 - before2));
+
+  // update InRecord
+  time::steady_clock::TimePoint before3 = time::steady_clock::now();
+  pit::InRecordCollection::iterator in2 =
+    entry.insertOrUpdateInRecord(face1, *interest2);
+  time::steady_clock::TimePoint after3 = time::steady_clock::now();
+  const pit::InRecordCollection& inRecords3 = entry.getInRecords();
+  BOOST_CHECK_EQUAL(inRecords3.size(), 1);
+  BOOST_CHECK(in2 == inRecords3.begin());
+  BOOST_CHECK_EQUAL(in2->getFace(), face1);
+  BOOST_CHECK_EQUAL(in2->getLastNonce(), interest2->getNonce());
+  BOOST_CHECK_LE(in2->getExpiry() - in2->getLastRenewed()
+                 - interest2->getInterestLifetime(),
+                 (after3 - before3));
+
+  // insert another InRecord
+  pit::InRecordCollection::iterator in3 =
+    entry.insertOrUpdateInRecord(face2, *interest3);
+  const pit::InRecordCollection& inRecords4 = entry.getInRecords();
+  BOOST_CHECK_EQUAL(inRecords4.size(), 2);
+  BOOST_CHECK_EQUAL(in3->getFace(), face2);
+
+  // delete all InRecords
+  entry.deleteInRecords();
+  const pit::InRecordCollection& inRecords5 = entry.getInRecords();
+  BOOST_CHECK_EQUAL(inRecords5.size(), 0);
+
+  // insert another OutRecord
+  pit::OutRecordCollection::iterator out2 =
+    entry.insertOrUpdateOutRecord(face2, *interest4);
+  const pit::OutRecordCollection& outRecords3 = entry.getOutRecords();
+  BOOST_CHECK_EQUAL(outRecords3.size(), 2);
+  BOOST_CHECK_EQUAL(out2->getFace(), face2);
+
+  // delete OutRecord
+  entry.deleteOutRecord(face2);
+  const pit::OutRecordCollection& outRecords4 = entry.getOutRecords();
+  BOOST_REQUIRE_EQUAL(outRecords4.size(), 1);
+  BOOST_CHECK_EQUAL(outRecords4.begin()->getFace(), face1);
+}
+
+BOOST_AUTO_TEST_CASE(EntryNonce)
+{
+  shared_ptr<Interest> interest = makeInterest("ndn:/qtCQ7I1c");
+
+  pit::Entry entry(*interest);
+
+  BOOST_CHECK_EQUAL(entry.addNonce(25559), true);
+  BOOST_CHECK_EQUAL(entry.addNonce(25559), false);
+  BOOST_CHECK_EQUAL(entry.addNonce(19004), true);
+  BOOST_CHECK_EQUAL(entry.addNonce(19004), false);
+}
+
+BOOST_AUTO_TEST_CASE(EntryLifetime)
+{
+  shared_ptr<Interest> interest = makeInterest("ndn:/7oIEurbgy6");
+  BOOST_ASSERT(interest->getInterestLifetime() < time::milliseconds::zero()); // library uses -1 to indicate unset lifetime
+
+  shared_ptr<Face> face = make_shared<DummyFace>();
+  pit::Entry entry(*interest);
+
+  pit::InRecordCollection::iterator inIt = entry.insertOrUpdateInRecord(face, *interest);
+  BOOST_CHECK_GT(inIt->getExpiry(), time::steady_clock::now());
+
+  pit::OutRecordCollection::iterator outIt = entry.insertOrUpdateOutRecord(face, *interest);
+  BOOST_CHECK_GT(outIt->getExpiry(), time::steady_clock::now());
+}
+
+BOOST_AUTO_TEST_CASE(EntryCanForwardTo)
+{
+  shared_ptr<Interest> interest = makeInterest("ndn:/WDsuBLIMG");
+  pit::Entry entry(*interest);
+
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+
+  entry.insertOrUpdateInRecord(face1, *interest);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face1), false);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face2), true);
+
+  entry.insertOrUpdateInRecord(face2, *interest);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face1), true);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face2), true);
+
+  entry.insertOrUpdateOutRecord(face1, *interest);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face1), false);
+  BOOST_CHECK_EQUAL(entry.canForwardTo(*face2), true);
+}
+
+BOOST_AUTO_TEST_CASE(Insert)
+{
+  Name name1("ndn:/5vzBNnMst");
+  Name name2("ndn:/igSGfEIM62");
+  Exclude exclude1;
+  exclude1.excludeOne(Name::Component("u26p47oep"));
+  Exclude exclude2;
+  exclude2.excludeBefore(Name::Component("u26p47oep"));
+  ndn::KeyLocator keyLocator1("ndn:/sGAE3peMHA");
+  ndn::KeyLocator keyLocator2("ndn:/nIJH6pr4");
+
+  NameTree nameTree(16);
+  Pit pit(nameTree);
+  BOOST_CHECK_EQUAL(pit.size(), 0);
+  std::pair<shared_ptr<pit::Entry>, bool> insertResult;
+
+  // base
+  shared_ptr<Interest> interestA = make_shared<Interest>(name1);
+  insertResult = pit.insert(*interestA);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 1);
+
+  // A+MinSuffixComponents
+  shared_ptr<Interest> interestB = make_shared<Interest>(*interestA);
+  interestB->setMinSuffixComponents(2);
+  insertResult = pit.insert(*interestB);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 2);
+
+  // A+MaxSuffixComponents
+  shared_ptr<Interest> interestC = make_shared<Interest>(*interestA);
+  interestC->setMaxSuffixComponents(4);
+  insertResult = pit.insert(*interestC);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 3);
+
+  // A+KeyLocator1
+  shared_ptr<Interest> interestD = make_shared<Interest>(*interestA);
+  interestD->setPublisherPublicKeyLocator(keyLocator1);
+  insertResult = pit.insert(*interestD);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 4);
+
+  // A+KeyLocator2
+  shared_ptr<Interest> interestE = make_shared<Interest>(*interestA);
+  interestE->setPublisherPublicKeyLocator(keyLocator2);
+  insertResult = pit.insert(*interestE);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 5);
+
+  // A+Exclude1
+  shared_ptr<Interest> interestF = make_shared<Interest>(*interestA);
+  interestF->setExclude(exclude1);
+  insertResult = pit.insert(*interestF);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 6);
+
+  // A+Exclude2
+  shared_ptr<Interest> interestG = make_shared<Interest>(*interestA);
+  interestG->setExclude(exclude2);
+  insertResult = pit.insert(*interestG);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 7);
+
+  // A+ChildSelector0
+  shared_ptr<Interest> interestH = make_shared<Interest>(*interestA);
+  interestH->setChildSelector(0);
+  insertResult = pit.insert(*interestH);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 8);
+
+  // A+ChildSelector1
+  shared_ptr<Interest> interestI = make_shared<Interest>(*interestA);
+  interestI->setChildSelector(1);
+  insertResult = pit.insert(*interestI);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 9);
+
+  // A+MustBeFresh
+  shared_ptr<Interest> interestJ = make_shared<Interest>(*interestA);
+  interestJ->setMustBeFresh(true);
+  insertResult = pit.insert(*interestJ);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 10);
+
+  // A+InterestLifetime
+  shared_ptr<Interest> interestK = make_shared<Interest>(*interestA);
+  interestK->setInterestLifetime(time::milliseconds(1000));
+  insertResult = pit.insert(*interestK);
+  BOOST_CHECK_EQUAL(insertResult.second, false);// only guiders differ
+  BOOST_CHECK_EQUAL(pit.size(), 10);
+
+  // A+Nonce
+  shared_ptr<Interest> interestL = make_shared<Interest>(*interestA);
+  interestL->setNonce(2192);
+  insertResult = pit.insert(*interestL);
+  BOOST_CHECK_EQUAL(insertResult.second, false);// only guiders differ
+  BOOST_CHECK_EQUAL(pit.size(), 10);
+
+  // different Name+Exclude1
+  shared_ptr<Interest> interestM = make_shared<Interest>(name2);
+  interestM->setExclude(exclude1);
+  insertResult = pit.insert(*interestM);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 11);
+}
+
+BOOST_AUTO_TEST_CASE(Erase)
+{
+  Interest interest(Name("ndn:/z88Admz6A2"));
+
+  NameTree nameTree(16);
+  Pit pit(nameTree);
+
+  std::pair<shared_ptr<pit::Entry>, bool> insertResult;
+
+  BOOST_CHECK_EQUAL(pit.size(), 0);
+
+  insertResult = pit.insert(interest);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 1);
+
+  insertResult = pit.insert(interest);
+  BOOST_CHECK_EQUAL(insertResult.second, false);
+  BOOST_CHECK_EQUAL(pit.size(), 1);
+
+  pit.erase(insertResult.first);
+  BOOST_CHECK_EQUAL(pit.size(), 0);
+
+  insertResult = pit.insert(interest);
+  BOOST_CHECK_EQUAL(insertResult.second, true);
+  BOOST_CHECK_EQUAL(pit.size(), 1);
+
+}
+
+BOOST_AUTO_TEST_CASE(FindAllDataMatches)
+{
+  Name nameA   ("ndn:/A");
+  Name nameAB  ("ndn:/A/B");
+  Name nameABC ("ndn:/A/B/C");
+  Name nameABCD("ndn:/A/B/C/D");
+  Name nameD   ("ndn:/D");
+
+  Interest interestA  (nameA  );
+  Interest interestABC(nameABC);
+  Interest interestD  (nameD  );
+
+  NameTree nameTree(16);
+  Pit pit(nameTree);
+  int count = 0;
+
+  BOOST_CHECK_EQUAL(pit.size(), 0);
+
+  pit.insert(interestA  );
+  pit.insert(interestABC);
+  pit.insert(interestD  );
+
+  nameTree.lookup(nameABCD); // make sure /A/B/C/D is in nameTree
+
+  BOOST_CHECK_EQUAL(pit.size(), 3);
+
+  Data data(nameABCD);
+
+  shared_ptr<pit::DataMatchResult> matches = pit.findAllDataMatches(data);
+
+  bool hasA   = false;
+  bool hasAB  = false;
+  bool hasABC = false;
+  bool hasD   = false;
+
+  for (pit::DataMatchResult::iterator it = matches->begin();
+       it != matches->end(); ++it) {
+    ++count;
+    shared_ptr<pit::Entry> entry = *it;
+
+    if (entry->getName().equals(nameA ))
+      hasA   = true;
+
+    if (entry->getName().equals(nameAB))
+      hasAB  = true;
+
+    if (entry->getName().equals(nameABC))
+      hasABC = true;
+
+    if (entry->getName().equals(nameD))
+      hasD   = true;
+  }
+  BOOST_CHECK_EQUAL(hasA  , true);
+  BOOST_CHECK_EQUAL(hasAB , false);
+  BOOST_CHECK_EQUAL(hasABC, true);
+  BOOST_CHECK_EQUAL(hasD  , false);
+
+  BOOST_CHECK_EQUAL(count, 2);
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/strategy-choice.cpp b/tests/daemon/table/strategy-choice.cpp
new file mode 100644
index 0000000..6927375
--- /dev/null
+++ b/tests/daemon/table/strategy-choice.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/strategy-choice.hpp"
+#include "tests/daemon/fw/dummy-strategy.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TableStrategyChoice, BaseFixture)
+
+using fw::Strategy;
+
+BOOST_AUTO_TEST_CASE(Effective)
+{
+  Forwarder forwarder;
+  Name nameP("ndn:/strategy/P");
+  Name nameQ("ndn:/strategy/Q");
+  Name nameZ("ndn:/strategy/Z");
+  shared_ptr<Strategy> strategyP = make_shared<DummyStrategy>(boost::ref(forwarder), nameP);
+  shared_ptr<Strategy> strategyQ = make_shared<DummyStrategy>(boost::ref(forwarder), nameQ);
+
+  StrategyChoice& table = forwarder.getStrategyChoice();
+
+  // install
+  BOOST_CHECK_EQUAL(table.install(strategyP), true);
+  BOOST_CHECK_EQUAL(table.install(strategyQ), true);
+  BOOST_CHECK_EQUAL(table.install(strategyQ), false);
+
+  BOOST_CHECK(table.insert("ndn:/", nameP));
+  // { '/'=>P }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameP);
+
+  BOOST_CHECK(table.insert("ndn:/A/B", nameP));
+  // { '/'=>P, '/A/B'=>P }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameP);
+  // same instance
+  BOOST_CHECK_EQUAL(&table.findEffectiveStrategy("ndn:/"),    strategyP.get());
+  BOOST_CHECK_EQUAL(&table.findEffectiveStrategy("ndn:/A"),   strategyP.get());
+  BOOST_CHECK_EQUAL(&table.findEffectiveStrategy("ndn:/A/B"), strategyP.get());
+
+  table.erase("ndn:/A"); // no effect
+  // { '/'=>P, '/A/B'=>P }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameP);
+
+  BOOST_CHECK(table.insert("ndn:/A", nameQ));
+  // { '/'=>P, '/A/B'=>P, '/A'=>Q }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameQ);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameP);
+
+  table.erase("ndn:/A/B");
+  // { '/'=>P, '/A'=>Q }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameQ);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameQ);
+
+  BOOST_CHECK(!table.insert("ndn:/", nameZ)); // non existent strategy
+
+  BOOST_CHECK(table.insert("ndn:/", nameQ));
+  BOOST_CHECK(table.insert("ndn:/A", nameP));
+  // { '/'=>Q, '/A'=>P }
+
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/")   .getName(), nameQ);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A")  .getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/A/B").getName(), nameP);
+  BOOST_CHECK_EQUAL(table.findEffectiveStrategy("ndn:/D")  .getName(), nameQ);
+}
+
+class PStrategyInfo : public fw::StrategyInfo
+{
+};
+
+BOOST_AUTO_TEST_CASE(ClearStrategyInfo)
+{
+  Forwarder forwarder;
+  Name nameP("ndn:/strategy/P");
+  Name nameQ("ndn:/strategy/Q");
+  shared_ptr<Strategy> strategyP = make_shared<DummyStrategy>(boost::ref(forwarder), nameP);
+  shared_ptr<Strategy> strategyQ = make_shared<DummyStrategy>(boost::ref(forwarder), nameQ);
+
+  StrategyChoice& table = forwarder.getStrategyChoice();
+  Measurements& measurements = forwarder.getMeasurements();
+
+  // install
+  table.install(strategyP);
+  table.install(strategyQ);
+
+  BOOST_CHECK(table.insert("ndn:/", nameP));
+  // { '/'=>P }
+  measurements.get("ndn:/")     ->getOrCreateStrategyInfo<PStrategyInfo>();
+  measurements.get("ndn:/A")    ->getOrCreateStrategyInfo<PStrategyInfo>();
+  measurements.get("ndn:/A/B")  ->getOrCreateStrategyInfo<PStrategyInfo>();
+  measurements.get("ndn:/A/C")  ->getOrCreateStrategyInfo<PStrategyInfo>();
+
+  BOOST_CHECK(table.insert("ndn:/A/B", nameP));
+  // { '/'=>P, '/A/B'=>P }
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/")   ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/A")  ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/A/B")->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/A/C")->getStrategyInfo<PStrategyInfo>()));
+
+  BOOST_CHECK(table.insert("ndn:/A", nameQ));
+  // { '/'=>P, '/A/B'=>P, '/A'=>Q }
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/")   ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK(!static_cast<bool>(measurements.get("ndn:/A")  ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/A/B")->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK(!static_cast<bool>(measurements.get("ndn:/A/C")->getStrategyInfo<PStrategyInfo>()));
+
+  table.erase("ndn:/A/B");
+  // { '/'=>P, '/A'=>Q }
+  BOOST_CHECK( static_cast<bool>(measurements.get("ndn:/")   ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK(!static_cast<bool>(measurements.get("ndn:/A")  ->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK(!static_cast<bool>(measurements.get("ndn:/A/B")->getStrategyInfo<PStrategyInfo>()));
+  BOOST_CHECK(!static_cast<bool>(measurements.get("ndn:/A/C")->getStrategyInfo<PStrategyInfo>()));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/table/strategy-info-host.cpp b/tests/daemon/table/strategy-info-host.cpp
new file mode 100644
index 0000000..82d4fcc
--- /dev/null
+++ b/tests/daemon/table/strategy-info-host.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "table/strategy-info-host.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+static int g_DummyStrategyInfo_count = 0;
+
+class DummyStrategyInfo : public fw::StrategyInfo
+{
+public:
+  DummyStrategyInfo(int id)
+    : m_id(id)
+  {
+    ++g_DummyStrategyInfo_count;
+  }
+
+  virtual ~DummyStrategyInfo()
+  {
+    --g_DummyStrategyInfo_count;
+  }
+
+  int m_id;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TableStrategyInfoHost, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(SetGetClear)
+{
+  StrategyInfoHost host;
+
+  BOOST_CHECK(!static_cast<bool>(host.getStrategyInfo<DummyStrategyInfo>()));
+
+  g_DummyStrategyInfo_count = 0;
+
+  shared_ptr<DummyStrategyInfo> info = make_shared<DummyStrategyInfo>(7591);
+  host.setStrategyInfo(info);
+  BOOST_REQUIRE(static_cast<bool>(host.getStrategyInfo<DummyStrategyInfo>()));
+  BOOST_CHECK_EQUAL(host.getStrategyInfo<DummyStrategyInfo>()->m_id, 7591);
+
+  info.reset(); // unlink local reference
+  // host should still have a reference to info
+  BOOST_REQUIRE(static_cast<bool>(host.getStrategyInfo<DummyStrategyInfo>()));
+  BOOST_CHECK_EQUAL(host.getStrategyInfo<DummyStrategyInfo>()->m_id, 7591);
+
+  host.clearStrategyInfo();
+  BOOST_CHECK(!static_cast<bool>(host.getStrategyInfo<DummyStrategyInfo>()));
+  BOOST_CHECK_EQUAL(g_DummyStrategyInfo_count, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd