face: process face_system.websocket config section in WebSocketFactory

refs #3904

Change-Id: I8e30a54ab0664432cebf5d74e0467e9bfba60f2f
diff --git a/daemon/face/face-system.cpp b/daemon/face/face-system.cpp
index 6ce6afe..e5b5175 100644
--- a/daemon/face/face-system.cpp
+++ b/daemon/face/face-system.cpp
@@ -59,6 +59,10 @@
 #ifdef HAVE_UNIX_SOCKETS
   m_factories["unix"] = make_shared<UnixStreamFactory>();
 #endif // HAVE_UNIX_SOCKETS
+
+#ifdef HAVE_WEBSOCKET
+  m_factories["websocket"] = make_shared<WebSocketFactory>();
+#endif // HAVE_WEBSOCKET
 }
 
 std::set<const ProtocolFactory*>
@@ -138,9 +142,6 @@
     if (sectionName == "udp") {
       processSectionUdp(subSection, isDryRun, context.m_netifs);
     }
-    else if (sectionName == "websocket") {
-      processSectionWebSocket(subSection, isDryRun);
-    }
     else {
       BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system." + sectionName));
     }
@@ -302,86 +303,5 @@
   }
 }
 
-void
-FaceSystem::processSectionWebSocket(const ConfigSection& configSection, bool isDryRun)
-{
-  // ; the websocket section contains settings of WebSocket faces and channels
-  // websocket
-  // {
-  //   listen yes ; set to 'no' to disable WebSocket listener, default 'yes'
-  //   port 9696 ; WebSocket listener port number
-  //   enable_v4 yes ; set to 'no' to disable listening on IPv4 socket, default 'yes'
-  //   enable_v6 yes ; set to 'no' to disable listening on IPv6 socket, default 'yes'
-  // }
-
-#if defined(HAVE_WEBSOCKET)
-  uint16_t port = 9696;
-  bool needToListen = true;
-  bool enableV4 = true;
-  bool enableV6 = true;
-
-  for (const auto& i : configSection) {
-    if (i.first == "port") {
-      port = ConfigFile::parseNumber<uint16_t>(i, "websocket");
-      NFD_LOG_TRACE("WebSocket port set to " << port);
-    }
-    else if (i.first == "listen") {
-      needToListen = ConfigFile::parseYesNo(i, "websocket");
-    }
-    else if (i.first == "enable_v4") {
-      enableV4 = ConfigFile::parseYesNo(i, "websocket");
-    }
-    else if (i.first == "enable_v6") {
-      enableV6 = ConfigFile::parseYesNo(i, "websocket");
-    }
-    else {
-      BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option \"" +
-                                              i.first + "\" in \"websocket\" section"));
-    }
-  }
-
-  if (!enableV4 && !enableV6) {
-    BOOST_THROW_EXCEPTION(ConfigFile::Error("IPv4 and IPv6 WebSocket channels have been disabled."
-                                            " Remove \"websocket\" section to disable WebSocket channels or"
-                                            " re-enable at least one channel type."));
-  }
-
-  if (!enableV4 && enableV6) {
-    BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD does not allow pure IPv6 WebSocket channel."));
-  }
-
-  if (!isDryRun) {
-    if (m_factoryByScheme.count("websocket") > 0) {
-      return;
-    }
-
-    auto factory = make_shared<WebSocketFactory>();
-    m_factoryByScheme.emplace("websocket", factory);
-
-    shared_ptr<WebSocketChannel> channel;
-
-    if (enableV6 && enableV4) {
-      websocket::Endpoint endpoint(boost::asio::ip::address_v6::any(), port);
-      channel = factory->createChannel(endpoint);
-
-      m_factoryByScheme.emplace("websocket46", factory);
-    }
-    else if (enableV4) {
-      websocket::Endpoint endpoint(boost::asio::ip::address_v4::any(), port);
-      channel = factory->createChannel(endpoint);
-
-      m_factoryByScheme.emplace("websocket4", factory);
-    }
-
-    if (channel && needToListen) {
-      channel->listen(bind(&FaceTable::add, &m_faceTable, _1));
-    }
-  }
-#else
-  BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD was compiled without WebSocket, "
-                                          "cannot process \"websocket\" section"));
-#endif // HAVE_WEBSOCKET
-}
-
 } // namespace face
 } // namespace nfd
diff --git a/daemon/face/face-system.hpp b/daemon/face/face-system.hpp
index 5696aa7..4d374c7 100644
--- a/daemon/face/face-system.hpp
+++ b/daemon/face/face-system.hpp
@@ -102,9 +102,6 @@
   processSectionUdp(const ConfigSection& configSection, bool isDryRun,
                     const std::vector<NetworkInterfaceInfo>& nicList);
 
-  void
-  processSectionWebSocket(const ConfigSection& configSection, bool isDryRun);
-
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   /** \brief config section name => protocol factory
    *
diff --git a/daemon/face/websocket-factory.cpp b/daemon/face/websocket-factory.cpp
index a92d93e..1898993 100644
--- a/daemon/face/websocket-factory.cpp
+++ b/daemon/face/websocket-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,
@@ -24,11 +24,97 @@
  */
 
 #include "websocket-factory.hpp"
+#include "core/logger.hpp"
 
 namespace nfd {
+namespace face {
 
 namespace ip = boost::asio::ip;
 
+NFD_LOG_INIT("WebSocketFactory");
+
+void
+WebSocketFactory::processConfig(OptionalConfigSection configSection,
+                                FaceSystem::ConfigContext& context)
+{
+  // websocket
+  // {
+  //   listen yes
+  //   port 9696
+  //   enable_v4 yes
+  //   enable_v6 yes
+  // }
+
+  bool wantListen = false;
+  uint16_t port = 9696;
+  bool enableV4 = true;
+  bool enableV6 = true;
+
+  if (configSection) {
+    wantListen = true;
+    for (const auto& pair : *configSection) {
+      const std::string& key = pair.first;
+
+      if (key == "listen") {
+        wantListen = ConfigFile::parseYesNo(pair, "face_system.websocket");
+      }
+      else if (key == "port") {
+        port = ConfigFile::parseNumber<uint16_t>(pair, "face_system.websocket");
+      }
+      else if (key == "enable_v4") {
+        enableV4 = ConfigFile::parseYesNo(pair, "face_system.websocket");
+      }
+      else if (key == "enable_v6") {
+        enableV6 = ConfigFile::parseYesNo(pair, "face_system.websocket");
+      }
+      else {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.websocket." + key));
+      }
+    }
+  }
+
+  if (!enableV4 && !enableV6) {
+    BOOST_THROW_EXCEPTION(ConfigFile::Error(
+      "IPv4 and IPv6 WebSocket channels have been disabled. Remove face_system.websocket section "
+      "to disable WebSocket channels or enable at least one channel type."));
+  }
+
+  if (!enableV4 && enableV6) {
+    // websocketpp's IPv6 socket always accepts IPv4 connections.
+    BOOST_THROW_EXCEPTION(ConfigFile::Error("NFD does not allow pure IPv6 WebSocket channel."));
+  }
+
+  if (!context.isDryRun) {
+    if (!wantListen) {
+      if (!m_channels.empty()) {
+        NFD_LOG_WARN("Cannot close WebSocket channel after initialization");
+      }
+      return;
+    }
+
+    BOOST_ASSERT(enableV4);
+    websocket::Endpoint endpoint(enableV6 ? ip::tcp::v6() : ip::tcp::v4(), port);
+
+    auto channel = this->createChannel(endpoint);
+    if (!channel->isListening()) {
+      channel->listen(context.addFace);
+      if (m_channels.size() > 1) {
+        NFD_LOG_WARN("Adding WebSocket channel for new endpoint; cannot close existing channels");
+      }
+    }
+  }
+}
+
+void
+WebSocketFactory::createFace(const FaceUri& uri,
+                             ndn::nfd::FacePersistency persistency,
+                             bool wantLocalFieldsEnabled,
+                             const FaceCreatedCallback& onCreated,
+                             const FaceCreationFailedCallback& onFailure)
+{
+  onFailure(406, "Unsupported protocol");
+}
+
 shared_ptr<WebSocketChannel>
 WebSocketFactory::createChannel(const websocket::Endpoint& endpoint)
 {
@@ -50,26 +136,10 @@
   return createChannel(endpoint);
 }
 
-void
-WebSocketFactory::createFace(const FaceUri& uri,
-                             ndn::nfd::FacePersistency persistency,
-                             bool wantLocalFieldsEnabled,
-                             const FaceCreatedCallback& onCreated,
-                             const FaceCreationFailedCallback& onFailure)
-{
-  onFailure(406, "Unsupported protocol");
-}
-
 std::vector<shared_ptr<const Channel>>
 WebSocketFactory::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<WebSocketChannel>
@@ -82,4 +152,5 @@
     return nullptr;
 }
 
+} // namespace face
 } // namespace nfd
diff --git a/daemon/face/websocket-factory.hpp b/daemon/face/websocket-factory.hpp
index 1f784b7..a31c7cd 100644
--- a/daemon/face/websocket-factory.hpp
+++ b/daemon/face/websocket-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,28 @@
 #include "websocket-channel.hpp"
 
 namespace nfd {
+namespace face {
 
+/** \brief protocol factory for WebSocket
+ */
 class WebSocketFactory : public ProtocolFactory
 {
 public:
+  /** \brief process face_system.websocket config section
+   */
+  void
+  processConfig(OptionalConfigSection configSection,
+                FaceSystem::ConfigContext& context) override;
+
+  /** \brief unicast face creation is not supported and will always fail
+   */
+  void
+  createFace(const FaceUri& uri,
+             ndn::nfd::FacePersistency persistency,
+             bool wantLocalFieldsEnabled,
+             const FaceCreatedCallback& onCreated,
+             const FaceCreationFailedCallback& onFailure) override;
+
   /**
    * \brief Create WebSocket-based channel using websocket::Endpoint
    *
@@ -58,15 +76,7 @@
   shared_ptr<WebSocketChannel>
   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;
 
 private:
@@ -83,6 +93,7 @@
   std::map<websocket::Endpoint, shared_ptr<WebSocketChannel>> m_channels;
 };
 
+} // namespace face
 } // namespace nfd
 
 #endif // NFD_DAEMON_FACE_WEBSOCKET_FACTORY_HPP