tools: nfdc

Change-Id: Ida7d3b4264fd3452acf69b443150e3ad639e3da9
diff --git a/tools/nfdc.cpp b/tools/nfdc.cpp
new file mode 100644
index 0000000..1ad54af
--- /dev/null
+++ b/tools/nfdc.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+#include "nfdc.hpp"
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/regex_find_format.hpp>
+#include <boost/regex.hpp>
+
+void
+usage(const char* programName)
+{
+  std::cout << "Usage:\n" << programName  << " [-h] COMMAND\n"
+  "       -h print usage and exit\n"
+  "\n"
+  "   COMMAND can be one of following:\n"
+  "       insert <name> \n"
+  "           Insert a FIB entry \n"
+  "       delete <name> \n"
+  "           Delete a FIB entry\n"
+  "       add-nexthop <name> <faceId> [<cost>]\n"
+  "           Add a nexthop to an existing FIB entry\n"
+  "       remove-nexthop <name> <faceId> \n"
+  "           Remove a nexthop from a FIB entry\n"
+  "       set-strategy <name> <stratgy>\n"
+  "           Set a forwarding strategy for a namespace\n"
+  "       create <uri> \n"
+  "           Create a face in one of the following formats:\n"
+  "           UDP unicast:    udp[4|6]://<remote-IP-or-host>[:<remote-port>]\n"
+  "           TCP:            tcp[4|6]://<remote-IP-or-host>[:<remote-port>] \n"
+  "       destroy <faceId> \n"
+  "           Destroy a face\n"
+  << std::endl;
+}
+
+namespace nfdc {
+  
+Controller::Controller(ndn::Face& face)
+  : ndn::nfd::Controller(face)
+{
+}
+  
+Controller::~Controller()
+{
+}
+bool
+Controller::dispatch(const std::string& command, const char* commandOptions[], int nOptions)
+{
+  if (command == "insert") {
+    if (nOptions != 1)
+      return false;
+    fibInsert(commandOptions);
+  }
+  else if (command == "delete") {
+    if (nOptions != 1)
+      return false;
+    fibDelete(commandOptions);
+  }
+  else if (command == "add-nexthop") {
+    if (nOptions == 2)
+      fibAddNextHop(commandOptions, false);
+    else if (nOptions == 3)
+      fibAddNextHop(commandOptions, true);
+    else
+      return false;
+  }
+  else if (command == "remove-nexthop") {
+    if (nOptions != 2)
+      return false;
+    fibRemoveNextHop(commandOptions);
+  }
+  else if (command == "set-strategy") {
+    if (nOptions != 2)
+      return false;
+    fibSetStrategy(commandOptions);
+  }
+  else if (command == "create") {
+    if (nOptions != 1)
+      return false;
+    faceCreate(commandOptions);
+  }
+  else if (command == "destroy") {
+    if (nOptions != 1)
+      return false;
+    faceDestroy(commandOptions);
+  }
+  else
+    usage(m_programName);
+
+  return true;
+}
+  
+void
+Controller::fibInsert(const char* commandOptions[])
+{
+  const std::string& name = commandOptions[0];
+
+  ndn::nfd::FibManagementOptions fibOptions;
+  fibOptions.setName(name);
+  startFibCommand("insert",
+                  fibOptions,
+                  bind(&Controller::onFibSuccess, this, _1, "Fib insertion succeeded"),
+                  bind(&Controller::onError, this, _1, "Fib insertion failed"));
+}
+    
+void
+Controller::fibDelete(const char* commandOptions[])
+{
+  const std::string& name = commandOptions[0];
+  ndn::nfd::FibManagementOptions fibOptions;
+  fibOptions.setName(name);
+  startFibCommand("delete",
+                  fibOptions,
+                  bind(&Controller::onFibSuccess, this, _1, "Fib deletion succeeded"),
+                  bind(&Controller::onError, this, _1, "Fib deletion failed" ));
+}
+
+
+void
+Controller::fibAddNextHop(const char* commandOptions[], bool hasCost)
+{
+  ndn::nfd::FibManagementOptions fibOptions;
+  
+  const std::string& name = commandOptions[0];
+  const int faceId = boost::lexical_cast<int>(commandOptions[1]);
+
+  fibOptions.setName(name);
+  fibOptions.setFaceId(faceId);
+
+  if (hasCost)
+  {
+    const int cost = boost::lexical_cast<int>(commandOptions[2]);
+    fibOptions.setCost(cost);
+  }
+  startFibCommand("add-nexthop",
+                  fibOptions,
+                  bind(&Controller::onFibSuccess, this, _1, "Nexthop insertion succeeded"),
+                  bind(&Controller::onError, this, _1, "Nexthop insertion failed"));
+}
+
+void
+Controller::fibRemoveNextHop(const char* commandOptions[])
+{
+  const std::string& name = commandOptions[0];
+  const int faceId = boost::lexical_cast<int>(commandOptions[1]);
+  ndn::nfd::FibManagementOptions fibOptions;
+
+  fibOptions.setName(name);
+  fibOptions.setFaceId(faceId);
+  startFibCommand("remove-nexthop",
+                  fibOptions,
+                  bind(&Controller::onFibSuccess, this, _1, "Nexthop Removal succeeded"),
+                  bind(&Controller::onError, this, _1, "Nexthop Removal failed"));
+}
+
+void
+Controller::fibSetStrategy(const char* commandOptions[])
+{
+    
+  // std::string name      = commandOptions[0];
+  // std::string strategy  = commandOptions[1];
+  // startFibCommand("set-strategy",
+  //                  ndn::nfd::FibManagementOptions()
+  //                  .setName(name)
+  //                  .setStrategy(strategy),
+  //                  bind(&Controller::onFibSuccess,this, _1),
+  //                  bind(&Controller::onError,this, _1));
+}
+ 
+namespace {
+bool
+isValidUri(const std::string& input)
+{
+  // an extended regex to support the validation of uri structure
+  // boost::regex e("^[a-z0-9]+-?+[a-z0-9]+\\:\\/\\/.*");
+  boost::regex e("^[a-z0-9]+\\:.*");
+  return boost::regex_match(input, e);
+}
+} // anonymous namespace
+
+void
+Controller::faceCreate(const char* commandOptions[])
+{
+  ndn::nfd::FaceManagementOptions faceOptions;
+  const std::string& uri = commandOptions[0];
+  faceOptions.setUri(uri);
+  
+  if (isValidUri(uri))
+  {
+    startFaceCommand("create",
+                     faceOptions,
+                     bind(&Controller::onFaceSuccess, this, _1, "Face creation succeeded"),
+                     bind(&Controller::onError, this, _1, "Face creation failed"));
+  }
+  else
+    throw Error("invalid uri format");
+}
+ 
+void
+Controller::faceDestroy(const char* commandOptions[])
+{
+  ndn::nfd::FaceManagementOptions faceOptions;
+  const int faceId = boost::lexical_cast<int>(commandOptions[0]);
+  faceOptions.setFaceId(faceId);
+                        
+  startFaceCommand("destroy",
+                   faceOptions,
+                   bind(&Controller::onFaceSuccess, this, _1, "Face destroy succeeded"),
+                   bind(&Controller::onError, this, _1, "Face destroy failed"));
+}
+
+void
+Controller::onFibSuccess(const ndn::nfd::FibManagementOptions& resp, const std::string& message)
+{
+  std::cout << resp << std::endl;
+}
+  
+void
+Controller::onFaceSuccess(const ndn::nfd::FaceManagementOptions& resp, const std::string& message)
+{
+  std::cout << resp << std::endl;
+}
+  
+void
+Controller::onError(const std::string& error, const std::string& message)
+{
+  throw Error(message + ": " + error);
+}
+}// namespace nfdc
+
+int
+main(int argc, char** argv)
+{
+  ndn::Face face;
+  nfdc::Controller p(face);
+  
+  p.m_programName = argv[0];
+  int opt;
+  while ((opt = getopt(argc, argv, "h")) != -1) {
+    switch (opt) {
+      case 'h':
+        usage(p.m_programName);
+        return 0;
+        
+      default:
+        usage(p.m_programName);
+        return 1;
+    }
+  }
+  try {
+    bool hasSucceeded = p.dispatch(argv[optind],
+                                   const_cast<const char**>(argv + optind + 1),
+                                   argc - optind - 1);
+    if (hasSucceeded == false) {
+      usage(p.m_programName);
+      return 1;
+    }
+    
+    face.processEvents();
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+  return 0;
+}
+
diff --git a/tools/nfdc.hpp b/tools/nfdc.hpp
new file mode 100644
index 0000000..a606100
--- /dev/null
+++ b/tools/nfdc.hpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_TOOLS_NFDC_HPP
+#define NFD_TOOLS_NFDC_HPP
+
+#include <ndn-cpp-dev/face.hpp>
+#include <ndn-cpp-dev/management/controller.hpp>
+#include <ndn-cpp-dev/management/nfd-controller.hpp>
+#include <ndn-cpp-dev/management/nfd-fib-management-options.hpp>
+#include <ndn-cpp-dev/management/nfd-face-management-options.hpp>
+#include <vector>
+
+namespace nfdc {
+    
+class Controller : public ndn::nfd::Controller
+{
+public:
+  struct Error : public std::runtime_error
+  {
+    Error(const std::string& what) : std::runtime_error(what) {}
+  };
+    
+  explicit
+  Controller(ndn::Face& face);
+  
+  ~Controller();
+  
+  bool
+  dispatch(const std::string& cmd,
+           const char* cmdOptions[],
+           int nOptions);
+  /**
+   * \brief Create a new FIB entry if it doesn't exist
+   *
+   * cmd format:
+   *   name
+   *
+   * @param cmdOptions           add command without leading 'insert' component
+   */
+  void
+  fibInsert(const char* cmdOptions[]);
+  /**
+   * \brief Delete a FIB entry if it exists
+   *
+   * cmd format:
+   *   name
+   *
+   * @param cmdOptions          del command without leading 'delete' component
+   */
+  void
+  fibDelete(const char* cmdOptions[]);
+  /**
+   * \brief Adds a nexthop to an existing FIB entry
+   *
+   *  If a nexthop of same FaceId exists on the FIB entry, its cost is updated.
+   *  FaceId is the FaceId returned in NFD Face Management protocol.
+   *  If FaceId is set to zero, it is implied as the face of the entity sending this command.
+   * cmd format:
+   *   name faceId cost
+   *
+   * @param cmdOptions          addNextHop command without leading 'add-nexthop' component
+   */
+  void
+  fibAddNextHop(const char* cmdOptions[], bool hasCost);
+  /**
+   * \brief Remove a nexthop from an existing FIB entry
+   *
+   *  This command removes a nexthop from a FIB entry. 
+   *  Removing the last nexthop in a FIB entry will not automatically delete the FIB entry.
+   *
+   * cmd format:
+   *   name faceId
+   *
+   * @param cmdOptions          delNext command without leading 'remove-nexthop' component
+   */
+  void
+  fibRemoveNextHop(const char* cmdOptions[]);
+  /**
+   * \brief Sets a forwarding strategy for a namespace
+   *
+   *  This command sets a forwarding strategy for a namespace.
+   *
+   * cmd format:
+   *   name strategy
+   *
+   * @param cmdOptions          setStrategy command without leading 'setStrategy' component
+   */
+  void
+  fibSetStrategy(const char* cmdOptions[]);
+  
+  /**
+   * \brief create new face
+   *
+   *  This command allows creation of UDP unicast and TCP faces only.
+   *
+   * cmd format:
+   *   uri
+   *
+   * @param cmdOptions          create command without leading 'create' component
+   */
+  void
+  faceCreate(const char* cmdOptions[]);
+  /**
+   * \brief destroy a face
+   *
+   * cmd format:
+   *   faceId
+   *
+   * @param cmdOptions          destroy command without leading 'destroy' component
+   */
+  void
+  faceDestroy(const char* cmdOptions[]);
+
+private:
+  void
+  onFibSuccess(const ndn::nfd::FibManagementOptions& fibOptions, const std::string& message);
+
+  void
+  onFaceSuccess(const ndn::nfd::FaceManagementOptions& faceOptions, const std::string& message);
+  
+  void
+  onError(const std::string& error, const std::string& message);
+  
+public:
+  const char* m_programName;
+};
+
+}// namespace nfdc
+
+#endif // NFD_TOOLS_NFDC_HPP
+
diff --git a/wscript b/wscript
index 3562e45..8591b35 100644
--- a/wscript
+++ b/wscript
@@ -2,6 +2,7 @@
 VERSION='0.1'
 
 from waflib import Build, Logs, Utils, Task, TaskGen, Configure
+import os
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -83,8 +84,15 @@
         source = 'daemon/main.cpp',
         use = 'nfd-objects',
         includes = [".", "daemon"],
-        )        
-    
+        )
+        
+    for app in bld.path.ant_glob('tools/*.cpp'):
+        bld(features=['cxx', 'cxxprogram'],
+            target = 'bin/%s' % (str(app.change_ext(''))),
+            source = ['tools/%s' % (str(app))],
+            use = 'BOOST NDN_CPP RT',
+            )
+                
     # Unit tests
     if bld.env['WITH_TESTS']:
         unit_tests = unittests = bld.program (
@@ -126,3 +134,4 @@
         bld.fatal("ERROR: cannot build documentation (`doxygen' is not found in $PATH)")
     bld(features="doxygen",
         doxyfile='docs/doxygen.conf')
+