mgmt: tables.cs_unsolicited_policy config option

This commit introduces tests/check-typeid.hpp which provides
unit testing tools to validate runtime type information.

refs #2181

Change-Id: I987c9875517001ea82878cbe2646839a03ad54f3
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index ae4369e..7fad250 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -36,7 +36,7 @@
 NFD_LOG_INIT("Forwarder");
 
 Forwarder::Forwarder()
-  : m_unsolicitedDataPolicy(new fw::AdmitLocalUnsolicitedDataPolicy())
+  : m_unsolicitedDataPolicy(new fw::DefaultUnsolicitedDataPolicy())
   , m_fib(m_nameTree)
   , m_pit(m_nameTree)
   , m_measurements(m_nameTree)
diff --git a/daemon/fw/unsolicited-data-policy.cpp b/daemon/fw/unsolicited-data-policy.cpp
index 79c6d77..67d4d37 100644
--- a/daemon/fw/unsolicited-data-policy.cpp
+++ b/daemon/fw/unsolicited-data-policy.cpp
@@ -70,5 +70,24 @@
   return UnsolicitedDataDecision::CACHE;
 }
 
+unique_ptr<UnsolicitedDataPolicy>
+makeUnsolicitedDataPolicy(const std::string& key)
+{
+  /// \todo register policy with a macro
+  if (key == "drop-all") {
+    return make_unique<DropAllUnsolicitedDataPolicy>();
+  }
+  if (key == "admit-local") {
+    return make_unique<AdmitLocalUnsolicitedDataPolicy>();
+  }
+  if (key == "admit-network") {
+    return make_unique<AdmitNetworkUnsolicitedDataPolicy>();
+  }
+  if (key == "admit-all") {
+    return make_unique<AdmitAllUnsolicitedDataPolicy>();
+  }
+  return nullptr;
+}
+
 } // namespace fw
 } // namespace nfd
diff --git a/daemon/fw/unsolicited-data-policy.hpp b/daemon/fw/unsolicited-data-policy.hpp
index 2cbad2a..06354d0 100644
--- a/daemon/fw/unsolicited-data-policy.hpp
+++ b/daemon/fw/unsolicited-data-policy.hpp
@@ -92,6 +92,15 @@
   decide(const Face& inFace, const Data& data) const final;
 };
 
+/** \return an UnsolicitedDataPolicy identified by \p key, or nullptr if \p key is unknown
+ */
+unique_ptr<UnsolicitedDataPolicy>
+makeUnsolicitedDataPolicy(const std::string& key);
+
+/** \brief the default UnsolicitedDataPolicy
+ */
+typedef AdmitLocalUnsolicitedDataPolicy DefaultUnsolicitedDataPolicy;
+
 } // namespace fw
 } // namespace nfd
 
diff --git a/daemon/mgmt/tables-config-section.cpp b/daemon/mgmt/tables-config-section.cpp
index 57230cd..2773a24 100644
--- a/daemon/mgmt/tables-config-section.cpp
+++ b/daemon/mgmt/tables-config-section.cpp
@@ -50,6 +50,7 @@
   }
 
   m_forwarder.getCs().setLimit(DEFAULT_CS_MAX_PACKETS);
+  m_forwarder.setUnsolicitedDataPolicy(make_unique<fw::DefaultUnsolicitedDataPolicy>());
 
   m_isConfigured = true;
 }
@@ -65,6 +66,20 @@
     nCsMaxPackets = ConfigFile::parseNumber<size_t>(*csMaxPacketsNode, "cs_max_packets", "tables");
   }
 
+  unique_ptr<fw::UnsolicitedDataPolicy> unsolicitedDataPolicy;
+  OptionalNode unsolicitedDataPolicyNode = section.get_child_optional("cs_unsolicited_policy");
+  if (unsolicitedDataPolicyNode) {
+    std::string policyKey = unsolicitedDataPolicyNode->get_value<std::string>();
+    unsolicitedDataPolicy = fw::makeUnsolicitedDataPolicy(policyKey);
+    if (unsolicitedDataPolicy == nullptr) {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error(
+        "Unknown cs_unsolicited_policy \"" + policyKey + "\" in \"tables\" section"));
+    }
+  }
+  else {
+    unsolicitedDataPolicy = make_unique<fw::DefaultUnsolicitedDataPolicy>();
+  }
+
   OptionalNode strategyChoiceSection = section.get_child_optional("strategy_choice");
   if (strategyChoiceSection) {
     processStrategyChoiceSection(*strategyChoiceSection, isDryRun);
@@ -81,6 +96,8 @@
 
   m_forwarder.getCs().setLimit(nCsMaxPackets);
 
+  m_forwarder.setUnsolicitedDataPolicy(std::move(unsolicitedDataPolicy));
+
   m_isConfigured = true;
 }
 
diff --git a/daemon/mgmt/tables-config-section.hpp b/daemon/mgmt/tables-config-section.hpp
index 4aff010..8afa608 100644
--- a/daemon/mgmt/tables-config-section.hpp
+++ b/daemon/mgmt/tables-config-section.hpp
@@ -39,6 +39,8 @@
  *  {
  *    cs_max_packets 65536
  *
+ *    cs_unsolicited_policy drop-all
+ *
  *    strategy_choice
  *    {
  *      /               /localhost/nfd/strategy/best-route
@@ -54,6 +56,16 @@
  *    }
  *  }
  *  \endcode
+ *
+ *  During a configuration reload,
+ *  \li cs_max_packets and cs_unsolicited_policy are applied;
+ *      defaults are used if an option is omitted.
+ *  \li strategy_choice entries are inserted, but old entries are not deleted.
+ *  \li network_region is applied; it's kept unchanged if the section is omitted.
+ *
+ *  It's necessary to call \p ensureConfigured() after initial configuration and
+ *  configuration reload, so that the correct defaults are applied in case
+ *  tables section is omitted.
  */
 class TablesConfigSection : noncopyable
 {
@@ -80,7 +92,6 @@
   processNetworkRegionSection(const ConfigSection& section, bool isDryRun);
 
 private:
-private:
   static const size_t DEFAULT_CS_MAX_PACKETS;
 
   Forwarder& m_forwarder;