tools: Add nlsrc command line tool

refs: #1834

Change-Id: Ifbe75a3f49e2689930e61ebfd348d9932b04c975
diff --git a/docs/conf.py b/docs/conf.py
index 83e251b..7314c6d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -59,7 +59,7 @@
 
 # General information about the project.
 project = u'NLSR - Named Data Link State Routing Protocol'
-copyright = u'2014, Named Data Networking Project'
+copyright = u'2014-2015, Named Data Networking Project'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -239,6 +239,7 @@
 man_pages = [
     ('manpages/nlsr', 'nlsr', u'Named Data Link State Routing Protocol Daemon', None, 1),
     ('manpages/nlsr.conf', 'nlsr.conf', u'Named Data Link State Routing Protocol Daemon config file', None, 5),
+    ('manpages/nlsrc', 'nlsrc', u'Command-line utility to interact with and collect statistics from NLSR', None, 1),
 ]
 
 
diff --git a/docs/manpages.rst b/docs/manpages.rst
index 40bd0de..e2e1582 100644
--- a/docs/manpages.rst
+++ b/docs/manpages.rst
@@ -6,4 +6,5 @@
 .. toctree::
    manpages/nlsr
    manpages/nlsr.conf
+   manpages/nlsrc
    :maxdepth: 1
diff --git a/docs/manpages/nlsrc.rst b/docs/manpages/nlsrc.rst
new file mode 100644
index 0000000..1db6cf3
--- /dev/null
+++ b/docs/manpages/nlsrc.rst
@@ -0,0 +1,55 @@
+nlsrc
+=====
+
+Usage
+-----
+
+::
+
+    nlsrc [-h] [-V] COMMAND [<Command Options>]
+
+
+Description
+-----------
+
+``nlsrc`` is a tool to retrieve link-state database (LSDB) status information from NLSR and
+announce/withdraw Name prefixes advertised by NLSR.
+
+Options
+-------
+
+``-h``
+  Print usage information
+
+``-V``
+  Show NLSRC version information
+
+``COMMAND``
+
+  ``status``
+    Retrieve LSDB status information
+
+  ``advertise``
+    Add a Name prefix to be advertised by NLSR
+
+    ``advertise <name>``
+
+      ``name``
+        The Name prefix to be advertised
+
+  ``withdraw``
+    Remove a Name prefix advertised through NLSR
+
+    ``withdraw <name>``
+
+      ``name``
+        The Name prefix to be withdrawn
+
+Exit Status
+-----------
+
+nlsrc exits with one of the following values:
+::
+
+  0     nlsrc exited successfully
+  >0    An error occurred
diff --git a/tools/nlsrc.cpp b/tools/nlsrc.cpp
new file mode 100644
index 0000000..9142774
--- /dev/null
+++ b/tools/nlsrc.cpp
@@ -0,0 +1,412 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  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 "nlsrc.hpp"
+
+#include "version.hpp"
+#include "src/publisher/lsa-publisher.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/encoding/block.hpp>
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+#include <ndn-cxx/management/nfd-control-response.hpp>
+#include <ndn-cxx/util/segment-fetcher.hpp>
+
+#include <iostream>
+
+namespace nlsrc {
+
+const ndn::Name Nlsrc::LOCALHOST_PREFIX = ndn::Name("/localhost/nlsr");
+const ndn::Name Nlsrc::LSDB_PREFIX = ndn::Name(Nlsrc::LOCALHOST_PREFIX).append("lsdb");
+const ndn::Name Nlsrc::NAME_UPDATE_PREFIX = ndn::Name(Nlsrc::LOCALHOST_PREFIX).append("prefix-update");
+
+const uint32_t Nlsrc::ERROR_CODE_TIMEOUT = 10060;
+const uint32_t Nlsrc::RESPONSE_CODE_SUCCESS = 200;
+
+Nlsrc::Nlsrc(ndn::Face& face)
+  : m_face(face)
+{
+}
+
+void
+Nlsrc::printUsage()
+{
+  std::cout << "Usage:\n" << programName  << " [-h] [-V] COMMAND [<Command Options>]\n"
+    "       -h print usage and exit\n"
+    "       -V print version and exit\n"
+    "\n"
+    "   COMMAND can be one of the following:\n"
+    "       status\n"
+    "           display NLSR status\n"
+    "       advertise name\n"
+    "           advertise a name prefix through NLSR\n"
+    "       withdraw name\n"
+    "           remove a name prefix advertised through NLSR"
+    << std::endl;
+}
+
+void
+Nlsrc::getStatus()
+{
+  m_fetchSteps.push_back(std::bind(&Nlsrc::fetchAdjacencyLsas, this));
+  m_fetchSteps.push_back(std::bind(&Nlsrc::fetchCoordinateLsas, this));
+  m_fetchSteps.push_back(std::bind(&Nlsrc::fetchNameLsas, this));
+  m_fetchSteps.push_back(std::bind(&Nlsrc::printLsdb, this));
+
+  runNextStep();
+}
+
+bool
+Nlsrc::dispatch(const std::string& command)
+{
+  if (command == "advertise") {
+    if (nOptions != 1) {
+      return false;
+    }
+
+    advertiseName();
+    return true;
+  }
+  else if (command == "withdraw") {
+    if (nOptions != 1) {
+      return false;
+    }
+
+    withdrawName();
+    return true;
+  }
+  else if (command == "status") {
+    if (nOptions != 0) {
+      return false;
+    }
+
+    getStatus();
+    return true;
+  }
+
+  return false;
+}
+
+void
+Nlsrc::runNextStep()
+{
+  if (m_fetchSteps.empty()) {
+    return;
+  }
+
+  std::function<void()> nextStep = m_fetchSteps.front();
+  m_fetchSteps.pop_front();
+
+  nextStep();
+}
+
+void
+Nlsrc::advertiseName()
+{
+  ndn::Name name = commandLineArguments[0];
+  ndn::Name::Component verb("advertise");
+  std::string info = "(Advertise: " + name.toUri() + ")";
+
+  sendNamePrefixUpdate(name, verb, info);
+}
+
+void
+Nlsrc::withdrawName()
+{
+  ndn::Name name = commandLineArguments[0];
+  ndn::Name::Component verb("withdraw");
+  std::string info = "(Withdraw: " + name.toUri() + ")";
+
+  sendNamePrefixUpdate(name, verb, info);
+}
+
+void
+Nlsrc::sendNamePrefixUpdate(const ndn::Name& name,
+                            const ndn::Name::Component& verb,
+                            const std::string& info)
+{
+  ndn::nfd::ControlParameters parameters;
+  parameters.setName(name);
+
+  ndn::Name commandName = NAME_UPDATE_PREFIX;
+  commandName.append(verb);
+
+  ndn::Interest interest(commandName.append(parameters.wireEncode()));
+  interest.setMustBeFresh(true);
+  m_keyChain.sign(interest);
+
+  m_face.expressInterest(interest,
+                         std::bind(&Nlsrc::onControlResponse, this, info, _2),
+                         std::bind(&Nlsrc::onTimeout, this, ERROR_CODE_TIMEOUT, "Timeout"));
+}
+
+void
+Nlsrc::onControlResponse(const std::string& info, const ndn::Data& data)
+{
+  ndn::nfd::ControlResponse response;
+
+  try {
+    response.wireDecode(data.getContent().blockFromValue());
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: Control response decoding error" << std::endl;
+    return;
+  }
+
+  uint32_t code = response.getCode();
+
+  if (code != RESPONSE_CODE_SUCCESS) {
+    std::cerr << "Name prefix update error (code: " << code << ")" << std::endl;
+    return;
+  }
+
+  std::cout << "Applied Name prefix update successfully: " << info << std::endl;
+}
+
+void
+Nlsrc::fetchAdjacencyLsas()
+{
+  fetchFromLsdb<nlsr::tlv::AdjacencyLsa>(nlsr::AdjacencyLsaPublisher::DATASET_COMPONENT,
+                                         std::bind(&Nlsrc::recordAdjacencyLsa, this, _1));
+}
+
+void
+Nlsrc::fetchCoordinateLsas()
+{
+  fetchFromLsdb<nlsr::tlv::CoordinateLsa>(nlsr::CoordinateLsaPublisher::DATASET_COMPONENT,
+                                          std::bind(&Nlsrc::recordCoordinateLsa, this, _1));
+}
+
+void
+Nlsrc::fetchNameLsas()
+{
+  fetchFromLsdb<nlsr::tlv::NameLsa>(nlsr::NameLsaPublisher::DATASET_COMPONENT,
+                                    std::bind(&Nlsrc::recordNameLsa, this, _1));
+}
+
+template <class T>
+void
+Nlsrc::fetchFromLsdb(const ndn::Name::Component& datasetType,
+                     const std::function<void(const T&)>& recordLsa)
+{
+  ndn::Name command = LSDB_PREFIX;
+  command.append(datasetType);
+
+  ndn::Interest interest(command);
+
+  ndn::util::SegmentFetcher::fetch(m_face,
+                                   interest,
+                                   ndn::util::DontVerifySegment(),
+                                   std::bind(&Nlsrc::onFetchSuccess<T>,
+                                             this, _1, recordLsa),
+                                   std::bind(&Nlsrc::onTimeout, this, _1, _2));
+}
+
+template <class T>
+void
+Nlsrc::onFetchSuccess(const ndn::ConstBufferPtr& data,
+                      const std::function<void(const T&)>& recordLsa)
+{
+  ndn::Block block;
+  size_t offset = 0;
+
+  while (offset < data->size()) {
+    bool isOk = false;
+    std::tie(isOk, block) = ndn::Block::fromBuffer(data, offset);
+
+    if (!isOk) {
+      std::cerr << "ERROR: cannot decode LSA TLV" << std::endl;
+      break;
+    }
+
+    offset += block.size();
+
+    T lsa(block);
+    recordLsa(lsa);
+  }
+
+  runNextStep();
+}
+
+void
+Nlsrc::onTimeout(uint32_t errorCode, const std::string& error)
+{
+  std::cerr << "Request timed out (code: " << errorCode
+            << ", error: " << error << ")"  << std::endl;
+}
+
+std::string
+Nlsrc::getLsaInfoString(const nlsr::tlv::LsaInfo& info)
+{
+  std::ostringstream os;
+  os << "      info=" << info;
+
+  return os.str();
+}
+
+void
+Nlsrc::recordAdjacencyLsa(const nlsr::tlv::AdjacencyLsa& lsa)
+{
+  Router& router = getRouter(lsa.getLsaInfo());
+
+  std::ostringstream os;
+  os << "    AdjacencyLsa:" << std::endl;
+
+  os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+  for (const auto& adjacency : lsa.getAdjacencies()) {
+    os << "      adjacency=" << adjacency << std::endl;
+  }
+
+  router.adjacencyLsaString = os.str();
+}
+
+void
+Nlsrc::recordCoordinateLsa(const nlsr::tlv::CoordinateLsa& lsa)
+{
+  Router& router = getRouter(lsa.getLsaInfo());
+
+  std::ostringstream os;
+  os << "    Coordinate LSA:" << std::endl;
+
+  os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+  os << "      angle=" << lsa.getHyperbolicAngle() << std::endl;
+  os << "      radius=" << lsa.getHyperbolicRadius() << std::endl;
+
+  router.coordinateLsaString = os.str();
+}
+
+void
+Nlsrc::recordNameLsa(const nlsr::tlv::NameLsa& lsa)
+{
+  Router& router = getRouter(lsa.getLsaInfo());
+
+  std::ostringstream os;
+  os << "    Name LSA:" << std::endl;
+
+  os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+  for (const auto& name : lsa.getNames()) {
+    os << "      name=" << name << std::endl;
+  }
+
+  router.nameLsaString = os.str();
+}
+
+void
+Nlsrc::printLsdb()
+{
+  std::cout << "NLSR Status" << std::endl;
+  std::cout << "LSDB:" << std::endl;
+
+  for (const auto& item : m_routers) {
+    std::cout << "  OriginRouter: " << item.first << std::endl;
+    std::cout << std::endl;
+
+    const Router& router = item.second;
+
+    if (!router.adjacencyLsaString.empty()) {
+      std::cout << router.adjacencyLsaString << std::endl;
+    }
+
+    if (!router.coordinateLsaString.empty()) {
+      std::cout << router.coordinateLsaString << std::endl;
+    }
+
+    if (!router.nameLsaString.empty()) {
+      std::cout << router.nameLsaString << std::endl;
+    }
+  }
+}
+
+Nlsrc::Router&
+Nlsrc::getRouter(const nlsr::tlv::LsaInfo& info)
+{
+  const ndn::Name& originRouterName = info.getOriginRouter();
+
+  const auto& pair =
+    m_routers.insert(std::make_pair(originRouterName, std::move(Router())));
+
+  return pair.first->second;
+}
+
+} // namespace nlsrc
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char** argv)
+{
+  ndn::Face face;
+  nlsrc::Nlsrc nlsrc(face);
+
+  nlsrc.programName = argv[0];
+
+  if (argc < 2) {
+    nlsrc.printUsage();
+    return 0;
+  }
+
+  int opt;
+  while ((opt = ::getopt(argc, argv, "hV")) != -1) {
+    switch (opt) {
+    case 'h':
+      nlsrc.printUsage();
+      return 0;
+    case 'V':
+      std::cout << NLSR_VERSION_BUILD_STRING << std::endl;
+      return 0;
+    default:
+      nlsrc.printUsage();
+      return 1;
+    }
+  }
+
+  if (argc == ::optind) {
+    nlsrc.printUsage();
+    return 1;
+  }
+
+  try {
+    ::optind = 2; // Set ::optind to the command's index
+
+    nlsrc.commandLineArguments = argv + ::optind;
+    nlsrc.nOptions = argc - ::optind;
+
+    // argv[1] points to the command, so pass it to the dispatch
+    bool isOk = nlsrc.dispatch(argv[1]);
+    if (!isOk) {
+      nlsrc.printUsage();
+      return 1;
+    }
+
+    face.processEvents();
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+  return 0;
+}
\ No newline at end of file
diff --git a/tools/nlsrc.hpp b/tools/nlsrc.hpp
new file mode 100644
index 0000000..b62cca0
--- /dev/null
+++ b/tools/nlsrc.hpp
@@ -0,0 +1,162 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  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 "tlv/adjacency-lsa.hpp"
+#include "tlv/coordinate-lsa.hpp"
+#include "tlv/name-lsa.hpp"
+
+#include <boost/noncopyable.hpp>
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+#include <deque>
+#include <map>
+#include <stdexcept>
+
+#ifndef NLSR_TOOLS_NLSRC_HPP
+#define NLSR_TOOLS_NLSRC_HPP
+
+namespace nlsrc {
+
+class Nlsrc : boost::noncopyable
+{
+public:
+  explicit
+  Nlsrc(ndn::Face& face);
+
+  void
+  printUsage();
+
+  void
+  getStatus();
+
+  bool
+  dispatch(const std::string& cmd);
+
+private:
+  void
+  runNextStep();
+
+  /**
+   * \brief Adds a name prefix to be advertised in NLSR's Name LSA
+   *
+   * cmd format:
+   *   name
+   *
+   */
+  void
+  advertiseName();
+
+  /**
+   * \brief Removes a name prefix from NLSR's Name LSA
+   *
+   * cmd format:
+   *  name
+   *
+   */
+  void
+  withdrawName();
+
+  void
+  sendNamePrefixUpdate(const ndn::Name& name,
+                       const ndn::Name::Component& verb,
+                       const std::string& info);
+
+  void
+  onControlResponse(const std::string& info, const ndn::Data& data);
+
+private:
+  void
+  fetchAdjacencyLsas();
+
+  void
+  fetchCoordinateLsas();
+
+  void
+  fetchNameLsas();
+
+  template <class T>
+  void
+  fetchFromLsdb(const ndn::Name::Component& datasetType,
+                const std::function<void(const T&)>& recordLsa);
+
+  template <class T>
+  void
+  onFetchSuccess(const ndn::ConstBufferPtr& data,
+                 const std::function<void(const T&)>& recordLsa);
+
+  void
+  onTimeout(uint32_t errorCode, const std::string& error);
+
+private:
+  std::string
+  getLsaInfoString(const nlsr::tlv::LsaInfo& info);
+
+  void
+  recordAdjacencyLsa(const nlsr::tlv::AdjacencyLsa& lsa);
+
+  void
+  recordCoordinateLsa(const nlsr::tlv::CoordinateLsa& lsa);
+
+  void
+  recordNameLsa(const nlsr::tlv::NameLsa& lsa);
+
+  void
+  printLsdb();
+
+public:
+  const char* programName;
+
+  // command parameters without leading 'cmd' component
+  const char* const* commandLineArguments;
+  int nOptions;
+
+private:
+  struct Router
+  {
+    std::string adjacencyLsaString;
+    std::string coordinateLsaString;
+    std::string nameLsaString;
+  };
+
+  Router&
+  getRouter(const nlsr::tlv::LsaInfo& info);
+
+  typedef std::map<const ndn::Name, Router> RouterMap;
+  RouterMap m_routers;
+
+private:
+  ndn::KeyChain m_keyChain;
+  ndn::Face& m_face;
+
+  std::deque<std::function<void()>> m_fetchSteps;
+
+  static const ndn::Name LOCALHOST_PREFIX;
+  static const ndn::Name LSDB_PREFIX;
+  static const ndn::Name NAME_UPDATE_PREFIX;
+
+  static const uint32_t ERROR_CODE_TIMEOUT;
+  static const uint32_t RESPONSE_CODE_SUCCESS;
+};
+
+} // namespace nlsrc
+
+#endif // NLSR_TOOLS_NLSRC_HPP
diff --git a/wscript b/wscript
index 946bb5a..0dbf533 100644
--- a/wscript
+++ b/wscript
@@ -128,10 +128,25 @@
         use='nlsr-objects',
         )
 
+    nlsrc = bld(
+        target='bin/nlsrc',
+        features='cxx cxxprogram',
+        source='tools/nlsrc.cpp',
+        use='nlsr-objects BOOST',
+        )
+
     if bld.env['WITH_TESTS']:
         bld.recurse('tests')
         bld.recurse('tests-integrated')
 
+    if bld.env['SPHINX_BUILD']:
+        bld(features="sphinx",
+            builder="man",
+            outdir="docs/manpages",
+            config="docs/conf.py",
+            source=bld.path.ant_glob('docs/manpages/**/*.rst'),
+            install_path="${MANDIR}/",
+            VERSION=VERSION)
 
 def docs(bld):
     from waflib import Options