face: process face_system.tcp config section in TcpFactory

refs #3904

Change-Id: I509f07e6835a96c7ba05137529f29da76a6514fd
diff --git a/core/config-file.hpp b/core/config-file.hpp
index 890a22c..363c66d 100644
--- a/core/config-file.hpp
+++ b/core/config-file.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -32,18 +32,26 @@
 
 namespace nfd {
 
+/** \brief a config file section
+ */
 typedef boost::property_tree::ptree ConfigSection;
 
-/// \brief callback for config file sections
-typedef function<void(const ConfigSection& /*section*/,
-                      bool /*isDryRun*/,
-                      const std::string& /*filename*/)> ConfigSectionHandler;
+/** \brief an optional config file section
+ */
+typedef boost::optional<const ConfigSection&> OptionalConfigSection;
 
-/// \brief callback for config file sections without a subscribed handler
-typedef function<void(const std::string& /*filename*/,
-                      const std::string& /*sectionName*/,
-                      const ConfigSection& /*section*/,
-                      bool /*isDryRun*/)> UnknownConfigSectionHandler;
+/** \brief callback to process a config file section
+ */
+typedef function<void(const ConfigSection& section,
+                      bool isDryRun,
+                      const std::string& filename)> ConfigSectionHandler;
+
+/** \brief callback to process a config file section without a \p ConfigSectionHandler
+ */
+typedef function<void(const std::string& filename,
+                      const std::string& sectionName,
+                      const ConfigSection& section,
+                      bool isDryRun)> UnknownConfigSectionHandler;
 
 /** \brief configuration file parsing utility
  */
diff --git a/daemon/face/channel.hpp b/daemon/face/channel.hpp
index 19beb7f..1c0452d 100644
--- a/daemon/face/channel.hpp
+++ b/daemon/face/channel.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -43,7 +43,12 @@
  */
 typedef function<void(uint32_t status, const std::string& reason)> FaceCreationFailedCallback;
 
-
+/** \brief represent a channel that communicates on a local endpoint
+ *  \sa FaceSystem
+ *
+ *  A channel can listen on a local endpoint and initiate outgoing connection from a local endpoint.
+ *  A channel creates Face objects and retains shared ownership of them.
+ */
 class Channel : noncopyable
 {
 public:
diff --git a/daemon/face/face-system.cpp b/daemon/face/face-system.cpp
index 0d4a308..8433a16 100644
--- a/daemon/face/face-system.cpp
+++ b/daemon/face/face-system.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,8 +25,6 @@
 
 #include "face-system.hpp"
 #include "core/logger.hpp"
-#include "core/network-interface.hpp"
-#include "core/network-interface-predicate.hpp"
 #include "fw/face-table.hpp"
 
 // ProtocolFactory includes, sorted alphabetically
@@ -51,25 +49,34 @@
 FaceSystem::FaceSystem(FaceTable& faceTable)
   : m_faceTable(faceTable)
 {
+  ///\todo #3904 make a registry, and construct instances from registry
+  m_factories["tcp"] = make_shared<TcpFactory>();
 }
 
 std::set<const ProtocolFactory*>
 FaceSystem::listProtocolFactories() const
 {
   std::set<const ProtocolFactory*> factories;
-  for (const auto& p : m_factories) {
+  for (const auto& p : m_factoryByScheme) {
     factories.insert(p.second.get());
   }
   return factories;
 }
 
 ProtocolFactory*
-FaceSystem::getProtocolFactory(const std::string& scheme)
+FaceSystem::getFactoryById(const std::string& id)
 {
-  auto found = m_factories.find(scheme);
+  auto found = m_factories.find(id);
   return found == m_factories.end() ? nullptr : found->second.get();
 }
 
+ProtocolFactory*
+FaceSystem::getFactoryByScheme(const std::string& scheme)
+{
+  auto found = m_factoryByScheme.find(scheme);
+  return found == m_factoryByScheme.end() ? nullptr : found->second.get();
+}
+
 void
 FaceSystem::setConfigFile(ConfigFile& configFile)
 {
@@ -79,31 +86,61 @@
 void
 FaceSystem::processConfig(const ConfigSection& configSection, bool isDryRun, const std::string& filename)
 {
+  ConfigContext context;
+  context.isDryRun = isDryRun;
+  context.addFace = bind(&FaceTable::add, &m_faceTable, _1);
+  context.m_nicList = listNetworkInterfaces();
+
+  // process sections in protocol factories
+  for (const auto& pair : m_factories) {
+    const std::string& sectionName = pair.first;
+    shared_ptr<ProtocolFactory> factory = pair.second;
+
+    std::set<std::string> oldProvidedSchemes = factory->getProvidedSchemes();
+    factory->processConfig(configSection.get_child_optional(sectionName), context);
+
+    if (!isDryRun) {
+      for (const std::string& scheme : factory->getProvidedSchemes()) {
+        m_factoryByScheme[scheme] = factory;
+        oldProvidedSchemes.erase(scheme);
+      }
+      for (const std::string& scheme : oldProvidedSchemes) {
+        m_factoryByScheme.erase(scheme);
+      }
+    }
+  }
+
+  // process other sections
   std::set<std::string> seenSections;
-  auto nicList = listNetworkInterfaces();
+  for (const auto& pair : configSection) {
+    const std::string& sectionName = pair.first;
+    const ConfigSection& subSection = pair.second;
 
-  for (const auto& item : configSection) {
-    if (!seenSections.insert(item.first).second) {
-      BOOST_THROW_EXCEPTION(ConfigFile::Error("Duplicate \"" + item.first + "\" section"));
+    if (!seenSections.insert(sectionName).second) {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error("Duplicate section face_system." + sectionName));
     }
 
-    if (item.first == "unix") {
-      processSectionUnix(item.second, isDryRun);
+    if (m_factories.count(sectionName) > 0) {
+      continue;
     }
-    else if (item.first == "tcp") {
-      processSectionTcp(item.second, isDryRun);
+
+    ///\todo #3521 nicfaces
+
+    ///\todo #3904 process these in protocol factory
+    if (sectionName == "unix") {
+      processSectionUnix(subSection, isDryRun);
     }
-    else if (item.first == "udp") {
-      processSectionUdp(item.second, isDryRun, nicList);
+    else if (sectionName == "udp") {
+      processSectionUdp(subSection, isDryRun, context.m_nicList);
     }
-    else if (item.first == "ether") {
-      processSectionEther(item.second, isDryRun, nicList);
+    else if (sectionName == "ether") {
+      processSectionEther(subSection, isDryRun, context.m_nicList);
     }
-    else if (item.first == "websocket") {
-      processSectionWebSocket(item.second, isDryRun);
+    else if (sectionName == "websocket") {
+      processSectionWebSocket(subSection, isDryRun);
     }
     else {
-      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" + item.first + "\""));
+      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system." + sectionName));
     }
   }
 }
@@ -131,12 +168,12 @@
   }
 
   if (!isDryRun) {
-    if (m_factories.count("unix") > 0) {
+    if (m_factoryByScheme.count("unix") > 0) {
       return;
     }
 
     auto factory = make_shared<UnixStreamFactory>();
-    m_factories.emplace("unix", factory);
+    m_factoryByScheme.emplace("unix", factory);
 
     auto channel = factory->createChannel(path);
     channel->listen(bind(&FaceTable::add, &m_faceTable, _1), nullptr);
@@ -148,77 +185,6 @@
 }
 
 void
-FaceSystem::processSectionTcp(const ConfigSection& configSection, bool isDryRun)
-{
-  // ; the tcp section contains settings of TCP faces and channels
-  // tcp
-  // {
-  //   listen yes ; set to 'no' to disable TCP listener, default 'yes'
-  //   port 6363 ; TCP listener port number
-  // }
-
-  uint16_t port = 6363;
-  bool needToListen = true;
-  bool enableV4 = true;
-  bool enableV6 = true;
-
-  for (const auto& i : configSection) {
-    if (i.first == "port") {
-      port = ConfigFile::parseNumber<uint16_t>(i, "tcp");
-      NFD_LOG_TRACE("TCP port set to " << port);
-    }
-    else if (i.first == "listen") {
-      needToListen = ConfigFile::parseYesNo(i, "tcp");
-    }
-    else if (i.first == "enable_v4") {
-      enableV4 = ConfigFile::parseYesNo(i, "tcp");
-    }
-    else if (i.first == "enable_v6") {
-      enableV6 = ConfigFile::parseYesNo(i, "tcp");
-    }
-    else {
-      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
-                                              i.first + "\" in \"tcp\" section"));
-    }
-  }
-
-  if (!enableV4 && !enableV6) {
-    BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 and IPv6 TCP channels have been disabled."
-                                            " Remove \"tcp\" section to disable TCP channels or"
-                                            " re-enable at least one channel type."));
-  }
-
-  if (!isDryRun) {
-    if (m_factories.count("tcp") > 0) {
-      return;
-    }
-
-    auto factory = make_shared<TcpFactory>();
-    m_factories.emplace("tcp", factory);
-
-    if (enableV4) {
-      tcp::Endpoint endpoint(boost::asio::ip::tcp::v4(), port);
-      shared_ptr<TcpChannel> v4Channel = factory->createChannel(endpoint);
-      if (needToListen) {
-        v4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1), nullptr);
-      }
-
-      m_factories.emplace("tcp4", factory);
-    }
-
-    if (enableV6) {
-      tcp::Endpoint endpoint(boost::asio::ip::tcp::v6(), port);
-      shared_ptr<TcpChannel> v6Channel = factory->createChannel(endpoint);
-      if (needToListen) {
-        v6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1), nullptr);
-      }
-
-      m_factories.emplace("tcp6", factory);
-    }
-  }
-}
-
-void
 FaceSystem::processSectionUdp(const ConfigSection& configSection, bool isDryRun,
                               const std::vector<NetworkInterfaceInfo>& nicList)
 {
@@ -310,13 +276,13 @@
   if (!isDryRun) {
     shared_ptr<UdpFactory> factory;
     bool isReload = false;
-    if (m_factories.count("udp") > 0) {
+    if (m_factoryByScheme.count("udp") > 0) {
       isReload = true;
-      factory = static_pointer_cast<UdpFactory>(m_factories["udp"]);
+      factory = static_pointer_cast<UdpFactory>(m_factoryByScheme["udp"]);
     }
     else {
       factory = make_shared<UdpFactory>();
-      m_factories.emplace("udp", factory);
+      m_factoryByScheme.emplace("udp", factory);
     }
 
     if (!isReload && enableV4) {
@@ -324,7 +290,7 @@
       shared_ptr<UdpChannel> v4Channel = factory->createChannel(endpoint, time::seconds(timeout));
       v4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1), nullptr);
 
-      m_factories.emplace("udp4", factory);
+      m_factoryByScheme.emplace("udp4", factory);
     }
 
     if (!isReload && enableV6) {
@@ -332,7 +298,7 @@
       shared_ptr<UdpChannel> v6Channel = factory->createChannel(endpoint, time::seconds(timeout));
       v6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1), nullptr);
 
-      m_factories.emplace("udp6", factory);
+      m_factoryByScheme.emplace("udp6", factory);
     }
 
     std::set<shared_ptr<Face>> multicastFacesToRemove;
@@ -416,12 +382,12 @@
 
   if (!isDryRun) {
     shared_ptr<EthernetFactory> factory;
-    if (m_factories.count("ether") > 0) {
-      factory = static_pointer_cast<EthernetFactory>(m_factories["ether"]);
+    if (m_factoryByScheme.count("ether") > 0) {
+      factory = static_pointer_cast<EthernetFactory>(m_factoryByScheme["ether"]);
     }
     else {
       factory = make_shared<EthernetFactory>();
-      m_factories.emplace("ether", factory);
+      m_factoryByScheme.emplace("ether", factory);
     }
 
     std::set<shared_ptr<Face>> multicastFacesToRemove;
@@ -505,12 +471,12 @@
   }
 
   if (!isDryRun) {
-    if (m_factories.count("websocket") > 0) {
+    if (m_factoryByScheme.count("websocket") > 0) {
       return;
     }
 
     auto factory = make_shared<WebSocketFactory>();
-    m_factories.emplace("websocket", factory);
+    m_factoryByScheme.emplace("websocket", factory);
 
     shared_ptr<WebSocketChannel> channel;
 
@@ -518,13 +484,13 @@
       websocket::Endpoint endpoint(boost::asio::ip::address_v6::any(), port);
       channel = factory->createChannel(endpoint);
 
-      m_factories.emplace("websocket46", factory);
+      m_factoryByScheme.emplace("websocket46", factory);
     }
     else if (enableV4) {
       websocket::Endpoint endpoint(boost::asio::ip::address_v4::any(), port);
       channel = factory->createChannel(endpoint);
 
-      m_factories.emplace("websocket4", factory);
+      m_factoryByScheme.emplace("websocket4", factory);
     }
 
     if (channel && needToListen) {
diff --git a/daemon/face/face-system.hpp b/daemon/face/face-system.hpp
index 22b2a14..48e3ea2 100644
--- a/daemon/face/face-system.hpp
+++ b/daemon/face/face-system.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -26,20 +26,23 @@
 #ifndef NFD_DAEMON_FACE_FACE_SYSTEM_HPP
 #define NFD_DAEMON_FACE_FACE_SYSTEM_HPP
 
+#include "channel.hpp"
 #include "core/config-file.hpp"
-#include "protocol-factory.hpp"
+#include "core/network-interface.hpp"
+#include "core/network-interface-predicate.hpp"
 
 namespace nfd {
 
 class FaceTable;
-class NetworkInterfaceInfo;
 
 namespace face {
 
+class ProtocolFactory;
+
 /** \brief entry point of the face system
  *
- *  FaceSystem class is the entry point of NFD's face system.
- *  It owns ProtocolFactory objects that are created from face_system section of NFD configuration file.
+ *  NFD's face system is organized as a FaceSystem-ProtocolFactory-Channel-Face hierarchy.
+ *  FaceSystem class is the entry point of NFD's face system and owns ProtocolFactory objects.
  */
 class FaceSystem : noncopyable
 {
@@ -52,16 +55,44 @@
   std::set<const ProtocolFactory*>
   listProtocolFactories() const;
 
-  /** \return ProtocolFactory for specified protocol scheme, or nullptr if not found
+  /** \return ProtocolFactory for the specified registered factory id or nullptr if not found
    */
   ProtocolFactory*
-  getProtocolFactory(const std::string& scheme);
+  getFactoryById(const std::string& id);
+
+  /** \return ProtocolFactory for the specified FaceUri scheme or nullptr if not found
+   */
+  ProtocolFactory*
+  getFactoryByScheme(const std::string& scheme);
 
   /** \brief register handler for face_system section of NFD configuration file
    */
   void
   setConfigFile(ConfigFile& configFile);
 
+  /** \brief context for processing a config section in ProtocolFactory
+   */
+  class ConfigContext : noncopyable
+  {
+  public:
+    const std::vector<NetworkInterfaceInfo>&
+    listNics() const
+    {
+      ///\todo get NIC list from NetworkMonitor
+      return m_nicList;
+    }
+
+  public:
+    bool isDryRun;
+    FaceCreatedCallback addFace;
+    ///\todo add NetworkMonitor
+
+  private:
+    std::vector<NetworkInterfaceInfo> m_nicList;
+
+    friend class FaceSystem;
+  };
+
 private:
   void
   processConfig(const ConfigSection& configSection, bool isDryRun,
@@ -71,9 +102,6 @@
   processSectionUnix(const ConfigSection& configSection, bool isDryRun);
 
   void
-  processSectionTcp(const ConfigSection& configSection, bool isDryRun);
-
-  void
   processSectionUdp(const ConfigSection& configSection, bool isDryRun,
                     const std::vector<NetworkInterfaceInfo>& nicList);
 
@@ -85,11 +113,18 @@
   processSectionWebSocket(const ConfigSection& configSection, bool isDryRun);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief config section name => protocol factory
+   *
+   *  \todo #3904 store unique_ptr<ProtocolFactory> here, and reference_wrapper<ProtocolFactory>
+   *              in m_factoryByScheme
+   */
+  std::map<std::string, shared_ptr<ProtocolFactory>> m_factories;
+
   /** \brief scheme => protocol factory
    *
    *  The same protocol factory may be available under multiple schemes.
    */
-  std::map<std::string, shared_ptr<ProtocolFactory>> m_factories;
+  std::map<std::string, shared_ptr<ProtocolFactory>> m_factoryByScheme;
 
   FaceTable& m_faceTable;
 };
diff --git a/daemon/face/protocol-factory.hpp b/daemon/face/protocol-factory.hpp
index ee19063..328d46e 100644
--- a/daemon/face/protocol-factory.hpp
+++ b/daemon/face/protocol-factory.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -27,14 +27,22 @@
 #define NFD_DAEMON_FACE_PROTOCOL_FACTORY_HPP
 
 #include "channel.hpp"
+#include "face-system.hpp"
 #include <ndn-cxx/encoding/nfd-constants.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/copy.hpp>
 
 namespace nfd {
+namespace face {
 
-/**
- * \brief Abstract base class for all protocol factories
+/** \brief provide support for an underlying protocol
+ *  \sa FaceSystem
+ *
+ *  A protocol factory provides support for an underlying protocol and owns Channel objects.
+ *  It can process a subsection of face_system config section and create channels and multicast
+ *  faces accordingly.
  */
-class ProtocolFactory
+class ProtocolFactory : noncopyable
 {
 public:
   /**
@@ -50,6 +58,29 @@
     }
   };
 
+  /** \brief process face_system subsection that corresponds to this ProtocolFactory type
+   *  \param configSection the configuration section or boost::null to indicate it is omitted
+   *  \param context provides access to data structures and contextual information
+   *  \throw ConfigFile::Error invalid configuration
+   *
+   *  This function updates \p providedSchemes
+   */
+  virtual void
+  processConfig(OptionalConfigSection configSection,
+                FaceSystem::ConfigContext& context)
+  {
+    ///\todo implement in every subclass and make this pure-virtual
+    BOOST_THROW_EXCEPTION(Error("processConfig is not implemented"));
+  }
+
+  /** \return FaceUri schemes accepted by this ProtocolFactory
+   */
+  const std::set<std::string>&
+  getProvidedSchemes()
+  {
+    return providedSchemes;
+  }
+
   /** \brief Try to create Face using the supplied FaceUri
    *
    * This method should automatically choose channel, based on supplied FaceUri
@@ -72,8 +103,27 @@
 
   virtual std::vector<shared_ptr<const Channel>>
   getChannels() const = 0;
+
+protected:
+  template<typename ChannelMap>
+  static std::vector<shared_ptr<const Channel>>
+  getChannelsFromMap(const ChannelMap& channelMap)
+  {
+    std::vector<shared_ptr<const Channel>> channels;
+    boost::copy(channelMap | boost::adaptors::map_values, std::back_inserter(channels));
+    return channels;
+  }
+
+protected:
+  /** \brief FaceUri schemes provided by this ProtocolFactory
+   */
+  std::set<std::string> providedSchemes;
 };
 
+} // namespace face
+
+using face::ProtocolFactory;
+
 } // namespace nfd
 
 #endif // NFD_DAEMON_FACE_PROTOCOL_FACTORY_HPP
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index 8ae2337..a3028b5 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,77 +25,93 @@
 
 #include "tcp-factory.hpp"
 #include "core/logger.hpp"
-#include "core/network-interface.hpp"
 
 namespace nfd {
+namespace face {
 
 namespace ip = boost::asio::ip;
 
 NFD_LOG_INIT("TcpFactory");
 
 void
-TcpFactory::prohibitEndpoint(const tcp::Endpoint& endpoint)
+TcpFactory::processConfig(OptionalConfigSection configSection,
+                          FaceSystem::ConfigContext& context)
 {
-  if (endpoint.address().is_v4() &&
-      endpoint.address() == ip::address_v4::any()) {
-    prohibitAllIpv4Endpoints(endpoint.port());
-  }
-  else if (endpoint.address().is_v6() &&
-           endpoint.address() == ip::address_v6::any()) {
-    prohibitAllIpv6Endpoints(endpoint.port());
+  // tcp
+  // {
+  //   listen yes
+  //   port 6363
+  //   enable_v4 yes
+  //   enable_v6 yes
+  // }
+
+  if (!configSection) {
+    if (!context.isDryRun && !m_channels.empty()) {
+      NFD_LOG_WARN("Cannot disable tcp4 and tcp6 channels after initialization");
+    }
+    return;
   }
 
-  NFD_LOG_TRACE("prohibiting TCP " << endpoint);
-  m_prohibitedEndpoints.insert(endpoint);
-}
+  bool wantListen = true;
+  uint16_t port = 6363;
+  bool enableV4 = true;
+  bool enableV6 = true;
 
-void
-TcpFactory::prohibitAllIpv4Endpoints(uint16_t port)
-{
-  for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
-    for (const auto& addr : nic.ipv4Addresses) {
-      if (addr != ip::address_v4::any()) {
-        prohibitEndpoint(tcp::Endpoint(addr, port));
-      }
+  for (const auto& pair : *configSection) {
+    const std::string& key = pair.first;
+
+    if (key == "listen") {
+      wantListen = ConfigFile::parseYesNo(pair, "face_system.tcp");
+    }
+    else if (key == "port") {
+      port = ConfigFile::parseNumber<uint16_t>(pair, "face_system.tcp");
+    }
+    else if (key == "enable_v4") {
+      enableV4 = ConfigFile::parseYesNo(pair, "face_system.tcp");
+    }
+    else if (key == "enable_v6") {
+      enableV6 = ConfigFile::parseYesNo(pair, "face_system.tcp");
+    }
+    else {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.tcp." + key));
     }
   }
-}
 
-void
-TcpFactory::prohibitAllIpv6Endpoints(uint16_t port)
-{
-  for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
-    for (const auto& addr : nic.ipv6Addresses) {
-      if (addr != ip::address_v6::any()) {
-        prohibitEndpoint(tcp::Endpoint(addr, port));
+  if (!enableV4 && !enableV6) {
+    BOOST_THROW_EXCEPTION(ConfigFile::Error(
+      "IPv4 and IPv6 TCP channels have been disabled. Remove face_system.tcp section to disable "
+      "TCP channels or enable at least one channel type."));
+  }
+
+  if (!context.isDryRun) {
+    providedSchemes.insert("tcp");
+
+    if (enableV4) {
+      tcp::Endpoint endpoint(ip::tcp::v4(), port);
+      shared_ptr<TcpChannel> v4Channel = this->createChannel(endpoint);
+      if (wantListen && !v4Channel->isListening()) {
+        v4Channel->listen(context.addFace, nullptr);
       }
+      providedSchemes.insert("tcp4");
+    }
+    else if (providedSchemes.count("tcp4") > 0) {
+      NFD_LOG_WARN("Cannot close tcp4 channel after its creation");
+    }
+
+    if (enableV6) {
+      tcp::Endpoint endpoint(ip::tcp::v6(), port);
+      shared_ptr<TcpChannel> v6Channel = this->createChannel(endpoint);
+      if (wantListen && !v6Channel->isListening()) {
+        v6Channel->listen(context.addFace, nullptr);
+      }
+      providedSchemes.insert("tcp6");
+    }
+    else if (providedSchemes.count("tcp6") > 0) {
+      NFD_LOG_WARN("Cannot close tcp6 channel after its creation");
     }
   }
 }
 
-shared_ptr<TcpChannel>
-TcpFactory::createChannel(const tcp::Endpoint& endpoint)
-{
-  auto channel = findChannel(endpoint);
-  if (channel)
-    return channel;
-
-  channel = make_shared<TcpChannel>(endpoint);
-  m_channels[endpoint] = channel;
-  prohibitEndpoint(endpoint);
-
-  NFD_LOG_DEBUG("Channel [" << endpoint << "] created");
-  return channel;
-}
-
-shared_ptr<TcpChannel>
-TcpFactory::createChannel(const std::string& localIp, const std::string& localPort)
-{
-  tcp::Endpoint endpoint(ip::address::from_string(localIp),
-                         boost::lexical_cast<uint16_t>(localPort));
-  return createChannel(endpoint);
-}
-
 void
 TcpFactory::createFace(const FaceUri& uri,
                        ndn::nfd::FacePersistency persistency,
@@ -146,16 +162,75 @@
   onFailure(504, "No channels available to connect");
 }
 
+void
+TcpFactory::prohibitEndpoint(const tcp::Endpoint& endpoint)
+{
+  if (endpoint.address().is_v4() &&
+      endpoint.address() == ip::address_v4::any()) {
+    prohibitAllIpv4Endpoints(endpoint.port());
+  }
+  else if (endpoint.address().is_v6() &&
+           endpoint.address() == ip::address_v6::any()) {
+    prohibitAllIpv6Endpoints(endpoint.port());
+  }
+
+  NFD_LOG_TRACE("prohibiting TCP " << endpoint);
+  m_prohibitedEndpoints.insert(endpoint);
+}
+
+void
+TcpFactory::prohibitAllIpv4Endpoints(uint16_t port)
+{
+  ///\todo prohibited endpoints need to react to dynamic NIC changes
+  for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
+    for (const auto& addr : nic.ipv4Addresses) {
+      if (addr != ip::address_v4::any()) {
+        prohibitEndpoint(tcp::Endpoint(addr, port));
+      }
+    }
+  }
+}
+
+void
+TcpFactory::prohibitAllIpv6Endpoints(uint16_t port)
+{
+  ///\todo prohibited endpoints need to react to dynamic NIC changes
+  for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
+    for (const auto& addr : nic.ipv6Addresses) {
+      if (addr != ip::address_v6::any()) {
+        prohibitEndpoint(tcp::Endpoint(addr, port));
+      }
+    }
+  }
+}
+
+shared_ptr<TcpChannel>
+TcpFactory::createChannel(const tcp::Endpoint& endpoint)
+{
+  auto channel = findChannel(endpoint);
+  if (channel)
+    return channel;
+
+  channel = make_shared<TcpChannel>(endpoint);
+  m_channels[endpoint] = channel;
+  prohibitEndpoint(endpoint);
+
+  NFD_LOG_DEBUG("Channel [" << endpoint << "] created");
+  return channel;
+}
+
+shared_ptr<TcpChannel>
+TcpFactory::createChannel(const std::string& localIp, const std::string& localPort)
+{
+  tcp::Endpoint endpoint(ip::address::from_string(localIp),
+                         boost::lexical_cast<uint16_t>(localPort));
+  return createChannel(endpoint);
+}
+
 std::vector<shared_ptr<const Channel>>
 TcpFactory::getChannels() const
 {
-  std::vector<shared_ptr<const Channel>> channels;
-  channels.reserve(m_channels.size());
-
-  for (const auto& i : m_channels)
-    channels.push_back(i.second);
-
-  return channels;
+  return getChannelsFromMap(m_channels);
 }
 
 shared_ptr<TcpChannel>
@@ -168,4 +243,5 @@
     return nullptr;
 }
 
+} // namespace face
 } // namespace nfd
diff --git a/daemon/face/tcp-factory.hpp b/daemon/face/tcp-factory.hpp
index 2ed6ad1..161c490 100644
--- a/daemon/face/tcp-factory.hpp
+++ b/daemon/face/tcp-factory.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -30,10 +30,26 @@
 #include "tcp-channel.hpp"
 
 namespace nfd {
+namespace face {
 
+/** \brief protocol factory for TCP over IPv4 and IPv6
+ */
 class TcpFactory : public ProtocolFactory
 {
 public:
+  /** \brief process face_system.tcp config section
+   */
+  void
+  processConfig(OptionalConfigSection configSection,
+                FaceSystem::ConfigContext& context) override;
+
+  void
+  createFace(const FaceUri& uri,
+             ndn::nfd::FacePersistency persistency,
+             bool wantLocalFieldsEnabled,
+             const FaceCreatedCallback& onCreated,
+             const FaceCreationFailedCallback& onFailure) override;
+
   /**
    * \brief Create TCP-based channel using tcp::Endpoint
    *
@@ -63,15 +79,7 @@
   shared_ptr<TcpChannel>
   createChannel(const std::string& localIp, const std::string& localPort);
 
-public: // from ProtocolFactory
-  virtual void
-  createFace(const FaceUri& uri,
-             ndn::nfd::FacePersistency persistency,
-             bool wantLocalFieldsEnabled,
-             const FaceCreatedCallback& onCreated,
-             const FaceCreationFailedCallback& onFailure) override;
-
-  virtual std::vector<shared_ptr<const Channel>>
+  std::vector<shared_ptr<const Channel>>
   getChannels() const override;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
@@ -101,6 +109,7 @@
   std::set<tcp::Endpoint> m_prohibitedEndpoints;
 };
 
+} // namespace face
 } // namespace nfd
 
 #endif // NFD_DAEMON_FACE_TCP_FACTORY_HPP
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index e8a79eb..a2bfc3c 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -93,7 +93,7 @@
     return;
   }
 
-  ProtocolFactory* factory = m_faceSystem.getProtocolFactory(uri.getScheme());
+  ProtocolFactory* factory = m_faceSystem.getFactoryByScheme(uri.getScheme());
   if (factory == nullptr) {
     NFD_LOG_TRACE("received create request for unsupported protocol");
     done(ControlResponse(406, "Unsupported protocol"));
diff --git a/daemon/mgmt/tables-config-section.cpp b/daemon/mgmt/tables-config-section.cpp
index fcd0916..678b6e5 100644
--- a/daemon/mgmt/tables-config-section.cpp
+++ b/daemon/mgmt/tables-config-section.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -60,16 +60,14 @@
 void
 TablesConfigSection::processConfig(const ConfigSection& section, bool isDryRun)
 {
-  typedef boost::optional<const ConfigSection&> OptionalNode;
-
   size_t nCsMaxPackets = DEFAULT_CS_MAX_PACKETS;
-  OptionalNode csMaxPacketsNode = section.get_child_optional("cs_max_packets");
+  OptionalConfigSection csMaxPacketsNode = section.get_child_optional("cs_max_packets");
   if (csMaxPacketsNode) {
     nCsMaxPackets = ConfigFile::parseNumber<size_t>(*csMaxPacketsNode, "cs_max_packets", "tables");
   }
 
   unique_ptr<cs::Policy> csPolicy;
-  OptionalNode csPolicyNode = section.get_child_optional("cs_policy");
+  OptionalConfigSection csPolicyNode = section.get_child_optional("cs_policy");
   if (csPolicyNode) {
     std::string policyName = csPolicyNode->get_value<std::string>();
     csPolicy = cs::Policy::create(policyName);
@@ -80,7 +78,7 @@
   }
 
   unique_ptr<fw::UnsolicitedDataPolicy> unsolicitedDataPolicy;
-  OptionalNode unsolicitedDataPolicyNode = section.get_child_optional("cs_unsolicited_policy");
+  OptionalConfigSection unsolicitedDataPolicyNode = section.get_child_optional("cs_unsolicited_policy");
   if (unsolicitedDataPolicyNode) {
     std::string policyName = unsolicitedDataPolicyNode->get_value<std::string>();
     unsolicitedDataPolicy = fw::UnsolicitedDataPolicy::create(policyName);
@@ -93,12 +91,12 @@
     unsolicitedDataPolicy = make_unique<fw::DefaultUnsolicitedDataPolicy>();
   }
 
-  OptionalNode strategyChoiceSection = section.get_child_optional("strategy_choice");
+  OptionalConfigSection strategyChoiceSection = section.get_child_optional("strategy_choice");
   if (strategyChoiceSection) {
     processStrategyChoiceSection(*strategyChoiceSection, isDryRun);
   }
 
-  OptionalNode networkRegionSection = section.get_child_optional("network_region");
+  OptionalConfigSection networkRegionSection = section.get_child_optional("network_region");
   if (networkRegionSection) {
     processNetworkRegionSection(*networkRegionSection, isDryRun);
   }
diff --git a/tests/daemon/face/face-system-fixture.hpp b/tests/daemon/face/face-system-fixture.hpp
new file mode 100644
index 0000000..6bdbb41
--- /dev/null
+++ b/tests/daemon/face/face-system-fixture.hpp
@@ -0,0 +1,95 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  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/>.
+ */
+
+#ifndef NFD_TESTS_DAEMON_FACE_FACE_SYSTEM_FIXTURE_HPP
+#define NFD_TESTS_DAEMON_FACE_FACE_SYSTEM_FIXTURE_HPP
+
+#include "face/face-system.hpp"
+#include "fw/face-table.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+
+class FaceSystemFixture : public BaseFixture
+{
+public:
+  FaceSystemFixture()
+    : faceSystem(faceTable)
+  {
+    faceSystem.setConfigFile(configFile);
+  }
+
+  void
+  parseConfig(const std::string& text, bool isDryRun)
+  {
+    configFile.parse(text, isDryRun, "test-config");
+  }
+
+  /** \brief get ProtocolFactory from FaceSystem
+   *  \tparam F ProtocolFactory subclass
+   *
+   *  If ProtocolFactory with \p scheme does not exist or has an incompatible type,
+   *  this fails the test case.
+   */
+  template<typename F>
+  F&
+  getFactoryById(const std::string& id)
+  {
+    F* factory = dynamic_cast<F*>(faceSystem.getFactoryById(id));
+    BOOST_REQUIRE(factory != nullptr);
+    return *factory;
+  }
+
+  /** \brief get ProtocolFactory from FaceSystem
+   *  \tparam F ProtocolFactory subclass
+   *
+   *  If ProtocolFactory with \p scheme does not exist or has an incompatible type,
+   *  this fails the test case.
+   */
+  template<typename F>
+  F&
+  getFactoryByScheme(const std::string& scheme)
+  {
+    F* factory = dynamic_cast<F*>(faceSystem.getFactoryByScheme(scheme));
+    BOOST_REQUIRE(factory != nullptr);
+    return *factory;
+  }
+
+protected:
+  ConfigFile configFile;
+  FaceTable faceTable;
+  FaceSystem faceSystem;
+};
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_TESTS_DAEMON_FACE_FACE_SYSTEM_FIXTURE_HPP
diff --git a/tests/daemon/face/face-system.t.cpp b/tests/daemon/face/face-system.t.cpp
index bcae725..36c904f 100644
--- a/tests/daemon/face/face-system.t.cpp
+++ b/tests/daemon/face/face-system.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -24,13 +24,12 @@
  */
 
 #include "face/face-system.hpp"
-#include "fw/face-table.hpp"
+#include "face-system-fixture.hpp"
 
 // ProtocolFactory includes, sorted alphabetically
 #ifdef HAVE_LIBPCAP
 #include "face/ethernet-factory.hpp"
 #endif // HAVE_LIBPCAP
-#include "face/tcp-factory.hpp"
 #include "face/udp-factory.hpp"
 #ifdef HAVE_UNIX_SOCKETS
 #include "face/unix-stream-factory.hpp"
@@ -47,29 +46,169 @@
 
 using namespace nfd::tests;
 
-class FaceSystemFixture : public BaseFixture
+BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestFaceSystem, FaceSystemFixture)
+
+BOOST_AUTO_TEST_SUITE(ProcessConfig)
+
+class DummyProtocolFactory : public ProtocolFactory
 {
 public:
-  FaceSystemFixture()
-    : faceSystem(faceTable)
+  void
+  processConfig(OptionalConfigSection configSection,
+                FaceSystem::ConfigContext& context) override
   {
-    faceSystem.setConfigFile(configFile);
+    processConfigHistory.push_back({configSection, context.isDryRun});
+    if (!context.isDryRun) {
+      this->providedSchemes = this->newProvidedSchemes;
+    }
   }
 
   void
-  parseConfig(const std::string& text, bool isDryRun)
+  createFace(const FaceUri& uri,
+             ndn::nfd::FacePersistency persistency,
+             bool wantLocalFieldsEnabled,
+             const FaceCreatedCallback& onCreated,
+             const FaceCreationFailedCallback& onFailure) override
   {
-    configFile.parse(text, isDryRun, "test-config");
+    BOOST_FAIL("createFace should not be called");
   }
 
-protected:
-  FaceTable faceTable;
-  FaceSystem faceSystem;
-  ConfigFile configFile;
+  std::vector<shared_ptr<const Channel>>
+  getChannels() const override
+  {
+    BOOST_FAIL("getChannels should not be called");
+    return {};
+  }
+
+public:
+  struct ProcessConfigArgs
+  {
+    OptionalConfigSection configSection;
+    bool isDryRun;
+  };
+  std::vector<ProcessConfigArgs> processConfigHistory;
+
+  std::set<std::string> newProvidedSchemes;
 };
 
-BOOST_AUTO_TEST_SUITE(Face)
-BOOST_FIXTURE_TEST_SUITE(TestFaceSystem, FaceSystemFixture)
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  auto f1 = make_shared<DummyProtocolFactory>();
+  auto f2 = make_shared<DummyProtocolFactory>();
+  faceSystem.m_factories["f1"] = f1;
+  faceSystem.m_factories["f2"] = f2;
+
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      f1
+      {
+        key v1
+      }
+      f2
+      {
+        key v2
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_REQUIRE_EQUAL(f1->processConfigHistory.size(), 1);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().isDryRun, true);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().configSection->get<std::string>("key"), "v1");
+  BOOST_REQUIRE_EQUAL(f2->processConfigHistory.size(), 1);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().isDryRun, true);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().configSection->get<std::string>("key"), "v2");
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+  BOOST_REQUIRE_EQUAL(f1->processConfigHistory.size(), 2);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().isDryRun, false);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().configSection->get<std::string>("key"), "v1");
+  BOOST_REQUIRE_EQUAL(f2->processConfigHistory.size(), 2);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().isDryRun, false);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().configSection->get<std::string>("key"), "v2");
+}
+
+BOOST_AUTO_TEST_CASE(OmittedSection)
+{
+  auto f1 = make_shared<DummyProtocolFactory>();
+  auto f2 = make_shared<DummyProtocolFactory>();
+  faceSystem.m_factories["f1"] = f1;
+  faceSystem.m_factories["f2"] = f2;
+
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      f1
+      {
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_REQUIRE_EQUAL(f1->processConfigHistory.size(), 1);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().isDryRun, true);
+  BOOST_REQUIRE_EQUAL(f2->processConfigHistory.size(), 1);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().isDryRun, true);
+  BOOST_CHECK(!f2->processConfigHistory.back().configSection);
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+  BOOST_REQUIRE_EQUAL(f1->processConfigHistory.size(), 2);
+  BOOST_CHECK_EQUAL(f1->processConfigHistory.back().isDryRun, false);
+  BOOST_REQUIRE_EQUAL(f2->processConfigHistory.size(), 2);
+  BOOST_CHECK_EQUAL(f2->processConfigHistory.back().isDryRun, false);
+  BOOST_CHECK(!f2->processConfigHistory.back().configSection);
+}
+
+BOOST_AUTO_TEST_CASE(UnknownSection)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      f0
+      {
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ChangeProvidedSchemes)
+{
+  auto f1 = make_shared<DummyProtocolFactory>();
+  faceSystem.m_factories["f1"] = f1;
+
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      f1
+      {
+      }
+    }
+  )CONFIG";
+
+  f1->newProvidedSchemes.insert("s1");
+  f1->newProvidedSchemes.insert("s2");
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+  BOOST_CHECK(faceSystem.getFactoryByScheme("f1") == nullptr);
+  BOOST_CHECK_EQUAL(faceSystem.getFactoryByScheme("s1"), f1.get());
+  BOOST_CHECK_EQUAL(faceSystem.getFactoryByScheme("s2"), f1.get());
+
+  f1->newProvidedSchemes.erase("s2");
+  f1->newProvidedSchemes.insert("s3");
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+  BOOST_CHECK(faceSystem.getFactoryByScheme("f1") == nullptr);
+  BOOST_CHECK_EQUAL(faceSystem.getFactoryByScheme("s1"), f1.get());
+  BOOST_CHECK(faceSystem.getFactoryByScheme("s2") == nullptr);
+  BOOST_CHECK_EQUAL(faceSystem.getFactoryByScheme("s3"), f1.get());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
+
+///\todo #3904 move Config* to *Factory test suite
 
 #ifdef HAVE_UNIX_SOCKETS
 BOOST_AUTO_TEST_SUITE(ConfigUnix)
@@ -89,9 +228,8 @@
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
 
-  auto factory = dynamic_cast<UnixStreamFactory*>(faceSystem.getProtocolFactory("unix"));
-  BOOST_REQUIRE(factory != nullptr);
-  BOOST_CHECK_EQUAL(factory->getChannels().size(), 1);
+  auto& factory = this->getFactoryByScheme<UnixStreamFactory>("unix");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 1);
 }
 
 BOOST_AUTO_TEST_CASE(UnknownOption)
@@ -113,83 +251,6 @@
 BOOST_AUTO_TEST_SUITE_END() // ConfigUnix
 #endif // HAVE_UNIX_SOCKETS
 
-BOOST_AUTO_TEST_SUITE(ConfigTcp)
-
-BOOST_AUTO_TEST_CASE(Normal)
-{
-  const std::string CONFIG = R"CONFIG(
-    face_system
-    {
-      tcp
-      {
-        listen yes
-        port 16363
-        enable_v4 yes
-        enable_v6 yes
-      }
-    }
-  )CONFIG";
-
-  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
-  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
-
-  auto factory = dynamic_cast<TcpFactory*>(faceSystem.getProtocolFactory("tcp"));
-  BOOST_REQUIRE(factory != nullptr);
-  BOOST_CHECK_EQUAL(factory->getChannels().size(), 2);
-}
-
-BOOST_AUTO_TEST_CASE(BadListen)
-{
-  const std::string CONFIG = R"CONFIG(
-    face_system
-    {
-      tcp
-      {
-        listen hello
-      }
-    }
-  )CONFIG";
-
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
-}
-
-BOOST_AUTO_TEST_CASE(ChannelsDisabled)
-{
-  const std::string CONFIG = R"CONFIG(
-    face_system
-    {
-      tcp
-      {
-        port 6363
-        enable_v4 no
-        enable_v6 no
-      }
-    }
-  )CONFIG";
-
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
-}
-
-BOOST_AUTO_TEST_CASE(UnknownOption)
-{
-  const std::string CONFIG = R"CONFIG(
-    face_system
-    {
-      tcp
-      {
-        hello
-      }
-    }
-  )CONFIG";
-
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
-}
-
-BOOST_AUTO_TEST_SUITE_END() // ConfigTcp
-
 BOOST_AUTO_TEST_SUITE(ConfigUdp)
 
 BOOST_AUTO_TEST_CASE(Normal)
@@ -216,9 +277,8 @@
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
 
-  auto factory = dynamic_cast<UdpFactory*>(faceSystem.getProtocolFactory("udp"));
-  BOOST_REQUIRE(factory != nullptr);
-  BOOST_CHECK_EQUAL(factory->getChannels().size(), 2);
+  auto& factory = this->getFactoryByScheme<UdpFactory>("udp");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 2);
 }
 
 BOOST_AUTO_TEST_CASE(BadIdleTimeout)
@@ -367,10 +427,9 @@
 
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
 
-  auto factory = dynamic_cast<UdpFactory*>(faceSystem.getProtocolFactory("udp"));
-  BOOST_REQUIRE(factory != nullptr);
+  auto& factory = this->getFactoryByScheme<UdpFactory>("udp");
 
-  if (factory->getMulticastFaces().empty()) {
+  if (factory.getMulticastFaces().empty()) {
     BOOST_WARN_MESSAGE(false, "skipping assertions that require at least one UDP multicast face");
     return;
   }
@@ -387,7 +446,7 @@
 
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
   BOOST_REQUIRE_NO_THROW(g_io.poll());
-  BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(factory.getMulticastFaces().size(), 0);
 }
 BOOST_AUTO_TEST_SUITE_END() // ConfigUdp
 
@@ -419,9 +478,8 @@
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
 
-  auto factory = dynamic_cast<EthernetFactory*>(faceSystem.getProtocolFactory("ether"));
-  BOOST_REQUIRE(factory != nullptr);
-  BOOST_CHECK_EQUAL(factory->getChannels().size(), 0);
+  auto& factory = this->getFactoryByScheme<EthernetFactory>("ether");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(BadMcast)
@@ -489,10 +547,9 @@
 
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITH_MCAST, false));
 
-  auto factory = dynamic_cast<EthernetFactory*>(faceSystem.getProtocolFactory("ether"));
-  BOOST_REQUIRE(factory != nullptr);
+  auto& factory = this->getFactoryByScheme<EthernetFactory>("ether");
 
-  if (factory->getMulticastFaces().empty()) {
+  if (factory.getMulticastFaces().empty()) {
     BOOST_WARN_MESSAGE(false, "skipping assertions that require at least one Ethernet multicast face");
     return;
   }
@@ -509,7 +566,7 @@
 
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
   BOOST_REQUIRE_NO_THROW(g_io.poll());
-  BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(factory.getMulticastFaces().size(), 0);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // ConfigEther
@@ -536,9 +593,8 @@
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
 
-  auto factory = dynamic_cast<WebSocketFactory*>(faceSystem.getProtocolFactory("websocket"));
-  BOOST_REQUIRE(factory != nullptr);
-  BOOST_CHECK_EQUAL(factory->getChannels().size(), 1);
+  auto& factory = this->getFactoryByScheme<WebSocketFactory>("websocket");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 1);
 }
 
 BOOST_AUTO_TEST_CASE(ChannelsDisabled)
diff --git a/tests/daemon/face/tcp-factory.t.cpp b/tests/daemon/face/tcp-factory.t.cpp
index a617011..617c4cc 100644
--- a/tests/daemon/face/tcp-factory.t.cpp
+++ b/tests/daemon/face/tcp-factory.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,18 +25,112 @@
 
 #include "face/tcp-factory.hpp"
 
-#include "core/network-interface.hpp"
 #include "factory-test-common.hpp"
+#include "face-system-fixture.hpp"
 #include "tests/limited-io.hpp"
 
 namespace nfd {
+namespace face {
 namespace tests {
 
+using namespace nfd::tests;
+
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_FIXTURE_TEST_SUITE(TestTcpFactory, BaseFixture)
 
 using nfd::Face;
 
+BOOST_FIXTURE_TEST_SUITE(ProcessConfig, FaceSystemFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        listen yes
+        port 16363
+        enable_v4 yes
+        enable_v6 yes
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+
+  auto& factory = this->getFactoryById<TcpFactory>("tcp");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Omitted)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+    }
+  )CONFIG";
+
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
+  BOOST_CHECK_NO_THROW(parseConfig(CONFIG, false));
+
+  auto& factory = this->getFactoryById<TcpFactory>("tcp");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(BadListen)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        listen hello
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ChannelsDisabled)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        port 6363
+        enable_v4 no
+        enable_v6 no
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnknownOption)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        hello
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
+
 BOOST_AUTO_TEST_CASE(ChannelMap)
 {
   TcpFactory factory;
@@ -243,4 +337,5 @@
 BOOST_AUTO_TEST_SUITE_END() // Face
 
 } // namespace tests
+} // namespace face
 } // namespace nfd
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 5e46d97..d6ef39c 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -146,7 +146,7 @@
 
 BOOST_AUTO_TEST_CASE(FaceCreate)
 {
-  UdpFactory factory = UdpFactory();
+  UdpFactory factory;
 
   createFace(factory,
              FaceUri("udp4://127.0.0.1:6363"),
@@ -177,7 +177,7 @@
 
 BOOST_AUTO_TEST_CASE(UnsupportedFaceCreate)
 {
-  UdpFactory factory = UdpFactory();
+  UdpFactory factory;
 
   factory.createChannel("127.0.0.1", "20070");
 
diff --git a/tests/daemon/mgmt/face-manager.t.cpp b/tests/daemon/mgmt/face-manager.t.cpp
index 1dbeaf7..913b5d6 100644
--- a/tests/daemon/mgmt/face-manager.t.cpp
+++ b/tests/daemon/mgmt/face-manager.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -281,7 +281,7 @@
 BOOST_AUTO_TEST_CASE(ChannelDataset)
 {
   auto factory = make_shared<TestProtocolFactory>();
-  m_manager.m_faceSystem.m_factories["test"] = factory;
+  m_manager.m_faceSystem.m_factoryByScheme["test"] = factory;
 
   std::map<std::string, shared_ptr<TestChannel>> addedChannels;
   size_t nEntries = 404;