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/nlsr.conf b/nlsr.conf
index 7c59dee..fecb57e 100644
--- a/nlsr.conf
+++ b/nlsr.conf
@@ -123,9 +123,10 @@
 {
   ; the ndnname is used to advertised name from the router. To advertise each name prefix
   ; configure one block of ndnname configuration command for every name prefix.
+  ; format: <name-prefix> <cost>
 
-  prefix /ndn/edu/memphis/cs/netlab           ; name in ndn URI format
-  prefix /ndn/edu/memphis/sports/basketball
+  /ndn/edu/memphis/cs/netlab 0
+  /ndn/edu/memphis/sports/basketball 0
 }
 
 security
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
diff --git a/tests/test-conf-file-processor.cpp b/tests/test-conf-file-processor.cpp
index c9acbd2..dc9564f 100644
--- a/tests/test-conf-file-processor.cpp
+++ b/tests/test-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.
  *
@@ -110,8 +110,8 @@
 const std::string SECTION_ADVERTISING =
   "advertising\n"
   "{\n"
-  "  prefix /ndn/edu/memphis/cs/netlab\n"
-  "  prefix /ndn/edu/memphis/sports/basketball\n"
+  "  /ndn/edu/memphis/cs/netlab 0\n"
+  "  /ndn/edu/memphis/sports/basketball 0\n"
   "}\n";
 
 // NEED TO TEST SECURITY SECTION SUCH AS LOADING CERTIFICATE
diff --git a/tests/update/test-save-delete-prefix.cpp b/tests/update/test-save-delete-prefix.cpp
index 5adec17..4f1d3a5 100644
--- a/tests/update/test-save-delete-prefix.cpp
+++ b/tests/update/test-save-delete-prefix.cpp
@@ -123,7 +123,7 @@
     // counter helps to check if multiple prefix of same name exists on conf file
     counter = 0;
     for (const auto& section : m_savePrefix.get_child("advertising")) {
-      auto b = section.second.get_value<std::string>();
+      auto b = section.first.data();
       if (b == prefixName) {
         counter++;
       }
diff --git a/tools/nlsrc.cpp b/tools/nlsrc.cpp
index 32a008c..c8bd76c 100644
--- a/tools/nlsrc.cpp
+++ b/tools/nlsrc.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.
  *
@@ -82,14 +82,14 @@
            display routing table status
        status
            display all NLSR status (lsdb & routingtable)
-       advertise <name>
-           advertise a name prefix through NLSR
-       advertise <name> save
-           advertise and save the name prefix to the conf file
+       advertise <name> [cost <cost>]
+           advertise a name prefix with optionally set cost through NLSR
+       advertise <name> [cost <cost>] save
+           advertise and save the name prefix and cost to the stateful conf file
        withdraw <name>
            remove a name prefix advertised through NLSR
        withdraw <name> delete
-           withdraw and delete the name prefix from the conf file
+           withdraw and delete the name prefix from the stateful conf file
 )EOT");
   boost::algorithm::replace_all_copy(std::ostream_iterator<char>(std::cout),
                                      help, "@NLSRC@", m_programName);
@@ -166,17 +166,39 @@
   if (subcommand.size() == 0) {
     return false;
   }
-
+  bool saveValue = false;
   if (subcommand[0] == "advertise") {
     switch (subcommand.size()) {
       case 2:
-        advertiseName(subcommand[1], false);
+        advertiseName(subcommand[1], false, 0);
         return true;
       case 3:
         if (subcommand[2] != "save") {
           return false;
         }
-        advertiseName(subcommand[1], true);
+        saveValue = true;
+        advertiseName(subcommand[1], saveValue, 0);
+        return true;
+      case 5:
+        if (subcommand[4] != "save") {
+          return false;
+        }
+        saveValue = true;
+        [[fallthrough]];
+      case 4:
+        if (subcommand[2] != "cost") {
+          return false;
+        }
+        uint64_t costValue = 0;
+        try {
+          costValue = std::stoi(subcommand[3]);
+        }
+        catch (const std::exception& e) {
+          std::cerr << "ERROR: Invalid cost given to advertise command" << std::endl;
+          m_exitCode = 1;
+          return false;
+        }
+        advertiseName(subcommand[1], saveValue, costValue);
         return true;
     }
     return false;
@@ -221,11 +243,11 @@
 }
 
 void
-Nlsrc::advertiseName(ndn::Name name, bool wantSave)
+Nlsrc::advertiseName(ndn::Name name, bool wantSave, uint64_t cost)
 {
   std::string info = (wantSave ? "(Save: " : "(Advertise: ") + name.toUri() + ")";
   ndn::Name::Component verb("advertise");
-  sendNamePrefixUpdate(name, verb, info, wantSave);
+  sendNamePrefixUpdate(name, verb, info, wantSave, cost);
 }
 
 void
@@ -240,7 +262,8 @@
 Nlsrc::sendNamePrefixUpdate(const ndn::Name& name,
                             const ndn::Name::Component& verb,
                             const std::string& info,
-                            bool flag)
+                            bool flag,
+                            uint64_t cost)
 {
   ndn::nfd::ControlParameters parameters;
   parameters.setName(name);
@@ -248,6 +271,10 @@
     parameters.setFlags(1);
   }
 
+  if (verb.toUri() == "advertise" and cost > 0) {
+    parameters.setCost(cost);
+  }
+
   auto paramWire = parameters.wireEncode();
   ndn::Name commandName = m_routerPrefix;
   commandName.append(NAME_UPDATE_SUFFIX);
diff --git a/tools/nlsrc.hpp b/tools/nlsrc.hpp
index a64c940..4337375 100644
--- a/tools/nlsrc.hpp
+++ b/tools/nlsrc.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2023,  The University of Memphis,
+ * Copyright (c) 2014-2025,  The University of Memphis,
  *                           Regents of the University of California,
  *                           Arizona Board of Regents.
  *
@@ -86,7 +86,7 @@
    *
    */
   void
-  advertiseName(ndn::Name name, bool wantSave);
+  advertiseName(ndn::Name name, bool wantSave, uint64_t cost = 0);
 
   /**
    * \brief Removes a name prefix from NLSR's Name LSA
@@ -102,7 +102,8 @@
   sendNamePrefixUpdate(const ndn::Name& name,
                        const ndn::Name::Component& verb,
                        const std::string& info,
-                       bool saveFlag);
+                       bool saveFlag,
+                       uint64_t cost = 0);
 
   void
   onControlResponse(const std::string& info, const ndn::Data& data);