mgmt refactoring: FaceManager

Change-Id: I292a3e3d702746d6e946a2844e6f88f64cd711a4
Refs: #2107
diff --git a/tests/daemon/mgmt/face-manager-create-face.t.cpp b/tests/daemon/mgmt/face-manager-create-face.t.cpp
new file mode 100644
index 0000000..ffee5aa
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager-create-face.t.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/face-manager.hpp"
+#include "fw/forwarder.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include <boost/property_tree/info_parser.hpp>
+
+namespace nfd {
+namespace tests {
+
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+BOOST_FIXTURE_TEST_SUITE(CreateFace, BaseFixture)
+
+class FaceManagerNode
+{
+public:
+  FaceManagerNode(ndn::KeyChain& keyChain, const std::string& port = "6363")
+    : face(ndn::util::makeDummyClientFace(getGlobalIoService(), {true, true}))
+    , dispatcher(*face, keyChain, ndn::security::SigningInfo())
+    , manager(forwarder.getFaceTable(), dispatcher, validator)
+  {
+    dispatcher.addTopPrefix("/localhost/nfd");
+
+    std::string basicConfig =
+      "face_system\n"
+      "{\n"
+      "  tcp\n"
+      "  {\n"
+      "    port " + port + "\n"
+      "  }\n"
+      "  udp\n"
+      "  {\n"
+      "    port " + port + "\n"
+      "  }\n"
+      "}\n"
+      "authorizations\n"
+      "{\n"
+      "  authorize\n"
+      "  {\n"
+      "    certfile any\n"
+      "    privileges\n"
+      "    {\n"
+      "      faces\n"
+      "    }\n"
+      "  }\n"
+      "}\n"
+      "\n";
+    std::istringstream input(basicConfig);
+    nfd::ConfigSection configSection;
+    boost::property_tree::read_info(input, configSection);
+
+    ConfigFile config;
+    manager.setConfigFile(config);
+    validator.setConfigFile(config);
+    config.parse(configSection, false, "dummy-config");
+  }
+
+  void
+  closeFaces()
+  {
+    std::vector<shared_ptr<Face>> facesToClose;
+    std::copy(forwarder.getFaceTable().begin(), forwarder.getFaceTable().end(),
+              std::back_inserter(facesToClose));
+    for (auto face : facesToClose) {
+      face->close();
+    }
+  }
+
+public:
+  Forwarder forwarder;
+  shared_ptr<ndn::util::DummyClientFace> face;
+  ndn::mgmt::Dispatcher dispatcher;
+  CommandValidator validator;
+  FaceManager manager;
+};
+
+class FaceManagerFixture : public UnitTestTimeFixture
+{
+public:
+  FaceManagerFixture()
+    : node1(keyChain, "16363")
+    , node2(keyChain, "26363")
+  {
+    advanceClocks(time::milliseconds(1), 100);
+  }
+
+  ~FaceManagerFixture()
+  {
+    node1.closeFaces();
+    node2.closeFaces();
+    advanceClocks(time::milliseconds(1), 100);
+  }
+
+public:
+  ndn::KeyChain keyChain;
+  FaceManagerNode node1; // used to test FaceManager
+  FaceManagerNode node2; // acts as a remote endpoint
+};
+
+class TcpFaceOnDemand
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("tcp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  }
+};
+
+class TcpFacePersistent
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("tcp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  }
+};
+
+class TcpFacePermanent
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("tcp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  }
+};
+
+class UdpFaceOnDemand
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("udp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  }
+};
+
+class UdpFaceCannotConnect // face that will cause afterCreateFaceFailure to be invoked
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("udp4://0.0.0.0:16363"); // cannot connect to self
+  }
+};
+
+
+class UdpFacePersistent
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("udp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  }
+};
+
+class UdpFacePermanent
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("udp4://127.0.0.1:26363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  }
+};
+
+class Success
+{
+public:
+  ControlResponse
+  getExpected()
+  {
+    return ControlResponse()
+      .setCode(200)
+      .setText("OK");
+  }
+};
+
+template<int CODE>
+class Failure
+{
+public:
+  ControlResponse
+  getExpected()
+  {
+    return ControlResponse()
+      .setCode(CODE)
+      .setText("Error"); // error description should not be checked
+  }
+};
+
+namespace mpl = boost::mpl;
+
+// pairs of CreateCommand and Success status
+typedef mpl::vector<mpl::pair<TcpFaceOnDemand, Failure<500>>,
+                    mpl::pair<TcpFacePersistent, Success>,
+                    mpl::pair<TcpFacePermanent, Failure<500>>,
+                    mpl::pair<UdpFaceOnDemand, Failure<500>>,
+                    mpl::pair<UdpFacePersistent, Success>,
+                    mpl::pair<UdpFacePermanent, Success>,
+                    mpl::pair<UdpFaceCannotConnect, Failure<408>>> Faces;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(NewFace, T, Faces, FaceManagerFixture)
+{
+  typedef typename T::first FaceType;
+  typedef typename T::second CreateResult;
+
+  Name commandName("/localhost/nfd/faces");
+  commandName.append("create");
+  commandName.append(FaceType().getParameters().wireEncode());
+
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+  this->keyChain.sign(*command);
+
+  bool hasCallbackFired = false;
+  this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+      // std::cout << response << std::endl;
+      if (!command->getName().isPrefixOf(response.getName())) {
+        return;
+      }
+
+      ControlResponse actual(response.getContent().blockFromValue());
+      ControlResponse expected(CreateResult().getExpected());
+      BOOST_CHECK_EQUAL(expected.getCode(), actual.getCode());
+      BOOST_MESSAGE(actual.getText());
+
+      if (actual.getBody().hasWire()) {
+        ControlParameters expectedParams(FaceType().getParameters());
+        ControlParameters actualParams(actual.getBody());
+
+        BOOST_CHECK_EQUAL(expectedParams.getUri(), actualParams.getUri());
+        BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+      }
+      hasCallbackFired = true;
+    });
+
+  this->node1.face->receive(*command);
+  this->advanceClocks(time::milliseconds(1), 100);
+
+  BOOST_CHECK(hasCallbackFired);
+}
+
+
+typedef mpl::vector<// mpl::pair<mpl::pair<TcpFacePersistent, TcpFacePermanent>, TcpFacePermanent>, // no need to check now
+                    // mpl::pair<mpl::pair<TcpFacePermanent, TcpFacePersistent>, TcpFacePermanent>, // no need to check now
+                    mpl::pair<mpl::pair<UdpFacePersistent, UdpFacePermanent>, UdpFacePermanent>,
+                    mpl::pair<mpl::pair<UdpFacePermanent, UdpFacePersistent>, UdpFacePermanent>> FaceTransitions;
+
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFace, T, FaceTransitions, FaceManagerFixture)
+{
+  typedef typename T::first::first FaceType1;
+  typedef typename T::first::second FaceType2;
+  typedef typename T::second FinalFaceType;
+
+  {
+    // create face
+
+    Name commandName("/localhost/nfd/faces");
+    commandName.append("create");
+    commandName.append(FaceType1().getParameters().wireEncode());
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    this->keyChain.sign(*command);
+
+    this->node1.face->receive(*command);
+    this->advanceClocks(time::milliseconds(1), 10);
+  }
+
+  //
+  {
+    // re-create face (= change face persistency)
+
+    Name commandName("/localhost/nfd/faces");
+    commandName.append("create");
+    commandName.append(FaceType2().getParameters().wireEncode());
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    this->keyChain.sign(*command);
+
+    bool hasCallbackFired = false;
+    this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+        if (!command->getName().isPrefixOf(response.getName())) {
+          return;
+        }
+
+        ControlResponse actual(response.getContent().blockFromValue());
+        BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
+
+        ControlParameters expectedParams(FinalFaceType().getParameters());
+        ControlParameters actualParams(actual.getBody());
+        BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+
+        hasCallbackFired = true;
+      });
+
+    this->node1.face->receive(*command);
+    this->advanceClocks(time::milliseconds(1), 10);
+
+    BOOST_CHECK(hasCallbackFired);
+  }
+}
+
+
+class UdpFace
+{
+public:
+  ControlParameters
+  getParameters()
+  {
+    return ControlParameters()
+      .setUri("udp4://127.0.0.1:16363")
+      .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  }
+};
+
+
+// Note that the transitions from on-demand TcpFace are intentionally not tested.
+// On-demand TcpFace has a remote endpoint with a randomized port number.  Normal face
+// creation operations will not need to create a face toward a remote port not listened by
+// a channel.
+
+typedef mpl::vector<mpl::pair<UdpFace, UdpFacePersistent>,
+                    mpl::pair<UdpFace, UdpFacePermanent>> OnDemandFaceTransitions;
+
+// need a slightly different logic to test transitions from OnDemand state
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFaceOnDemand, T, OnDemandFaceTransitions, FaceManagerFixture)
+{
+  typedef typename T::first  OtherNodeFace;
+  typedef typename T::second FaceType;
+
+  {
+    // create on-demand face
+
+    Name commandName("/localhost/nfd/faces");
+    commandName.append("create");
+    commandName.append(OtherNodeFace().getParameters().wireEncode());
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    this->keyChain.sign(*command);
+
+    ndn::util::signal::ScopedConnection connection =
+      this->node2.face->onSendData.connect([this, command] (const Data& response) {
+          if (!command->getName().isPrefixOf(response.getName())) {
+            return;
+          }
+
+          ControlResponse controlResponse(response.getContent().blockFromValue());
+          BOOST_REQUIRE_EQUAL(controlResponse.getText(), "OK");
+          BOOST_REQUIRE_EQUAL(controlResponse.getCode(), 200);
+          uint64_t faceId = ControlParameters(controlResponse.getBody()).getFaceId();
+          auto face = this->node2.forwarder.getFace(static_cast<FaceId>(faceId));
+
+          // to force creation of on-demand face
+          auto dummyInterest = make_shared<Interest>("/hello/world");
+          face->sendInterest(*dummyInterest);
+        });
+
+    this->node2.face->receive(*command);
+    this->advanceClocks(time::milliseconds(1), 10);
+  }
+
+  // make sure there is on-demand face
+  bool onDemandFaceFound = false;
+  FaceUri onDemandFaceUri(FaceType().getParameters().getUri());
+  for (auto face : this->node1.forwarder.getFaceTable()) {
+    if (face->getRemoteUri() == onDemandFaceUri) {
+      onDemandFaceFound = true;
+      break;
+    }
+  }
+  BOOST_REQUIRE(onDemandFaceFound);
+
+  //
+  {
+    // re-create face (= change face persistency)
+
+    Name commandName("/localhost/nfd/faces");
+    commandName.append("create");
+    commandName.append(FaceType().getParameters().wireEncode());
+
+    shared_ptr<Interest> command(make_shared<Interest>(commandName));
+    this->keyChain.sign(*command);
+
+    bool hasCallbackFired = false;
+    this->node1.face->onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
+        if (!command->getName().isPrefixOf(response.getName())) {
+          return;
+        }
+
+        ControlResponse actual(response.getContent().blockFromValue());
+        BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
+
+        ControlParameters expectedParams(FaceType().getParameters());
+        ControlParameters actualParams(actual.getBody());
+        BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
+
+        hasCallbackFired = true;
+      });
+
+    this->node1.face->receive(*command);
+    this->advanceClocks(time::milliseconds(1), 10);
+
+    BOOST_CHECK(hasCallbackFired);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // CreateFace
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // tests
+} // nfd
diff --git a/tests/daemon/mgmt/face-manager-process-config.t.cpp b/tests/daemon/mgmt/face-manager-process-config.t.cpp
new file mode 100644
index 0000000..9463ac4
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager-process-config.t.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/face-manager.hpp"
+#include "face/udp-factory.hpp"
+
+#ifdef HAVE_LIBPCAP
+#include "face/ethernet-factory.hpp"
+#endif // HAVE_LIBPCAP
+
+#include "manager-common-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+class FaceManagerProcessConfigFixture : public ManagerCommonFixture
+{
+public:
+  FaceManagerProcessConfigFixture()
+    : m_manager(m_forwarder.getFaceTable(), m_dispatcher, m_validator)
+  {
+    m_manager.setConfigFile(m_config);
+  }
+
+public:
+  void
+  parseConfig(const std::string& type, bool isDryRun)
+  {
+    m_config.parse(type, isDryRun, "test-config");
+  }
+
+protected:
+  FaceManager m_manager;
+  ConfigFile m_config;
+};
+
+
+BOOST_FIXTURE_TEST_SUITE(ProcessConfig, FaceManagerProcessConfigFixture)
+
+#ifdef HAVE_UNIX_SOCKETS
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUnix)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    path /tmp/nfd.sock\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUnixUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  unix\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+#endif // HAVE_UNIX_SOCKETS
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcp)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    listen yes\n"
+    "    port 16363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpBadListen)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    listen hello\n"
+    "  }\n"
+    "}\n";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpChannelsDisabled)
+{
+  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_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionTcpUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdp)
+{
+  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, true));
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadIdleTimeout)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    idle_timeout hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcastGroup)
+{
+  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_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpBadMcastGroupV6)
+{
+  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_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpChannelsDisabled)
+{
+  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_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpConflictingMcast)
+{
+  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_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+
+BOOST_AUTO_TEST_CASE(ProcessSectionUdpMulticastReinit)
+{
+  const std::string CONFIG_WITH_MCAST =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast yes\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
+
+  BOOST_REQUIRE(m_manager.m_factories.find("udp") != m_manager.m_factories.end());
+  auto factory = dynamic_pointer_cast<UdpFactory>(m_manager.m_factories.find("udp")->second);
+  BOOST_REQUIRE(factory != nullptr);
+
+  if (factory->getMulticastFaces().size() == 0) {
+    BOOST_TEST_MESSAGE("Destroying multicast faces is not tested because "
+                       "no UDP multicast faces are available");
+    return;
+  }
+  BOOST_CHECK_GT(factory->getMulticastFaces().size(), 0);
+
+  const std::string CONFIG_WITHOUT_MCAST =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    mcast no\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
+  BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+}
+
+#ifdef HAVE_LIBPCAP
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEther)
+{
+
+  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_CHECK_NO_THROW(parseConfig(CONFIG, false));
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherBadMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherBadMcastGroup)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast yes\n"
+    "    mcast_group\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherUnknownOption)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    hello\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ProcessSectionEtherMulticastReinit)
+{
+  const std::string CONFIG_WITH_MCAST =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast yes\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
+
+  BOOST_REQUIRE(m_manager.m_factories.find("ether") != m_manager.m_factories.end());
+  auto factory = dynamic_pointer_cast<EthernetFactory>(m_manager.m_factories.find("ether")->second);
+  BOOST_REQUIRE(factory != nullptr);
+
+  if (factory->getMulticastFaces().size() == 0) {
+    BOOST_TEST_MESSAGE("Destroying multicast faces is not tested because "
+                       "no Ethernet multicast faces are available");
+    return;
+  }
+  BOOST_CHECK_GT(factory->getMulticastFaces().size(), 0);
+
+  const std::string CONFIG_WITHOUT_MCAST =
+    "face_system\n"
+    "{\n"
+    "  ether\n"
+    "  {\n"
+    "    mcast no\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
+  BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+}
+
+#endif // HAVE_LIBPCAP
+
+BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/face-manager.t.cpp b/tests/daemon/mgmt/face-manager.t.cpp
new file mode 100644
index 0000000..db24029
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager.t.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/face-manager.hpp"
+#include "manager-common-fixture.hpp"
+#include "../face/dummy-face.hpp"
+#include "face/tcp-factory.hpp"
+#include "face/udp-factory.hpp"
+
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/encoding/tlv.hpp>
+#include <ndn-cxx/management/nfd-channel-status.hpp>
+#include <ndn-cxx/management/nfd-face-event-notification.hpp>
+
+namespace nfd {
+namespace tests {
+
+class FaceManagerFixture : public ManagerCommonFixture
+{
+public:
+  FaceManagerFixture()
+    : m_faceTable(m_forwarder.getFaceTable())
+    , m_manager(m_faceTable, m_dispatcher, m_validator)
+  {
+    setTopPrefixAndPrivilege("/localhost/nfd", "faces");
+  }
+
+public:
+  template<typename Face>
+  shared_ptr<Face>
+  addFace(bool wantRemoveLastNotification = false)
+  {
+    auto face = make_shared<Face>();
+    m_faceTable.add(face);
+    advanceClocks(time::milliseconds(1), 10); // wait for notification posted
+    if (wantRemoveLastNotification) {
+      m_responses.pop_back();
+    }
+    return face;
+  }
+
+protected:
+  FaceTable& m_faceTable;
+  FaceManager m_manager;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Mgmt, FaceManagerFixture)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+BOOST_AUTO_TEST_SUITE(DestroyFace)
+
+BOOST_AUTO_TEST_CASE(Existing)
+{
+  auto addedFace = addFace<DummyFace>(true); // clear notification for creation
+
+  auto parameters = ControlParameters().setFaceId(addedFace->getId());
+  auto command = makeControlCommandRequest("/localhost/nfd/faces/destroy", parameters);
+
+  receiveInterest(command);
+
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 2); // one response and one notification
+  // notification is already tested, so ignore it
+
+  BOOST_CHECK_EQUAL(checkResponse(1, command->getName(), makeResponse(200, "OK", parameters)),
+                    CheckResponseResult::OK);
+
+  BOOST_CHECK_EQUAL(addedFace->getId(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(NonExisting)
+{
+  auto parameters = ControlParameters().setFaceId(65535);
+  auto command = makeControlCommandRequest("/localhost/nfd/faces/destroy", parameters);
+
+  receiveInterest(command);
+
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "OK", parameters)),
+                    CheckResponseResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // DestroyFace
+
+BOOST_AUTO_TEST_CASE(FaceEvents)
+{
+  auto addedFace = addFace<DummyFace>(); // trigger FACE_EVENT_CREATED notification
+  BOOST_CHECK_NE(addedFace->getId(), -1);
+  int64_t faceId = addedFace->getId();
+
+  // check notification
+  {
+    Block payload;
+    ndn::nfd::FaceEventNotification notification;
+    BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+    BOOST_CHECK_NO_THROW(payload = m_responses[0].getContent().blockFromValue());
+    BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+    BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_CREATED);
+    BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+    BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
+    BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
+    BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+    BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+    BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+  }
+
+  addedFace->close(); // trigger FaceDestroy FACE_EVENT_DESTROYED
+  advanceClocks(time::milliseconds(1), 10);
+
+  // check notification
+  {
+    Block payload;
+    ndn::nfd::FaceEventNotification notification;
+    BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
+    BOOST_CHECK_NO_THROW(payload = m_responses[1].getContent().blockFromValue());
+    BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+    BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_DESTROYED);
+    BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+    BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
+    BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
+    BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+    BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+    BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+  }
+  BOOST_CHECK_EQUAL(addedFace->getId(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(EnableDisableLocalControl)
+{
+  auto nonLocalFace = addFace<DummyFace>(true); // clear notification
+  auto localFace = addFace<DummyLocalFace>(true); // clear notification
+  BOOST_CHECK(localFace->isLocal());
+  BOOST_CHECK(!nonLocalFace->isLocal());
+
+  std::vector<Name> commandNames;
+  auto testLocalControl = [&] (const Name& name, const ControlParameters& params, uint64_t faceId) {
+    auto command = makeControlCommandRequest(name, params,
+                                             [faceId] (shared_ptr<Interest> interest) {
+                                               interest->setIncomingFaceId(faceId);
+                                             });
+    receiveInterest(command);
+    commandNames.push_back(command->getName());
+  };
+
+  auto paramsF = ControlParameters().setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+  auto paramsN = ControlParameters().setLocalControlFeature(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID);
+
+  // non-existing face: 0~3
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, FACEID_NULL);
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, FACEID_NULL);
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, FACEID_NULL);
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, FACEID_NULL);
+
+  // non-local face: 4~7
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, nonLocalFace->getId());
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, nonLocalFace->getId());
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, nonLocalFace->getId());
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, nonLocalFace->getId());
+
+  // enableLocalControl for Incoming FaceId on existing local face:
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsF, localFace->getId()); // 8
+  BOOST_CHECK(localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  // disableLocalControl for Incoming FaceId on existing local face
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsF, localFace->getId()); // 9
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  // enableLocalControl for NextHop ID on existing local face
+  testLocalControl("/localhost/nfd/faces/enable-local-control", paramsN, localFace->getId()); // 10
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  // disableLocalControl for NextHop ID on existing local face
+  testLocalControl("/localhost/nfd/faces/disable-local-control", paramsN, localFace->getId()); // 11
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID));
+  BOOST_CHECK(!localFace->isLocalControlHeaderEnabled(LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID));
+
+  // check responses
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 12);
+  BOOST_CHECK_EQUAL(checkResponse(0,  commandNames[0],  ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(1,  commandNames[1],  ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(2,  commandNames[2],  ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(3,  commandNames[3],  ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(4,  commandNames[4],  ControlResponse(412, "Face is non-local")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(5,  commandNames[5],  ControlResponse(412, "Face is non-local")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(6,  commandNames[6],  ControlResponse(412, "Face is non-local")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(7,  commandNames[7],  ControlResponse(412, "Face is non-local")),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(8,  commandNames[8],  makeResponse(200, "OK", paramsF)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(9,  commandNames[9],  makeResponse(200, "OK", paramsF)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(10, commandNames[10], makeResponse(200, "OK", paramsN)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(11, commandNames[11], makeResponse(200, "OK", paramsN)),
+                    CheckResponseResult::OK);
+}
+
+class TestFace : public DummyFace
+{
+public:
+  explicit
+  TestFace(const std::string& uri = "test://")
+    : DummyFace(uri, uri)
+  {
+    getMutableCounters().getNInInterests().set(ndn::random::generateWord64());
+    getMutableCounters().getNInDatas().set(ndn::random::generateWord64());
+    getMutableCounters().getNOutInterests().set(ndn::random::generateWord64());
+    getMutableCounters().getNOutDatas().set(ndn::random::generateWord64());
+    getMutableCounters().getNInBytes().set(ndn::random::generateWord64());
+    getMutableCounters().getNOutBytes().set(ndn::random::generateWord64());
+  }
+};
+
+// @todo Refactor when ndn::nfd::FaceStatus implementes operator!= and operator<<
+class FaceStatus : public ndn::nfd::FaceStatus
+{
+public:
+  FaceStatus(const ndn::nfd::FaceStatus& s)
+    : ndn::nfd::FaceStatus(s)
+  {
+  }
+};
+
+bool
+operator!=(const FaceStatus& left, const FaceStatus& right)
+{
+  return left.getRemoteUri() != right.getRemoteUri() ||
+    left.getLocalUri() != right.getLocalUri() ||
+    left.getFaceScope() != right.getFaceScope() ||
+    left.getFacePersistency() != right.getFacePersistency() ||
+    left.getLinkType() != right.getLinkType() ||
+    left.getNInInterests() != right.getNInInterests() ||
+    left.getNInDatas() != right.getNInDatas() ||
+    left.getNOutInterests() != right.getNOutInterests() ||
+    left.getNOutDatas() != right.getNOutDatas() ||
+    left.getNInBytes() != right.getNInBytes() ||
+    left.getNOutBytes() != right.getNOutBytes();
+}
+
+std::ostream&
+operator<<(std::ostream &os, const FaceStatus& status)
+{
+  os << "[" << status.getRemoteUri() << ", "
+     << status.getLocalUri() << ", "
+     << status.getFacePersistency() << ", "
+     << status.getLinkType() << ", "
+     << status.getNInInterests() << ", "
+     << status.getNInDatas() << ", "
+     << status.getNOutInterests() << ", "
+     << status.getNOutDatas() << ", "
+     << status.getNInBytes() << ", "
+     << status.getNOutBytes() << "]";
+  return os;
+}
+
+BOOST_AUTO_TEST_CASE(FaceDataset)
+{
+  size_t nEntries = 303;
+  for (size_t i = 0 ; i < nEntries ; i ++) {
+    addFace<TestFace>(true);
+  }
+
+  receiveInterest(makeInterest("/localhost/nfd/faces/list"));
+
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+  std::vector<FaceStatus> expectedStatuses, receivedStatuses;
+  std::set<FaceId> faceIds;
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    ndn::nfd::FaceStatus decodedStatus;
+    BOOST_REQUIRE_NO_THROW(decodedStatus.wireDecode(content.elements()[idx]));
+    BOOST_REQUIRE(m_faceTable.get(decodedStatus.getFaceId()) != nullptr);
+    faceIds.insert(decodedStatus.getFaceId());
+    receivedStatuses.push_back(decodedStatus);
+    expectedStatuses.push_back(m_faceTable.get(decodedStatus.getFaceId())->getFaceStatus());
+  }
+
+  BOOST_CHECK_EQUAL(faceIds.size(), nEntries);
+  BOOST_CHECK_EQUAL_COLLECTIONS(receivedStatuses.begin(), receivedStatuses.end(),
+                                expectedStatuses.begin(), expectedStatuses.end());
+}
+
+BOOST_AUTO_TEST_CASE(FaceQuery)
+{
+  auto face1 = addFace<DummyFace>(true); // dummy://
+  auto face2 = addFace<DummyLocalFace>(true); // dummy://, local
+  auto face3 = addFace<TestFace>(true); // test://
+
+  auto generateQueryName = [] (const ndn::nfd::FaceQueryFilter& filter) {
+    return Name("/localhost/nfd/faces/query").append(filter.wireEncode());
+  };
+
+  auto querySchemeName =
+    generateQueryName(ndn::nfd::FaceQueryFilter().setUriScheme("dummy"));
+  auto queryIdName =
+    generateQueryName(ndn::nfd::FaceQueryFilter().setFaceId(face1->getId()));
+  auto queryScopeName =
+    generateQueryName(ndn::nfd::FaceQueryFilter().setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL));
+  auto invalidQueryName =
+    Name("/localhost/nfd/faces/query").append(ndn::makeStringBlock(tlv::Content, "invalid"));
+
+  receiveInterest(makeInterest(querySchemeName)); // face1 and face2 expected
+  receiveInterest(makeInterest(queryIdName)); // face1 expected
+  receiveInterest(makeInterest(queryScopeName)); // face1 and face3 expected
+  receiveInterest(makeInterest(invalidQueryName)); // nack expected
+
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 4);
+
+  Block content;
+  ndn::nfd::FaceStatus status;
+
+  content = m_responses[0].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_CHECK_EQUAL(content.elements().size(), 2); // face1 and face2
+  BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+  BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+  BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[1]));
+  BOOST_CHECK_EQUAL(face2->getId(), status.getFaceId());
+
+  content = m_responses[1].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_CHECK_EQUAL(content.elements().size(), 1); // face1
+  BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+  BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+
+  content = m_responses[2].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_CHECK_EQUAL(content.elements().size(), 2); // face1 and face3
+  BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[0]));
+  BOOST_CHECK_EQUAL(face1->getId(), status.getFaceId());
+  BOOST_CHECK_NO_THROW(status.wireDecode(content.elements()[1]));
+  BOOST_CHECK_EQUAL(face3->getId(), status.getFaceId());
+
+  ControlResponse expectedResponse(400, "malformed filter"); // nack, 400, malformed filter
+  BOOST_CHECK_EQUAL(checkResponse(3, invalidQueryName, expectedResponse, tlv::ContentType_Nack),
+                    CheckResponseResult::OK);
+}
+
+class TestChannel : public Channel
+{
+public:
+  TestChannel(const std::string& uri)
+  {
+    setUri(FaceUri(uri));
+  }
+};
+
+class TestProtocolFactory : public ProtocolFactory
+{
+public:
+  virtual void
+  createFace(const FaceUri& uri,
+             ndn::nfd::FacePersistency persistency,
+             const FaceCreatedCallback& onCreated,
+             const FaceConnectFailedCallback& onConnectFailed) DECL_OVERRIDE
+  {
+  }
+
+  virtual std::list<shared_ptr<const Channel>>
+  getChannels() const DECL_OVERRIDE
+  {
+    return m_channels;
+  }
+
+public:
+  shared_ptr<TestChannel>
+  addChannel(const std::string& channelUri)
+  {
+    auto channel = make_shared<TestChannel>(channelUri);
+    m_channels.push_back(channel);
+    return channel;
+  }
+
+private:
+  std::list<shared_ptr<const Channel> > m_channels;
+};
+
+BOOST_AUTO_TEST_CASE(ChannelDataset)
+{
+  auto factory = make_shared<TestProtocolFactory>();
+  m_manager.m_factories["test"] = factory;
+
+  std::map<std::string, shared_ptr<TestChannel>> addedChannels;
+  size_t nEntries = 404;
+  for (size_t i = 0 ; i < nEntries ; i ++) {
+    auto channel = factory->addChannel("test" + boost::lexical_cast<std::string>(i) + "://");
+    addedChannels[channel->getUri().toString()] = channel;
+  }
+
+  receiveInterest(makeInterest("/localhost/nfd/faces/channels"));
+
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    ndn::nfd::ChannelStatus decodedStatus;
+    BOOST_CHECK_NO_THROW(decodedStatus.wireDecode(content.elements()[idx]));
+    BOOST_CHECK(addedChannels.find(decodedStatus.getLocalUri()) != addedChannels.end());
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd