rib: simplify Route class

Route::expires is changed to an optional, with nullopt representing
"never expires". This avoids an integer overflow.

RouteFlags accessors are now provided by ndn::nfd::RouteFlagsTraits.

Stream insertion operator is improved.

refs #3502

Change-Id: Ia912eab771fb00020385bf84d486955feae6aafa
diff --git a/rib/fib-updater.cpp b/rib/fib-updater.cpp
index a430c68..bce3a5d 100644
--- a/rib/fib-updater.cpp
+++ b/rib/fib-updater.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -71,9 +71,8 @@
   NFD_LOG_DEBUG("Computing updates for batch with faceID: " << batch.getFaceId());
 
   // Compute updates and add to m_fibUpdates
-  for (const RibUpdate& update : batch)
-    {
-      switch(update.getAction()) {
+  for (const RibUpdate& update : batch) {
+    switch (update.getAction()) {
       case RibUpdate::REGISTER:
         computeUpdatesForRegistration(update);
         break;
@@ -87,8 +86,8 @@
         // since they will be rejected by the FIB
         m_updatesForBatchFaceId.clear();
         break;
-      }
     }
+  }
 }
 
 void
@@ -108,7 +107,7 @@
     // Route will be new
     if (existingRoute == entry->end()) {
       // Will the new route change the namespace's capture flag?
-      bool willCaptureBeTurnedOn = (entry->hasCapture() == false && route.isCapture());
+      bool willCaptureBeTurnedOn = (entry->hasCapture() == false && route.isRibCapture());
 
       createFibUpdatesForNewRoute(*entry, route, willCaptureBeTurnedOn);
     }
@@ -383,11 +382,11 @@
   addFibUpdate(FibUpdate::createAddUpdate(name, route.faceId, route.cost));
 
   // No flags are set
-  if (!route.isChildInherit() && !route.isCapture()) {
+  if (!route.isChildInherit() && !route.isRibCapture()) {
     // Add ancestor routes to self
     addInheritedRoutes(name, m_rib.getAncestorRoutes(name), route);
   }
-  else if (route.isChildInherit() && route.isCapture()) {
+  else if (route.isChildInherit() && route.isRibCapture()) {
     // Add route to children
     Rib::RouteSet routesToAdd;
     routesToAdd.insert(route);
@@ -416,7 +415,7 @@
     // Add ancestor routes to children
     modifyChildrensInheritedRoutes(children, ancestorRoutes, Rib::RouteSet());
   }
-  else if (route.isCapture()) {
+  else if (route.isRibCapture()) {
     // Remove routes blocked by capture
     modifyChildrensInheritedRoutes(children, Rib::RouteSet(), m_rib.getAncestorRoutes(name));
   }
@@ -540,7 +539,7 @@
   }
 
   // Capture was turned on
-  if (!existingRoute.isCapture() && route.isCapture()) {
+  if (!existingRoute.isRibCapture() && route.isRibCapture()) {
     Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
 
     // Remove ancestor routes from self
@@ -549,7 +548,7 @@
     // Remove ancestor routes from children
     modifyChildrensInheritedRoutes(entry.getChildren(), Rib::RouteSet(), ancestorRoutes);
   }  // Capture was turned off
-  else if (existingRoute.isCapture() && !route.isCapture()) {
+  else if (existingRoute.isRibCapture() && !route.isRibCapture()) {
     Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
 
     // Add ancestor routes to self
@@ -566,7 +565,7 @@
 {
   addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
 
-  if (route.isChildInherit() && route.isCapture()) {
+  if (route.isChildInherit() && route.isRibCapture()) {
     // Remove self from children
     Rib::RouteSet routesToRemove;
     routesToRemove.insert(route);
@@ -597,7 +596,7 @@
     // Add ancestor routes to children
     modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
   }
-  else if (route.isCapture()) {
+  else if (route.isRibCapture()) {
     // If capture is turned off for the route and another route is installed in the RibEntry,
     // add ancestors to self
     Rib::RouteSet routesToAdd;
diff --git a/rib/rib-entry.cpp b/rib/rib-entry.cpp
index a33e113..e26e6d6 100644
--- a/rib/rib-entry.cpp
+++ b/rib/rib-entry.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -249,10 +249,10 @@
 operator<<(std::ostream& os, const RibEntry& entry)
 {
   os << "RibEntry {\n";
-  os << "\tName: " << entry.getName() << "\n";
+  os << "  Name: " << entry.getName() << "\n";
 
   for (const Route& route : entry) {
-    os << "\t" << route << "\n";
+    os << "  " << route << "\n";
   }
 
   os << "}";
diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp
index 46819bd..71a7cf3 100644
--- a/rib/rib-manager.cpp
+++ b/rib/rib-manager.cpp
@@ -33,13 +33,11 @@
 
 #include <ndn-cxx/lp/tags.hpp>
 #include <ndn-cxx/mgmt/nfd/control-command.hpp>
-#include <ndn-cxx/mgmt/nfd/control-response.hpp>
 #include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+#include <ndn-cxx/mgmt/nfd/control-response.hpp>
 #include <ndn-cxx/mgmt/nfd/face-status.hpp>
 #include <ndn-cxx/mgmt/nfd/rib-entry.hpp>
 
-#include <boost/range/adaptor/transformed.hpp>
-
 namespace nfd {
 namespace rib {
 
@@ -219,14 +217,14 @@
     scheduler::EventId eventId = scheduler::schedule(parameters.getExpirationPeriod(),
       bind(&Rib::onRouteExpiration, &m_rib, parameters.getName(), route));
 
-    NFD_LOG_TRACE("Scheduled unregistration at: " << route.expires <<
+    NFD_LOG_TRACE("Scheduled unregistration at: " << *route.expires <<
                   " with EventId: " << eventId);
 
     // Set the  NewEventId of this entry
     route.setExpirationEvent(eventId);
   }
   else {
-    route.expires = time::steady_clock::TimePoint::max();
+    route.expires = ndn::nullopt;
   }
 
   NFD_LOG_INFO("Adding route " << parameters.getName() << " nexthop=" << route.faceId
@@ -276,25 +274,23 @@
 RibManager::listEntries(const Name& topPrefix, const Interest& interest,
                         ndn::mgmt::StatusDatasetContext& context)
 {
-  for (const auto& ribTableEntry : m_rib) {
-    const auto& ribEntry = *ribTableEntry.second;
-    const auto& routes = ribEntry.getRoutes() |
-                         boost::adaptors::transformed([] (const Route& route) {
-                           auto r = ndn::nfd::Route()
-                                    .setFaceId(route.faceId)
-                                    .setOrigin(route.origin)
-                                    .setCost(route.cost)
-                                    .setFlags(route.flags);
-                           if (route.expires < time::steady_clock::TimePoint::max()) {
-                             r.setExpirationPeriod(time::duration_cast<time::milliseconds>(
-                                                     route.expires - time::steady_clock::now()));
-                           }
-                           return r;
-                         });
-    context.append(ndn::nfd::RibEntry()
-                   .setName(ribEntry.getName())
-                   .setRoutes(std::begin(routes), std::end(routes))
-                   .wireEncode());
+  auto now = time::steady_clock::now();
+  for (const auto& kv : m_rib) {
+    const RibEntry& entry = *kv.second;
+    ndn::nfd::RibEntry item;
+    item.setName(entry.getName());
+    for (const Route& route : entry.getRoutes()) {
+      ndn::nfd::Route r;
+      r.setFaceId(route.faceId);
+      r.setOrigin(route.origin);
+      r.setCost(route.cost);
+      r.setFlags(route.flags);
+      if (route.expires) {
+        r.setExpirationPeriod(time::duration_cast<time::milliseconds>(*route.expires - now));
+      }
+      item.addRoute(r);
+    }
+    context.append(item.wireEncode());
   }
   context.end();
 }
@@ -409,7 +405,7 @@
   Route route;
   route.faceId = result.getFaceId();
   route.origin = ndn::nfd::ROUTE_ORIGIN_APP;
-  route.expires = time::steady_clock::TimePoint::max();
+  route.expires = ndn::nullopt;
   route.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
 
   m_rib.insert(prefix, route);
diff --git a/rib/route.cpp b/rib/route.cpp
index 7ee9353..08e469b 100644
--- a/rib/route.cpp
+++ b/rib/route.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2015,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -24,18 +24,27 @@
  */
 
 #include "route.hpp"
+#include <ndn-cxx/util/string-helper.hpp>
 
 namespace nfd {
 namespace rib {
 
-bool
-Route::operator==(const Route& other) const
+Route::Route()
+  : faceId(0)
+  , origin(ndn::nfd::ROUTE_ORIGIN_APP)
+  , cost(0)
+  , flags(ndn::nfd::ROUTE_FLAGS_NONE)
 {
-  return (this->faceId == other.faceId &&
-          this->origin == other.origin &&
-          this->flags == other.flags &&
-          this->cost == other.cost &&
-          this->expires == other.expires);
+}
+
+bool
+operator==(const Route& lhs, const Route& rhs)
+{
+  return lhs.faceId == rhs.faceId &&
+         lhs.origin == rhs.origin &&
+         lhs.flags == rhs.flags &&
+         lhs.cost == rhs.cost &&
+         lhs.expires == rhs.expires;
 }
 
 std::ostream&
@@ -45,9 +54,9 @@
      << "faceid: " << route.faceId
      << ", origin: " << route.origin
      << ", cost: " << route.cost
-     << ", flags: " << route.flags;
-  if (route.expires != time::steady_clock::TimePoint::max()) {
-    os << ", expires in: " << (route.expires - time::steady_clock::now());
+     << ", flags: " << ndn::AsHex{route.flags};
+  if (route.expires) {
+    os << ", expires in: " << time::duration_cast<time::milliseconds>(*route.expires - time::steady_clock::now());
   }
   else {
     os << ", never expires";
diff --git a/rib/route.hpp b/rib/route.hpp
index f739741..2dfeabb 100644
--- a/rib/route.hpp
+++ b/rib/route.hpp
@@ -27,30 +27,20 @@
 #define NFD_RIB_ROUTE_HPP
 
 #include "core/scheduler.hpp"
-
 #include <ndn-cxx/encoding/nfd-constants.hpp>
+#include <ndn-cxx/mgmt/nfd/route-flags-traits.hpp>
+#include <type_traits>
 
 namespace nfd {
 namespace rib {
 
 /** \brief represents a route for a name prefix
  */
-class Route
+class Route : public ndn::nfd::RouteFlagsTraits<Route>
 {
 public:
-  Route()
-    : faceId(0)
-    , origin(ndn::nfd::ROUTE_ORIGIN_APP)
-    , flags(0)
-    , cost(0)
-    , expires(time::steady_clock::TimePoint::min())
-  {
-  }
+  Route();
 
-  bool
-  operator==(const Route& other) const;
-
-public:
   void
   setExpirationEvent(const scheduler::EventId eid)
   {
@@ -63,29 +53,32 @@
     return m_expirationEvent;
   }
 
-  bool
-  isChildInherit() const
+  std::underlying_type<ndn::nfd::RouteFlags>::type
+  getFlags() const
   {
-    return flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
-  }
-
-  bool
-  isCapture() const
-  {
-    return flags & ndn::nfd::ROUTE_FLAG_CAPTURE;
+    return flags;
   }
 
 public:
   uint64_t faceId;
   ndn::nfd::RouteOrigin origin;
-  uint64_t flags;
   uint64_t cost;
-  time::steady_clock::TimePoint expires;
+  std::underlying_type<ndn::nfd::RouteFlags>::type flags;
+  ndn::optional<time::steady_clock::TimePoint> expires;
 
 private:
   scheduler::EventId m_expirationEvent;
 };
 
+bool
+operator==(const Route& lhs, const Route& rhs);
+
+inline bool
+operator!=(const Route& lhs, const Route& rhs)
+{
+  return !(lhs == rhs);
+}
+
 inline bool
 compareFaceIdAndOrigin(const Route& lhs, const Route& rhs)
 {
diff --git a/tests/rib/fib-updates-common.hpp b/tests/rib/fib-updates-common.hpp
index 59505bc..925ca81 100644
--- a/tests/rib/fib-updates-common.hpp
+++ b/tests/rib/fib-updates-common.hpp
@@ -72,7 +72,7 @@
   void
   insertRoute(const Name& name, uint64_t faceId,
               std::underlying_type<ndn::nfd::RouteOrigin>::type origin,
-              uint64_t cost, uint64_t flags)
+              uint64_t cost, std::underlying_type<ndn::nfd::RouteFlags>::type flags)
   {
     Route route = createRoute(faceId, origin, cost, flags);
 
diff --git a/tests/rib/rib-manager.t.cpp b/tests/rib/rib-manager.t.cpp
index 117b264..42e8c93 100644
--- a/tests/rib/rib-manager.t.cpp
+++ b/tests/rib/rib-manager.t.cpp
@@ -434,7 +434,7 @@
     Route route;
     route.faceId = ++faceId;
     route.cost = route.faceId * 10;
-    route.expires = time::steady_clock::TimePoint::max();
+    route.expires = ndn::nullopt;
     return route;
   };
 
diff --git a/tests/rib/rib-test-common.hpp b/tests/rib/rib-test-common.hpp
index 3a09d4b..8cddafe 100644
--- a/tests/rib/rib-test-common.hpp
+++ b/tests/rib/rib-test-common.hpp
@@ -36,7 +36,7 @@
 createRoute(uint64_t faceId,
             std::underlying_type<ndn::nfd::RouteOrigin>::type origin,
             uint64_t cost = 0,
-            uint64_t flags = 0)
+            std::underlying_type<ndn::nfd::RouteFlags>::type flags = ndn::nfd::ROUTE_FLAGS_NONE)
 {
   Route temp;
   temp.faceId = faceId;
diff --git a/tests/rib/rib.t.cpp b/tests/rib/rib.t.cpp
index d856f94..e572a72 100644
--- a/tests/rib/rib.t.cpp
+++ b/tests/rib/rib.t.cpp
@@ -291,31 +291,31 @@
 
   Route root = createRoute(1, 20);
   Name name1("/");
-  root.expires = time::steady_clock::TimePoint::max();
+  root.expires = ndn::nullopt;
   rib.insert(name1, root);
 
   Route route1 = createRoute(2, 20);
   Name name2("/hello");
-  route1.expires = time::steady_clock::TimePoint::max();
+  route1.expires = ndn::nullopt;
   rib.insert(name2, route1);
 
   Route route2 = createRoute(3, 20);
   Name name3("/hello/world");
-  route2.expires = time::steady_clock::TimePoint::max();
+  route2.expires = ndn::nullopt;
   rib.insert(name3, route2);
 
   const std::string ribStr = std::string(R"TEXT(
 RibEntry {
-	Name: /
-	Route(faceid: 1, origin: 20, cost: 0, flags: 0, never expires)
+  Name: /
+  Route(faceid: 1, origin: 20, cost: 0, flags: 0x0, never expires)
 }
 RibEntry {
-	Name: /hello
-	Route(faceid: 2, origin: 20, cost: 0, flags: 0, never expires)
+  Name: /hello
+  Route(faceid: 2, origin: 20, cost: 0, flags: 0x0, never expires)
 }
 RibEntry {
-	Name: /hello/world
-	Route(faceid: 3, origin: 20, cost: 0, flags: 0, never expires)
+  Name: /hello/world
+  Route(faceid: 3, origin: 20, cost: 0, flags: 0x0, never expires)
 }
 )TEXT").substr(1);
 
diff --git a/tests/rib/route.t.cpp b/tests/rib/route.t.cpp
new file mode 100644
index 0000000..d7b70eb
--- /dev/null
+++ b/tests/rib/route.t.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib/route.hpp"
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace rib {
+namespace tests {
+
+using namespace nfd::tests;
+
+BOOST_FIXTURE_TEST_SUITE(TestRoute, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  Route a;
+  Route b;
+  BOOST_CHECK_EQUAL(a, b);
+
+  a.faceId = b.faceId = 15404;
+  a.origin = b.origin = ndn::nfd::ROUTE_ORIGIN_NLSR;
+  a.flags = b.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
+  a.cost = b.cost = 28826;
+  a.expires = b.expires = time::steady_clock::now() + time::milliseconds(26782232);
+  BOOST_CHECK_EQUAL(a, b);
+
+  b.faceId = 18559;
+  BOOST_CHECK_NE(a, b);
+  a.faceId = 18559;
+
+  b.origin = ndn::nfd::ROUTE_ORIGIN_CLIENT;
+  BOOST_CHECK_NE(a, b);
+  a.origin = ndn::nfd::ROUTE_ORIGIN_CLIENT;
+
+  b.flags = ndn::nfd::ROUTE_FLAG_CAPTURE;
+  BOOST_CHECK_NE(a, b);
+  a.flags = ndn::nfd::ROUTE_FLAG_CAPTURE;
+
+  b.cost = 103;
+  BOOST_CHECK_NE(a, b);
+  a.cost = 103;
+
+  b.expires = ndn::nullopt;
+  BOOST_CHECK_NE(a, b);
+  a.expires = ndn::nullopt;
+
+  BOOST_CHECK_EQUAL(a, b);
+}
+
+BOOST_FIXTURE_TEST_CASE(Output, UnitTestTimeFixture)
+{
+  Route r;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(r),
+                    "Route(faceid: 0, origin: app, cost: 0, flags: 0x0, never expires)");
+
+  r.faceId = 4980;
+  r.origin = ndn::nfd::ROUTE_ORIGIN_STATIC;
+  r.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
+  r.cost = 2312;
+  r.expires = time::steady_clock::now() + time::milliseconds(791214234);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(r),
+                    "Route(faceid: 4980, origin: static, cost: 2312, flags: 0x1, expires in: 791214234 milliseconds)");
+
+  r.expires = ndn::nullopt;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(r),
+                    "Route(faceid: 4980, origin: static, cost: 2312, flags: 0x1, never expires)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRoute
+
+} // namespace tests
+} // namespace rib
+} // namespace nfd