tools: nfdc route list and nfd route show commands

refs #3866

Change-Id: Ic8feab0ce9e5707c1cf382cdea7264e28e3edb30
diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp
index 5645e26..5c23c7a 100644
--- a/tools/nfdc/find-face.cpp
+++ b/tools/nfdc/find-face.cpp
@@ -148,6 +148,16 @@
   m_ctx.face.processEvents();
 }
 
+std::set<uint64_t>
+FindFace::getFaceIds() const
+{
+  std::set<uint64_t> faceIds;
+  for (const FaceStatus& faceStatus : m_results) {
+    faceIds.insert(faceStatus.getFaceId());
+  }
+  return faceIds;
+}
+
 const FaceStatus&
 FindFace::getFaceStatus() const
 {
diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp
index 2661aa6..120718b 100644
--- a/tools/nfdc/find-face.hpp
+++ b/tools/nfdc/find-face.hpp
@@ -92,6 +92,11 @@
     return m_results;
   }
 
+  /** \return FaceId for all results
+   */
+  std::set<uint64_t>
+  getFaceIds() const;
+
   /** \return a single face status
    *  \pre getResults().size() == 1
    */
diff --git a/tools/nfdc/rib-module.cpp b/tools/nfdc/rib-module.cpp
index 4342894..d7f04cd 100644
--- a/tools/nfdc/rib-module.cpp
+++ b/tools/nfdc/rib-module.cpp
@@ -34,6 +34,19 @@
 void
 RibModule::registerCommands(CommandParser& parser)
 {
+  CommandDefinition defRouteList("route", "list");
+  defRouteList
+    .setTitle("print RIB routes")
+    .addArg("nexthop", ArgValueType::FACE_ID_OR_URI, Required::NO, Positional::YES)
+    .addArg("origin", ArgValueType::UNSIGNED, Required::NO, Positional::NO);
+  parser.addCommand(defRouteList, &RibModule::list);
+
+  CommandDefinition defRouteShow("route", "show");
+  defRouteShow
+    .setTitle("show routes toward a prefix")
+    .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES);
+  parser.addCommand(defRouteShow, &RibModule::show);
+
   CommandDefinition defRouteAdd("route", "add");
   defRouteAdd
     .setTitle("add a route")
@@ -56,6 +69,77 @@
 }
 
 void
+RibModule::list(ExecuteContext& ctx)
+{
+  auto nexthopIt = ctx.args.find("nexthop");
+  std::set<uint64_t> nexthops;
+  auto origin = ctx.args.getOptional<uint64_t>("origin");
+
+  if (nexthopIt != ctx.args.end()) {
+    FindFace findFace(ctx);
+    FindFace::Code res = findFace.execute(nexthopIt->second, true);
+
+    ctx.exitCode = static_cast<int>(res);
+    switch (res) {
+      case FindFace::Code::OK:
+        break;
+      case FindFace::Code::ERROR:
+      case FindFace::Code::CANONIZE_ERROR:
+      case FindFace::Code::NOT_FOUND:
+        ctx.err << findFace.getErrorReason() << '\n';
+        return;
+      default:
+        BOOST_ASSERT_MSG(false, "unexpected FindFace result");
+        return;
+    }
+
+    nexthops = findFace.getFaceIds();
+  }
+
+  listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) {
+    return (nexthops.empty() || nexthops.count(route.getFaceId()) > 0) &&
+           (!origin || route.getOrigin() == *origin);
+  });
+}
+
+void
+RibModule::show(ExecuteContext& ctx)
+{
+  auto prefix = ctx.args.get<Name>("prefix");
+
+  listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) {
+    return entry.getName() == prefix;
+  });
+}
+
+void
+RibModule::listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter)
+{
+  ctx.controller.fetch<ndn::nfd::RibDataset>(
+    [&] (const std::vector<RibEntry>& dataset) {
+      bool hasRoute = false;
+      for (const RibEntry& entry : dataset) {
+        for (const Route& route : entry.getRoutes()) {
+          if (filter(entry, route)) {
+            hasRoute = true;
+            formatRouteText(ctx.out, entry, route, true);
+            ctx.out << '\n';
+          }
+        }
+      }
+
+      if (!hasRoute) {
+        ctx.exitCode = 6;
+        ctx.err << "Route not found\n";
+      }
+    },
+    ctx.makeDatasetFailureHandler("RIB dataset"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
 RibModule::add(ExecuteContext& ctx)
 {
   auto prefix = ctx.args.get<Name>("prefix");
@@ -147,11 +231,11 @@
       return;
   }
 
-  for (const FaceStatus& faceStatus : findFace.getResults()) {
+  for (uint64_t faceId : findFace.getFaceIds()) {
     ControlParameters unregisterParams;
     unregisterParams
       .setName(prefix)
-      .setFaceId(faceStatus.getFaceId())
+      .setFaceId(faceId)
       .setOrigin(origin);
 
     ctx.controller.start<ndn::nfd::RibUnregisterCommand>(
@@ -238,35 +322,50 @@
 {
   os << "RIB:\n";
   for (const RibEntry& item : m_status) {
-    this->formatItemText(os, item);
+    os << "  ";
+    formatEntryText(os, item);
+    os << '\n';
   }
 }
 
 void
-RibModule::formatItemText(std::ostream& os, const RibEntry& item) const
+RibModule::formatEntryText(std::ostream& os, const RibEntry& entry)
 {
-  os << "  " << item.getName() << " route={";
+  os << entry.getName() << " routes={";
 
   text::Separator sep(", ");
-  for (const Route& route : item.getRoutes()) {
-    os << sep
-       << "faceid=" << route.getFaceId()
-       << " (origin=" << route.getOrigin()
-       << " cost=" << route.getCost();
-    if (route.hasExpirationPeriod()) {
-      os << " expires=" << text::formatDuration(route.getExpirationPeriod());
-    }
-    if (route.isChildInherit()) {
-      os << " ChildInherit";
-    }
-    if (route.isRibCapture()) {
-      os << " RibCapture";
-    }
-    os << ")";
+  for (const Route& route : entry.getRoutes()) {
+    os << sep;
+    formatRouteText(os, entry, route, false);
   }
 
   os << "}";
-  os << "\n";
+}
+
+void
+RibModule::formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route,
+                           bool includePrefix)
+{
+  text::ItemAttributes ia;
+
+  if (includePrefix) {
+    os << ia("prefix") << entry.getName();
+  }
+  os << ia("nexthop") << route.getFaceId();
+  os << ia("origin") << static_cast<uint64_t>(route.getOrigin());
+  os << ia("cost") << route.getCost();
+  os << ia("flags") << static_cast<ndn::nfd::RouteFlags>(route.getFlags());
+
+  // 'origin' field is printed as a number, because printing 'origin' as string may mislead user
+  // into passing strings to 'origin' command line argument which currently only accepts numbers.
+  ///\todo #3987 print 'origin' with RouteOrigin stream insertion operator
+
+  if (route.hasExpirationPeriod()) {
+    os << ia("expires") << text::formatDuration(route.getExpirationPeriod());
+  }
+  else {
+    os << ia("expires") << "never";
+  }
 }
 
 } // namespace nfdc
diff --git a/tools/nfdc/rib-module.hpp b/tools/nfdc/rib-module.hpp
index aee2c8b..ae53928 100644
--- a/tools/nfdc/rib-module.hpp
+++ b/tools/nfdc/rib-module.hpp
@@ -47,6 +47,16 @@
   static void
   registerCommands(CommandParser& parser);
 
+  /** \brief the 'route list' command
+   */
+  static void
+  list(ExecuteContext& ctx);
+
+  /** \brief the 'route show' command
+   */
+  static void
+  show(ExecuteContext& ctx);
+
   /** \brief the 'route add' command
    */
   static void
@@ -66,6 +76,15 @@
   void
   formatStatusXml(std::ostream& os) const override;
 
+  void
+  formatStatusText(std::ostream& os) const override;
+
+private:
+  using RoutePredicate = function<bool(const RibEntry&, const Route&)>;
+
+  static void
+  listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter);
+
   /** \brief format a single status item as XML
    *  \param os output stream
    *  \param item status item
@@ -73,15 +92,22 @@
   void
   formatItemXml(std::ostream& os, const RibEntry& item) const;
 
-  void
-  formatStatusText(std::ostream& os) const override;
-
-  /** \brief format a single status item as text
+  /** \brief format a RibEntry as text
    *  \param os output stream
-   *  \param item status item
+   *  \param entry RIB entry
    */
-  void
-  formatItemText(std::ostream& os, const RibEntry& item) const;
+  static void
+  formatEntryText(std::ostream& os, const RibEntry& entry);
+
+  /** \brief format a Route as text
+   *  \param os output stream
+   *  \param entry RIB entry
+   *  \param route RIB route within \p entry
+   *  \param includePrefix whether to print the name prefix
+   */
+  static void
+  formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route,
+                  bool includePrefix);
 
 private:
   std::vector<RibEntry> m_status;
diff --git a/tools/nfdc/status.cpp b/tools/nfdc/status.cpp
index 6165565..a09fd7e 100644
--- a/tools/nfdc/status.cpp
+++ b/tools/nfdc/status.cpp
@@ -145,11 +145,6 @@
   defFibList
     .setTitle("print FIB entries");
   parser.addCommand(defFibList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantFib));
-
-  CommandDefinition defRouteList("route", "list");
-  defRouteList
-    .setTitle("print RIB entries");
-  parser.addCommand(defRouteList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantRib));
 }
 
 } // namespace nfdc