rib: refactor RibManager to use ManagementDispatcher

Change-Id: I3f689de7d043e85531f0b3f4accf65345cde5d9e
refs: #2857
diff --git a/tests/rib/rib-manager.t.cpp b/tests/rib/rib-manager.t.cpp
index 1287284..0f1e3f7 100644
--- a/tests/rib/rib-manager.t.cpp
+++ b/tests/rib/rib-manager.t.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-2016,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -24,11 +24,12 @@
  */
 
 #include "rib/rib-manager.hpp"
-#include <ndn-cxx/management/nfd-face-status.hpp>
-#include "rib/rib-status-publisher-common.hpp"
+#include "manager-common-fixture.hpp"
 
-#include "tests/test-common.hpp"
-#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/management/nfd-rib-entry.hpp>
+#include <ndn-cxx/management/nfd-face-status.hpp>
+#include <ndn-cxx/management/nfd-face-event-notification.hpp>
+#include <ndn-cxx/util/random.hpp>
 
 namespace nfd {
 namespace rib {
@@ -36,448 +37,587 @@
 
 using namespace nfd::tests;
 
-class RibManagerFixture : public UnitTestTimeFixture
+struct ConfigurationStatus
 {
-public:
-  RibManagerFixture()
-  {
-    face = ndn::util::makeDummyClientFace(g_io);
-
-    manager = make_shared<RibManager>(*face, keyChain);
-    manager->registerWithNfd();
-
-    advanceClocks(time::milliseconds(1));
-    face->sentInterests.clear();
-  }
-
-  ~RibManagerFixture()
-  {
-    manager.reset();
-    face.reset();
-  }
-
-  void
-  extractParameters(Interest& interest, Name::Component& verb,
-                    ControlParameters& extractedParameters)
-  {
-    const Name& name = interest.getName();
-    verb = name[COMMAND_PREFIX.size()];
-    const Name::Component& parameterComponent = name[COMMAND_PREFIX.size() + 1];
-
-    Block rawParameters = parameterComponent.blockFromValue();
-    extractedParameters.wireDecode(rawParameters);
-  }
-
-  void
-  receiveCommandInterest(const Name& name, ControlParameters& parameters,
-                         uint64_t incomingFaceId = DEFAULT_INCOMING_FACE_ID)
-  {
-    Name commandName = name;
-    commandName.append(parameters.wireEncode());
-
-    Interest commandInterest(commandName);
-    commandInterest.setTag(make_shared<lp::IncomingFaceIdTag>(incomingFaceId));
-
-    manager->m_managedRib.m_onSendBatchFromQueue = bind(&RibManagerFixture::onSendBatchFromQueue,
-                                                        this, _1, parameters);
-
-    face->receive(commandInterest);
-    advanceClocks(time::milliseconds(1));
-  }
-
-  void
-  onSendBatchFromQueue(const RibUpdateBatch& batch, const ControlParameters parameters)
-  {
-    BOOST_REQUIRE(batch.begin() != batch.end());
-    RibUpdate update = *(batch.begin());
-
-    Rib::UpdateSuccessCallback managerCallback =
-      bind(&RibManager::onRibUpdateSuccess, manager, update);
-
-    Rib& rib = manager->m_managedRib;
-
-    // Simulate a successful response from NFD
-    FibUpdater& updater = manager->m_fibUpdater;
-    rib.onFibUpdateSuccess(batch, updater.m_inheritedRoutes, managerCallback);
-  }
-
-
-public:
-  shared_ptr<RibManager> manager;
-  shared_ptr<ndn::util::DummyClientFace> face;
-  ndn::KeyChain keyChain;
-
-  static const uint64_t DEFAULT_INCOMING_FACE_ID;
-
-  static const Name COMMAND_PREFIX;
-  static const name::Component ADD_NEXTHOP_VERB;
-  static const name::Component REMOVE_NEXTHOP_VERB;
-
-  static const Name REGISTER_COMMAND;
-  static const Name UNREGISTER_COMMAND;
+  bool isLocalhostConfigured;
+  bool isLocalhopConfigured;
 };
 
-const uint64_t RibManagerFixture::DEFAULT_INCOMING_FACE_ID = 25122;
-const Name RibManagerFixture::COMMAND_PREFIX("/localhost/nfd/rib");
-const name::Component RibManagerFixture::ADD_NEXTHOP_VERB("add-nexthop");
-const name::Component RibManagerFixture::REMOVE_NEXTHOP_VERB("remove-nexthop");
-const Name RibManagerFixture::REGISTER_COMMAND("/localhost/nfd/rib/register");
-const Name RibManagerFixture::UNREGISTER_COMMAND("/localhost/nfd/rib/unregister");
-
-class AuthorizedRibManager : public RibManagerFixture
+class RibManagerFixture : public ManagerCommonFixture
 {
 public:
-  AuthorizedRibManager()
+  explicit
+  RibManagerFixture(const ConfigurationStatus& status,
+                    bool shouldClearRib)
+    : m_commands(m_face->sentInterests)
+    , m_status(status)
+    , m_manager(m_dispatcher, *m_face, m_keyChain)
+    , m_rib(m_manager.m_rib)
   {
-    ConfigFile config;
-    manager->setConfigFile(config);
+    m_rib.m_onSendBatchFromQueue = bind(&RibManagerFixture::onSendBatchFromQueue, this, _1);
 
-    const std::string CONFIG_STRING =
-    "rib\n"
-    "{\n"
-    "  localhost_security\n"
+    const std::string prefix = "rib\n{\n";
+    const std::string suffix = "}";
+    const std::string localhostSection = "  localhost_security\n"
     "  {\n"
     "    trust-anchor\n"
     "    {\n"
     "      type any\n"
     "    }\n"
-    "  }"
-    "}";
+    "  }\n";
+    const std::string localhopSection = "  localhop_security\n"
+    "  {\n"
+    "    trust-anchor\n"
+    "    {\n"
+    "      type any\n"
+    "    }\n"
+    "  }\n";
 
-    config.parse(CONFIG_STRING, true, "test-rib");
+    std::string ribSection = "";
+    if (m_status.isLocalhostConfigured) {
+      ribSection += localhostSection;
+    }
+    if (m_status.isLocalhopConfigured) {
+      ribSection += localhopSection;
+    }
+    const std::string CONFIG_STR = prefix + ribSection + suffix;
+
+    ConfigFile config;
+    m_manager.setConfigFile(config);
+    config.parse(CONFIG_STR, true, "test-rib");
+
+    registerWithNfd();
+
+    if (shouldClearRib) {
+      clearRib();
+    }
+  }
+
+private:
+  void
+  registerWithNfd()
+  {
+    m_manager.registerWithNfd();
+    advanceClocks(time::milliseconds(1));
+
+    auto replyFibAddCommand = [this] (const Interest& interest) {
+      nfd::ControlParameters params(interest.getName().get(-5).blockFromValue());
+      params.setFaceId(1).setCost(0);
+      nfd::ControlResponse resp;
+
+      resp.setCode(200).setBody(params.wireEncode());
+      shared_ptr<Data> data = make_shared<Data>(interest.getName());
+      data->setContent(resp.wireEncode());
+
+      m_keyChain.sign(*data, ndn::security::SigningInfo(ndn::security::SigningInfo::SIGNER_TYPE_SHA256));
+
+      m_face->getIoService().post([this, data] { m_face->receive(*data); });
+    };
+
+    Name commandPrefix("/localhost/nfd/fib/add-nexthop");
+    for (auto&& command : m_commands) {
+      if (commandPrefix.isPrefixOf(command.getName())) {
+        replyFibAddCommand(command);
+        advanceClocks(time::milliseconds(1));
+      }
+    }
+
+    // clear commands and responses
+    m_responses.clear();
+    m_commands.clear();
+  }
+
+  void clearRib()
+  {
+    while (!m_rib.empty()) {
+      auto& name = m_rib.begin()->first;
+      auto& routes = m_rib.begin()->second->getRoutes();
+      while (!routes.empty()) {
+        m_rib.erase(name, *routes.begin());
+      }
+    }
+  }
+
+public:
+  ControlParameters
+  makeRegisterParameters(const Name& name, uint64_t id = 0,
+                         const time::milliseconds expiry = time::milliseconds::max())
+  {
+    return ControlParameters()
+      .setName(name)
+      .setFaceId(id)
+      .setOrigin(128)
+      .setCost(10)
+      .setFlags(0)
+      .setExpirationPeriod(expiry);
+  }
+
+  ControlParameters
+  makeUnregisterParameters(const Name& name, uint64_t id = 0)
+  {
+    return ControlParameters()
+      .setName(name)
+      .setFaceId(id)
+      .setOrigin(128);
+  }
+
+  void
+  onSendBatchFromQueue(const RibUpdateBatch& batch)
+  {
+    BOOST_ASSERT(batch.begin() != batch.end());
+    RibUpdate update = *(batch.begin());
+
+    // Simulate a successful response from NFD
+    FibUpdater& updater = m_manager.m_fibUpdater;
+    m_manager.m_rib.onFibUpdateSuccess(batch, updater.m_inheritedRoutes,
+                                              bind(&RibManager::onRibUpdateSuccess, &m_manager, update));
+  }
+
+public:
+  enum class CheckCommandResult {
+    OK,
+    OUT_OF_BOUNDARY,
+    WRONG_FORMAT,
+    WRONG_VERB,
+    WRONG_PARAMS_FORMAT,
+    WRONG_PARAMS_NAME,
+    WRONG_PARAMS_FACE
+   };
+
+  CheckCommandResult
+  checkCommand(size_t idx, const char* verbStr, ControlParameters expectedParams)
+  {
+    if (idx > m_commands.size()) {
+      return CheckCommandResult::OUT_OF_BOUNDARY;
+    }
+    const auto& name = m_commands[idx].getName();
+
+    if (!FIB_COMMAND_PREFIX.isPrefixOf(name) || FIB_COMMAND_PREFIX.size() >= name.size()) {
+      return CheckCommandResult::WRONG_FORMAT;
+    }
+    const auto& verb = name[FIB_COMMAND_PREFIX.size()];
+
+    Name::Component expectedVerb(verbStr);
+    if (verb != expectedVerb) {
+      return CheckCommandResult::WRONG_VERB;
+    }
+
+    ControlParameters parameters;
+    try {
+      Block rawParameters = name[FIB_COMMAND_PREFIX.size() + 1].blockFromValue();
+      parameters.wireDecode(rawParameters);
+    }
+    catch (...) {
+      return CheckCommandResult::WRONG_PARAMS_FORMAT;
+    }
+
+    if (parameters.getName() != expectedParams.getName()) {
+      return CheckCommandResult::WRONG_PARAMS_NAME;
+    }
+
+    if (parameters.getFaceId() != expectedParams.getFaceId()) {
+      return CheckCommandResult::WRONG_PARAMS_FACE;
+    }
+
+    return CheckCommandResult::OK;
+  }
+
+public:
+  std::vector<Interest>& m_commands;
+  ConfigurationStatus m_status;
+  static const Name FIB_COMMAND_PREFIX;
+
+protected:
+  RibManager m_manager;
+  Rib& m_rib;
+};
+
+const Name RibManagerFixture::FIB_COMMAND_PREFIX("/localhost/nfd/fib");
+
+std::ostream&
+operator<<(std::ostream& os, const RibManagerFixture::CheckCommandResult& result)
+{
+  switch (result) {
+  case RibManagerFixture::CheckCommandResult::OK:
+    os << "OK";
+    break;
+  case RibManagerFixture::CheckCommandResult::OUT_OF_BOUNDARY:
+    os << "OUT_OF_BOUNDARY";
+    break;
+  case RibManagerFixture::CheckCommandResult::WRONG_FORMAT:
+    os << "WRONG_FORMAT";
+    break;
+  case RibManagerFixture::CheckCommandResult::WRONG_VERB:
+    os << "WRONG_VERB";
+    break;
+  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_FORMAT:
+    os << "WRONG_COST";
+    break;
+  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_NAME:
+    os << "WRONG_PARAMS_NAME";
+    break;
+  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_FACE:
+    os << "WRONG_PARAMS_FACE";
+    break;
+  default:
+    break;
+  };
+
+  return os;
+}
+
+BOOST_AUTO_TEST_SUITE(Rib)
+BOOST_AUTO_TEST_SUITE(TestRibManager)
+
+class AddTopPrefixFixture : public RibManagerFixture
+{
+public:
+  AddTopPrefixFixture()
+    : RibManagerFixture({true, true}, false)
+  {
   }
 };
 
-typedef RibManagerFixture UnauthorizedRibManager;
-
-BOOST_FIXTURE_TEST_SUITE(TestRibManager, RibManagerFixture)
-
-BOOST_FIXTURE_TEST_CASE(ShortName, AuthorizedRibManager)
+BOOST_FIXTURE_TEST_CASE(AddTopPrefix, AddTopPrefixFixture)
 {
-  Name commandName("/localhost/nfd/rib");
-  ndn::nfd::ControlParameters parameters;
+  BOOST_CHECK_EQUAL(m_rib.size(), 2);
 
-  receiveCommandInterest(commandName, parameters);
-  // TODO verify error response
+  std::vector<Name> ribEntryNames;
+  for (auto&& entry : m_rib) {
+    ribEntryNames.push_back(entry.first);
+  }
+  BOOST_CHECK_EQUAL(ribEntryNames[0], "/localhop/nfd");
+  BOOST_CHECK_EQUAL(ribEntryNames[1], "/localhost/nfd");
 }
 
-BOOST_FIXTURE_TEST_CASE(Basic, AuthorizedRibManager)
+class UnauthorizedRibManagerFixture : public RibManagerFixture
 {
-  ControlParameters parameters;
-  parameters
-    .setName("/hello")
-    .setFaceId(1)
-    .setCost(10)
-    .setFlags(0)
-    .setOrigin(128)
-    .setExpirationPeriod(ndn::time::milliseconds::max());
+public:
+  UnauthorizedRibManagerFixture()
+    : RibManagerFixture({false, false}, true)
+  {
+  }
+};
 
-  receiveCommandInterest(REGISTER_COMMAND, parameters);
+class LocalhostAuthorizedRibManagerFixture : public RibManagerFixture
+{
+public:
+  LocalhostAuthorizedRibManagerFixture()
+    : RibManagerFixture({true, false}, true)
+  {
+  }
+};
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+class LocalhopAuthorizedRibManagerFixture : public RibManagerFixture
+{
+public:
+  LocalhopAuthorizedRibManagerFixture()
+    : RibManagerFixture({false, true}, true)
+  {
+  }
+};
+
+class AuthorizedRibManagerFixture : public RibManagerFixture
+{
+public:
+  AuthorizedRibManagerFixture()
+    : RibManagerFixture({true, true}, true)
+  {
+  }
+};
+
+typedef boost::mpl::vector<
+  UnauthorizedRibManagerFixture,
+  LocalhostAuthorizedRibManagerFixture,
+  LocalhopAuthorizedRibManagerFixture,
+  AuthorizedRibManagerFixture
+> AllFixtures;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(CommandAuthorization, T, AllFixtures, T)
+{
+  auto parameters  = this->makeRegisterParameters("/test-authorization", 9527);
+  auto commandHost = this->makeControlCommandRequest("/localhost/nfd/rib/register", parameters);
+  auto commandHop  = this->makeControlCommandRequest("/localhop/nfd/rib/register", parameters);
+  auto successResp = this->makeResponse(200, "Success", parameters);
+  auto failureResp = ControlResponse(403, "authorization rejected");
+
+  BOOST_CHECK_EQUAL(this->m_responses.size(), 0);
+  this->receiveInterest(commandHost);
+  this->receiveInterest(commandHop);
+
+  auto nExpectedResponses = this->m_status.isLocalhopConfigured ? 2 : 1;
+  auto expectedLocalhostResponse = this->m_status.isLocalhostConfigured ? successResp : failureResp;
+  auto expectedLocalhopResponse = this->m_status.isLocalhopConfigured ? successResp : failureResp;
+
+  BOOST_REQUIRE_EQUAL(this->m_responses.size(), nExpectedResponses);
+  BOOST_CHECK_EQUAL(this->checkResponse(0, commandHost->getName(), expectedLocalhostResponse),
+                    ManagerCommonFixture::CheckResponseResult::OK);
+  if (nExpectedResponses == 2) {
+    BOOST_CHECK_EQUAL(this->checkResponse(1, commandHop->getName(), expectedLocalhopResponse),
+                      ManagerCommonFixture::CheckResponseResult::OK);
+  }
 }
 
-BOOST_FIXTURE_TEST_CASE(Register, AuthorizedRibManager)
+BOOST_FIXTURE_TEST_SUITE(RegisterUnregister, LocalhostAuthorizedRibManagerFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
 {
-  ControlParameters parameters;
-  parameters
-    .setName("/hello")
-    .setFaceId(1)
-    .setCost(10)
-    .setFlags(0)
-    .setOrigin(128)
-    .setExpirationPeriod(ndn::time::milliseconds::max());
+  auto paramsRegister    = makeRegisterParameters("/test-register-unregister", 9527);
+  auto paramsUnregister  = makeUnregisterParameters("/test-register-unregister", 9527);
 
-  receiveCommandInterest(REGISTER_COMMAND, parameters);
+  auto setInFaceId = [] (shared_ptr<Interest> commandInterest) {
+    commandInterest->setTag(make_shared<lp::IncomingFaceIdTag>(1234));
+  };
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+  auto commandRegister   = makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister, setInFaceId);
+  auto commandUnregister = makeControlCommandRequest("/localhost/nfd/rib/unregister", paramsUnregister, setInFaceId);
 
-  Interest& request = face->sentInterests[0];
+  receiveInterest(commandRegister);
+  receiveInterest(commandUnregister);
 
-  ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandRegister->getName(), makeResponse(200, "Success", paramsRegister)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(1, commandUnregister->getName(), makeResponse(200, "Success", paramsUnregister)),
+                    CheckResponseResult::OK);
 
-  BOOST_CHECK_EQUAL(verb, ADD_NEXTHOP_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), parameters.getName());
-  BOOST_CHECK_EQUAL(extractedParameters.getFaceId(), parameters.getFaceId());
-  BOOST_CHECK_EQUAL(extractedParameters.getCost(), parameters.getCost());
+  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
+  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
+  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
 }
 
-BOOST_FIXTURE_TEST_CASE(Unregister, AuthorizedRibManager)
+BOOST_AUTO_TEST_CASE(SelfOperation)
 {
-  ControlParameters addParameters;
-  addParameters
-    .setName("/hello")
-    .setFaceId(1)
-    .setCost(10)
-    .setFlags(0)
-    .setOrigin(128)
-    .setExpirationPeriod(ndn::time::milliseconds::max());
+  auto paramsRegister = makeRegisterParameters("/test-self-register-unregister");
+  auto paramsUnregister = makeUnregisterParameters("/test-self-register-unregister");
+  BOOST_CHECK_EQUAL(paramsRegister.getFaceId(), 0);
+  BOOST_CHECK_EQUAL(paramsUnregister.getFaceId(), 0);
 
-  receiveCommandInterest(REGISTER_COMMAND, addParameters);
-  face->sentInterests.clear();
+  const uint64_t inFaceId = 9527;
+  auto setInFaceId = [&inFaceId] (shared_ptr<Interest> commandInterest) {
+    commandInterest->setTag(make_shared<lp::IncomingFaceIdTag>(inFaceId));
+  };
+  auto commandRegister   = makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister, setInFaceId);
+  auto commandUnregister = makeControlCommandRequest("/localhost/nfd/rib/unregister", paramsUnregister, setInFaceId);
 
-  ControlParameters removeParameters;
-  removeParameters
-    .setName("/hello")
-    .setFaceId(1)
-    .setOrigin(128);
+  receiveInterest(commandRegister);
+  receiveInterest(commandUnregister);
 
-  receiveCommandInterest(UNREGISTER_COMMAND, removeParameters);
+  paramsRegister.setFaceId(inFaceId);
+  paramsUnregister.setFaceId(inFaceId);
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandRegister->getName(), makeResponse(200, "Success", paramsRegister)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkResponse(1, commandUnregister->getName(), makeResponse(200, "Success", paramsUnregister)),
+                    CheckResponseResult::OK);
 
-  Interest& request = face->sentInterests[0];
-
-  ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REMOVE_NEXTHOP_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), removeParameters.getName());
-  BOOST_CHECK_EQUAL(extractedParameters.getFaceId(), removeParameters.getFaceId());
+  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
+  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
+  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
 }
 
-BOOST_FIXTURE_TEST_CASE(SelfRegister, AuthorizedRibManager)
+BOOST_AUTO_TEST_CASE(Expiration)
 {
-  ControlParameters parameters;
-  parameters
-    .setName("/hello");
+  auto paramsRegister = makeRegisterParameters("/test-expiry", 9527, time::milliseconds(50));
+  auto paramsUnregister = makeRegisterParameters("/test-expiry", 9527);
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister));
 
-  receiveCommandInterest(REGISTER_COMMAND, parameters, 10129);
+  advanceClocks(time::milliseconds(55));
+  BOOST_REQUIRE_EQUAL(m_commands.size(), 2); // the registered route has expired
+  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
+  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+  m_commands.clear();
+  paramsRegister.setExpirationPeriod(time::milliseconds(100));
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister));
 
-  Interest& request = face->sentInterests[0];
-
-  ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, ADD_NEXTHOP_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), parameters.getName());
-  BOOST_CHECK_EQUAL(extractedParameters.getFaceId(), 10129);
+  advanceClocks(time::milliseconds(55));
+  BOOST_REQUIRE_EQUAL(m_commands.size(), 1); // the registered route is still active
+  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
 }
 
-BOOST_FIXTURE_TEST_CASE(SelfUnregister, AuthorizedRibManager)
+BOOST_AUTO_TEST_SUITE_END() // RegisterUnregister
+
+// @todo Remove when ndn::nfd::RibEntry implements operator!=
+class RibEntry : public ndn::nfd::RibEntry
 {
-  ControlParameters addParameters;
-  addParameters
-    .setName("/hello")
-    .setFaceId(10129);
+public:
+  RibEntry() = default;
 
-  receiveCommandInterest(REGISTER_COMMAND, addParameters);
-  face->sentInterests.clear();
+  RibEntry(const ndn::nfd::RibEntry& entry)
+    : ndn::nfd::RibEntry(entry)
+  {
+  }
+};
 
-  ControlParameters removeParameters;
-  removeParameters
-    .setName("/hello");
+bool
+operator!=(const RibEntry& left, const RibEntry& right)
+{
+  if (left.getName() != right.getName()) {
+    return true;
+  }
 
-  receiveCommandInterest(UNREGISTER_COMMAND, removeParameters, 10129);
+  auto leftRoutes = left.getRoutes();
+  auto rightRoutes = right.getRoutes();
+  if (leftRoutes.size() != rightRoutes.size()) {
+    return true;
+  }
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+  for (auto&& route : leftRoutes) {
+    auto hitEntry =
+      std::find_if(rightRoutes.begin(), rightRoutes.end(), [&] (const ndn::nfd::Route& record) {
+          return route.getFaceId() == record.getFaceId() &&
+            route.getCost() == record.getCost() &&
+            route.getOrigin() == record.getOrigin() &&
+            route.getFlags() == record.getFlags() &&
+            route.getExpirationPeriod() == record.getExpirationPeriod();
+        });
 
-  Interest& request = face->sentInterests[0];
+    if (hitEntry == rightRoutes.end()) {
+      return true;
+    }
+  }
 
-  ControlParameters extractedParameters;
-  Name::Component verb;
-  extractParameters(request, verb, extractedParameters);
-
-  BOOST_CHECK_EQUAL(verb, REMOVE_NEXTHOP_VERB);
-  BOOST_CHECK_EQUAL(extractedParameters.getName(), removeParameters.getName());
-  BOOST_CHECK_EQUAL(extractedParameters.getFaceId(), 10129);
+  return false;
 }
 
-BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, UnauthorizedRibManager)
+BOOST_FIXTURE_TEST_CASE(RibDataset, UnauthorizedRibManagerFixture)
 {
-  ControlParameters parameters;
-  parameters
-    .setName("/hello")
-    .setFaceId(1)
-    .setCost(10)
-    .setFlags(0)
-    .setOrigin(128)
-    .setExpirationPeriod(ndn::time::milliseconds::max());
+  uint64_t faceId = 0;
+  auto generateRoute = [&faceId] () -> Route {
+    Route route;
+    route.faceId = ++faceId;
+    route.cost = ndn::random::generateWord64();
+    route.expires = time::steady_clock::TimePoint::max();
+    return route;
+  };
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
+  const size_t nEntries = 108;
+  std::set<Name> actualPrefixes;
+  for (size_t i = 0; i < nEntries; ++i) {
+    Name prefix = Name("/test-dataset").appendNumber(i);
+    actualPrefixes.insert(prefix);
+    m_rib.insert(prefix, generateRoute());
+    if (i & 0x1) {
+      m_rib.insert(prefix, generateRoute());
+      m_rib.insert(prefix, generateRoute());
+    }
+  }
 
-  receiveCommandInterest(REGISTER_COMMAND, parameters);
+  receiveInterest(makeInterest("/localhost/nfd/rib/list"));
 
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 0);
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+  std::vector<RibEntry> receivedRecords, expectedRecords;
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    RibEntry decodedEntry;
+    BOOST_REQUIRE_NO_THROW(decodedEntry.wireDecode(content.elements()[idx]));
+    receivedRecords.push_back(decodedEntry);
+
+    actualPrefixes.erase(decodedEntry.getName());
+
+    auto matchedEntryIt = m_rib.find(decodedEntry.getName());
+    BOOST_REQUIRE(matchedEntryIt != m_rib.end());
+
+    auto matchedEntry = matchedEntryIt->second;
+    BOOST_REQUIRE(matchedEntry != nullptr);
+
+    RibEntry record;
+    record.setName(matchedEntry->getName());
+    const auto& routes = matchedEntry->getRoutes();
+    for (auto&& route : routes) {
+      ndn::nfd::Route routeRecord;
+      routeRecord.setFaceId(route.faceId);
+      routeRecord.setOrigin(route.origin);
+      routeRecord.setFlags(route.flags);
+      routeRecord.setCost(route.cost);
+      record.addRoute(routeRecord);
+    }
+    expectedRecords.push_back(record);
+  }
+
+  BOOST_CHECK_EQUAL(actualPrefixes.size(), 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(receivedRecords.begin(), receivedRecords.end(),
+                                expectedRecords.begin(), expectedRecords.end());
 }
 
-BOOST_FIXTURE_TEST_CASE(RibStatusRequest, AuthorizedRibManager)
+BOOST_FIXTURE_TEST_SUITE(FaceMonitor, LocalhostAuthorizedRibManagerFixture)
+
+BOOST_AUTO_TEST_CASE(FetchActiveFacesEvent)
 {
-  Name prefix("/");
+  BOOST_CHECK_EQUAL(m_commands.size(), 0);
 
-  Route route;
-  route.faceId = 1;
-  route.origin = 128;
-  route.cost = 32;
-  route.flags = ndn::nfd::ROUTE_FLAG_CAPTURE;
-
-  manager->m_managedRib.insert(prefix, route);
-
-  face->receive(Interest("/localhost/nfd/rib/list"));
-  advanceClocks(time::milliseconds(1));
-
-  BOOST_REQUIRE_EQUAL(face->sentDatas.size(), 1);
-  RibStatusPublisherFixture::decodeRibEntryBlock(face->sentDatas[0], prefix, route);
+  advanceClocks(time::seconds(301)); // RibManager::ACTIVE_FACE_FETCH_INTERVAL = 300s
+  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
+  BOOST_CHECK_EQUAL(m_commands[0].getName(), "/localhost/nfd/faces/events");
+  BOOST_CHECK_EQUAL(m_commands[1].getName(), "/localhost/nfd/faces/list");
 }
 
-BOOST_FIXTURE_TEST_CASE(CancelExpirationEvent, AuthorizedRibManager)
+BOOST_AUTO_TEST_CASE(RemoveInvalidFaces)
 {
-  // Register route
-  ControlParameters addParameters;
-  addParameters
-    .setName("/expire")
-    .setFaceId(1)
-    .setCost(10)
-    .setFlags(0)
-    .setOrigin(128)
-    .setExpirationPeriod(ndn::time::milliseconds(500));
+  auto parameters1 = makeRegisterParameters("/test-remove-invalid-faces-1");
+  auto parameters2 = makeRegisterParameters("/test-remove-invalid-faces-2");
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", parameters1.setFaceId(1)));
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", parameters1.setFaceId(2)));
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", parameters2.setFaceId(2)));
+  BOOST_REQUIRE_EQUAL(m_rib.size(), 3);
 
-  receiveCommandInterest(REGISTER_COMMAND, addParameters);
-  face->sentInterests.clear();
-
-  // Unregister route
-  ControlParameters removeParameters;
-  removeParameters
-    .setName("/expire")
-    .setFaceId(1)
-    .setOrigin(128);
-
-  receiveCommandInterest(UNREGISTER_COMMAND, removeParameters);
-
-  // Reregister route
-  addParameters.setExpirationPeriod(ndn::time::milliseconds::max());
-  receiveCommandInterest(REGISTER_COMMAND, addParameters);
-
-  advanceClocks(time::milliseconds(100), time::seconds(1));
-
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 1);
-}
-
-BOOST_FIXTURE_TEST_CASE(RemoveInvalidFaces, AuthorizedRibManager)
-{
-  // Register valid face
-  ControlParameters validParameters;
-  validParameters
-    .setName("/test")
-    .setFaceId(1);
-
-  receiveCommandInterest(REGISTER_COMMAND, validParameters);
-
-  // Register invalid face
-  ControlParameters invalidParameters;
-  invalidParameters
-    .setName("/test")
-    .setFaceId(2);
-
-  receiveCommandInterest(REGISTER_COMMAND, invalidParameters);
-
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 2);
-
-  // Receive status with only faceId: 1
   ndn::nfd::FaceStatus status;
   status.setFaceId(1);
 
-  shared_ptr<Data> data = nfd::tests::makeData("/localhost/nfd/faces/list");
+  auto data = makeData("/localhost/nfd/faces/list");
   data->setContent(status.wireEncode());
 
-  shared_ptr<ndn::OBufferStream> buffer = make_shared<ndn::OBufferStream>();
+  auto buffer = make_shared<ndn::OBufferStream>();
   buffer->write(reinterpret_cast<const char*>(data->getContent().value()),
                 data->getContent().value_size());
 
-  manager->removeInvalidFaces(buffer);
+  m_manager.removeInvalidFaces(buffer);
+  advanceClocks(time::milliseconds(100));
+  BOOST_REQUIRE_EQUAL(m_rib.size(), 1);
 
-  // Run scheduler
-  advanceClocks(time::milliseconds(100), time::seconds(1));
-
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 1);
-
-  Rib::const_iterator it = manager->m_managedRib.find("/test");
-  BOOST_REQUIRE(it != manager->m_managedRib.end());
-
-  shared_ptr<RibEntry> entry = it->second;
-  BOOST_CHECK_EQUAL(entry->hasFaceId(1), true);
-  BOOST_CHECK_EQUAL(entry->hasFaceId(2), false);
+  auto it1 = m_rib.find("/test-remove-invalid-faces-1");
+  auto it2 = m_rib.find("/test-remove-invalid-faces-2");
+  BOOST_CHECK(it2 == m_rib.end());
+  BOOST_REQUIRE(it1 != m_rib.end());
+  BOOST_CHECK(it1->second->hasFaceId(1));
+  BOOST_CHECK(!it1->second->hasFaceId(2));
 }
 
-BOOST_FIXTURE_TEST_CASE(LocalHopInherit, AuthorizedRibManager)
+BOOST_AUTO_TEST_CASE(OnNotification)
 {
-  using nfd::rib::RibManager;
+  auto parameters1 = makeRegisterParameters("/test-face-event-notification-1", 1);
+  auto parameters2 = makeRegisterParameters("/test-face-event-notification-2", 1);
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", parameters1));
+  receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", parameters2));
+  BOOST_REQUIRE_EQUAL(m_rib.size(), 2);
 
-  // Simulate NFD response
-  ControlParameters result;
-  result.setFaceId(261);
+  auto makeNotification = [] (ndn::nfd::FaceEventKind eventKind, uint64_t faceId) -> ndn::nfd::FaceEventNotification {
+    ndn::nfd::FaceEventNotification notification;
+    notification.setKind(eventKind).setFaceId(faceId);
+    return notification;
+  };
 
-  manager->onNrdCommandPrefixAddNextHopSuccess(RibManager::REMOTE_COMMAND_PREFIX, result);
+  m_manager.onNotification(makeNotification(ndn::nfd::FaceEventKind::FACE_EVENT_DESTROYED, 1));
+  advanceClocks(time::milliseconds(100));
+  BOOST_CHECK_EQUAL(m_rib.size(), 0);
 
-  // Register route that localhop prefix should inherit
-  ControlParameters parameters;
-  parameters
-    .setName("/localhop/nfd")
-    .setFaceId(262)
-    .setCost(25)
-    .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
-
-  Name commandName("/localhost/nfd/rib/register");
-
-  receiveCommandInterest(commandName, parameters);
-
-  // REMOTE_COMMAND_PREFIX should have its original route and the inherited route
-  auto it = manager->m_managedRib.find(RibManager::REMOTE_COMMAND_PREFIX);
-
-  BOOST_REQUIRE(it != manager->m_managedRib.end());
-  const RibEntry::RouteList& inheritedRoutes = (*(it->second)).getInheritedRoutes();
-
-  BOOST_CHECK_EQUAL(inheritedRoutes.size(), 1);
-  auto routeIt = inheritedRoutes.begin();
-
-  BOOST_CHECK_EQUAL(routeIt->faceId, 262);
-  BOOST_CHECK_EQUAL(routeIt->cost, 25);
+  m_manager.onNotification(makeNotification(ndn::nfd::FaceEventKind::FACE_EVENT_CREATED, 2));
+  advanceClocks(time::milliseconds(100));
+  BOOST_CHECK_EQUAL(m_rib.size(), 0);
 }
 
-BOOST_FIXTURE_TEST_CASE(RouteExpiration, AuthorizedRibManager)
-{
-  // Register route
-  ControlParameters parameters;
-  parameters.setName("/expire")
-            .setExpirationPeriod(ndn::time::milliseconds(500));
+BOOST_AUTO_TEST_SUITE_END() // FaceMonitor
 
-  receiveCommandInterest(REGISTER_COMMAND, parameters);
-  face->sentInterests.clear();
-
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 1);
-
-  // Route should expire
-  advanceClocks(time::milliseconds(100), time::seconds(1));
-
-  BOOST_CHECK_EQUAL(manager->m_managedRib.size(), 0);
-}
-
-BOOST_FIXTURE_TEST_CASE(FaceDestroyEvent, AuthorizedRibManager)
-{
-  uint64_t faceToDestroy = 128;
-
-  // Register valid face
-  ControlParameters parameters;
-  parameters.setName("/test")
-            .setFaceId(faceToDestroy);
-
-  receiveCommandInterest(REGISTER_COMMAND, parameters);
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 1);
-
-  // Don't respond with a success message from the FIB
-  manager->m_managedRib.m_onSendBatchFromQueue = nullptr;
-
-  manager->onFaceDestroyedEvent(faceToDestroy);
-  BOOST_REQUIRE_EQUAL(manager->m_managedRib.size(), 0);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END() // TestRibManager
+BOOST_AUTO_TEST_SUITE_END() // Rib
 
 } // namespace tests
 } // namespace rib