mgmt: RibEntry equality operators and formatted output

This commit also changes the return type of getRoutes()
from std::list to std::vector, and adds setRoutes(),
hasExpirationPeriod() and unsetExpirationPeriod().

Change-Id: I53f0ea846067c9087fa72273fc131826884ce12f
Refs: #3903
diff --git a/src/mgmt/nfd/rib-entry.cpp b/src/mgmt/nfd/rib-entry.cpp
index ed7fd67..17b53d6 100644
--- a/src/mgmt/nfd/rib-entry.cpp
+++ b/src/mgmt/nfd/rib-entry.cpp
@@ -20,34 +20,24 @@
  */
 
 #include "rib-entry.hpp"
-#include "encoding/tlv-nfd.hpp"
 #include "encoding/block-helpers.hpp"
+#include "encoding/encoding-buffer.hpp"
+#include "encoding/tlv-nfd.hpp"
 #include "util/concepts.hpp"
 
+#include <boost/range/adaptor/reversed.hpp>
+
 namespace ndn {
 namespace nfd {
 
-//BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Route>));
-BOOST_CONCEPT_ASSERT((WireEncodable<Route>));
-BOOST_CONCEPT_ASSERT((WireDecodable<Route>));
-static_assert(std::is_base_of<tlv::Error, Route::Error>::value,
-              "Route::Error must inherit from tlv::Error");
-
-//BOOST_CONCEPT_ASSERT((boost::EqualityComparable<RibEntry>));
-BOOST_CONCEPT_ASSERT((WireEncodable<RibEntry>));
-BOOST_CONCEPT_ASSERT((WireDecodable<RibEntry>));
-static_assert(std::is_base_of<tlv::Error, RibEntry::Error>::value,
-              "RibEntry::Error must inherit from tlv::Error");
-
-const time::milliseconds Route::INFINITE_EXPIRATION_PERIOD(time::milliseconds::max());
+BOOST_CONCEPT_ASSERT((StatusDatasetItem<Route>));
+BOOST_CONCEPT_ASSERT((StatusDatasetItem<RibEntry>));
 
 Route::Route()
   : m_faceId(INVALID_FACE_ID)
   , m_origin(0)
   , m_cost(0)
   , m_flags(ROUTE_FLAG_CHILD_INHERIT)
-  , m_expirationPeriod(INFINITE_EXPIRATION_PERIOD)
-  , m_hasInfiniteExpirationPeriod(true)
 {
 }
 
@@ -91,8 +81,18 @@
 Route&
 Route::setExpirationPeriod(time::milliseconds expirationPeriod)
 {
+  if (expirationPeriod == time::milliseconds::max())
+    return unsetExpirationPeriod();
+
   m_expirationPeriod = expirationPeriod;
-  m_hasInfiniteExpirationPeriod = m_expirationPeriod == INFINITE_EXPIRATION_PERIOD;
+  m_wire.reset();
+  return *this;
+}
+
+Route&
+Route::unsetExpirationPeriod()
+{
+  m_expirationPeriod = nullopt;
   m_wire.reset();
   return *this;
 }
@@ -103,10 +103,9 @@
 {
   size_t totalLength = 0;
 
-  // Absence of an ExpirationPeriod signifies non-expiration
-  if (!m_hasInfiniteExpirationPeriod) {
+  if (m_expirationPeriod) {
     totalLength += prependNonNegativeIntegerBlock(block, ndn::tlv::nfd::ExpirationPeriod,
-                                                  static_cast<uint64_t>(m_expirationPeriod.count()));
+                                                  static_cast<uint64_t>(m_expirationPeriod->count()));
   }
   totalLength += prependNonNegativeIntegerBlock(block, ndn::tlv::nfd::Flags, m_flags);
   totalLength += prependNonNegativeIntegerBlock(block, ndn::tlv::nfd::Cost, m_cost);
@@ -141,25 +140,13 @@
 }
 
 void
-Route::wireDecode(const Block& wire)
+Route::wireDecode(const Block& block)
 {
-  m_faceId = 0;
-  m_origin = 0;
-  m_cost = 0;
-  m_flags = 0;
-  m_expirationPeriod = time::milliseconds::min();
-
-  m_wire = wire;
-
-  if (m_wire.type() != tlv::nfd::Route) {
-    std::stringstream error;
-    error << "Expected Route Block, but Block is of a different type: #"
-          << m_wire.type();
-    BOOST_THROW_EXCEPTION(Error(error.str()));
+  if (block.type() != tlv::nfd::Route) {
+    BOOST_THROW_EXCEPTION(Error("expecting Route, but Block has type " + to_string(block.type())));
   }
-
+  m_wire = block;
   m_wire.parse();
-
   Block::element_const_iterator val = m_wire.elements_begin();
 
   if (val != m_wire.elements_end() && val->type() == tlv::nfd::FaceId) {
@@ -167,7 +154,7 @@
     ++val;
   }
   else {
-    BOOST_THROW_EXCEPTION(Error("Missing required FaceId field"));
+    BOOST_THROW_EXCEPTION(Error("missing required FaceId field"));
   }
 
   if (val != m_wire.elements_end() && val->type() == tlv::nfd::Origin) {
@@ -175,7 +162,7 @@
     ++val;
   }
   else {
-    BOOST_THROW_EXCEPTION(Error("Missing required Origin field"));
+    BOOST_THROW_EXCEPTION(Error("missing required Origin field"));
   }
 
   if (val != m_wire.elements_end() && val->type() == tlv::nfd::Cost) {
@@ -183,7 +170,7 @@
     ++val;
   }
   else {
-    BOOST_THROW_EXCEPTION(Error("Missing required Cost field"));
+    BOOST_THROW_EXCEPTION(Error("missing required Cost field"));
   }
 
   if (val != m_wire.elements_end() && val->type() == tlv::nfd::Flags) {
@@ -191,38 +178,50 @@
     ++val;
   }
   else {
-    BOOST_THROW_EXCEPTION(Error("Missing required Flags field"));
+    BOOST_THROW_EXCEPTION(Error("missing required Flags field"));
   }
 
   if (val != m_wire.elements_end() && val->type() == tlv::nfd::ExpirationPeriod) {
-    m_expirationPeriod = time::milliseconds(readNonNegativeInteger(*val));
-    m_hasInfiniteExpirationPeriod = false;
+    m_expirationPeriod.emplace(readNonNegativeInteger(*val));
+    ++val;
   }
   else {
-    m_expirationPeriod = INFINITE_EXPIRATION_PERIOD;
-    m_hasInfiniteExpirationPeriod = true;
+    m_expirationPeriod = nullopt;
   }
 }
 
+bool
+operator==(const Route& a, const Route& b)
+{
+  return a.getFaceId() == b.getFaceId() &&
+      a.getOrigin() == b.getOrigin() &&
+      a.getCost() == b.getCost() &&
+      a.getFlags() == b.getFlags() &&
+      a.getExpirationPeriod() == b.getExpirationPeriod();
+}
+
 std::ostream&
 operator<<(std::ostream& os, const Route& route)
 {
   os << "Route("
      << "FaceId: " << route.getFaceId() << ", "
      << "Origin: " << route.getOrigin() << ", "
-     << "Cost: " << route.getCost() << ", "
-     << "Flags: " << route.getFlags() << ", ";
+     << "Cost: " << route.getCost() << ", ";
 
-  if (!route.hasInfiniteExpirationPeriod()) {
+  auto osFlags = os.flags();
+  // std::showbase doesn't work with number 0
+  os << "Flags: 0x" << std::noshowbase << std::noshowpos << std::nouppercase
+     << std::hex << route.getFlags() << ", ";
+  os.flags(osFlags);
+
+  if (route.hasExpirationPeriod()) {
     os << "ExpirationPeriod: " << route.getExpirationPeriod();
   }
   else {
-    os << "ExpirationPeriod: Infinity";
+    os << "ExpirationPeriod: infinite";
   }
 
-  os << ")";
-
-  return os;
+  return os << ")";
 }
 
 ////////////////////
@@ -264,12 +263,9 @@
 {
   size_t totalLength = 0;
 
-  for (std::list<Route>::const_reverse_iterator it = m_routes.rbegin();
-       it != m_routes.rend(); ++it)
-    {
-      totalLength += it->wireEncode(block);
-    }
-
+  for (const auto& route : m_routes | boost::adaptors::reversed) {
+    totalLength += route.wireEncode(block);
+  }
   totalLength += m_prefix.wireEncode(block);
 
   totalLength += block.prependVarNumber(totalLength);
@@ -300,59 +296,72 @@
 }
 
 void
-RibEntry::wireDecode(const Block& wire)
+RibEntry::wireDecode(const Block& block)
 {
-  m_prefix.clear();
-  m_routes.clear();
-
-  m_wire = wire;
-
-  if (m_wire.type() != tlv::nfd::RibEntry) {
-    std::stringstream error;
-    error << "Expected RibEntry Block, but Block is of a different type: #"
-          << m_wire.type();
-    BOOST_THROW_EXCEPTION(Error(error.str()));
+  if (block.type() != tlv::nfd::RibEntry) {
+    BOOST_THROW_EXCEPTION(Error("expecting RibEntry, but Block has type " + to_string(block.type())));
   }
-
+  m_wire = block;
   m_wire.parse();
-
   Block::element_const_iterator val = m_wire.elements_begin();
 
-  if (val != m_wire.elements_end() && val->type() == tlv::Name) {
-    m_prefix.wireDecode(*val);
-    ++val;
+  if (val == m_wire.elements_end()) {
+    BOOST_THROW_EXCEPTION(Error("unexpected end of RibEntry"));
   }
-  else {
-    BOOST_THROW_EXCEPTION(Error("Missing required Name field"));
+  else if (val->type() != tlv::Name) {
+    BOOST_THROW_EXCEPTION(Error("expecting Name, but Block has type " + to_string(val->type())));
   }
+  m_prefix.wireDecode(*val);
+  ++val;
 
+  m_routes.clear();
   for (; val != m_wire.elements_end(); ++val) {
-
-    if (val->type() == tlv::nfd::Route) {
-      m_routes.push_back(Route(*val));
+    if (val->type() != tlv::nfd::Route) {
+      BOOST_THROW_EXCEPTION(Error("expecting Route, but Block has type " + to_string(val->type())));
     }
-    else {
-      std::stringstream error;
-      error << "Expected Route Block, but Block is of a different type: #"
-            << m_wire.type();
-      BOOST_THROW_EXCEPTION(Error(error.str()));
-    }
+    m_routes.emplace_back(*val);
   }
 }
 
+bool
+operator==(const RibEntry& a, const RibEntry& b)
+{
+  const auto& aRoutes = a.getRoutes();
+  const auto& bRoutes = b.getRoutes();
+
+  if (a.getName() != b.getName() ||
+      aRoutes.size() != bRoutes.size())
+    return false;
+
+  std::vector<bool> matched(bRoutes.size(), false);
+  return std::all_of(aRoutes.begin(), aRoutes.end(),
+                     [&] (const Route& route) {
+                       for (size_t i = 0; i < bRoutes.size(); ++i) {
+                         if (!matched[i] && bRoutes[i] == route) {
+                           matched[i] = true;
+                           return true;
+                         }
+                       }
+                       return false;
+                     });
+}
+
 std::ostream&
 operator<<(std::ostream& os, const RibEntry& entry)
 {
-  os << "RibEntry{\n"
-     << "  Name: " << entry.getName() << "\n";
+  os << "RibEntry(Prefix: " << entry.getName() << ",\n"
+     << "         Routes: [";
 
-  for (RibEntry::iterator it = entry.begin(); it != entry.end(); ++it) {
-    os << "  " << *it << "\n";
+  bool isFirst = true;
+  for (const auto& route : entry.getRoutes()) {
+    if (!isFirst)
+      os << ",\n                  ";
+    isFirst = false;
+    os << route;
   }
+  os << "]\n";
 
-  os << "}";
-
-  return os;
+  return os << "         )";
 }
 
 } // namespace nfd
diff --git a/src/mgmt/nfd/rib-entry.hpp b/src/mgmt/nfd/rib-entry.hpp
index b0d7289..0c2071d 100644
--- a/src/mgmt/nfd/rib-entry.hpp
+++ b/src/mgmt/nfd/rib-entry.hpp
@@ -23,30 +23,21 @@
 #define NDN_MGMT_NFD_RIB_ENTRY_HPP
 
 #include "rib-flags.hpp"
+#include "../../encoding/block.hpp"
 #include "../../name.hpp"
 #include "../../util/time.hpp"
 
-#include <list>
-
 namespace ndn {
 namespace nfd {
 
 /**
- * @ingroup management
- *
- * @brief Data abstraction for Route
+ * \ingroup management
+ * \brief represents a route in a RibEntry
  *
  * A route indicates the availability of content via a certain face and
  * provides meta-information about the face.
  *
- *     Route := ROUTE-TYPE TLV-LENGTH
- *                FaceId
- *                Origin
- *                Cost
- *                Flags
- *                ExpirationPeriod?
- *
- * @sa http://redmine.named-data.net/projects/nfd/wiki/RibMgmt
+ * \sa https://redmine.named-data.net/projects/nfd/wiki/RibMgmt#Route
  */
 class Route : public RibFlagsTraits<Route>
 {
@@ -108,22 +99,23 @@
   Route&
   setFlags(uint64_t flags);
 
-  static const time::milliseconds INFINITE_EXPIRATION_PERIOD;
+  bool
+  hasExpirationPeriod() const
+  {
+    return !!m_expirationPeriod;
+  }
 
   time::milliseconds
   getExpirationPeriod() const
   {
-    return m_expirationPeriod;
+    return m_expirationPeriod ? *m_expirationPeriod : time::milliseconds::max();
   }
 
   Route&
   setExpirationPeriod(time::milliseconds expirationPeriod);
 
-  bool
-  hasInfiniteExpirationPeriod() const
-  {
-    return m_hasInfiniteExpirationPeriod;
-  }
+  Route&
+  unsetExpirationPeriod();
 
   template<encoding::Tag TAG>
   size_t
@@ -133,34 +125,38 @@
   wireEncode() const;
 
   void
-  wireDecode(const Block& wire);
+  wireDecode(const Block& block);
 
 private:
   uint64_t m_faceId;
   uint64_t m_origin;
   uint64_t m_cost;
   uint64_t m_flags;
-  time::milliseconds m_expirationPeriod;
-  bool m_hasInfiniteExpirationPeriod;
+  optional<time::milliseconds> m_expirationPeriod;
 
   mutable Block m_wire;
 };
 
+bool
+operator==(const Route& a, const Route& b);
+
+inline bool
+operator!=(const Route& a, const Route& b)
+{
+  return !(a == b);
+}
+
 std::ostream&
 operator<<(std::ostream& os, const Route& route);
 
+
 /**
- * @ingroup management
+ * \ingroup management
+ * \brief represents an item in NFD RIB dataset
  *
- * @brief Data abstraction for RIB entry
+ * A RIB entry contains one or more routes for a name prefix
  *
- * A RIB entry contains one or more routes for the name prefix
- *
- *     RibEntry := RIB-ENTRY-TYPE TLV-LENGTH
- *                Name
- *                Route+
- *
- * @sa http://redmine.named-data.net/projects/nfd/wiki/RibMgmt
+ * \sa https://redmine.named-data.net/projects/nfd/wiki/RibMgmt#RIB-Dataset
  */
 class RibEntry
 {
@@ -175,9 +171,6 @@
     }
   };
 
-  typedef std::list<Route> RouteList;
-  typedef RouteList::const_iterator iterator;
-
   RibEntry();
 
   explicit
@@ -192,12 +185,21 @@
   RibEntry&
   setName(const Name& prefix);
 
-  const std::list<Route>&
+  const std::vector<Route>&
   getRoutes() const
   {
     return m_routes;
   }
 
+  template<typename InputIt>
+  RibEntry&
+  setRoutes(InputIt first, InputIt last)
+  {
+    m_routes.assign(first, last);
+    m_wire.reset();
+    return *this;
+  }
+
   RibEntry&
   addRoute(const Route& route);
 
@@ -212,31 +214,22 @@
   wireEncode() const;
 
   void
-  wireDecode(const Block& wire);
-
-  iterator
-  begin() const;
-
-  iterator
-  end() const;
+  wireDecode(const Block& block);
 
 private:
   Name m_prefix;
-  RouteList m_routes;
+  std::vector<Route> m_routes;
 
   mutable Block m_wire;
 };
 
-inline RibEntry::iterator
-RibEntry::begin() const
-{
-  return m_routes.begin();
-}
+bool
+operator==(const RibEntry& a, const RibEntry& b);
 
-inline RibEntry::iterator
-RibEntry::end() const
+inline bool
+operator!=(const RibEntry& a, const RibEntry& b)
 {
-  return m_routes.end();
+  return !(a == b);
 }
 
 std::ostream&
diff --git a/src/mgmt/nfd/rib-flags.hpp b/src/mgmt/nfd/rib-flags.hpp
index 158048f..ebc2b9e 100644
--- a/src/mgmt/nfd/rib-flags.hpp
+++ b/src/mgmt/nfd/rib-flags.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -29,10 +29,10 @@
 
 /**
  * \ingroup management
- * \brief implements getters to each RIB flag
+ * \brief defines getters for each route inheritance flag
  *
- * \tparam T class containing a RibFlags field and implements
- *           `RibFlags getFlags() const` method
+ * \tparam T class containing a RouteFlags field and implementing
+ *           a `RouteFlags getFlags() const` member function
  */
 template<typename T>
 class RibFlagsTraits
@@ -49,6 +49,9 @@
   {
     return static_cast<const T*>(this)->getFlags() & ROUTE_FLAG_CAPTURE;
   }
+
+protected:
+  RibFlagsTraits() = default;
 };
 
 } // namespace nfd
diff --git a/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp b/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp
index 415be29..cc3ed58 100644
--- a/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp
+++ b/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -20,9 +20,9 @@
  */
 
 #include "mgmt/nfd/rib-entry.hpp"
-#include "mgmt/nfd/control-command.hpp"
 
 #include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
 
 namespace ndn {
 namespace nfd {
@@ -32,310 +32,182 @@
 BOOST_AUTO_TEST_SUITE(Nfd)
 BOOST_AUTO_TEST_SUITE(TestRibEntry)
 
-const uint8_t RouteData[] =
+static Route
+makeRoute()
 {
-  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
-  0x6d, 0x02, 0x27, 0x10
-};
+  return Route()
+      .setFaceId(1)
+      .setOrigin(128)
+      .setCost(100)
+      .setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+}
 
-const uint8_t RouteInfiniteExpirationPeriod[] =
+static RibEntry
+makeRibEntry()
 {
-  0x81, 0x0C, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02
-};
-
-const uint8_t RibEntryData[] =
-{
-  // Header + Name
-  0x80, 0x34, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
-  0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64,
-  // Route
-  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
-  0x6d, 0x02, 0x27, 0x10,
-  // Route
-  0x81, 0x10, 0x69, 0x01, 0x02, 0x6f, 0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01,
-  0x6d, 0x02, 0x13, 0x88
-};
-
-const uint8_t RibEntryInfiniteExpirationPeriod[] =
-{
-  // Header + Name
-  0x80, 0x30, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
-  0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64,
-  // Route
-  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
-  0x6d, 0x02, 0x27, 0x10,
-  // Route with no ExpirationPeriod
-  0x81, 0x0C, 0x69, 0x01, 0x02, 0x6f, 0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01,
-};
+  return RibEntry()
+      .setName("/hello/world")
+      .addRoute(makeRoute()
+                .setExpirationPeriod(time::seconds(10)));
+}
 
 BOOST_AUTO_TEST_CASE(RouteEncode)
 {
-  Route route;
-  route.setFaceId(1);
-  route.setOrigin(128);
-  route.setCost(100);
-  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route.setExpirationPeriod(time::milliseconds(10000));
+  Route route1 = makeRoute();
+  route1.setExpirationPeriod(time::seconds(10));
+  const Block& wire = route1.wireEncode();
 
-  const Block& wire = route.wireEncode();
+  static const uint8_t expected[] = {
+    0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+    0x6d, 0x02, 0x27, 0x10
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
 
-  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteData,
-                                  RouteData + sizeof(RouteData),
-                                  wire.begin(), wire.end());
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
 }
 
-BOOST_AUTO_TEST_CASE(RouteDecode)
+BOOST_AUTO_TEST_CASE(RouteNoExpirationPeriodEncode)
 {
-  Route route;
+  Route route1 = makeRoute();
+  const Block& wire = route1.wireEncode();
 
-  BOOST_REQUIRE_NO_THROW(route.wireDecode(Block(RouteData, sizeof(RouteData))));
+  static const uint8_t expected[] = {
+    0x81, 0x0C, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
 
-  BOOST_REQUIRE_EQUAL(route.getFaceId(), 1);
-  BOOST_REQUIRE_EQUAL(route.getOrigin(), 128);
-  BOOST_REQUIRE_EQUAL(route.getCost(), 100);
-  BOOST_REQUIRE_EQUAL(route.getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
-  BOOST_REQUIRE_EQUAL(route.getExpirationPeriod(), time::milliseconds(10000));
-  BOOST_REQUIRE_EQUAL(route.hasInfiniteExpirationPeriod(), false);
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
 }
 
-BOOST_AUTO_TEST_CASE(RouteInfiniteExpirationPeriodEncode)
+BOOST_AUTO_TEST_CASE(RouteEquality)
 {
-  Route route;
-  route.setFaceId(1);
-  route.setOrigin(128);
-  route.setCost(100);
-  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+  Route route1, route2;
 
-  const Block& wire = route.wireEncode();
+  route1 = makeRoute();
+  route2 = route1;
+  BOOST_CHECK_EQUAL(route1, route2);
 
-  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteInfiniteExpirationPeriod,
-                                  RouteInfiniteExpirationPeriod + sizeof(RouteInfiniteExpirationPeriod),
-                                  wire.begin(), wire.end());
-}
+  route2.setFaceId(42);
+  BOOST_CHECK_NE(route1, route2);
 
-BOOST_AUTO_TEST_CASE(RouteInfiniteExpirationPeriodDecode)
-{
-  Route route;
-
-  BOOST_REQUIRE_NO_THROW(route.wireDecode(Block(RouteInfiniteExpirationPeriod,
-                                                sizeof(RouteInfiniteExpirationPeriod))));
-
-  BOOST_REQUIRE_EQUAL(route.getFaceId(), 1);
-  BOOST_REQUIRE_EQUAL(route.getOrigin(), 128);
-  BOOST_REQUIRE_EQUAL(route.getCost(), 100);
-  BOOST_REQUIRE_EQUAL(route.getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
-  BOOST_REQUIRE_EQUAL(route.getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
-  BOOST_REQUIRE_EQUAL(route.hasInfiniteExpirationPeriod(), true);
-}
-
-BOOST_AUTO_TEST_CASE(RouteOutputStream)
-{
-  Route route;
-  route.setFaceId(1);
-  route.setOrigin(128);
-  route.setCost(100);
-  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route.setExpirationPeriod(time::milliseconds(10000));
-
-  std::ostringstream os;
-  os << route;
-
-  BOOST_CHECK_EQUAL(os.str(), "Route(FaceId: 1, Origin: 128, Cost: 100, "
-                              "Flags: 2, ExpirationPeriod: 10000 milliseconds)");
+  route2 = route1;
+  route2.setExpirationPeriod(time::minutes(1));
+  BOOST_CHECK_NE(route1, route2);
 }
 
 BOOST_AUTO_TEST_CASE(RibEntryEncode)
 {
-  RibEntry entry;
-  entry.setName("/hello/world");
+  RibEntry entry1 = makeRibEntry();
+  entry1.addRoute(Route()
+                  .setFaceId(2)
+                  .setOrigin(0)
+                  .setCost(32)
+                  .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+                  .setExpirationPeriod(time::seconds(5)));
+  const Block& wire = entry1.wireEncode();
 
-  Route route1;
-  route1.setFaceId(1);
-  route1.setOrigin(128);
-  route1.setCost(100);
-  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route1.setExpirationPeriod(time::milliseconds(10000));
-  entry.addRoute(route1);
-
-  Route route2;
-  route2.setFaceId(2);
-  route2.setOrigin(0);
-  route2.setCost(32);
-  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-  route2.setExpirationPeriod(time::milliseconds(5000));
-  entry.addRoute(route2);
-
-  const Block& wire = entry.wireEncode();
-
-  BOOST_CHECK_EQUAL_COLLECTIONS(RibEntryData,
-                                RibEntryData + sizeof(RibEntryData),
+  static const uint8_t expected[] = {
+    0x80, 0x34, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01,
+    0x64, 0x6c, 0x01, 0x02, 0x6d, 0x02, 0x27, 0x10, 0x81, 0x10, 0x69, 0x01, 0x02, 0x6f,
+    0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01, 0x6d, 0x02, 0x13, 0x88
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
                                 wire.begin(), wire.end());
+
+  RibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
 }
 
-BOOST_AUTO_TEST_CASE(RibEntryDecode)
-{
-  RibEntry entry;
-  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(RibEntryData,
-                                          sizeof(RibEntryData))));
-
-  BOOST_CHECK_EQUAL(entry.getName(), "/hello/world");
-  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 2);
-
-  std::list<Route> routes = entry.getRoutes();
-
-  std::list<Route>::const_iterator it = routes.begin();
-  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
-  BOOST_CHECK_EQUAL(it->getCost(), 100);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
-
-  ++it;
-  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
-  BOOST_CHECK_EQUAL(it->getCost(), 32);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(5000));
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
-}
-
-BOOST_AUTO_TEST_CASE(RibEntryInfiniteExpirationPeriodEncode)
+BOOST_AUTO_TEST_CASE(RibEntryClearRoutes)
 {
   RibEntry entry;
   entry.setName("/hello/world");
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
 
   Route route1;
-  route1.setFaceId(1);
-  route1.setOrigin(128);
-  route1.setCost(100);
-  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route1.setExpirationPeriod(time::milliseconds(10000));
-  entry.addRoute(route1);
-
-  Route route2;
-  route2.setFaceId(2);
-  route2.setOrigin(0);
-  route2.setCost(32);
-  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
-  entry.addRoute(route2);
-
-  const Block& wire = entry.wireEncode();
-
-  BOOST_CHECK_EQUAL_COLLECTIONS(RibEntryInfiniteExpirationPeriod,
-                                RibEntryInfiniteExpirationPeriod +
-                                sizeof(RibEntryInfiniteExpirationPeriod),
-                                wire.begin(), wire.end());
-}
-
-BOOST_AUTO_TEST_CASE(RibEntryInfiniteExpirationPeriodDecode)
-{
-  RibEntry entry;
-  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(RibEntryInfiniteExpirationPeriod,
-                                          sizeof(RibEntryInfiniteExpirationPeriod))));
-
-  BOOST_CHECK_EQUAL(entry.getName(), "/hello/world");
-  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 2);
-
-  std::list<Route> routes = entry.getRoutes();
-
-  std::list<Route>::const_iterator it = routes.begin();
-  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
-  BOOST_CHECK_EQUAL(it->getCost(), 100);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
-
-  ++it;
-  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
-  BOOST_CHECK_EQUAL(it->getCost(), 32);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), true);
-}
-
-BOOST_AUTO_TEST_CASE(RibEntryClear)
-{
-  RibEntry entry;
-  entry.setName("/hello/world");
-
-  Route route1;
-  route1.setFaceId(1);
-  route1.setOrigin(128);
-  route1.setCost(100);
-  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route1.setExpirationPeriod(time::milliseconds(10000));
+  route1.setFaceId(42);
   entry.addRoute(route1);
   BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
-
-  std::list<Route> routes = entry.getRoutes();
-
-  std::list<Route>::const_iterator it = routes.begin();
-  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
-  BOOST_CHECK_EQUAL(it->getCost(), 100);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(entry.getRoutes().front(), route1);
 
   entry.clearRoutes();
   BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
-
-  Route route2;
-  route2.setFaceId(2);
-  route2.setOrigin(0);
-  route2.setCost(32);
-  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
-  entry.addRoute(route2);
-  BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
-
-  routes = entry.getRoutes();
-
-  it = routes.begin();
-  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
-  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
-  BOOST_CHECK_EQUAL(it->getCost(), 32);
-  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
-  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
-  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), true);
 }
 
-BOOST_AUTO_TEST_CASE(RibEntryOutputStream)
+BOOST_AUTO_TEST_CASE(RibEntryEquality)
 {
+  RibEntry entry1, entry2;
+  BOOST_CHECK_EQUAL(entry1, entry2);
+
+  entry1 = entry2 = makeRibEntry();
+  BOOST_CHECK_EQUAL(entry1, entry2);
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry2.setName("/different/name");
+  BOOST_CHECK_NE(entry1, entry2);
+
+  entry2 = entry1;
+  std::vector<Route> empty;
+  entry2.setRoutes(empty.begin(), empty.end());
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  entry2 = entry1;
+  auto r1 = Route()
+            .setFaceId(1)
+            .setCost(1000);
+  entry1.addRoute(r1);
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  auto r42 = Route()
+             .setFaceId(42)
+             .setCost(42);
+  entry1.addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r1);
+  BOOST_CHECK_EQUAL(entry1, entry2); // order of Routes is irrelevant
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry1 = entry2 = makeRibEntry();
+  entry1.addRoute(r1)
+      .addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r42);
+  BOOST_CHECK_NE(entry1, entry2); // match each Route at most once
+  BOOST_CHECK_NE(entry2, entry1);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  Route route;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(route),
+                    "Route(FaceId: 0, Origin: 0, Cost: 0, Flags: 0x1, ExpirationPeriod: infinite)");
+
   RibEntry entry;
-  entry.setName("/hello/world");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /,\n"
+                    "         Routes: []\n"
+                    "         )");
 
-  Route route1;
-  route1.setFaceId(1);
-  route1.setOrigin(128);
-  route1.setCost(100);
-  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
-  route1.setExpirationPeriod(time::milliseconds(10000));
-  entry.addRoute(route1);
-
-  Route route2;
-  route2.setFaceId(2);
-  route2.setOrigin(0);
-  route2.setCost(32);
-  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
-  entry.addRoute(route2);
-
-  std::ostringstream os;
-  os << entry;
-
-  BOOST_CHECK_EQUAL(os.str(), "RibEntry{\n"
-                              "  Name: /hello/world\n"
-                              "  Route(FaceId: 1, Origin: 128, Cost: 100, "
-                              "Flags: 2, ExpirationPeriod: 10000 milliseconds)\n"
-                              "  Route(FaceId: 2, Origin: 0, Cost: 32, "
-                              "Flags: 1, ExpirationPeriod: Infinity)\n"
-                              "}");
+  entry = makeRibEntry();
+  entry.addRoute(Route()
+                 .setFaceId(2)
+                 .setOrigin(0)
+                 .setCost(32)
+                 .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /hello/world,\n"
+                    "         Routes: [Route(FaceId: 1, Origin: 128, Cost: 100, Flags: 0x2, "
+                    "ExpirationPeriod: 10000 milliseconds),\n"
+                    "                  Route(FaceId: 2, Origin: 0, Cost: 32, Flags: 0x1, "
+                    "ExpirationPeriod: infinite)]\n"
+                    "         )");
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestRibEntry