Add support for setting NLSR prefix costs via configuration file and nlsrc

This change alters the nlsr.conf 'advertising' format, as the previous
setup used unnecessary keys which made dynamically adjusting the
stateful conf file difficult.

Incorporates code written by Yanbiao Li.

Change-Id: I3ed5d0a564099be8cc1389ee6acc6f2d04cef889
diff --git a/src/conf-file-processor.cpp b/src/conf-file-processor.cpp
index 29f22cf..40751b3 100644
--- a/src/conf-file-processor.cpp
+++ b/src/conf-file-processor.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2024,  The University of Memphis,
+ * Copyright (c) 2014-2025,  The University of Memphis,
  *                           Regents of the University of California,
  *                           Arizona Board of Regents.
  *
@@ -30,6 +30,7 @@
 
 #include <boost/algorithm/string.hpp>
 #include <boost/property_tree/info_parser.hpp>
+#include <boost/property_tree/exceptions.hpp>
 
 #include <filesystem>
 #include <fstream>
@@ -501,8 +502,8 @@
           m_confParam.getAdjacencyList().insert(adj);
         }
         else {
-          std::cerr << " Wrong command format ! [name /nbr/name/ \n face-uri /uri\n]";
-          std::cerr << " or bad URI format" << std::endl;
+          std::cerr << "No neighbor name found or bad URI format! Expected:\n"
+                    << " name [neighbor router name]\n face-uri [face uri]\n link-cost [link cost] OPTIONAL" << std::endl;
         }
       }
       catch (const std::exception& ex) {
@@ -605,23 +606,27 @@
 ConfFileProcessor::processConfSectionAdvertising(const ConfigSection& section)
 {
   for (const auto& tn : section) {
-   if (tn.first == "prefix") {
      try {
-       ndn::Name namePrefix(tn.second.data());
+       ndn::Name namePrefix(tn.first.data());
        if (!namePrefix.empty()) {
-         m_confParam.getNamePrefixList().insert(namePrefix);
+         m_confParam.getNamePrefixList().insert(namePrefix, "", tn.second.get_value<uint64_t>());
        }
        else {
-         std::cerr << " Wrong command format ! [prefix /name/prefix] or bad URI" << std::endl;
+         std::cerr << " Wrong format or bad URI!\nExpected [name in ndn URI format] [cost],"
+                   << " got prefix: " << tn.first.data() << " cost:" << tn.second.data() << std::endl;
          return false;
        }
      }
+     catch (const boost::property_tree::ptree_bad_data& ex) {
+       //Catches errors from get_value above
+       std::cerr << "Invalid cost format; only integers are allowed" << std::endl;
+       return false;
+     }
      catch (const std::exception& ex) {
        std::cerr << ex.what() << std::endl;
        return false;
      }
     }
-  }
   return true;
 }
 
diff --git a/src/update/command-processor.cpp b/src/update/command-processor.cpp
index f260bd6..95290d5 100644
--- a/src/update/command-processor.cpp
+++ b/src/update/command-processor.cpp
@@ -56,7 +56,7 @@
     m_lsdb.buildAndInstallOwnNameLsa();
     if (castParams.hasFlags() && castParams.getFlags() == PREFIX_FLAG) {
       NLSR_LOG_INFO("Saving name to the configuration file ");
-      auto [afterAdvertiseReturn, afterAdvertiseMessage] = afterAdvertise(castParams.getName());
+      auto [afterAdvertiseReturn, afterAdvertiseMessage] = afterAdvertise(castParams.getName(), castParamCost);
       if (afterAdvertiseReturn) {
         return done(ndn::nfd::ControlResponse(205, afterAdvertiseMessage).setBody(responseParams.wireEncode()));
       }
@@ -71,7 +71,7 @@
     if (castParams.hasFlags() && castParams.getFlags() == PREFIX_FLAG) {
       // Save an already advertised prefix
       NLSR_LOG_INFO("Saving an already advertised name: " << castParams.getName());
-      auto [afterAdvertiseReturn, afterAdvertiseMessage] = afterAdvertise(castParams.getName());
+      auto [afterAdvertiseReturn, afterAdvertiseMessage] = afterAdvertise(castParams.getName(), castParamCost);
       if (afterAdvertiseReturn) {
         return done(ndn::nfd::ControlResponse(205, afterAdvertiseMessage).setBody(responseParams.wireEncode()));
       }
diff --git a/src/update/command-processor.hpp b/src/update/command-processor.hpp
index 1d25ec0..0973839 100644
--- a/src/update/command-processor.hpp
+++ b/src/update/command-processor.hpp
@@ -70,7 +70,7 @@
    *  \return tuple {bool indicating success/failure, message string}.
    */
   virtual std::tuple<bool, std::string>
-  afterAdvertise(const ndn::Name& prefix)
+  afterAdvertise(const ndn::Name& prefix, uint64_t cost)
   {
     return {true, "OK"};
   }
diff --git a/src/update/prefix-update-commands.cpp b/src/update/prefix-update-commands.cpp
index da43fe7..0e2c3d6 100644
--- a/src/update/prefix-update-commands.cpp
+++ b/src/update/prefix-update-commands.cpp
@@ -26,10 +26,12 @@
 const AdvertisePrefixCommand::RequestFormat AdvertisePrefixCommand::s_requestFormat =
     RequestFormat()
     .required(ndn::nfd::CONTROL_PARAMETER_NAME)
+    .optional(ndn::nfd::CONTROL_PARAMETER_COST)
     .optional(ndn::nfd::CONTROL_PARAMETER_FLAGS);
 const AdvertisePrefixCommand::ResponseFormat AdvertisePrefixCommand::s_responseFormat =
     ResponseFormat()
     .required(ndn::nfd::CONTROL_PARAMETER_NAME)
+    .optional(ndn::nfd::CONTROL_PARAMETER_COST)
     .optional(ndn::nfd::CONTROL_PARAMETER_FLAGS);
 
 const WithdrawPrefixCommand::RequestFormat WithdrawPrefixCommand::s_requestFormat =
diff --git a/src/update/prefix-update-processor.cpp b/src/update/prefix-update-processor.cpp
index 2f403fb..2bec0ae 100644
--- a/src/update/prefix-update-processor.cpp
+++ b/src/update/prefix-update-processor.cpp
@@ -22,6 +22,7 @@
 #include "prefix-update-processor.hpp"
 #include "logger.hpp"
 #include "prefix-update-commands.hpp"
+#include "utility/boost-info-editor.hpp"
 
 #include <boost/algorithm/string.hpp>
 #include <fstream>
@@ -115,33 +116,25 @@
 }
 
 std::tuple<bool, std::string>
-PrefixUpdateProcessor::addOrDeletePrefix(const ndn::Name& prefix, bool addPrefix)
+PrefixUpdateProcessor::addOrDeletePrefix(const ndn::Name& prefix, uint64_t cost, bool addPrefix)
 {
-  std::string value = " prefix " + prefix.toUri();
-  std::string fileString;
-  std::string line;
-  std::string trimedLine;
+  std::string section = "advertising." + prefix.toUri();
+  std::string value = "    " + prefix.toUri() + " " + std::to_string(cost);
   std::fstream input(m_confFileNameDynamic, input.in);
   if (!input.good() || !input.is_open()) {
     NLSR_LOG_ERROR("Failed to open configuration file for parsing");
     return {false, "Failed to open configuration file for parsing"};
   }
-
+  input.close();
   if (addPrefix) {
     //check if prefix already exist in the nlsr configuration file
     if (checkForPrefixInFile(value)) {
       NLSR_LOG_ERROR("Prefix already exists in the configuration file");
       return {false, "Prefix already exists in the configuration file"};
     }
-    while (!input.eof()) {
-      getline(input, line);
-      if (!line.empty()) {
-        fileString.append(line + "\n");
-        if (line == "advertising") {
-          getline(input, line);
-          fileString.append(line + "\n" + value + "\n");
-        }
-      }
+    if (!util::boost_info_editor::put(m_confFileNameDynamic, section, std::to_string(cost))) {
+      NLSR_LOG_ERROR("Unable to save changes to configuration file");
+      return {false, "Unable to save changes to configuration file"};
     }
   }
   else {
@@ -149,35 +142,25 @@
       NLSR_LOG_ERROR("Prefix doesn't exists in the configuration file");
       return {false, "Prefix doesn't exists in the configuration file"};
     }
-    boost::trim(value);
-    while (!input.eof()) {
-      getline(input, line);
-      if (!line.empty()) {
-        std::string trimLine = line;
-        boost::trim(trimLine);
-        if (trimLine != value) {
-          fileString.append(line + "\n");
-        }
-      }
+    if (!util::boost_info_editor::remove(m_confFileNameDynamic, section)) {
+      NLSR_LOG_ERROR("Unable to save changes to configuration file");
+      return {false, "Unable to save changes to configuration file"};
     }
   }
-  input.close();
-  std::ofstream output(m_confFileNameDynamic);
-  output << fileString;
-  output.close();
+
   return {true, "OK"};
 }
 
 std::tuple<bool, std::string>
-PrefixUpdateProcessor::afterAdvertise(const ndn::Name& prefix)
+PrefixUpdateProcessor::afterAdvertise(const ndn::Name& prefix, uint64_t cost)
 {
-  return addOrDeletePrefix(prefix, true);
+  return addOrDeletePrefix(prefix, cost, true);
 }
 
 std::tuple<bool, std::string>
 PrefixUpdateProcessor::afterWithdraw(const ndn::Name& prefix)
 {
-  return addOrDeletePrefix(prefix, false);
+  return addOrDeletePrefix(prefix, 0, false);
 }
 
 } // namespace nlsr::update
diff --git a/src/update/prefix-update-processor.hpp b/src/update/prefix-update-processor.hpp
index bc05937..1d937b1 100644
--- a/src/update/prefix-update-processor.hpp
+++ b/src/update/prefix-update-processor.hpp
@@ -58,13 +58,13 @@
    * configuration file
    */
   std::tuple<bool, std::string>
-  addOrDeletePrefix(const ndn::Name& prefix, bool addPrefix);
+  addOrDeletePrefix(const ndn::Name& prefix, uint64_t cost, bool addPrefix);
 
   /*! \brief Save an advertised prefix to the nlsr configuration file.
    *  \return tuple {bool indicating success/failure, message string}.
    */
   std::tuple<bool, std::string>
-  afterAdvertise(const ndn::Name& prefix) override;
+  afterAdvertise(const ndn::Name& prefix, uint64_t cost) override;
 
   /*! \brief Remove an advertised prefix from the nlsr configuration file.
    *  \return tuple {bool indicating success/failure, message string}.
diff --git a/src/utility/boost-info-editor.cpp b/src/utility/boost-info-editor.cpp
new file mode 100644
index 0000000..bd6893a
--- /dev/null
+++ b/src/utility/boost-info-editor.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2025,  The University of Memphis,
+ *                           Regents of the University of California,
+ *                           Arizona Board of Regents.
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR 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.
+ *
+ * NLSR 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
+ * NLSR, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "boost-info-editor.hpp"
+#include "logger.hpp"
+
+#include <boost/property_tree/info_parser.hpp>
+
+namespace nlsr::util::boost_info_editor {
+
+// Incorporates Apache 2.0 licensed code by Yanbiao Li under a compatible license
+boost::property_tree::ptree
+load(const std::string& fileName)
+{
+  boost::property_tree::ptree info;
+  std::ifstream input(fileName);
+
+  // Thrown errors are received at calling function
+  boost::property_tree::info_parser::read_info(input, info);
+
+  input.close();
+  return info;
+}
+
+bool
+save(const std::string& fileName, boost::property_tree::ptree& info)
+{
+  std::ofstream output(fileName);
+  try {
+    write_info(output, info);
+  } catch (const boost::property_tree::info_parser::info_parser_error& error) {
+    return false;
+  }
+  output.close();
+  return true;
+}
+
+bool
+put(const std::string& fileName, const std::string& section, const std::string& value)
+{
+  boost::property_tree::ptree info;
+  try {
+    info = load(fileName);
+  }
+  catch (const boost::property_tree::info_parser::info_parser_error& error) {
+    return false;
+  }
+  info.put(section.c_str(), value.c_str());
+  return save(fileName, info);
+}
+
+bool
+remove(const std::string& fileName, const std::string& section)
+{
+  boost::property_tree::ptree info;
+  try {
+    info = load(fileName);
+  }
+  catch (const boost::property_tree::info_parser::info_parser_error& error) {
+    return false;
+  }
+  std::size_t pos = section.find_last_of(".");
+  if (pos == std::string::npos) {
+    info.erase(section.c_str());
+  }
+  else {
+    boost::optional<boost::property_tree::ptree&> child =
+      info.get_child_optional(section.substr(0, pos));
+    if (child) {
+      child->erase(section.substr(pos + 1));
+    }
+  }
+  return save(fileName, info);
+}
+} // namespace nlsr::util::boost_info_editor
diff --git a/src/utility/boost-info-editor.hpp b/src/utility/boost-info-editor.hpp
new file mode 100644
index 0000000..d4ba0e5
--- /dev/null
+++ b/src/utility/boost-info-editor.hpp
@@ -0,0 +1,45 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2025,  The University of Memphis,
+ *                           Regents of the University of California,
+ *                           Arizona Board of Regents.
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR 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.
+ *
+ * NLSR 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
+ * NLSR, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NLSR_BOOST_INFO_EDITOR_HPP
+#define NLSR_BOOST_INFO_EDITOR_HPP
+
+#include <string>
+
+namespace nlsr::util::boost_info_editor {
+
+/*! Insert a specified combination of configuration field name and value into a specific file path
+  Returns false if any errors occur on I/O, otherwise true.
+  \param fileName Path to conf file as string
+  \param section Section name to add/modify in conf file; expects [section].[subsection]... formatting
+  \param value The value to insert into the sheet as a string. Typechecking should be done in caller.
+*/
+bool put(const std::string& fileName, const std::string& section, const std::string& value);
+
+/*! Remove a specified key and its value from a configuration file at a specific path
+  Returns false if any errors occur on I/O, otherwise true.
+  \param fileName Path to conf file as string
+  \param section Section name to add/modify in conf file; expects [section].[subsection]... formatting
+*/
+bool remove(const std::string& fileName, const std::string& section);
+} // namespace nlsr::util::boost_info_editor
+
+#endif // NLSR_BOOST_INFO_EDITOR_HPP