face: parse face_system.netdev_bound config section

refs #3521

Change-Id: I803a1651d5b44e021ec7bedb8001e216c849b9ab
diff --git a/daemon/face/face-system.cpp b/daemon/face/face-system.cpp
index 872efef..79880da 100644
--- a/daemon/face/face-system.cpp
+++ b/daemon/face/face-system.cpp
@@ -25,6 +25,7 @@
 
 #include "face-system.hpp"
 #include "protocol-factory.hpp"
+#include "netdev-bound.hpp"
 #include "core/global-io.hpp"
 #include "fw/face-table.hpp"
 
@@ -33,6 +34,9 @@
 
 NFD_LOG_INIT(FaceSystem);
 
+static const std::string SECTION_GENERAL = "general";
+static const std::string SECTION_NETDEVBOUND = "netdev_bound";
+
 FaceSystem::FaceSystem(FaceTable& faceTable, shared_ptr<ndn::net::NetworkMonitor> netmon)
   : m_faceTable(faceTable)
   , m_netmon(std::move(netmon))
@@ -42,6 +46,8 @@
     NFD_LOG_TRACE("creating factory " << id);
     m_factories[id] = ProtocolFactory::create(id, pfCtorParams);
   }
+
+  m_netdevBound = make_unique<NetdevBound>(pfCtorParams, *this);
 }
 
 ProtocolFactoryCtorParams
@@ -77,6 +83,12 @@
   return found == m_factoryByScheme.end() ? nullptr : found->second;
 }
 
+bool
+FaceSystem::hasFactoryForScheme(const std::string& scheme) const
+{
+  return m_factoryByScheme.count(scheme) > 0;
+}
+
 void
 FaceSystem::setConfigFile(ConfigFile& configFile)
 {
@@ -90,7 +102,7 @@
   context.isDryRun = isDryRun;
 
   // process general protocol factory config section
-  auto generalSection = configSection.get_child_optional("general");
+  auto generalSection = configSection.get_child_optional(SECTION_GENERAL);
   if (generalSection) {
     for (const auto& pair : *generalSection) {
       const std::string& key = pair.first;
@@ -103,7 +115,7 @@
     }
   }
 
-  // process sections in protocol factories
+  // process in protocol factories
   for (const auto& pair : m_factories) {
     const std::string& sectionName = pair.first;
     ProtocolFactory* factory = pair.second.get();
@@ -127,6 +139,10 @@
     }
   }
 
+  // process netdev_bound section, after factories start providing *+dev schemes
+  auto netdevBoundSection = configSection.get_child_optional(SECTION_NETDEVBOUND);
+  m_netdevBound->processConfig(netdevBoundSection, context);
+
   // process other sections
   std::set<std::string> seenSections;
   for (const auto& pair : configSection) {
@@ -137,12 +153,11 @@
       BOOST_THROW_EXCEPTION(ConfigFile::Error("Duplicate section face_system." + sectionName));
     }
 
-    if (sectionName == "general" || m_factories.count(sectionName) > 0) {
+    if (sectionName == SECTION_GENERAL || sectionName == SECTION_NETDEVBOUND ||
+        m_factories.count(sectionName) > 0) {
       continue;
     }
 
-    ///\todo #3521 nicfaces
-
     BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system." + sectionName));
   }
 }
diff --git a/daemon/face/face-system.hpp b/daemon/face/face-system.hpp
index 18da37e..833da1b 100644
--- a/daemon/face/face-system.hpp
+++ b/daemon/face/face-system.hpp
@@ -39,6 +39,7 @@
 
 namespace face {
 
+class NetdevBound;
 class ProtocolFactory;
 struct ProtocolFactoryCtorParams;
 
@@ -69,6 +70,9 @@
   ProtocolFactory*
   getFactoryByScheme(const std::string& scheme);
 
+  bool
+  hasFactoryForScheme(const std::string& scheme) const;
+
   FaceTable&
   getFaceTable()
   {
@@ -109,6 +113,7 @@
   /** \brief config section name => protocol factory
    */
   std::map<std::string, unique_ptr<ProtocolFactory>> m_factories;
+  unique_ptr<NetdevBound> m_netdevBound;
 
 private:
   /** \brief scheme => protocol factory
@@ -118,7 +123,6 @@
   std::map<std::string, ProtocolFactory*> m_factoryByScheme;
 
   FaceTable& m_faceTable;
-
   shared_ptr<ndn::net::NetworkMonitor> m_netmon;
 };
 
diff --git a/daemon/face/netdev-bound.cpp b/daemon/face/netdev-bound.cpp
new file mode 100644
index 0000000..a47bd43
--- /dev/null
+++ b/daemon/face/netdev-bound.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  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 "netdev-bound.hpp"
+#include "face-system.hpp"
+#include "core/logger.hpp"
+
+namespace nfd {
+namespace face {
+
+NFD_LOG_INIT(NetdevBound);
+
+NetdevBound::NetdevBound(const ProtocolFactoryCtorParams& params, const FaceSystem& faceSystem)
+  : m_faceSystem(faceSystem)
+  , m_addFace(params.addFace)
+  , m_netmon(params.netmon)
+{
+}
+
+void
+NetdevBound::processConfig(OptionalConfigSection configSection,
+                           FaceSystem::ConfigContext& context)
+{
+  std::vector<Rule> rules;
+  if (configSection) {
+    int ruleIndex = 0;
+    for (const auto& pair : *configSection) {
+      const std::string& key = pair.first;
+      const ConfigSection& value = pair.second;
+      if (key == "rule") {
+        rules.push_back(parseRule(ruleIndex++, value));
+      }
+      else {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error(
+          "Unrecognized option face_system.netdev_bound." + key));
+      }
+    }
+  }
+
+  if (context.isDryRun) {
+    return;
+  }
+
+  ///\todo #3521 this should be verified in dry-run, but PFs won't publish their *+dev schemes
+  ///            in dry-run
+  for (size_t i = 0; i < rules.size(); ++i) {
+    const Rule& rule = rules[i];
+    for (const FaceUri& remote : rule.remotes) {
+      std::string devScheme = remote.getScheme() + "+dev";
+      if (!m_faceSystem.hasFactoryForScheme(devScheme)) {
+        BOOST_THROW_EXCEPTION(RuleParseError(
+          i, "scheme " + devScheme + " for " + remote.toString() + " is unavailable"));
+      }
+    }
+  }
+  NFD_LOG_DEBUG("processConfig: processed " << rules.size() << " rules");
+
+  m_rules.swap(rules);
+  std::map<Key, shared_ptr<Face>> oldFaces;
+  oldFaces.swap(m_faces);
+
+  ///\todo #3521 for each face needed under m_rules:
+  ///            if it's in oldFaces, add to m_faces and remove from oldFaces;
+  ///            otherwise, create via factory and add to m_faces
+
+  ///\todo #3521 close faces in oldFaces
+}
+
+NetdevBound::Rule
+NetdevBound::parseRule(int index, const ConfigSection& confRule) const
+{
+  Rule rule;
+
+  bool hasWhitelist = false;
+  bool hasBlacklist = false;
+  for (const auto& pair : confRule) {
+    const std::string& key = pair.first;
+    const ConfigSection& value = pair.second;
+    if (key == "remote") {
+      try {
+        rule.remotes.emplace_back(value.get_value<std::string>());
+      }
+      catch (const FaceUri::Error& ex) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "invalid remote FaceUri", ex.what()));
+      }
+      if (!rule.remotes.back().isCanonical()) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "remote FaceUri is not canonical"));
+      }
+    }
+    else if (key == "whitelist") {
+      if (hasWhitelist) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "duplicate whitelist"));
+      }
+      try {
+        rule.netifPredicate.parseWhitelist(value);
+      }
+      catch (const ConfigFile::Error& ex) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "invalid whitelist", ex.what()));
+      }
+      hasWhitelist = true;
+    }
+    else if (key == "blacklist") {
+      if (hasBlacklist) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "duplicate blacklist"));
+      }
+      try {
+        rule.netifPredicate.parseBlacklist(value);
+      }
+      catch (const ConfigFile::Error& ex) {
+        BOOST_THROW_EXCEPTION(RuleParseError(index, "invalid blacklist", ex.what()));
+      }
+      hasBlacklist = true;
+    }
+    else {
+      BOOST_THROW_EXCEPTION(RuleParseError(index, "unrecognized option " + key));
+    }
+  }
+
+  if (rule.remotes.empty()) {
+    BOOST_THROW_EXCEPTION(RuleParseError(index, "remote FaceUri is missing"));
+  }
+
+  ///\todo #3521 for each remote, check that there is a factory providing scheme+dev scheme
+  return rule;
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/netdev-bound.hpp b/daemon/face/netdev-bound.hpp
new file mode 100644
index 0000000..17a6ce8
--- /dev/null
+++ b/daemon/face/netdev-bound.hpp
@@ -0,0 +1,93 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  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_DAEMON_FACE_NETDEV_BOUND_HPP
+#define NFD_DAEMON_FACE_NETDEV_BOUND_HPP
+
+#include "protocol-factory.hpp"
+
+namespace nfd {
+namespace face {
+
+class FaceSystem;
+
+/** \brief manages netdev-bound faces
+ */
+class NetdevBound : noncopyable
+{
+public:
+  class RuleParseError : public ConfigFile::Error
+  {
+  public:
+    RuleParseError(int index, std::string msg)
+      : Error("Error parsing face_system.netdev_bound.rule[" + to_string(index) + "]: " + msg)
+      , index(index)
+      , msg(std::move(msg))
+    {
+    }
+
+    RuleParseError(int index, std::string msg, const char* innerMsg)
+      : RuleParseError(index, msg + " - " + innerMsg)
+    {
+    }
+
+  public:
+    int index;
+    std::string msg;
+  };
+
+  NetdevBound(const ProtocolFactoryCtorParams& params, const FaceSystem& faceSystem);
+
+  /** \brief process face_system.netdev_bound config section
+   */
+  void
+  processConfig(OptionalConfigSection configSection,
+                FaceSystem::ConfigContext& context);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  struct Rule
+  {
+    std::vector<FaceUri> remotes;
+    NetworkInterfacePredicate netifPredicate;
+  };
+
+  Rule
+  parseRule(int index, const ConfigSection& confRule) const;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  const FaceSystem& m_faceSystem;
+  FaceCreatedCallback m_addFace;
+  shared_ptr<ndn::net::NetworkMonitor> m_netmon;
+
+  std::vector<Rule> m_rules;
+
+  using Key = std::pair<FaceUri, std::string>;
+  std::map<Key, shared_ptr<Face>> m_faces;
+};
+
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_NETDEV_BOUND_HPP
diff --git a/daemon/face/protocol-factory.cpp b/daemon/face/protocol-factory.cpp
index 0a8a150..fb1440d 100644
--- a/daemon/face/protocol-factory.cpp
+++ b/daemon/face/protocol-factory.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -66,5 +66,21 @@
   BOOST_ASSERT(netmon != nullptr);
 }
 
+shared_ptr<Face>
+ProtocolFactory::createNetdevBoundFace(const FaceUri& remote,
+                                       const shared_ptr<const ndn::net::NetworkInterface>& netif)
+{
+  BOOST_ASSERT(remote.isCanonical());
+  return this->doCreateNetdevBoundFace(remote, netif);
+}
+
+shared_ptr<Face>
+ProtocolFactory::doCreateNetdevBoundFace(const FaceUri& remote,
+                                         const shared_ptr<const ndn::net::NetworkInterface>& netif)
+{
+  BOOST_THROW_EXCEPTION(Error(
+    "this protocol factory does not support netdev-bound faces"));
+}
+
 } // namespace face
 } // namespace nfd
diff --git a/daemon/face/protocol-factory.hpp b/daemon/face/protocol-factory.hpp
index 74dbde7..f3d7807 100644
--- a/daemon/face/protocol-factory.hpp
+++ b/daemon/face/protocol-factory.hpp
@@ -156,6 +156,18 @@
   virtual std::vector<shared_ptr<const Channel>>
   getChannels() const = 0;
 
+  /** \brief Create a netdev-bound face
+   *  \param remote remote FaceUri, must be canonical
+   *  \param netdev local network interface
+   *  \return new face
+   *  \throw Error cannot create a face using specified arguments
+   *  \note The caller must ensure there is no existing netdev-bound face with same remote FaceUri
+   *        on the same local network interface.
+   */
+  shared_ptr<Face>
+  createNetdevBoundFace(const FaceUri& remote,
+                        const shared_ptr<const ndn::net::NetworkInterface>& netdev);
+
 protected:
   explicit
   ProtocolFactory(const CtorParams& params);
@@ -169,6 +181,26 @@
     return channels;
   }
 
+private:
+  /** \brief Create a netdev-bound face
+   *  \sa createNetdevBoundFace
+   *
+   *  The base class implementation always throws Error indicating netdev-bound faces are not
+   *  supported.
+   *
+   *  A subclass that offers netdev-bound faces should override this method, and also expose
+   *  "scheme+dev" in providedSchemes. For example, UdpFactory should provide "udp4+dev" scheme,
+   *  in addition to "udp4" scheme.
+   *
+   *  The face should be constructed immediately. Face persistency shall be reported as PERMANENT.
+   *  Face state shall remain DOWN until underlying transport is connected. The face shall remain
+   *  open until after .close() is invoked, and survive all socket errors; in case the network
+   *  interface disappears, the face shall remain DOWN until .close() is invoked.
+   */
+  virtual shared_ptr<Face>
+  doCreateNetdevBoundFace(const FaceUri& remote,
+                          const shared_ptr<const ndn::net::NetworkInterface>& netif);
+
 private: // registry
   using CreateFunc = std::function<unique_ptr<ProtocolFactory>(const CtorParams&)>;
   using Registry = std::map<std::string, CreateFunc>; // indexed by factory id