mgmt: Add router name configuration options

refs: #3026

Change-Id: I44250476e7e7f25cbc2b40d6a59f64b55872b24a
diff --git a/daemon/mgmt/general-config-section.cpp b/daemon/mgmt/general-config-section.cpp
index 02f45e8..4064548 100644
--- a/daemon/mgmt/general-config-section.cpp
+++ b/daemon/mgmt/general-config-section.cpp
@@ -35,6 +35,67 @@
 
 NFD_LOG_INIT("GeneralConfigSection");
 
+const ndn::Name
+RouterName::getName() const
+{
+  ndn::Name routerName;
+
+  if (network.empty() || site.empty() || router.empty())
+    {
+      return routerName;
+    }
+
+  routerName = network;
+  routerName.append(site);
+  routerName.append(ROUTER_MARKER);
+  routerName.append(router);
+
+  return routerName;
+}
+
+const ndn::PartialName RouterName::ROUTER_MARKER("%C1.Router");
+
+static RouterName&
+getRouterNameInstance()
+{
+  static RouterName routerName;
+  return routerName;
+}
+
+ndn::PartialName
+loadPartialNameFromSection(const ConfigSection& section, const std::string& key)
+{
+  ndn::PartialName value;
+
+  try
+    {
+      value = section.get<ndn::PartialName>(key);
+
+      if (value.empty())
+        {
+          throw ConfigFile::Error("Invalid value for \"router_name." + key + "\""
+                                  " in \"general\" section");
+        }
+    }
+  catch (const boost::property_tree::ptree_error& error)
+    {
+      throw ConfigFile::Error("Invalid value for \"router_name." + key + "\""
+                              " in \"general\" section");
+    }
+
+  return value;
+}
+
+void
+processSectionRouterName(const ConfigSection& section, bool isDryRun)
+{
+  RouterName& routerName = getRouterNameInstance();
+
+  routerName.network = loadPartialNameFromSection(section, "network");
+  routerName.site = loadPartialNameFromSection(section, "site");
+  routerName.router = loadPartialNameFromSection(section, "router");
+}
+
 static void
 onConfig(const ConfigSection& configSection,
          bool isDryRun,
@@ -44,6 +105,13 @@
   // {
   //    ; user "ndn-user"
   //    ; group "ndn-user"
+  //
+  //    ; router_name
+  //    ; {
+  //    ;   network ndn
+  //    ;   site    edu/site
+  //    ;   router  router/name
+  //    ; }
   // }
 
   std::string user;
@@ -93,6 +161,14 @@
   NFD_LOG_TRACE("using user \"" << user << "\" group \"" << group << "\"");
 
   PrivilegeHelper::initialize(user, group);
+
+  boost::optional<const ConfigSection&> routerNameSection =
+    configSection.get_child_optional("router_name");
+
+  if (routerNameSection)
+    {
+      processSectionRouterName(*routerNameSection, isDryRun);
+    }
 }
 
 void
@@ -101,6 +177,12 @@
   configFile.addSectionHandler("general", &onConfig);
 }
 
+const RouterName&
+getRouterName()
+{
+  return getRouterNameInstance();
+}
+
 } // namespace general
 
 } // namespace nfd
diff --git a/daemon/mgmt/general-config-section.hpp b/daemon/mgmt/general-config-section.hpp
index 6ce5473..e86abd2 100644
--- a/daemon/mgmt/general-config-section.hpp
+++ b/daemon/mgmt/general-config-section.hpp
@@ -25,6 +25,8 @@
 #ifndef NFD_MGMT_GENERAL_CONFIG_SECTION_HPP
 #define NFD_MGMT_GENERAL_CONFIG_SECTION_HPP
 
+#include <ndn-cxx/name.hpp>
+
 namespace nfd {
 
 class ConfigFile;
@@ -34,6 +36,33 @@
 void
 setConfigFile(ConfigFile& configFile);
 
+class RouterName
+{
+public:
+  /**
+   * \brief Return the router name constructed from the network, site, and
+   *        router variables.
+   *
+   *        The router name is constructed in the following manner:
+   *        /<network>/<site>/<ROUTER_MARKER>/<router>
+   *
+   * \return The constructed router name if the network, site, and router
+   *         configuration options are non-empty; otherwise, an empty ndn::Name.
+   */
+  const ndn::Name
+  getName() const;
+
+public:
+  ndn::PartialName network;
+  ndn::PartialName site;
+  ndn::PartialName router;
+
+  static const ndn::PartialName ROUTER_MARKER;
+};
+
+const RouterName&
+getRouterName();
+
 } // namespace general
 
 } // namespace nfd
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index fa229b4..bb4d839 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -7,6 +7,18 @@
 
   ; user ndn-user
   ; group ndn-user
+
+  ; The router_name section specifies the name components used
+  ; to build this router's name. The router name is constructed
+  ; using the configuration options in the following manner:
+  ;   /<network>/<site>/<%C1.Router>/<router>
+
+  ; router_name
+  ; {
+  ;   network ndn ; the network to which this router belongs
+  ;   site    edu/site ; the site to which this router belongs
+  ;   router  router/name ; name used to uniquely identify this router under the site
+  ; }
 }
 
 log
diff --git a/tests/daemon/mgmt/general-config-section.t.cpp b/tests/daemon/mgmt/general-config-section.t.cpp
index 83063b9..ed52046 100644
--- a/tests/daemon/mgmt/general-config-section.t.cpp
+++ b/tests/daemon/mgmt/general-config-section.t.cpp
@@ -74,6 +74,8 @@
 
   general::setConfigFile(configFile);
   BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+
+  BOOST_CHECK(getRouterName().getName().empty());
 }
 
 BOOST_AUTO_TEST_CASE(NoUserConfig)
@@ -144,6 +146,159 @@
                           bind(&checkExceptionMessage, _1, expected));
 }
 
+BOOST_AUTO_TEST_CASE(RouterNameConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network ndn\n"
+    "    site    edu/site\n"
+    "    router  router/name\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+
+  BOOST_CHECK_EQUAL(getRouterName().network, ndn::PartialName("ndn"));
+  BOOST_CHECK_EQUAL(getRouterName().site, ndn::PartialName("edu/site"));
+  BOOST_CHECK_EQUAL(getRouterName().router, ndn::PartialName("router/name"));
+  BOOST_CHECK_EQUAL(getRouterName().getName(), ndn::Name("/ndn/edu/site/%C1.Router/router/name"));
+}
+
+BOOST_AUTO_TEST_CASE(NoNetworkConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    site    edu/site\n"
+    "    router  router/name\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.network\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(NoSiteConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network ndn\n"
+    "    router  router/name\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.site\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(NoRouterConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network ndn\n"
+    "    site    edu/site\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.router\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidNetworkConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network\n"
+    "    site    edu/site\n"
+    "    router  router/name\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.network\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidSiteConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network ndn\n"
+    "    site\n"
+    "    router  router/name\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.site\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidRouterConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  router_name\n"
+    "  {\n"
+    "    network ndn\n"
+    "    site    edu/site\n"
+    "    router\n"
+    "  }\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"router_name.router\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests