rib: refactor RibManager to use ManagementDispatcher

Change-Id: I3f689de7d043e85531f0b3f4accf65345cde5d9e
refs: #2857
diff --git a/rib/nrd.cpp b/rib/nrd.cpp
index bcee83c..d723a3f 100644
--- a/rib/nrd.cpp
+++ b/rib/nrd.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,
@@ -63,10 +63,11 @@
 Nrd::initialize()
 {
   m_face.reset(new ndn::Face(getLocalNfdTransport(), getGlobalIoService(), m_keyChain));
+  m_dispatcher.reset(new ndn::mgmt::Dispatcher(*m_face, m_keyChain));
 
   initializeLogging();
 
-  m_ribManager.reset(new RibManager(*m_face, m_keyChain));
+  m_ribManager.reset(new RibManager(*m_dispatcher, *m_face, m_keyChain));
 
   ConfigFile config([] (const std::string& filename, const std::string& sectionName,
                         const ConfigSection& section, bool isDryRun) {
diff --git a/rib/nrd.hpp b/rib/nrd.hpp
index e861422..0d8743d 100644
--- a/rib/nrd.hpp
+++ b/rib/nrd.hpp
@@ -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,
@@ -32,6 +32,7 @@
 #include <ndn-cxx/face.hpp>
 #include <ndn-cxx/security/key-chain.hpp>
 #include <ndn-cxx/transport/transport.hpp>
+#include <ndn-cxx/mgmt/dispatcher.hpp>
 
 namespace nfd {
 namespace rib {
@@ -98,6 +99,7 @@
 
   ndn::KeyChain& m_keyChain;
   unique_ptr<ndn::Face> m_face;
+  unique_ptr<ndn::mgmt::Dispatcher> m_dispatcher;
   unique_ptr<RibManager> m_ribManager;
 };
 
diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp
index 03faf21..edd6c5d 100644
--- a/rib/rib-manager.cpp
+++ b/rib/rib-manager.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,
@@ -28,69 +28,41 @@
 #include "core/logger.hpp"
 #include "core/scheduler.hpp"
 #include <ndn-cxx/management/nfd-face-status.hpp>
+#include <ndn-cxx/management/nfd-rib-entry.hpp>
 
 namespace nfd {
 namespace rib {
 
 NFD_LOG_INIT("RibManager");
 
-const Name RibManager::COMMAND_PREFIX = "/localhost/nfd/rib";
-const Name RibManager::REMOTE_COMMAND_PREFIX = "/localhop/nfd/rib";
+const Name RibManager::LOCAL_HOST_TOP_PREFIX = "/localhost/nfd";
+const Name RibManager::LOCAL_HOP_TOP_PREFIX = "/localhop/nfd";
 const Name RibManager::FACES_LIST_DATASET_PREFIX = "/localhost/nfd/faces/list";
-
-const size_t RibManager::COMMAND_UNSIGNED_NCOMPS =
-  RibManager::COMMAND_PREFIX.size() +
-  1 + // verb
-  1;  // verb options
-
-const size_t RibManager::COMMAND_SIGNED_NCOMPS =
-  RibManager::COMMAND_UNSIGNED_NCOMPS +
-  4; // (timestamp, nonce, signed info tlv, signature tlv)
-
-const RibManager::SignedVerbAndProcessor RibManager::SIGNED_COMMAND_VERBS[] =
-  {
-    SignedVerbAndProcessor(
-                           Name::Component("register"),
-                           &RibManager::registerEntry
-                           ),
-
-    SignedVerbAndProcessor(
-                           Name::Component("unregister"),
-                           &RibManager::unregisterEntry
-                           ),
-  };
-
-const RibManager::UnsignedVerbAndProcessor RibManager::UNSIGNED_COMMAND_VERBS[] =
-  {
-    UnsignedVerbAndProcessor(
-                             Name::Component("list"),
-                             &RibManager::listEntries
-                             ),
-  };
-
-const Name RibManager::LIST_COMMAND_PREFIX("/localhost/nfd/rib/list");
-const size_t RibManager::LIST_COMMAND_NCOMPS = LIST_COMMAND_PREFIX.size();
-
 const time::seconds RibManager::ACTIVE_FACE_FETCH_INTERVAL = time::seconds(300);
 
-RibManager::RibManager(ndn::Face& face, ndn::KeyChain& keyChain)
-  : m_face(face)
+RibManager::RibManager(Dispatcher& dispatcher,
+                       ndn::Face& face,
+                       ndn::KeyChain& keyChain)
+  : ManagerBase(dispatcher, "rib")
+  , m_face(face)
   , m_keyChain(keyChain)
   , m_nfdController(m_face, m_keyChain)
+  , m_faceMonitor(m_face)
   , m_localhostValidator(m_face)
   , m_localhopValidator(m_face)
-  , m_faceMonitor(m_face)
   , m_isLocalhopEnabled(false)
-  , m_prefixPropagator(m_nfdController, m_keyChain, m_managedRib)
-  , m_ribStatusPublisher(m_managedRib, face, LIST_COMMAND_PREFIX, m_keyChain)
-  , m_fibUpdater(m_managedRib, m_nfdController)
-  , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
-                         SIGNED_COMMAND_VERBS +
-                         (sizeof(SIGNED_COMMAND_VERBS) / sizeof(SignedVerbAndProcessor)))
-  , m_unsignedVerbDispatch(UNSIGNED_COMMAND_VERBS,
-                           UNSIGNED_COMMAND_VERBS +
-                           (sizeof(UNSIGNED_COMMAND_VERBS) / sizeof(UnsignedVerbAndProcessor)))
+  , m_prefixPropagator(m_nfdController, m_keyChain, m_rib)
+  , m_fibUpdater(m_rib, m_nfdController)
+  , m_addTopPrefix([&dispatcher] (const Name& topPrefix) {
+      dispatcher.addTopPrefix(topPrefix, false);
+    })
 {
+  registerCommandHandler<ndn::nfd::RibRegisterCommand>("register",
+    bind(&RibManager::registerEntry, this, _2, _3, _4, _5));
+  registerCommandHandler<ndn::nfd::RibUnregisterCommand>("unregister",
+    bind(&RibManager::unregisterEntry, this, _2, _3, _4, _5));
+
+  registerStatusDatasetHandler("list", bind(&RibManager::listEntries, this, _1, _2, _3));
 }
 
 RibManager::~RibManager()
@@ -99,31 +71,12 @@
 }
 
 void
-RibManager::startListening(const Name& commandPrefix, const ndn::OnInterest& onRequest)
-{
-  NFD_LOG_INFO("Listening on: " << commandPrefix);
-
-  m_nfdController.start<ndn::nfd::FibAddNextHopCommand>(
-    ControlParameters()
-      .setName(commandPrefix)
-      .setFaceId(0),
-    bind(&RibManager::onNrdCommandPrefixAddNextHopSuccess, this, cref(commandPrefix), _1),
-    bind(&RibManager::onNrdCommandPrefixAddNextHopError, this, cref(commandPrefix), _2));
-
-  m_face.setInterestFilter(commandPrefix, onRequest);
-}
-
-void
 RibManager::registerWithNfd()
 {
-  //check whether the components of localhop and localhost prefixes are same
-  BOOST_ASSERT(COMMAND_PREFIX.size() == REMOTE_COMMAND_PREFIX.size());
-
-  this->startListening(COMMAND_PREFIX, bind(&RibManager::onLocalhostRequest, this, _2));
+  registerTopPrefix(LOCAL_HOST_TOP_PREFIX);
 
   if (m_isLocalhopEnabled) {
-    this->startListening(REMOTE_COMMAND_PREFIX,
-                         bind(&RibManager::onLocalhopRequest, this, _2));
+    registerTopPrefix(LOCAL_HOP_TOP_PREFIX);
   }
 
   NFD_LOG_INFO("Start monitoring face create/destroy events");
@@ -134,6 +87,16 @@
 }
 
 void
+RibManager::enableLocalControlHeader()
+{
+  m_nfdController.start<ndn::nfd::FaceEnableLocalControlCommand>(
+    ControlParameters()
+      .setLocalControlFeature(ndn::nfd::LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID),
+    bind(&RibManager::onControlHeaderSuccess, this),
+    bind(&RibManager::onControlHeaderError, this, _1, _2));
+}
+
+void
 RibManager::setConfigFile(ConfigFile& configFile)
 {
   configFile.addSectionHandler("rib",
@@ -141,6 +104,22 @@
 }
 
 void
+RibManager::onRibUpdateSuccess(const RibUpdate& update)
+{
+  NFD_LOG_DEBUG("RIB update succeeded for " << update);
+}
+
+void
+RibManager::onRibUpdateFailure(const RibUpdate& update, uint32_t code, const std::string& error)
+{
+  NFD_LOG_DEBUG("RIB update failed for " << update << " (code: " << code
+                                                   << ", error: " << error << ")");
+
+  // Since the FIB rejected the update, clean up invalid routes
+  scheduleActiveFaceFetch(time::seconds(1));
+}
+
+void
 RibManager::onConfig(const ConfigSection& configSection,
                      bool isDryRun,
                      const std::string& filename)
@@ -177,120 +156,29 @@
 }
 
 void
-RibManager::sendResponse(const Name& name,
-                         const ControlResponse& response)
+RibManager::registerTopPrefix(const Name& topPrefix)
 {
-  const Block& encodedControl = response.wireEncode();
+  // register entry to the FIB
+  m_nfdController.start<ndn::nfd::FibAddNextHopCommand>(
+     ControlParameters()
+       .setName(topPrefix)
+       .setFaceId(0),
+     bind(&RibManager::onNrdCommandPrefixAddNextHopSuccess, this, cref(topPrefix), _1),
+     bind(&RibManager::onNrdCommandPrefixAddNextHopError, this, cref(topPrefix), _2));
 
-  shared_ptr<Data> responseData = make_shared<Data>(name);
-  responseData->setContent(encodedControl);
-
-  m_keyChain.sign(*responseData);
-  m_face.put(*responseData);
+  // add top prefix to the dispatcher
+  m_addTopPrefix(topPrefix);
 }
 
 void
-RibManager::sendResponse(const Name& name,
-                         uint32_t code,
-                         const std::string& text)
+RibManager::registerEntry(const Name& topPrefix, const Interest& interest,
+                          ControlParameters parameters,
+                          const ndn::mgmt::CommandContinuation& done)
 {
-  ControlResponse response(code, text);
-  sendResponse(name, response);
-}
-
-void
-RibManager::onLocalhostRequest(const Interest& request)
-{
-  const Name& command = request.getName();
-  const Name::Component& verb = command.get(COMMAND_PREFIX.size());
-
-  UnsignedVerbDispatchTable::const_iterator unsignedVerbProcessor = m_unsignedVerbDispatch.find(verb);
-
-  if (unsignedVerbProcessor != m_unsignedVerbDispatch.end()) {
-    NFD_LOG_DEBUG("command result: processing unsigned verb: " << verb);
-    (unsignedVerbProcessor->second)(this, request);
-  }
-  else {
-    m_localhostValidator.validate(request,
-                                  bind(&RibManager::onCommandValidated, this, _1),
-                                  bind(&RibManager::onCommandValidationFailed, this, _1, _2));
-  }
-}
-
-void
-RibManager::onLocalhopRequest(const Interest& request)
-{
-  m_localhopValidator.validate(request,
-                               bind(&RibManager::onCommandValidated, this, _1),
-                               bind(&RibManager::onCommandValidationFailed, this, _1, _2));
-}
-
-void
-RibManager::onCommandValidated(const shared_ptr<const Interest>& request)
-{
-  // REMOTE_COMMAND_PREFIX number of componenets are same as
-  // NRD_COMMAND_PREFIX's so no extra checks are required.
-
-  const Name& command = request->getName();
-  const Name::Component& verb = command[COMMAND_PREFIX.size()];
-  const Name::Component& parameterComponent = command[COMMAND_PREFIX.size() + 1];
-
-  SignedVerbDispatchTable::const_iterator verbProcessor = m_signedVerbDispatch.find(verb);
-
-  if (verbProcessor != m_signedVerbDispatch.end()) {
-
-    ControlParameters parameters;
-    if (!extractParameters(parameterComponent, parameters)) {
-      NFD_LOG_DEBUG("command result: malformed verb: " << verb);
-
-      if (static_cast<bool>(request)) {
-        sendResponse(command, 400, "Malformed command");
-      }
-
-      return;
-    }
-
-    NFD_LOG_DEBUG("command result: processing verb: " << verb);
-    (verbProcessor->second)(this, request, parameters);
-  }
-  else {
-    NFD_LOG_DEBUG("Unsupported command: " << verb);
-
-    if (static_cast<bool>(request)) {
-      sendResponse(request->getName(), 501, "Unsupported command");
-    }
-  }
-}
-
-void
-RibManager::registerEntry(const shared_ptr<const Interest>& request,
-                          ControlParameters& parameters)
-{
-  ndn::nfd::RibRegisterCommand command;
-
-  if (!validateParameters(command, parameters)) {
-    NFD_LOG_DEBUG("register result: FAIL reason: malformed");
-
-    if (static_cast<bool>(request)) {
-      sendResponse(request->getName(), 400, "Malformed command");
-    }
-
-    return;
-  }
-
-  bool isSelfRegistration = (!parameters.hasFaceId() || parameters.getFaceId() == 0);
-  if (isSelfRegistration) {
-    shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = request->getTag<lp::IncomingFaceIdTag>();
-    if (incomingFaceIdTag == nullptr) {
-      sendResponse(request->getName(), 503,
-                   "requested self-registration, but IncomingFaceId is unavailable");
-      return;
-    }
-    parameters.setFaceId(*incomingFaceIdTag);
-  }
+  setFaceForSelfRegistration(interest, parameters);
 
   // Respond since command is valid and authorized
-  sendSuccessResponse(request, parameters);
+  done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
 
   Route route;
   route.faceId = parameters.getFaceId();
@@ -305,7 +193,7 @@
 
     // Schedule a new event, the old one will be cancelled during rib insertion.
     scheduler::EventId eventId = scheduler::schedule(parameters.getExpirationPeriod(),
-      bind(&Rib::onRouteExpiration, &m_managedRib, parameters.getName(), route));
+      bind(&Rib::onRouteExpiration, &m_rib, parameters.getName(), route));
 
     NFD_LOG_TRACE("Scheduled unregistration at: " << route.expires <<
                   " with EventId: " << eventId);
@@ -326,7 +214,7 @@
         .setName(parameters.getName())
         .setRoute(route);
 
-  m_managedRib.beginApplyUpdate(update,
+  m_rib.beginApplyUpdate(update,
                                 bind(&RibManager::onRibUpdateSuccess, this, update),
                                 bind(&RibManager::onRibUpdateFailure, this, update, _1, _2));
 
@@ -334,47 +222,14 @@
 }
 
 void
-RibManager::unregisterEntry(const shared_ptr<const Interest>& request,
-                            ControlParameters& params)
+RibManager::unregisterEntry(const Name& topPrefix, const Interest& interest,
+                            ControlParameters parameters,
+                            const ndn::mgmt::CommandContinuation& done)
 {
-  ndn::nfd::RibUnregisterCommand command;
-
-  // Passing all parameters gives error in validation,
-  // so passing only the required arguments.
-  ControlParameters parameters;
-  parameters.setName(params.getName());
-
-  if (params.hasFaceId()) {
-    parameters.setFaceId(params.getFaceId());
-  }
-
-  if (params.hasOrigin()) {
-    parameters.setOrigin(params.getOrigin());
-  }
-
-  if (!validateParameters(command, parameters)) {
-    NFD_LOG_DEBUG("unregister result: FAIL reason: malformed");
-
-    if (static_cast<bool>(request)) {
-      sendResponse(request->getName(), 400, "Malformed command");
-    }
-
-    return;
-  }
-
-  bool isSelfRegistration = (!parameters.hasFaceId() || parameters.getFaceId() == 0);
-  if (isSelfRegistration) {
-    shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = request->getTag<lp::IncomingFaceIdTag>();
-    if (incomingFaceIdTag == nullptr) {
-      sendResponse(request->getName(), 503,
-                   "requested self-registration, but IncomingFaceId is unavailable");
-      return;
-    }
-    parameters.setFaceId(*incomingFaceIdTag);
-  }
+  setFaceForSelfRegistration(interest, parameters);
 
   // Respond since command is valid and authorized
-  sendSuccessResponse(request, parameters);
+  done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
 
   Route route;
   route.faceId = parameters.getFaceId();
@@ -388,272 +243,72 @@
         .setName(parameters.getName())
         .setRoute(route);
 
-  m_managedRib.beginApplyUpdate(update,
+  m_rib.beginApplyUpdate(update,
                                 bind(&RibManager::onRibUpdateSuccess, this, update),
                                 bind(&RibManager::onRibUpdateFailure, this, update, _1, _2));
 }
 
 void
-RibManager::onCommandValidationFailed(const shared_ptr<const Interest>& request,
-                                      const std::string& failureInfo)
+RibManager::listEntries(const Name& topPrefix, const Interest& interest,
+                        ndn::mgmt::StatusDatasetContext& context)
 {
-  NFD_LOG_DEBUG("RibRequestValidationFailed: " << failureInfo);
+  for (auto&& ribTableEntry : m_rib) {
+    const auto& ribEntry = *ribTableEntry.second;
+    ndn::nfd::RibEntry record;
 
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), 403, failureInfo);
-  }
-}
+    for (auto&& route : ribEntry) {
+      ndn::nfd::Route routeElement;
+      routeElement.setFaceId(route.faceId)
+              .setOrigin(route.origin)
+              .setCost(route.cost)
+              .setFlags(route.flags);
 
+      if (route.expires < time::steady_clock::TimePoint::max()) {
+        routeElement.setExpirationPeriod(time::duration_cast<time::milliseconds>(
+          route.expires - time::steady_clock::now()));
+      }
 
-bool
-RibManager::extractParameters(const Name::Component& parameterComponent,
-                              ControlParameters& extractedParameters)
-{
-  try {
-    Block rawParameters = parameterComponent.blockFromValue();
-    extractedParameters.wireDecode(rawParameters);
-  }
-  catch (const tlv::Error&) {
-    return false;
+      record.addRoute(routeElement);
+    }
+
+    record.setName(ribEntry.getName());
+    context.append(record.wireEncode());
   }
 
-  NFD_LOG_DEBUG("Parameters parsed OK");
-  return true;
-}
-
-bool
-RibManager::validateParameters(const ControlCommand& command,
-                               ControlParameters& parameters)
-{
-  try {
-    command.validateRequest(parameters);
-  }
-  catch (const ControlCommand::ArgumentError&) {
-    return false;
-  }
-
-  command.applyDefaultsToRequest(parameters);
-
-  return true;
+  context.end();
 }
 
 void
-RibManager::onCommandError(uint32_t code, const std::string& error,
-                           const shared_ptr<const Interest>& request,
-                           const Route& route)
+RibManager::setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters)
 {
-  NFD_LOG_ERROR("NFD returned an error: " << error << " (code: " << code << ")");
-
-  ControlResponse response;
-
-  if (code == 404) {
-    response.setCode(code);
-    response.setText(error);
-  }
-  else {
-    response.setCode(533);
-    std::ostringstream os;
-    os << "Failure to update NFD " << "(NFD Error: " << code << " " << error << ")";
-    response.setText(os.str());
-  }
-
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), response);
+  bool isSelfRegistration = (parameters.getFaceId() == 0);
+  if (isSelfRegistration) {
+    shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = request.getTag<lp::IncomingFaceIdTag>();
+    // NDNLPv2 says "application MUST be prepared to receive a packet without IncomingFaceId field",
+    // but it's fine to assert IncomingFaceId is available, because InternalFace lives inside NFD
+    // and is initialized synchronously with IncomingFaceId field enabled.
+    BOOST_ASSERT(incomingFaceIdTag != nullptr);
+    parameters.setFaceId(*incomingFaceIdTag);
   }
 }
 
 void
-RibManager::onRegSuccess(const shared_ptr<const Interest>& request,
-                         const ControlParameters& parameters,
-                         const Route& route)
+RibManager::authorize(const Name& prefix, const Interest& interest,
+                      const ndn::mgmt::ControlParameters* params,
+                      ndn::mgmt::AcceptContinuation accept,
+                      ndn::mgmt::RejectContinuation reject)
 {
-  ControlResponse response;
+  BOOST_ASSERT(params != nullptr);
+  BOOST_ASSERT(typeid(*params) == typeid(ndn::nfd::ControlParameters));
+  BOOST_ASSERT(prefix == LOCAL_HOST_TOP_PREFIX || prefix == LOCAL_HOP_TOP_PREFIX);
 
-  response.setCode(200);
-  response.setText("Success");
-  response.setBody(parameters.wireEncode());
+  auto& validator = [this, &prefix] () -> ndn::ValidatorConfig & {
+    return prefix == LOCAL_HOST_TOP_PREFIX ? m_localhostValidator : m_localhopValidator;
+  }();
 
-  NFD_LOG_TRACE("onRegSuccess: registered " << route);
-
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), response);
-  }
-}
-
-
-void
-RibManager::onUnRegSuccess(const shared_ptr<const Interest>& request,
-                           const ControlParameters& parameters,
-                           const Route& route)
-{
-  ControlResponse response;
-
-  response.setCode(200);
-  response.setText("Success");
-  response.setBody(parameters.wireEncode());
-
-  NFD_LOG_TRACE("onUnRegSuccess: unregistered " << route);
-
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), response);
-  }
-}
-
-void
-RibManager::sendSuccessResponse(const shared_ptr<const Interest>& request,
-                                const ControlParameters& parameters)
-{
-  if (!static_cast<bool>(request)) {
-    return;
-  }
-
-  ControlResponse response;
-
-  response.setCode(200);
-  response.setText("Success");
-  response.setBody(parameters.wireEncode());
-
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), response);
-  }
-}
-
-void
-RibManager::sendErrorResponse(uint32_t code, const std::string& error,
-                              const shared_ptr<const Interest>& request)
-{
-  NFD_LOG_ERROR("NFD returned an error: " << error << " (code: " << code << ")");
-
-  if (!static_cast<bool>(request)) {
-    return;
-  }
-
-  ControlResponse response;
-
-  if (code == 404) {
-    response.setCode(code);
-    response.setText(error);
-  }
-  else {
-    response.setCode(533);
-    std::ostringstream os;
-    os << "Failure to update NFD " << "(NFD Error: " << code << " " << error << ")";
-    response.setText(os.str());
-  }
-
-  if (static_cast<bool>(request)) {
-    sendResponse(request->getName(), response);
-  }
-}
-
-void
-RibManager::onRibUpdateSuccess(const RibUpdate& update)
-{
-  NFD_LOG_DEBUG("RIB update succeeded for " << update);
-}
-
-void
-RibManager::onRibUpdateFailure(const RibUpdate& update, uint32_t code, const std::string& error)
-{
-  NFD_LOG_DEBUG("RIB update failed for " << update << " (code: " << code
-                                                   << ", error: " << error << ")");
-
-  // Since the FIB rejected the update, clean up invalid routes
-  scheduleActiveFaceFetch(time::seconds(1));
-}
-
-void
-RibManager::onNrdCommandPrefixAddNextHopSuccess(const Name& prefix,
-                                                const ndn::nfd::ControlParameters& result)
-{
-  NFD_LOG_DEBUG("Successfully registered " + prefix.toUri() + " with NFD");
-
-  // Routes must be inserted into the RIB so route flags can be applied
-  Route route;
-  route.faceId = result.getFaceId();
-  route.origin = ndn::nfd::ROUTE_ORIGIN_APP;
-  route.expires = time::steady_clock::TimePoint::max();
-  route.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
-
-  m_managedRib.insert(prefix, route);
-
-  m_registeredFaces.insert(route.faceId);
-}
-
-void
-RibManager::onNrdCommandPrefixAddNextHopError(const Name& name, const std::string& msg)
-{
-  BOOST_THROW_EXCEPTION(Error("Error in setting interest filter (" + name.toUri() + "): " + msg));
-}
-
-void
-RibManager::onControlHeaderSuccess()
-{
-  NFD_LOG_DEBUG("Local control header enabled");
-}
-
-void
-RibManager::onControlHeaderError(uint32_t code, const std::string& reason)
-{
-  std::ostringstream os;
-  os << "Couldn't enable local control header "
-     << "(code: " << code << ", info: " << reason << ")";
-  BOOST_THROW_EXCEPTION(Error(os.str()));
-}
-
-void
-RibManager::enableLocalControlHeader()
-{
-  m_nfdController.start<ndn::nfd::FaceEnableLocalControlCommand>(
-    ControlParameters()
-      .setLocalControlFeature(ndn::nfd::LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID),
-    bind(&RibManager::onControlHeaderSuccess, this),
-    bind(&RibManager::onControlHeaderError, this, _1, _2));
-}
-
-void
-RibManager::onNotification(const FaceEventNotification& notification)
-{
-  NFD_LOG_TRACE("onNotification: " << notification);
-
-  if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED) {
-    NFD_LOG_DEBUG("Received notification for destroyed faceId: " << notification.getFaceId());
-
-    scheduler::schedule(time::seconds(0),
-                        bind(&RibManager::onFaceDestroyedEvent, this, notification.getFaceId()));
-  }
-}
-
-void
-RibManager::onFaceDestroyedEvent(uint64_t faceId)
-{
-  m_managedRib.beginRemoveFace(faceId);
-  m_registeredFaces.erase(faceId);
-}
-
-void
-RibManager::listEntries(const Interest& request)
-{
-  const Name& command = request.getName();
-  const size_t commandNComps = command.size();
-
-  if (commandNComps < LIST_COMMAND_NCOMPS || !LIST_COMMAND_PREFIX.isPrefixOf(command)) {
-    NFD_LOG_DEBUG("command result: malformed");
-
-    sendResponse(command, 400, "Malformed command");
-    return;
-  }
-
-  m_ribStatusPublisher.publish();
-}
-
-void
-RibManager::scheduleActiveFaceFetch(const time::seconds& timeToWait)
-{
-  scheduler::cancel(m_activeFaceFetchEvent);
-
-  m_activeFaceFetchEvent = scheduler::schedule(timeToWait,
-                                               bind(&RibManager::fetchActiveFaces, this));
+  validator.validate(interest,
+                     bind([&interest, this, accept] { extractRequester(interest, accept); }),
+                     bind([reject] { reject(ndn::mgmt::RejectReply::STATUS403); }));
 }
 
 void
@@ -693,6 +348,29 @@
 }
 
 void
+RibManager::onFetchFaceStatusTimeout()
+{
+  std::cerr << "Face Status Dataset request timed out" << std::endl;
+  scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
+}
+
+void
+RibManager::onFaceDestroyedEvent(uint64_t faceId)
+{
+  m_rib.beginRemoveFace(faceId);
+  m_registeredFaces.erase(faceId);
+}
+
+void
+RibManager::scheduleActiveFaceFetch(const time::seconds& timeToWait)
+{
+  scheduler::cancel(m_activeFaceFetchEvent);
+
+  m_activeFaceFetchEvent = scheduler::schedule(timeToWait,
+                                               bind(&RibManager::fetchActiveFaces, this));
+}
+
+void
 RibManager::removeInvalidFaces(shared_ptr<ndn::OBufferStream> buffer)
 {
   NFD_LOG_DEBUG("Checking for invalid face registrations");
@@ -719,7 +397,7 @@
 
   // Look for face IDs that were registered but not active to find missed
   // face destroyed events
-  for (uint64_t faceId : m_registeredFaces) {
+  for (auto&& faceId : m_registeredFaces) {
     if (activeFaces.find(faceId) == activeFaces.end()) {
       NFD_LOG_DEBUG("Removing invalid face ID: " << faceId);
 
@@ -733,10 +411,55 @@
 }
 
 void
-RibManager::onFetchFaceStatusTimeout()
+RibManager::onNotification(const FaceEventNotification& notification)
 {
-  std::cerr << "Face Status Dataset request timed out" << std::endl;
-  scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
+  NFD_LOG_TRACE("onNotification: " << notification);
+
+  if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED) {
+    NFD_LOG_DEBUG("Received notification for destroyed faceId: " << notification.getFaceId());
+
+    scheduler::schedule(time::seconds(0),
+                        bind(&RibManager::onFaceDestroyedEvent, this, notification.getFaceId()));
+  }
+}
+
+void
+RibManager::onNrdCommandPrefixAddNextHopSuccess(const Name& prefix,
+                                                const ndn::nfd::ControlParameters& result)
+{
+  NFD_LOG_DEBUG("Successfully registered " + prefix.toUri() + " with NFD");
+
+  // Routes must be inserted into the RIB so route flags can be applied
+  Route route;
+  route.faceId = result.getFaceId();
+  route.origin = ndn::nfd::ROUTE_ORIGIN_APP;
+  route.expires = time::steady_clock::TimePoint::max();
+  route.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
+
+  m_rib.insert(prefix, route);
+
+  m_registeredFaces.insert(route.faceId);
+}
+
+void
+RibManager::onNrdCommandPrefixAddNextHopError(const Name& name, const std::string& msg)
+{
+  BOOST_THROW_EXCEPTION(Error("Error in setting interest filter (" + name.toUri() + "): " + msg));
+}
+
+void
+RibManager::onControlHeaderSuccess()
+{
+  NFD_LOG_DEBUG("Local control header enabled");
+}
+
+void
+RibManager::onControlHeaderError(uint32_t code, const std::string& reason)
+{
+  std::ostringstream os;
+  os << "Couldn't enable local control header "
+     << "(code: " << code << ", info: " << reason << ")";
+  BOOST_THROW_EXCEPTION(Error(os.str()));
 }
 
 } // namespace rib
diff --git a/rib/rib-manager.hpp b/rib/rib-manager.hpp
index d7c6819..0e5d68d 100644
--- a/rib/rib-manager.hpp
+++ b/rib/rib-manager.hpp
@@ -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,
@@ -28,6 +28,7 @@
 
 #include "rib.hpp"
 #include "core/config-file.hpp"
+#include "core/manager-base.hpp"
 #include "rib-status-publisher.hpp"
 #include "auto-prefix-propagator.hpp"
 #include "fib-updater.hpp"
@@ -48,7 +49,7 @@
 
 using ndn::nfd::FaceEventNotification;
 
-class RibManager : noncopyable
+class RibManager : public nfd::ManagerBase
 {
 public:
   class Error : public std::runtime_error
@@ -61,7 +62,8 @@
     }
   };
 
-  RibManager(ndn::Face& face, ndn::KeyChain& keyChain);
+public:
+  RibManager(Dispatcher& dispatcher, ndn::Face& face, ndn::KeyChain& keyChain);
 
   ~RibManager();
 
@@ -80,107 +82,55 @@
   void
   onRibUpdateFailure(const RibUpdate& update, uint32_t code, const std::string& error);
 
-private:
+private: // initialization helpers
   void
-  onConfig(const ConfigSection& configSection,
-           bool isDryRun,
-           const std::string& filename);
+  onConfig(const ConfigSection& configSection, bool isDryRun, const std::string& filename);
 
   void
-  startListening(const Name& commandPrefix, const ndn::OnInterest& onRequest);
+  registerTopPrefix(const Name& topPrefix);
+
+private: // ControlCommand and StatusDataset
+  void
+  registerEntry(const Name& topPrefix, const Interest& interest,
+                ControlParameters parameters,
+                const ndn::mgmt::CommandContinuation& done);
 
   void
-  onLocalhopRequest(const Interest& request);
+  unregisterEntry(const Name& topPrefix, const Interest& interest,
+                  ControlParameters parameters,
+                  const ndn::mgmt::CommandContinuation& done);
 
   void
-  onLocalhostRequest(const Interest& request);
+  listEntries(const Name& topPrefix, const Interest& interest,
+              ndn::mgmt::StatusDatasetContext& context);
 
   void
-  sendResponse(const Name& name,
-               const ControlResponse& response);
+  setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters);
 
-  void
-  sendResponse(const Name& name,
-               uint32_t code,
-               const std::string& text);
+private: // command validation
+  /**
+   * @brief validate a request for ControlCommand.
+   *
+   * This is called by the dispatcher.
+   *
+   * @pre params != null
+   * @pre typeid(*params) == typeid(ndn::nfd::ControlParameters)
+   *
+   * @param prefix the top prefix
+   * @param interest a request for ControlCommand
+   * @param params the parameters for ControlCommand
+   * @param accept callback of successful validation, take the requester string as a argument
+   * @param reject callback of failure in validation, take the action code as a argument
+   *
+   * use m_localhostValidator / m_localhopValidator to validate commands according to @p prefix.
+   */
+  virtual void
+  authorize(const Name& prefix, const Interest& interest,
+            const ndn::mgmt::ControlParameters* params,
+            ndn::mgmt::AcceptContinuation accept,
+            ndn::mgmt::RejectContinuation reject) override;
 
-  void
-  sendSuccessResponse(const shared_ptr<const Interest>& request,
-                      const ControlParameters& parameters);
-
-  void
-  sendErrorResponse(uint32_t code, const std::string& error,
-                    const shared_ptr<const Interest>& request);
-
-  void
-  registerEntry(const shared_ptr<const Interest>& request,
-                ControlParameters& parameters);
-
-  void
-  unregisterEntry(const shared_ptr<const Interest>& request,
-                  ControlParameters& parameters);
-
-private:
-  void
-  onCommandValidated(const shared_ptr<const Interest>& request);
-
-  void
-  onCommandValidationFailed(const shared_ptr<const Interest>& request,
-                            const std::string& failureInfo);
-
-
-  void
-  onCommandError(uint32_t code, const std::string& error,
-                 const shared_ptr<const Interest>& request,
-                 const Route& route);
-
-  void
-  onRegSuccess(const shared_ptr<const Interest>& request,
-               const ControlParameters& parameters,
-               const Route& route);
-
-  void
-  onUnRegSuccess(const shared_ptr<const Interest>& request,
-                 const ControlParameters& parameters,
-                 const Route& route);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  void
-  onNrdCommandPrefixAddNextHopSuccess(const Name& prefix,
-                                      const ndn::nfd::ControlParameters& result);
-
-private:
-  void
-  onNrdCommandPrefixAddNextHopError(const Name& name, const std::string& msg);
-
-  void
-  onControlHeaderSuccess();
-
-  void
-  onControlHeaderError(uint32_t code, const std::string& reason);
-
-  static bool
-  extractParameters(const Name::Component& parameterComponent,
-                    ControlParameters& extractedParameters);
-
-  bool
-  validateParameters(const ControlCommand& command,
-                     ControlParameters& parameters);
-
-  void
-  onNotification(const FaceEventNotification& notification);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  void
-  onFaceDestroyedEvent(uint64_t faceId);
-
-private:
-  void
-  listEntries(const Interest& request);
-
-  void
-  scheduleActiveFaceFetch(const time::seconds& timeToWait);
-
+private: // Face monitor
   void
   fetchActiveFaces();
 
@@ -190,70 +140,61 @@
   void
   onFetchFaceStatusTimeout();
 
+  void
+  onFaceDestroyedEvent(uint64_t faceId);
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  /** \param buffer Face dataset contents
+  void
+  scheduleActiveFaceFetch(const time::seconds& timeToWait);
+
+  /**
+   * @brief remove invalid faces
+   *
+   * @param buffer Face dataset contents
   */
   void
   removeInvalidFaces(shared_ptr<ndn::OBufferStream> buffer);
 
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  Rib m_managedRib;
+  /**
+   * @brief response to face events
+   *
+   * @param notification
+   */
+  void
+  onNotification(const FaceEventNotification& notification);
+
+private:
+  void
+  onNrdCommandPrefixAddNextHopSuccess(const Name& prefix,
+                                      const ndn::nfd::ControlParameters& result);
+
+  void
+  onNrdCommandPrefixAddNextHopError(const Name& name, const std::string& msg);
+
+  void
+  onControlHeaderSuccess();
+
+  void
+  onControlHeaderError(uint32_t code, const std::string& reason);
 
 private:
   ndn::Face& m_face;
   ndn::KeyChain& m_keyChain;
   ndn::nfd::Controller m_nfdController;
+  ndn::nfd::FaceMonitor m_faceMonitor;
   ndn::ValidatorConfig m_localhostValidator;
   ndn::ValidatorConfig m_localhopValidator;
-  ndn::nfd::FaceMonitor m_faceMonitor;
   bool m_isLocalhopEnabled;
   AutoPrefixPropagator m_prefixPropagator;
 
-  RibStatusPublisher m_ribStatusPublisher;
-
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  Rib m_rib;
   FibUpdater m_fibUpdater;
 
 private:
-  typedef function<void(RibManager*,
-                        const shared_ptr<const Interest>& request,
-                        ControlParameters& parameters)> SignedVerbProcessor;
-
-  typedef std::map<name::Component, SignedVerbProcessor> SignedVerbDispatchTable;
-
-  typedef std::pair<name::Component, SignedVerbProcessor> SignedVerbAndProcessor;
-
-
-  const SignedVerbDispatchTable m_signedVerbDispatch;
-
-  static const Name COMMAND_PREFIX; // /localhost/nrd
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  static const Name REMOTE_COMMAND_PREFIX; // /localhop/nrd
-
-private:
-  // number of components in an invalid, but not malformed, unsigned command.
-  // (/localhost/nrd + verb + options) = 4
-  static const size_t COMMAND_UNSIGNED_NCOMPS;
-
-  // number of components in a valid signed Interest.
-  // 8 with signed Interest support.
-  static const size_t COMMAND_SIGNED_NCOMPS;
-
-  static const SignedVerbAndProcessor SIGNED_COMMAND_VERBS[];
-
-  typedef function<void(RibManager*, const Interest&)> UnsignedVerbProcessor;
-  typedef std::map<Name::Component, UnsignedVerbProcessor> UnsignedVerbDispatchTable;
-  typedef std::pair<Name::Component, UnsignedVerbProcessor> UnsignedVerbAndProcessor;
-
-  const UnsignedVerbDispatchTable m_unsignedVerbDispatch;
-  static const UnsignedVerbAndProcessor UNSIGNED_COMMAND_VERBS[];
-
-  static const Name LIST_COMMAND_PREFIX;
-  static const size_t LIST_COMMAND_NCOMPS;
-
+  static const Name LOCAL_HOST_TOP_PREFIX;
+  static const Name LOCAL_HOP_TOP_PREFIX;
   static const Name FACES_LIST_DATASET_PREFIX;
-
   static const time::seconds ACTIVE_FACE_FETCH_INTERVAL;
   scheduler::EventId m_activeFaceFetchEvent;
 
@@ -261,6 +202,8 @@
   /** \brief contains FaceIds with one or more Routes in the RIB
   */
   FaceIdSet m_registeredFaces;
+
+  std::function<void(const Name& topPrefix)> m_addTopPrefix;
 };
 
 } // namespace rib
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