management: Add RibEntry and Route data structures

refs: #1764

Change-Id: I3d06e8ef8273c315bb1a6d76ad5c12a2ab33ca66
diff --git a/src/encoding/tlv-nfd.hpp b/src/encoding/tlv-nfd.hpp
index 011ad32..ba00cad 100644
--- a/src/encoding/tlv-nfd.hpp
+++ b/src/encoding/tlv-nfd.hpp
@@ -77,7 +77,11 @@
   NextHopRecord = 129,
 
   // Strategy Choice Management
-  StrategyChoice = 128
+  StrategyChoice = 128,
+
+  // RIB Management
+  RibEntry = 128,
+  Route    = 129
 
 };
 
diff --git a/src/management/nfd-rib-entry.cpp b/src/management/nfd-rib-entry.cpp
new file mode 100644
index 0000000..d075cc6
--- /dev/null
+++ b/src/management/nfd-rib-entry.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2014 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "nfd-rib-entry.hpp"
+
+#include "management/nfd-control-command.hpp"
+
+namespace ndn {
+namespace nfd {
+
+const time::milliseconds Route::INFINITE_EXPIRATION_PERIOD(time::milliseconds::max());
+
+Route::Route()
+  : m_faceId(0)
+  , m_origin(0)
+  , m_cost(0)
+  , m_flags(ROUTE_FLAG_CHILD_INHERIT)
+  , m_expirationPeriod(INFINITE_EXPIRATION_PERIOD)
+  , m_hasInfiniteExpirationPeriod(true)
+{
+}
+
+Route::Route(const Block& block)
+{
+  wireDecode(block);
+}
+
+template<bool T>
+size_t
+Route::wireEncode(EncodingImpl<T>& block) const
+{
+  size_t totalLength = 0;
+
+  // Absence of an ExpirationPeriod signifies non-expiration
+  if (!m_hasInfiniteExpirationPeriod) {
+    totalLength += prependNonNegativeIntegerBlock(block,
+                                                  ndn::tlv::nfd::ExpirationPeriod,
+                                                  m_expirationPeriod.count());
+  }
+
+  totalLength += prependNonNegativeIntegerBlock(block,
+                                                ndn::tlv::nfd::Flags,
+                                                m_flags);
+
+  totalLength += prependNonNegativeIntegerBlock(block,
+                                                ndn::tlv::nfd::Cost,
+                                                m_cost);
+
+  totalLength += prependNonNegativeIntegerBlock(block,
+                                                ndn::tlv::nfd::Origin,
+                                                m_origin);
+
+  totalLength += prependNonNegativeIntegerBlock(block,
+                                                ndn::tlv::nfd::FaceId,
+                                                m_faceId);
+
+  totalLength += block.prependVarNumber(totalLength);
+  totalLength += block.prependVarNumber(ndn::tlv::nfd::Route);
+
+  return totalLength;
+}
+
+template size_t
+Route::wireEncode<true>(EncodingImpl<true>& block) const;
+
+template size_t
+Route::wireEncode<false>(EncodingImpl<false>& block) const;
+
+const Block&
+Route::wireEncode() const
+{
+  if (m_wire.hasWire()) {
+    return m_wire;
+  }
+
+  EncodingEstimator estimator;
+  size_t estimatedSize = wireEncode(estimator);
+
+  EncodingBuffer buffer(estimatedSize, 0);
+  wireEncode(buffer);
+
+  m_wire = buffer.block();
+
+  return m_wire;
+}
+
+void
+Route::wireDecode(const Block& wire)
+{
+  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();
+    throw Error(error.str());
+  }
+
+  m_wire.parse();
+
+  Block::element_const_iterator val = m_wire.elements_begin();
+
+  if (val != m_wire.elements_end() && val->type() == tlv::nfd::FaceId) {
+    m_faceId = readNonNegativeInteger(*val);
+    ++val;
+  }
+  else {
+    throw Error("Missing required FaceId field");
+  }
+
+  if (val != m_wire.elements_end() && val->type() == tlv::nfd::Origin) {
+    m_origin = readNonNegativeInteger(*val);
+    ++val;
+  }
+  else {
+    throw Error("Missing required Origin field");
+  }
+
+  if (val != m_wire.elements_end() && val->type() == tlv::nfd::Cost) {
+    m_cost = readNonNegativeInteger(*val);
+    ++val;
+  }
+  else {
+    throw Error("Missing required Cost field");
+  }
+
+  if (val != m_wire.elements_end() && val->type() == tlv::nfd::Flags) {
+    m_flags = readNonNegativeInteger(*val);
+    ++val;
+  }
+  else {
+    throw 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;
+  }
+  else {
+    m_expirationPeriod = INFINITE_EXPIRATION_PERIOD;
+    m_hasInfiniteExpirationPeriod = true;
+  }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Route& route)
+{
+  os << "Route("
+     << "FaceId: " << route.getFaceId() << ", "
+     << "Origin: " << route.getOrigin() << ", "
+     << "Cost: " << route.getCost() << ", "
+     << "Flags: " << route.getFlags() << ", ";
+
+  if (!route.hasInfiniteExpirationPeriod()) {
+    os << "ExpirationPeriod: " << route.getExpirationPeriod();
+  }
+  else {
+    os << "ExpirationPeriod: Infinity";
+  }
+
+  os << ")";
+
+  return os;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+
+
+RibEntry::RibEntry()
+{
+}
+
+RibEntry::RibEntry(const Block& block)
+{
+  wireDecode(block);
+}
+
+
+template<bool T>
+size_t
+RibEntry::wireEncode(EncodingImpl<T>& block) const
+{
+  size_t totalLength = 0;
+
+  for (std::list<Route>::const_reverse_iterator it = m_routes.rbegin();
+       it != m_routes.rend(); ++it)
+    {
+      totalLength += it->wireEncode(block);
+    }
+
+  totalLength += m_prefix.wireEncode(block);
+
+  totalLength += block.prependVarNumber(totalLength);
+  totalLength += block.prependVarNumber(tlv::nfd::RibEntry);
+
+  return totalLength;
+}
+
+template size_t
+RibEntry::wireEncode<true>(EncodingImpl<true>& block) const;
+
+template size_t
+RibEntry::wireEncode<false>(EncodingImpl<false>& block) const;
+
+const Block&
+RibEntry::wireEncode() const
+{
+  if (m_wire.hasWire()) {
+    return m_wire;
+  }
+
+  EncodingEstimator estimator;
+  size_t estimatedSize = wireEncode(estimator);
+
+  EncodingBuffer buffer(estimatedSize, 0);
+  wireEncode(buffer);
+
+  m_wire = buffer.block();
+
+  return m_wire;
+}
+
+void
+RibEntry::wireDecode(const Block& wire)
+{
+  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();
+    throw Error(error.str());
+  }
+
+  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;
+  }
+  else {
+    throw Error("Missing required Name field");
+  }
+
+  for (; val != m_wire.elements_end(); ++val) {
+
+    if (val->type() == tlv::nfd::Route) {
+      m_routes.push_back(Route(*val));
+    }
+    else {
+      std::stringstream error;
+      error << "Expected Route Block, but Block is of a different type: #"
+            << m_wire.type();
+      throw Error(error.str());
+    }
+  }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibEntry& entry)
+{
+  os << "RibEntry{\n"
+     << "  Name: " << entry.getName() << "\n";
+
+  for (RibEntry::iterator it = entry.begin(); it != entry.end(); ++it) {
+    os << "  " << *it << "\n";
+  }
+
+  os << "}";
+
+  return os;
+}
+
+} // namespace nfd
+} // namespace ndn
diff --git a/src/management/nfd-rib-entry.hpp b/src/management/nfd-rib-entry.hpp
new file mode 100644
index 0000000..9ea84c2
--- /dev/null
+++ b/src/management/nfd-rib-entry.hpp
@@ -0,0 +1,282 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2014 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_MANAGEMENT_NFD_RIB_ENTRY_HPP
+#define NDN_MANAGEMENT_NFD_RIB_ENTRY_HPP
+
+#include "../encoding/block.hpp"
+#include "../encoding/encoding-buffer.hpp"
+#include "../encoding/tlv-nfd.hpp"
+#include "../name.hpp"
+
+#include <list>
+
+namespace ndn {
+namespace nfd {
+
+/**
+ * @ingroup management
+ *
+ * @brief Data abstraction for Route
+ *
+ * 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
+ */
+class Route
+{
+public:
+  class Error : public Tlv::Error
+  {
+  public:
+    explicit
+    Error(const std::string& what) : Tlv::Error(what)
+    {
+    }
+  };
+
+  Route();
+
+  explicit
+  Route(const Block& block);
+
+  uint64_t
+  getFaceId() const
+  {
+    return m_faceId;
+  }
+
+  Route&
+  setFaceId(uint64_t faceId)
+  {
+    m_faceId = faceId;
+    m_wire.reset();
+    return *this;
+  }
+
+  uint64_t
+  getOrigin() const
+  {
+    return m_origin;
+  }
+
+  Route&
+  setOrigin(uint64_t origin)
+  {
+    m_origin = origin;
+    m_wire.reset();
+    return *this;
+  }
+
+  uint64_t
+  getCost() const
+  {
+    return m_cost;
+  }
+
+  Route&
+  setCost(uint64_t cost)
+  {
+    m_cost = cost;
+    m_wire.reset();
+    return *this;
+  }
+
+  uint64_t
+  getFlags() const
+  {
+    return m_flags;
+  }
+
+  Route&
+  setFlags(uint64_t flags)
+  {
+    m_flags = flags;
+    m_wire.reset();
+    return *this;
+  }
+
+  static const time::milliseconds INFINITE_EXPIRATION_PERIOD;
+
+  const time::milliseconds&
+  getExpirationPeriod() const
+  {
+    return m_expirationPeriod;
+  }
+
+  Route&
+  setExpirationPeriod(const time::milliseconds& expirationPeriod)
+  {
+    m_expirationPeriod = expirationPeriod;
+
+    m_hasInfiniteExpirationPeriod = m_expirationPeriod == INFINITE_EXPIRATION_PERIOD;
+
+    m_wire.reset();
+    return *this;
+  }
+
+  bool
+  hasInfiniteExpirationPeriod() const
+  {
+    return m_hasInfiniteExpirationPeriod;
+  }
+
+  template<bool T>
+  size_t
+  wireEncode(EncodingImpl<T>& block) const;
+
+  const Block&
+  wireEncode() const;
+
+  void
+  wireDecode(const Block& wire);
+
+private:
+  uint64_t m_faceId;
+  uint64_t m_origin;
+  uint64_t m_cost;
+  uint64_t m_flags;
+  time::milliseconds m_expirationPeriod;
+  bool m_hasInfiniteExpirationPeriod;
+
+  mutable Block m_wire;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const Route& route);
+
+/**
+ * @ingroup management
+ *
+ * @brief Data abstraction for RIB entry
+ *
+ * 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
+ */
+class RibEntry
+{
+public:
+  class Error : public Tlv::Error
+  {
+  public:
+    Error(const std::string& what) : Tlv::Error(what)
+    {
+    }
+  };
+
+  typedef std::list<Route> RouteList;
+  typedef RouteList::const_iterator iterator;
+
+  RibEntry();
+
+  explicit
+  RibEntry(const Block& block);
+
+  const Name&
+  getName() const
+  {
+    return m_prefix;
+  }
+
+  RibEntry&
+  setName(const Name& prefix)
+  {
+    m_prefix = prefix;
+    m_wire.reset();
+    return *this;
+  }
+
+  const std::list<Route>&
+  getRoutes() const
+  {
+    return m_routes;
+  }
+
+  RibEntry&
+  addRoute(const Route& route)
+  {
+    m_routes.push_back(route);
+    m_wire.reset();
+    return *this;
+  }
+
+  RibEntry&
+  clearRoutes()
+  {
+    m_routes.clear();
+    return *this;
+  }
+
+  template<bool T>
+  size_t
+  wireEncode(EncodingImpl<T>& block) const;
+
+  const Block&
+  wireEncode() const;
+
+  void
+  wireDecode(const Block& wire);
+
+  iterator
+  begin() const;
+
+  iterator
+  end() const;
+
+private:
+  Name m_prefix;
+  RouteList m_routes;
+
+  mutable Block m_wire;
+};
+
+inline RibEntry::iterator
+RibEntry::begin() const
+{
+  return m_routes.begin();
+}
+
+inline RibEntry::iterator
+RibEntry::end() const
+{
+  return m_routes.end();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibEntry& entry);
+
+} // namespace nfd
+} // namespace ndn
+
+#endif // NDN_MANAGEMENT_NFD_RIB_ENTRY_HPP
diff --git a/tests/unit-tests/management/test-nfd-rib-entry.cpp b/tests/unit-tests/management/test-nfd-rib-entry.cpp
new file mode 100644
index 0000000..858598f
--- /dev/null
+++ b/tests/unit-tests/management/test-nfd-rib-entry.cpp
@@ -0,0 +1,341 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2014 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "management/nfd-rib-entry.hpp"
+#include "management/nfd-control-command.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+
+BOOST_AUTO_TEST_SUITE(ManagementTestNfdRibEntry)
+
+const uint8_t RouteData[] =
+{
+  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+  0x6d, 0x02, 0x27, 0x10
+};
+
+const uint8_t RouteInfiniteExpirationPeriod[] =
+{
+  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,
+};
+
+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));
+
+  const Block& wire = route.wireEncode();
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteData,
+                                  RouteData + sizeof(RouteData),
+                                  wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(RouteDecode)
+{
+  Route route;
+
+  BOOST_REQUIRE_NO_THROW(route.wireDecode(Block(RouteData, sizeof(RouteData))));
+
+  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);
+}
+
+BOOST_AUTO_TEST_CASE(RouteInfiniteExpirationPeriodEncode)
+{
+  Route route;
+  route.setFaceId(1);
+  route.setOrigin(128);
+  route.setCost(100);
+  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+
+  const Block& wire = route.wireEncode();
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteInfiniteExpirationPeriod,
+                                  RouteInfiniteExpirationPeriod + sizeof(RouteInfiniteExpirationPeriod),
+                                  wire.begin(), wire.end());
+}
+
+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)");
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEncode)
+{
+  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));
+  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),
+                                wire.begin(), wire.end());
+}
+
+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)
+{
+  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));
+  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));
+  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);
+
+  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)
+{
+  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));
+  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"
+                              "}");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace nfd
+} // namespace ndn