rib: Perform FIB updates before modifying RIB

refs: #1941

Change-Id: I1457f71ddd1c120daae48308d5cc02a7c0ecf93d
diff --git a/rib/fib-update.cpp b/rib/fib-update.cpp
index cda4f14..25e2a62 100644
--- a/rib/fib-update.cpp
+++ b/rib/fib-update.cpp
@@ -1,12 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014,  Regents of the University of California,
- *                      Arizona Board of Regents,
- *                      Colorado State University,
- *                      University Pierre & Marie Curie, Sorbonne University,
- *                      Washington University in St. Louis,
- *                      Beijing Institute of Technology,
- *                      The University of Memphis
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
  *
  * This file is part of NFD (Named Data Networking Forwarding Daemon).
  * See AUTHORS.md for complete list of NFD authors and contributors.
@@ -28,27 +28,27 @@
 namespace nfd {
 namespace rib {
 
-shared_ptr<FibUpdate>
+FibUpdate
 FibUpdate::createAddUpdate(const Name& name, const uint64_t faceId, const uint64_t cost)
 {
-  shared_ptr<FibUpdate> update = make_shared<FibUpdate>();
+  FibUpdate update;
 
-  update->name = name;
-  update->faceId = faceId;
-  update->cost = cost;
-  update->action = ADD_NEXTHOP;
+  update.name = name;
+  update.faceId = faceId;
+  update.cost = cost;
+  update.action = ADD_NEXTHOP;
 
   return update;
 }
 
-shared_ptr<FibUpdate>
+FibUpdate
 FibUpdate::createRemoveUpdate(const Name& name, const uint64_t faceId)
 {
-  shared_ptr<FibUpdate> update = make_shared<FibUpdate>();
+  FibUpdate update;
 
-  update->name = name;
-  update->faceId = faceId;
-  update->action = REMOVE_NEXTHOP;
+  update.name = name;
+  update.faceId = faceId;
+  update.action = REMOVE_NEXTHOP;
 
   return update;
 }
diff --git a/rib/fib-update.hpp b/rib/fib-update.hpp
index 02713ab..589871a 100644
--- a/rib/fib-update.hpp
+++ b/rib/fib-update.hpp
@@ -1,12 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014,  Regents of the University of California,
- *                      Arizona Board of Regents,
- *                      Colorado State University,
- *                      University Pierre & Marie Curie, Sorbonne University,
- *                      Washington University in St. Louis,
- *                      Beijing Institute of Technology,
- *                      The University of Memphis
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
  *
  * This file is part of NFD (Named Data Networking Forwarding Daemon).
  * See AUTHORS.md for complete list of NFD authors and contributors.
@@ -43,14 +43,22 @@
   {
   }
 
-  static shared_ptr<FibUpdate>
+  bool
+  operator==(const FibUpdate& other) const
+  {
+    return (this->name == other.name &&
+            this->faceId == other.faceId &&
+            this->cost == other.cost &&
+            this->action == other.action);
+  }
+
+  static FibUpdate
   createAddUpdate(const Name& name, const uint64_t faceId, const uint64_t cost);
 
-  static shared_ptr<FibUpdate>
+  static FibUpdate
   createRemoveUpdate(const Name& name, const uint64_t faceId);
 
-  enum Action
-  {
+  enum Action {
     ADD_NEXTHOP    = 0,
     REMOVE_NEXTHOP = 1
   };
@@ -69,15 +77,13 @@
      << " Name: " << update.name << ", "
      << "faceId: " << update.faceId << ", ";
 
-  if (update.action == FibUpdate::ADD_NEXTHOP)
-    {
-      os << "cost: " << update.cost << ", "
-         << "action: ADD_NEXTHOP";
-    }
-  else
-    {
-      os << "action: REMOVE_NEXTHOP";
-    }
+  if (update.action == FibUpdate::ADD_NEXTHOP) {
+    os << "cost: " << update.cost << ", "
+       << "action: ADD_NEXTHOP";
+  }
+  else {
+    os << "action: REMOVE_NEXTHOP";
+  }
 
   os << ")";
 
diff --git a/rib/fib-updater.cpp b/rib/fib-updater.cpp
new file mode 100644
index 0000000..212bccf
--- /dev/null
+++ b/rib/fib-updater.cpp
@@ -0,0 +1,720 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fib-updater.hpp"
+
+#include "core/logger.hpp"
+
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+
+namespace nfd {
+namespace rib {
+
+using ndn::nfd::ControlParameters;
+
+NFD_LOG_INIT("FibUpdater");
+
+const unsigned int FibUpdater::MAX_NUM_TIMEOUTS = 10;
+const uint32_t FibUpdater::ERROR_FACE_NOT_FOUND = 410;
+
+FibUpdater::FibUpdater(Rib& rib, ndn::nfd::Controller& controller)
+  : m_rib(rib)
+  , m_controller(controller)
+{
+  rib.setFibUpdater(this);
+}
+
+void
+FibUpdater::computeAndSendFibUpdates(const RibUpdateBatch& batch,
+                                     const FibUpdateSuccessCallback& onSuccess,
+                                     const FibUpdateFailureCallback& onFailure)
+{
+  m_batchFaceId = batch.getFaceId();
+
+  // Erase previously calculated inherited routes
+  m_inheritedRoutes.clear();
+
+  // Erase previously calculated FIB updates
+  m_updatesForBatchFaceId.clear();
+  m_updatesForNonBatchFaceId.clear();
+
+  computeUpdates(batch);
+
+  sendUpdatesForBatchFaceId(onSuccess, onFailure);
+}
+
+void
+FibUpdater::computeUpdates(const RibUpdateBatch& batch)
+{
+  NFD_LOG_DEBUG("Computing updates for batch with faceID: " << batch.getFaceId());
+
+  // Compute updates and add to m_fibUpdates
+  for (const RibUpdate& update : batch)
+    {
+      switch(update.getAction()) {
+      case RibUpdate::REGISTER:
+        computeUpdatesForRegistration(update);
+        break;
+      case RibUpdate::UNREGISTER:
+        computeUpdatesForUnregistration(update);
+        break;
+      case RibUpdate::REMOVE_FACE:
+        computeUpdatesForUnregistration(update);
+
+        // Do not apply updates with the same face ID as the destroyed face
+        // since they will be rejected by the FIB
+        m_updatesForBatchFaceId.clear();
+        break;
+      }
+    }
+}
+
+void
+FibUpdater::computeUpdatesForRegistration(const RibUpdate& update)
+{
+  const Name& prefix = update.getName();
+  const Route& route = update.getRoute();
+
+  Rib::const_iterator it = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (it != m_rib.end()) {
+    shared_ptr<const RibEntry> entry(it->second);
+
+    RibEntry::const_iterator existingRoute = entry->findRoute(route);
+
+    // Route will be new
+    if (existingRoute == entry->end()) {
+      // Will the new route change the namespace's capture flag?
+      bool willCaptureBeTurnedOn = (entry->hasCapture() == false && route.isCapture());
+
+      createFibUpdatesForNewRoute(*entry, route, willCaptureBeTurnedOn);
+    }
+    else {
+      // Route already exists
+      RibEntry entryCopy = *entry;
+
+      Route& routeToUpdate = *(entryCopy.findRoute(route));
+
+      routeToUpdate.flags = route.flags;
+      routeToUpdate.cost = route.cost;
+      routeToUpdate.expires = route.expires;
+
+      createFibUpdatesForUpdatedRoute(entryCopy, route, *existingRoute);
+    }
+  }
+  else {
+    // New name in RIB
+    // Find prefix's parent
+    shared_ptr<RibEntry> parent = m_rib.findParent(prefix);
+
+    Rib::RibEntryList descendants = m_rib.findDescendantsForNonInsertedName(prefix);
+    Rib::RibEntryList children;
+
+    for (const auto& descendant : descendants) {
+      // If the child has the same parent as the new entry,
+      // the new entry must be the child's new parent
+      if (descendant->getParent() == parent) {
+        children.push_back(descendant);
+      }
+    }
+
+    createFibUpdatesForNewRibEntry(prefix, route, children);
+  }
+}
+
+void
+FibUpdater::computeUpdatesForUnregistration(const RibUpdate& update)
+{
+  const Name& prefix = update.getName();
+  const Route& route = update.getRoute();
+
+  Rib::const_iterator ribIt = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (ribIt != m_rib.end()) {
+    shared_ptr<const RibEntry> entry(ribIt->second);
+
+    const bool hadCapture = entry->hasCapture();
+
+    RibEntry::const_iterator existing = entry->findRoute(route);
+
+    if (existing != entry->end()) {
+      RibEntry temp = *entry;
+
+      // Erase route in temp entry
+      temp.eraseRoute(route);
+
+      const bool captureWasTurnedOff = (hadCapture && !temp.hasCapture());
+
+      createFibUpdatesForErasedRoute(temp, *existing, captureWasTurnedOff);
+
+      // The RibEntry still has the face ID; need to update FIB
+      // with lowest cost for the same face instead of removing the face from the FIB
+      const Route* next = entry->getRouteWithSecondLowestCostByFaceId(route.faceId);
+
+      if (next != nullptr) {
+        createFibUpdatesForNewRoute(temp, *next, false);
+      }
+
+      // The RibEntry will be empty after this removal
+      if (entry->getNRoutes() == 1) {
+        createFibUpdatesForErasedRibEntry(*entry);
+      }
+    }
+  }
+}
+
+void
+FibUpdater::sendUpdates(const FibUpdateList& updates,
+                        const FibUpdateSuccessCallback& onSuccess,
+                        const FibUpdateFailureCallback& onFailure)
+{
+  std::string updateString = (updates.size() == 1) ? " update" : " updates";
+  NFD_LOG_DEBUG("Applying " << updates.size() << updateString << " to FIB");
+
+  for (const FibUpdate& update : updates) {
+    NFD_LOG_DEBUG("Sending FIB update: " << update);
+
+    if (update.action == FibUpdate::ADD_NEXTHOP) {
+      sendAddNextHopUpdate(update, onSuccess, onFailure);
+    }
+    else if (update.action == FibUpdate::REMOVE_NEXTHOP) {
+      sendRemoveNextHopUpdate(update, onSuccess, onFailure);
+    }
+  }
+}
+
+void
+FibUpdater::sendUpdatesForBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                                      const FibUpdateFailureCallback& onFailure)
+{
+  if (m_updatesForBatchFaceId.size() > 0) {
+    sendUpdates(m_updatesForBatchFaceId, onSuccess, onFailure);
+  }
+  else {
+    sendUpdatesForNonBatchFaceId(onSuccess, onFailure);
+  }
+}
+
+void
+FibUpdater::sendUpdatesForNonBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                                         const FibUpdateFailureCallback& onFailure)
+{
+  if (m_updatesForNonBatchFaceId.size() > 0) {
+    sendUpdates(m_updatesForNonBatchFaceId, onSuccess, onFailure);
+  }
+  else {
+    onSuccess(m_inheritedRoutes);
+  }
+}
+
+void
+FibUpdater::sendAddNextHopUpdate(const FibUpdate& update,
+                                 const FibUpdateSuccessCallback& onSuccess,
+                                 const FibUpdateFailureCallback& onFailure,
+                                 uint32_t nTimeouts)
+{
+  m_controller.start<ndn::nfd::FibAddNextHopCommand>(
+    ControlParameters()
+      .setName(update.name)
+      .setFaceId(update.faceId)
+      .setCost(update.cost),
+    bind(&FibUpdater::onUpdateSuccess, this, update, onSuccess, onFailure),
+    bind(&FibUpdater::onUpdateError, this, update, onSuccess, onFailure, _1, _2, nTimeouts));
+}
+
+void
+FibUpdater::sendRemoveNextHopUpdate(const FibUpdate& update,
+                                    const FibUpdateSuccessCallback& onSuccess,
+                                    const FibUpdateFailureCallback& onFailure,
+                                    uint32_t nTimeouts)
+{
+  m_controller.start<ndn::nfd::FibRemoveNextHopCommand>(
+    ControlParameters()
+      .setName(update.name)
+      .setFaceId(update.faceId),
+    bind(&FibUpdater::onUpdateSuccess, this, update, onSuccess, onFailure),
+    bind(&FibUpdater::onUpdateError, this, update, onSuccess, onFailure, _1, _2, nTimeouts));
+}
+
+void
+FibUpdater::onUpdateSuccess(const FibUpdate update,
+                            const FibUpdateSuccessCallback& onSuccess,
+                            const FibUpdateFailureCallback& onFailure)
+{
+  if (update.faceId == m_batchFaceId) {
+    m_updatesForBatchFaceId.remove(update);
+
+    if (m_updatesForBatchFaceId.size() == 0) {
+      sendUpdatesForNonBatchFaceId(onSuccess, onFailure);
+    }
+  }
+  else {
+    m_updatesForNonBatchFaceId.remove(update);
+
+    if (m_updatesForNonBatchFaceId.size() == 0) {
+      onSuccess(m_inheritedRoutes);
+    }
+  }
+}
+
+void
+FibUpdater::onUpdateError(const FibUpdate update,
+                          const FibUpdateSuccessCallback& onSuccess,
+                          const FibUpdateFailureCallback& onFailure,
+                          uint32_t code, const std::string& error, uint32_t nTimeouts)
+{
+  NFD_LOG_DEBUG("Failed to apply " << update << " (code: " << code << ", error: " << error << ")");
+
+  if (code == ndn::nfd::Controller::ERROR_TIMEOUT && nTimeouts < MAX_NUM_TIMEOUTS) {
+    sendAddNextHopUpdate(update, onSuccess, onFailure, ++nTimeouts);
+  }
+  else if (code == ERROR_FACE_NOT_FOUND) {
+    if (update.faceId == m_batchFaceId) {
+      onFailure(code, error);
+    }
+    else {
+      m_updatesForNonBatchFaceId.remove(update);
+
+      if (m_updatesForNonBatchFaceId.size() == 0) {
+        onSuccess(m_inheritedRoutes);
+      }
+    }
+  }
+  else {
+    throw Error("Non-recoverable error: " + error + " code: " + std::to_string(code));
+  }
+}
+
+void
+FibUpdater::addFibUpdate(FibUpdate update)
+{
+  FibUpdateList& updates = (update.faceId == m_batchFaceId) ? m_updatesForBatchFaceId :
+                                                              m_updatesForNonBatchFaceId;
+
+  // If an update with the same name and route already exists,
+  // replace it
+  FibUpdateList::iterator it = std::find_if(updates.begin(), updates.end(),
+    [&update] (const FibUpdate& other) {
+      return update.name == other.name && update.faceId == other.faceId;
+    });
+
+  if (it != updates.end()) {
+    FibUpdate& existingUpdate = *it;
+    existingUpdate.action = update.action;
+    existingUpdate.cost = update.cost;
+  }
+  else {
+    updates.push_back(update);
+  }
+}
+
+void
+FibUpdater::addInheritedRoutes(const RibEntry& entry, const Rib::Rib::RouteSet& routesToAdd)
+{
+  for (const Route& route : routesToAdd) {
+    // Don't add an ancestor faceId if the namespace has an entry for that faceId
+    if (!entry.hasFaceId(route.faceId)) {
+      // Create a record of the inherited route so it can be added to the RIB later
+      addInheritedRoute(entry.getName(), route);
+
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+    }
+  }
+}
+
+void
+FibUpdater::addInheritedRoutes(const Name& name, const Rib::Rib::RouteSet& routesToAdd,
+                               const Route& ignore)
+{
+  for (const Route& route : routesToAdd) {
+    if (route.faceId != ignore.faceId) {
+      // Create a record of the inherited route so it can be added to the RIB later
+      addInheritedRoute(name, route);
+
+      addFibUpdate(FibUpdate::createAddUpdate(name, route.faceId, route.cost));
+    }
+  }
+}
+
+void
+FibUpdater::removeInheritedRoutes(const RibEntry& entry, const Rib::Rib::RouteSet& routesToRemove)
+{
+  for (const Route& route : routesToRemove) {
+    // Only remove if the route has been inherited
+    if (entry.hasInheritedRoute(route)) {
+      removeInheritedRoute(entry.getName(), route);
+      addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+    }
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForNewRibEntry(const Name& name, const Route& route,
+                                           const Rib::RibEntryList& children)
+{
+  // Create FIB update for new entry
+  addFibUpdate(FibUpdate::createAddUpdate(name, route.faceId, route.cost));
+
+  // No flags are set
+  if (!route.isChildInherit() && !route.isCapture()) {
+    // Add ancestor routes to self
+    addInheritedRoutes(name, m_rib.getAncestorRoutes(name), route);
+  }
+  else if (route.isChildInherit() && route.isCapture()) {
+    // Add route to children
+    Rib::RouteSet routesToAdd;
+    routesToAdd.insert(route);
+
+    // Remove routes blocked by capture and add self to children
+    modifyChildrensInheritedRoutes(children, routesToAdd, m_rib.getAncestorRoutes(name));
+  }
+  else if (route.isChildInherit()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(name);
+
+    // Add ancestor routes to self
+    addInheritedRoutes(name, ancestorRoutes, route);
+
+    // If there is an ancestor route which is the same as the new route, replace it
+    // with the new route
+    Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+    // There is a route that needs to be overwritten, erase and then replace
+    if (it != ancestorRoutes.end()) {
+      ancestorRoutes.erase(it);
+    }
+
+    // Add new route to ancestor list so it can be added to children
+    ancestorRoutes.insert(route);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(children, ancestorRoutes, Rib::RouteSet());
+  }
+  else if (route.isCapture()) {
+    // Remove routes blocked by capture
+    modifyChildrensInheritedRoutes(children, Rib::RouteSet(), m_rib.getAncestorRoutes(name));
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForNewRoute(const RibEntry& entry, const Route& route,
+                                        bool captureWasTurnedOn)
+{
+  // Only update if the new route has a lower cost than a previously installed route
+  shared_ptr<const Route> prevRoute =
+    entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
+
+  Rib::RouteSet routesToAdd;
+  if (route.isChildInherit()) {
+    // Add to children if this new route doesn't override a previous lower cost, or
+    // add to children if this new route is lower cost than a previous route.
+    // Less than equal, since entry may find this route
+    if (!static_cast<bool>(prevRoute) || route.cost <= prevRoute->cost) {
+      // Add self to children
+      routesToAdd.insert(route);
+    }
+  }
+
+  Rib::RouteSet routesToRemove;
+  if (captureWasTurnedOn) {
+    // Capture flag on
+    routesToRemove = m_rib.getAncestorRoutes(entry);
+
+    // Remove ancestor routes from self
+    removeInheritedRoutes(entry, routesToRemove);
+  }
+
+  modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+
+  // If another route with same faceId and lower cost exists, don't update.
+  // Must be done last so that add updates replace removal updates
+  // Create FIB update for new entry
+  shared_ptr<const Route> other = entry.getRouteWithLowestCostByFaceId(route.faceId);
+
+  if (!other || route.cost <= other->cost) {
+    addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForUpdatedRoute(const RibEntry& entry, const Route& route,
+                                            const Route& existingRoute)
+{
+  const bool costDidChange = (route.cost != existingRoute.cost);
+
+  // Look for an installed route with the lowest cost and child inherit set
+  shared_ptr<const Route> prevRoute =
+    entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
+
+  // No flags changed and cost didn't change, no change in FIB
+  if (route.flags == existingRoute.flags && !costDidChange) {
+    return;
+  }
+
+  // Cost changed so create update for the entry itself
+  if (costDidChange) {
+    // Create update if this route's cost is lower than other routes
+    if (route.cost <= entry.getRouteWithLowestCostByFaceId(route.faceId)->cost) {
+      // Create FIB update for the updated entry
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+    }
+    else if (existingRoute.cost < entry.getRouteWithLowestCostByFaceId(route.faceId)->cost) {
+      // Create update if this route used to be the lowest route but is no longer
+      // the lowest cost route.
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), prevRoute->faceId, prevRoute->cost));
+    }
+
+    // If another route with same faceId and lower cost and ChildInherit exists,
+    // don't update children.
+    if (!static_cast<bool>(prevRoute) || route.cost <= prevRoute->cost) {
+      // If no flags changed but child inheritance is set, need to update children
+      // with new cost
+      if ((route.flags == existingRoute.flags) && route.isChildInherit()) {
+        // Add self to children
+        Rib::RouteSet routesToAdd;
+        routesToAdd.insert(route);
+        modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+
+        return;
+      }
+    }
+  }
+
+  // Child inherit was turned on
+  if (!existingRoute.isChildInherit() && route.isChildInherit()) {
+    // If another route with same faceId and lower cost and ChildInherit exists,
+    // don't update children.
+    if (!static_cast<bool>(prevRoute) || route.cost <= prevRoute->cost) {
+      // Add self to children
+      Rib::RouteSet routesToAdd;
+      routesToAdd.insert(route);
+      modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+    }
+  } // Child inherit was turned off
+  else if (existingRoute.isChildInherit() && !route.isChildInherit()) {
+    // Remove self from children
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    Rib::RouteSet routesToAdd;
+    // If another route with same faceId and ChildInherit exists, update children with this route.
+    if (static_cast<bool>(prevRoute)) {
+      routesToAdd.insert(*prevRoute);
+    }
+    else {
+      // Look for an ancestor that was blocked previously
+      const Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+      Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+      // If an ancestor is found, add it to children
+      if (it != ancestorRoutes.end()) {
+        routesToAdd.insert(*it);
+      }
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+
+  // Capture was turned on
+  if (!existingRoute.isCapture() && route.isCapture()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+    // Remove ancestor routes from self
+    removeInheritedRoutes(entry, ancestorRoutes);
+
+    // Remove ancestor routes from children
+    modifyChildrensInheritedRoutes(entry.getChildren(), Rib::RouteSet(), ancestorRoutes);
+  }  // Capture was turned off
+  else if (existingRoute.isCapture() && !route.isCapture()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+    // Add ancestor routes to self
+    addInheritedRoutes(entry, ancestorRoutes);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(entry.getChildren(), ancestorRoutes, Rib::RouteSet());
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForErasedRoute(const RibEntry& entry, const Route& route,
+                                               const bool captureWasTurnedOff)
+{
+  addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+
+  if (route.isChildInherit() && route.isCapture()) {
+    // Remove self from children
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    // If capture is turned off for the route, need to add ancestors
+    // to self and children
+    Rib::RouteSet routesToAdd;
+    if (captureWasTurnedOff) {
+      // Look for an ancestors that were blocked previously
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+
+      // Add ancestor routes to self
+      addInheritedRoutes(entry, routesToAdd);
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+  else if (route.isChildInherit()) {
+    // If not blocked by capture, add inherited routes to children
+    Rib::RouteSet routesToAdd;
+    if (!entry.hasCapture()) {
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+    }
+
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+  else if (route.isCapture()) {
+    // If capture is turned off for the route, need to add ancestors
+    // to self and children
+    Rib::RouteSet routesToAdd;
+    if (captureWasTurnedOff) {
+      // Look for an ancestors that were blocked previously
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+
+      // Add ancestor routes to self
+      addInheritedRoutes(entry, routesToAdd);
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+  }
+
+  // Need to check if the removed route was blocking an inherited route
+  Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+  if (!entry.hasCapture()) {
+    // If there is an ancestor route which is the same as the erased route, add that route
+    // to the current entry
+    Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+    if (it != ancestorRoutes.end()) {
+      addInheritedRoute(entry.getName(), *it);
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), it->faceId, it->cost));
+    }
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForErasedRibEntry(const RibEntry& entry)
+{
+  for (const Route& route : entry.getInheritedRoutes()) {
+    addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+  }
+}
+
+void
+FibUpdater::modifyChildrensInheritedRoutes(const Rib::RibEntryList& children,
+                                           const Rib::RouteSet& routesToAdd,
+                                           const Rib::RouteSet& routesToRemove)
+{
+  for (const auto& child : children) {
+    traverseSubTree(*child, routesToAdd, routesToRemove);
+  }
+}
+
+void
+FibUpdater::traverseSubTree(const RibEntry& entry, Rib::Rib::RouteSet routesToAdd,
+                            Rib::Rib::RouteSet routesToRemove)
+{
+  // If a route on the namespace has the capture flag set, ignore self and children
+  if (entry.hasCapture()) {
+    return;
+  }
+
+  // Remove inherited routes from current namespace
+  for (Rib::RouteSet::const_iterator removeIt = routesToRemove.begin();
+       removeIt != routesToRemove.end(); )
+  {
+    // If a route on the namespace has the same face ID and child inheritance set,
+    // ignore this route
+    if (entry.hasChildInheritOnFaceId(removeIt->faceId)) {
+      routesToRemove.erase(removeIt++);
+      continue;
+    }
+
+    // Only remove route if it removes an existing inherited route
+    if (entry.hasInheritedRoute(*removeIt)) {
+      removeInheritedRoute(entry.getName(), *removeIt);
+      addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), removeIt->faceId));
+    }
+
+    ++removeIt;
+  }
+
+  // Add inherited routes to current namespace
+  for (Rib::RouteSet::const_iterator addIt = routesToAdd.begin(); addIt != routesToAdd.end(); ) {
+
+    // If a route on the namespace has the same face ID and child inherit set, ignore this face
+    if (entry.hasChildInheritOnFaceId(addIt->faceId)) {
+      routesToAdd.erase(addIt++);
+      continue;
+    }
+
+    // Only add route if it does not override an existing route
+    if (!entry.hasFaceId(addIt->faceId)) {
+      addInheritedRoute(entry.getName(), *addIt);
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), addIt->faceId, addIt->cost));
+    }
+
+    ++addIt;
+  }
+
+  modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+}
+
+void
+FibUpdater::addInheritedRoute(const Name& name, const Route& route)
+{
+  RibUpdate update;
+  update.setAction(RibUpdate::REGISTER)
+        .setName(name)
+        .setRoute(route);
+
+  m_inheritedRoutes.push_back(update);
+}
+
+void
+FibUpdater::removeInheritedRoute(const Name& name, const Route& route)
+{
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(name)
+        .setRoute(route);
+
+  m_inheritedRoutes.push_back(update);
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/fib-updater.hpp b/rib/fib-updater.hpp
new file mode 100644
index 0000000..bfa0901
--- /dev/null
+++ b/rib/fib-updater.hpp
@@ -0,0 +1,279 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_RIB_FIB_UPDATER_HPP
+#define NFD_RIB_FIB_UPDATER_HPP
+
+#include "common.hpp"
+#include "fib-update.hpp"
+#include "rib.hpp"
+#include "rib-update-batch.hpp"
+
+#include <ndn-cxx/management/nfd-controller.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief computes FibUpdates based on updates to the RIB and sends them to NFD
+ */
+class FibUpdater : noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  typedef std::list<FibUpdate> FibUpdateList;
+
+  typedef function<void(RibUpdateList inheritedRoutes)> FibUpdateSuccessCallback;
+  typedef function<void(uint32_t code, const std::string& error)> FibUpdateFailureCallback;
+
+  FibUpdater(Rib& rib, ndn::nfd::Controller& controller);
+
+  /** \brief computes FibUpdates using the provided RibUpdateBatch and then sends the
+   *         updates to NFD's FIB
+   *
+   *  \note  Caller must guarantee that the previous batch has either succeeded or failed
+   *         before calling this method
+   */
+  void
+  computeAndSendFibUpdates(const RibUpdateBatch& batch,
+                           const FibUpdateSuccessCallback& onSuccess,
+                           const FibUpdateFailureCallback& onFailure);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief determines the type of action that will be performed on the RIB and calls the
+  *          corresponding computation method
+  */
+  void
+  computeUpdates(const RibUpdateBatch& batch);
+
+  /** \brief sends the passed updates to NFD
+  *
+  *   onSuccess or onFailure will be called based on the results in
+  *   onUpdateSuccess or onUpdateFailure
+  *
+  *   \see FibUpdater::onUpdateSuccess
+  *   \see FibUpdater::onUpdateFailure
+  */
+  void
+  sendUpdates(const FibUpdateList& updates,
+              const FibUpdateSuccessCallback& onSuccess,
+              const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends the updates in m_updatesForBatchFaceId to NFD if any exist,
+  *          otherwise calls FibUpdater::sendUpdatesForNonBatchFaceId.
+  */
+  void
+  sendUpdatesForBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                            const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends the updates in m_updatesForNonBatchFaceId to NFD if any exist,
+  *          otherwise calls onSuccess.
+  */
+  void
+  sendUpdatesForNonBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                               const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends a FibAddNextHopCommand to NFD using the parameters supplied by
+  *          the passed update
+  *
+  *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
+  */
+  void
+  sendAddNextHopUpdate(const FibUpdate& update,
+                       const FibUpdateSuccessCallback& onSuccess,
+                       const FibUpdateFailureCallback& onFailure,
+                       uint32_t nTimeouts = 0);
+
+  /** \brief sends a FibRemoveNextHopCommand to NFD using the parameters supplied by
+  *          the passed update
+  *
+  *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
+  */
+  void
+  sendRemoveNextHopUpdate(const FibUpdate& update,
+                          const FibUpdateSuccessCallback& onSuccess,
+                          const FibUpdateFailureCallback& onFailure,
+                          uint32_t nTimeouts = 0);
+
+private:
+  /** \brief calculates the FibUpdates generated by a RIB registration
+  */
+  void
+  computeUpdatesForRegistration(const RibUpdate& update);
+
+  /** \brief calculates the FibUpdates generated by a RIB unregistration
+  */
+  void
+  computeUpdatesForUnregistration(const RibUpdate& update);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief callback used by NfdController when a FibAddNextHopCommand or FibRemoveNextHopCommand
+  *          is successful.
+  *
+  *   If the update has the same Face ID as the batch being processed, the update is
+  *   removed from m_updatesForBatchFaceId. If m_updatesForBatchFaceId becomes empty,
+  *   the updates with a different Face ID than the batch are sent to NFD.
+  *
+  *   If the update has a different Face ID than the batch being processed, the update is
+  *   removed from m_updatesForBatchNonFaceId. If m_updatesForBatchNonFaceId becomes empty,
+  *   the FIB update process is considered a success.
+  */
+  void
+  onUpdateSuccess(const FibUpdate update,
+                  const FibUpdateSuccessCallback& onSuccess,
+                  const FibUpdateFailureCallback& onFailure);
+
+  /** \brief callback used by NfdController when a FibAddNextHopCommand or FibRemoveNextHopCommand
+  *          is successful.
+  *
+  *   If the update has not reached the max number of timeouts allowed, the update
+  *   is retried.
+  *
+  *   If the update failed due to a non-existent face and the update has the same Face ID
+  *   as the update batch, the FIB update process fails.
+  *
+  *   If the update failed due to a non-existent face and the update has a different
+  *   face than the update batch, the update is not retried and the error is
+  *   ignored.
+  *
+  *   Otherwise, a non-recoverable error has occurred and an exception is thrown.
+  */
+  void
+  onUpdateError(const FibUpdate update,
+                const FibUpdateSuccessCallback& onSuccess,
+                const FibUpdateFailureCallback& onFailure,
+                uint32_t code, const std::string& error, uint32_t nTimeouts);
+
+private:
+  /** \brief adds the update to an update list based on its Face ID
+  *
+  *   If the update has the same Face ID as the update batch, the update is added
+  *   to m_updatesForBatchFaceId.
+  *
+  *   Otherwise, the update is added to m_updatesForBatchNonFaceId.
+  */
+  void
+  addFibUpdate(const FibUpdate update);
+
+  /** \brief creates records of the passed routes added to the entry and creates FIB updates
+  */
+  void
+  addInheritedRoutes(const RibEntry& entry, const Rib::RouteSet& routesToAdd);
+
+  /** \brief creates records of the passed routes added to the name and creates FIB updates.
+  *          Routes in routesToAdd with the same Face ID as ignore will be not be considered.
+  */
+  void
+  addInheritedRoutes(const Name& name, const Rib::RouteSet& routesToAdd, const Route& ignore);
+
+  /** \brief creates records of the passed routes removed from the entry and creates FIB updates
+  */
+  void
+  removeInheritedRoutes(const RibEntry& entry, const Rib::RouteSet& routesToRemove);
+
+  /** \brief calculates updates for a name that will create a new RIB entry
+  */
+  void
+  createFibUpdatesForNewRibEntry(const Name& name, const Route& route,
+                                 const Rib::RibEntryList& children);
+
+  /** \brief calculates updates for a new route added to a RIB entry
+  */
+  void
+  createFibUpdatesForNewRoute(const RibEntry& entry, const Route& route,
+                              const bool captureWasTurnedOn);
+
+  /** \brief calculates updates for changes to an existing route for a RIB entry
+  */
+  void
+  createFibUpdatesForUpdatedRoute(const RibEntry& entry, const Route& route,
+                                  const Route& existingRoute);
+
+  /** \brief calculates updates for a an existing route removed from a RIB entry
+  */
+  void
+  createFibUpdatesForErasedRoute(const RibEntry& entry, const Route& route,
+                                 const bool captureWasTurnedOff);
+
+  /** \brief calculates updates for an entry that will be removed from the RIB
+  */
+  void
+  createFibUpdatesForErasedRibEntry(const RibEntry& entry);
+
+  /** \brief adds and removes passed routes to children's inherited routes
+  */
+  void
+  modifyChildrensInheritedRoutes(const Rib::RibEntryList& children,
+                                 const Rib::RouteSet& routesToAdd,
+                                 const Rib::RouteSet& routesToRemove);
+
+  /** \brief traverses the entry's children adding and removing the passed routes
+  */
+  void
+  traverseSubTree(const RibEntry& entry, Rib::RouteSet routesToAdd, Rib::RouteSet routesToRemove);
+
+private:
+  /** \brief creates a record of a calculated inherited route that should be added to the entry
+  */
+  void
+  addInheritedRoute(const Name& name, const Route& route);
+
+  /** \brief creates a record of an existing inherited route that should be removed from the entry
+  */
+  void
+  removeInheritedRoute(const Name& name, const Route& route);
+
+private:
+  const Rib& m_rib;
+  ndn::nfd::Controller& m_controller;
+  uint64_t m_batchFaceId;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  FibUpdateList m_updatesForBatchFaceId;
+  FibUpdateList m_updatesForNonBatchFaceId;
+
+  /** \brief list of inherited routes generated during FIB update calculation;
+   *         passed to the RIB when updates are completed successfully
+   */
+  RibUpdateList m_inheritedRoutes;
+
+private:
+  static const unsigned int MAX_NUM_TIMEOUTS;
+  static const uint32_t ERROR_FACE_NOT_FOUND;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_RIB_FIB_UPDATER_HPP
diff --git a/rib/rib-entry.cpp b/rib/rib-entry.cpp
index b841908..22b6dee 100644
--- a/rib/rib-entry.cpp
+++ b/rib/rib-entry.cpp
@@ -40,25 +40,29 @@
   return std::find_if(begin(), end(), bind(&compareFaceIdAndOrigin, _1, route));
 }
 
+RibEntry::RouteList::const_iterator
+RibEntry::findRoute(const Route& route) const
+{
+  return std::find_if(begin(), end(), bind(&compareFaceIdAndOrigin, _1, route));
+}
+
 bool
 RibEntry::insertRoute(const Route& route)
 {
   iterator it = findRoute(route);
 
-  if (it == end())
-    {
-      if (route.flags & ndn::nfd::ROUTE_FLAG_CAPTURE)
-        {
-          m_nRoutesWithCaptureSet++;
-        }
+  if (it == end()) {
+    if (route.flags & ndn::nfd::ROUTE_FLAG_CAPTURE) {
+      m_nRoutesWithCaptureSet++;
+    }
 
-      m_routes.push_back(route);
-      return true;
-    }
-  else
-    {
-      return false;
-    }
+    m_routes.push_back(route);
+
+    return true;
+  }
+  else {
+    return false;
+  }
 }
 
 void
@@ -84,6 +88,12 @@
   return it != end();
 }
 
+size_t
+RibEntry::getNRoutes() const
+{
+  return m_routes.size();
+}
+
 void
 RibEntry::addChild(shared_ptr<RibEntry> child)
 {
@@ -103,20 +113,18 @@
 RibEntry::RouteList::iterator
 RibEntry::eraseRoute(RouteList::iterator route)
 {
-  if (route != m_routes.end())
-    {
-      if (route->flags & ndn::nfd::ROUTE_FLAG_CAPTURE)
-        {
-          m_nRoutesWithCaptureSet--;
-        }
-
-      //cancel any scheduled event
-      NFD_LOG_TRACE("Cancelling expiration eventId: " << route->getExpirationEvent());
-      scheduler::cancel(route->getExpirationEvent());
-
-      return m_routes.erase(route);
+  if (route != m_routes.end()) {
+    if (route->flags & ndn::nfd::ROUTE_FLAG_CAPTURE) {
+      m_nRoutesWithCaptureSet--;
     }
 
+    // Cancel any scheduled event
+    NFD_LOG_TRACE("Cancelling expiration eventId: " << route->getExpirationEvent());
+    scheduler::cancel(route->getExpirationEvent());
+
+    return m_routes.erase(route);
+  }
+
   return m_routes.end();
 }
 
@@ -129,19 +137,20 @@
 void
 RibEntry::removeInheritedRoute(const Route& route)
 {
-  RouteList::iterator it = findInheritedRoute(route);
+  RouteList::iterator it = std::find_if(m_inheritedRoutes.begin(), m_inheritedRoutes.end(),
+                                        bind(&compareFaceId, _1, route.faceId));
   m_inheritedRoutes.erase(it);
 }
 
-RibEntry::RouteList::iterator
-RibEntry::findInheritedRoute(const Route& route)
+RibEntry::RouteList::const_iterator
+RibEntry::findInheritedRoute(const Route& route) const
 {
   return std::find_if(m_inheritedRoutes.begin(), m_inheritedRoutes.end(),
                       bind(&compareFaceId, _1, route.faceId));
 }
 
 bool
-RibEntry::hasInheritedRoute(const Route& route)
+RibEntry::hasInheritedRoute(const Route& route) const
 {
   RouteList::const_iterator it = findInheritedRoute(route);
 
@@ -157,97 +166,91 @@
 bool
 RibEntry::hasChildInheritOnFaceId(uint64_t faceId) const
 {
-  for (RibEntry::const_iterator it = m_routes.begin(); it != m_routes.end(); ++it)
-    {
-      if (it->faceId == faceId && (it->flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT))
-        {
-          return true;
-        }
+  for (const Route& route : m_routes) {
+    if (route.faceId == faceId && (route.flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)) {
+      return true;
     }
+  }
 
   return false;
 }
 
-shared_ptr<Route>
-RibEntry::getRouteWithLowestCostByFaceId(uint64_t faceId)
+shared_ptr<const Route>
+RibEntry::getRouteWithLowestCostByFaceId(uint64_t faceId) const
 {
   shared_ptr<Route> candidate;
 
-  for (const Route& route : m_routes)
-    {
-      // Correct face ID
-      if (route.faceId == faceId)
-        {
-          // If this is the first route with this Face ID found
-          if (candidate == nullptr)
-            {
-              candidate = make_shared<Route>(route);
-            }
-          else if (route.cost < candidate->cost) // Found a route with a lower cost
-            {
-              candidate = make_shared<Route>(route);
-            }
-        }
+  for (const Route& route : m_routes) {
+    // Matching face ID
+    if (route.faceId == faceId) {
+      // If this is the first route with this Face ID found
+      if (candidate == nullptr) {
+        candidate = make_shared<Route>(route);
+      }
+      else if (route.cost < candidate->cost) {
+        // Found a route with a lower cost
+        candidate = make_shared<Route>(route);
+      }
     }
+  }
 
   return candidate;
 }
 
-shared_ptr<Route>
-RibEntry::getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId)
+const Route*
+RibEntry::getRouteWithSecondLowestCostByFaceId(uint64_t faceId) const
+{
+  std::vector<Route> matches;
+
+  // Copy routes which have faceId
+  std::copy_if(m_routes.begin(), m_routes.end(), std::back_inserter(matches),
+    [faceId] (const Route& route) { return route.faceId == faceId; });
+
+  // If there are not at least 2 routes, there is no second lowest
+  if (matches.size() < 2) {
+    return nullptr;
+  }
+
+  // Get second lowest cost
+  std::nth_element(matches.begin(), matches.begin() + 1, matches.end(),
+    [] (const Route& lhs, const Route& rhs) { return lhs.cost < rhs.cost; });
+
+  return &matches.at(1);
+}
+
+shared_ptr<const Route>
+RibEntry::getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId) const
 {
   shared_ptr<Route> candidate;
 
-  for (const Route& route : m_routes)
+  for (const Route& route : m_routes) {
+    // Correct face ID and Child Inherit flag set
+    if (route.faceId == faceId &&
+        (route.flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT) == ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
     {
-      // Correct face ID and Child Inherit flag set
-      if (route.faceId == faceId &&
-          (route.flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT) == ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
-        {
-          // If this is the first route with this Face ID found
-          if (candidate == nullptr)
-            {
-              candidate = make_shared<Route>(route);
-            }
-          else if (route.cost < candidate->cost) // Found a route with a lower cost
-            {
-              candidate = make_shared<Route>(route);
-            }
-        }
+      // If this is the first route with this Face ID found
+      if (candidate == nullptr) {
+        candidate = make_shared<Route>(route);
+      }
+      else if (route.cost < candidate->cost) {
+        // Found a route with a lower cost
+        candidate = make_shared<Route>(route);
+      }
     }
+  }
 
   return candidate;
 }
 
 std::ostream&
-operator<<(std::ostream& os, const Route& route)
-{
-  os << "Route("
-     << "faceid: " << route.faceId
-     << ", origin: " << route.origin
-     << ", cost: " << route.cost
-     << ", flags: " << route.flags;
-  if (route.expires != time::steady_clock::TimePoint::max()) {
-    os << ", expires in: " << (route.expires - time::steady_clock::now());
-  }
-  else {
-    os << ", never expires";
-  }
-  os << ")";
-
-  return os;
-}
-
-std::ostream&
 operator<<(std::ostream& os, const RibEntry& entry)
 {
   os << "RibEntry {\n";
   os << "\tName: " << entry.getName() << "\n";
 
-  for (const Route& route : entry)
-    {
-      os << "\t" << route << "\n";
-    }
+  for (const Route& route : entry) {
+    os << "\t" << route << "\n";
+  }
 
   os << "}";
 
diff --git a/rib/rib-entry.hpp b/rib/rib-entry.hpp
index 52adfd9..c3efbb7 100644
--- a/rib/rib-entry.hpp
+++ b/rib/rib-entry.hpp
@@ -63,8 +63,8 @@
   void
   removeChild(shared_ptr<RibEntry> child);
 
-  std::list<shared_ptr<RibEntry>>&
-  getChildren();
+  const std::list<shared_ptr<RibEntry>>&
+  getChildren() const;
 
   bool
   hasChildren() const;
@@ -94,9 +94,15 @@
   RouteList&
   getRoutes();
 
+  size_t
+  getNRoutes() const;
+
   iterator
   findRoute(const Route& route);
 
+  const_iterator
+  findRoute(const Route& route) const;
+
   bool
   hasRoute(const Route& route);
 
@@ -110,22 +116,22 @@
    *  The inherited routes returned represent inherited routes this namespace has in the FIB.
    *  \return{ routes inherited by this namespace }
    */
-  RouteList&
-  getInheritedRoutes();
+  const RouteList&
+  getInheritedRoutes() const;
 
   /** \brief Finds an inherited route with a matching face ID.
    *  \return{ An iterator to the matching route if one is found;
    *           otherwise, an iterator to the end of the entry's
    *           inherited route list }
    */
-  RouteList::iterator
-  findInheritedRoute(const Route& route);
+  RouteList::const_iterator
+  findInheritedRoute(const Route& route) const;
 
   /** \brief Determines if the entry has an inherited route with a matching face ID.
    *  \return{ True, if a matching inherited route is found; otherwise, false. }
    */
   bool
-  hasInheritedRoute(const Route& route);
+  hasInheritedRoute(const Route& route) const;
 
   bool
   hasCapture() const;
@@ -140,16 +146,19 @@
   /** \brief Returns the route with the lowest cost that has the passed face ID.
    *  \return{ The route with the lowest cost that has the passed face ID}
    */
-  shared_ptr<Route>
-  getRouteWithLowestCostByFaceId(uint64_t faceId);
+  shared_ptr<const Route>
+  getRouteWithLowestCostByFaceId(uint64_t faceId) const;
+
+const Route*
+  getRouteWithSecondLowestCostByFaceId(uint64_t faceId) const;
 
   /** \brief Returns the route with the lowest cost that has the passed face ID
    *         and its child inherit flag set.
    *  \return{ The route with the lowest cost that has the passed face ID
    *           and its child inherit flag set }
    */
-  shared_ptr<Route>
-  getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId);
+  shared_ptr<const Route>
+  getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId) const;
 
   const_iterator
   begin() const;
@@ -207,8 +216,8 @@
   return m_parent;
 }
 
-inline std::list<shared_ptr<RibEntry>>&
-RibEntry::getChildren()
+inline const std::list<shared_ptr<RibEntry> >&
+RibEntry::getChildren() const
 {
   return m_children;
 }
@@ -219,8 +228,8 @@
   return m_routes;
 }
 
-inline RibEntry::RouteList&
-RibEntry::getInheritedRoutes()
+inline const RibEntry::RouteList&
+RibEntry::getInheritedRoutes() const
 {
   return m_inheritedRoutes;
 }
diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp
index 2b79a53..8215ade 100644
--- a/rib/rib-manager.cpp
+++ b/rib/rib-manager.cpp
@@ -82,7 +82,7 @@
   , m_isLocalhopEnabled(false)
   , m_remoteRegistrator(m_nfdController, m_keyChain, m_managedRib)
   , m_ribStatusPublisher(m_managedRib, face, LIST_COMMAND_PREFIX, m_keyChain)
-  , m_lastTransactionId(0)
+  , m_fibUpdater(m_managedRib, m_nfdController)
   , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
                          SIGNED_COMMAND_VERBS +
                          (sizeof(SIGNED_COMMAND_VERBS) / sizeof(SignedVerbAndProcessor)))
@@ -146,36 +146,33 @@
 {
   bool isRemoteRegisterEnabled = false;
 
-  for (ConfigSection::const_iterator i = configSection.begin();
-       i != configSection.end(); ++i)
-    {
-      if (i->first == "localhost_security")
-          m_localhostValidator.load(i->second, filename);
-      else if (i->first == "localhop_security")
-        {
-          m_localhopValidator.load(i->second, filename);
-          m_isLocalhopEnabled = true;
-        }
-      else if (i->first == "remote_register")
-        {
-          m_remoteRegistrator.loadConfig(i->second);
-          isRemoteRegisterEnabled = true;
-          // avoid other actions when isDryRun == true
-          if (isDryRun)
-            {
-              continue;
-            }
-
-          m_remoteRegistrator.enable();
-        }
-      else
-        throw Error("Unrecognized rib property: " + i->first);
+  for (const auto& item : configSection) {
+    if (item.first == "localhost_security") {
+      m_localhostValidator.load(item.second, filename);
     }
-
-  if (!isRemoteRegisterEnabled)
-    {
-      m_remoteRegistrator.disable();
+    else if (item.first == "localhop_security") {
+      m_localhopValidator.load(item.second, filename);
+      m_isLocalhopEnabled = true;
     }
+    else if (item.first == "remote_register") {
+      m_remoteRegistrator.loadConfig(item.second);
+      isRemoteRegisterEnabled = true;
+
+      // Avoid other actions when isDryRun == true
+      if (isDryRun) {
+        continue;
+      }
+
+      m_remoteRegistrator.enable();
+    }
+    else {
+      throw Error("Unrecognized rib property: " + item.first);
+    }
+  }
+
+  if (!isRemoteRegisterEnabled) {
+    m_remoteRegistrator.disable();
+  }
 }
 
 void
@@ -208,17 +205,15 @@
 
   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));
-    }
+  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
@@ -240,26 +235,30 @@
   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);
+  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;
     }
-  else
-    {
-      NFD_LOG_DEBUG("Unsupported command: " << verb);
-      if (static_cast<bool>(request))
-        sendResponse(request->getName(), 501, "Unsupported command");
+
+    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
@@ -268,23 +267,23 @@
 {
   ndn::nfd::RibRegisterCommand command;
 
-  if (!validateParameters(command, parameters))
-    {
-      NFD_LOG_DEBUG("register result: FAIL reason: malformed");
+  if (!validateParameters(command, parameters)) {
+    NFD_LOG_DEBUG("register result: FAIL reason: malformed");
 
-      if (static_cast<bool>(request))
-        {
-          sendResponse(request->getName(), 400, "Malformed command");
-        }
-
-      return;
+    if (static_cast<bool>(request)) {
+      sendResponse(request->getName(), 400, "Malformed command");
     }
 
+    return;
+  }
+
   bool isSelfRegistration = (!parameters.hasFaceId() || parameters.getFaceId() == 0);
-  if (isSelfRegistration)
-    {
-      parameters.setFaceId(request->getIncomingFaceId());
-    }
+  if (isSelfRegistration) {
+    parameters.setFaceId(request->getIncomingFaceId());
+  }
+
+  // Respond since command is valid and authorized
+  sendSuccessResponse(request, parameters);
 
   Route route;
   route.faceId = parameters.getFaceId();
@@ -294,44 +293,37 @@
 
   if (parameters.hasExpirationPeriod() &&
       parameters.getExpirationPeriod() != time::milliseconds::max())
-    {
-      route.expires = time::steady_clock::now() + parameters.getExpirationPeriod();
+  {
+    route.expires = time::steady_clock::now() + parameters.getExpirationPeriod();
 
-      // Schedule a new event, the old one will be cancelled during rib insertion.
-      scheduler::EventId eventId = scheduler::schedule(parameters.getExpirationPeriod(),
-          bind(&RibManager::expireEntry, this, shared_ptr<Interest>(), parameters));
-      NFD_LOG_TRACE("Scheduled unregistration at: " << route.expires <<
-                    " with EventId: " << eventId);
+    // 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));
 
-      //set the  NewEventId of this entry
-      route.setExpirationEvent(eventId);
-    }
-  else
-    {
-      route.expires = time::steady_clock::TimePoint::max();
-    }
+    NFD_LOG_TRACE("Scheduled unregistration at: " << route.expires <<
+                  " with EventId: " << eventId);
+
+    // Set the  NewEventId of this entry
+    route.setExpirationEvent(eventId);
+  }
+  else {
+    route.expires = time::steady_clock::TimePoint::max();
+  }
 
   NFD_LOG_INFO("Adding route " << parameters.getName() << " nexthop=" << route.faceId
                                                        << " origin=" << route.origin
                                                        << " cost=" << route.cost);
 
-  m_managedRib.insert(parameters.getName(), route);
+  RibUpdate update;
+  update.setAction(RibUpdate::REGISTER)
+        .setName(parameters.getName())
+        .setRoute(route);
+
+  m_managedRib.beginApplyUpdate(update,
+                                bind(&RibManager::onRibUpdateSuccess, this, update),
+                                bind(&RibManager::onRibUpdateFailure, this, update, _1, _2));
+
   m_registeredFaces.insert(route.faceId);
-
-  sendUpdatesToFib(request, parameters);
-}
-
-void
-RibManager::expireEntry(const shared_ptr<const Interest>& request, ControlParameters& params)
-{
-  Route route;
-  route.faceId = params.getFaceId();
-  route.origin = params.getOrigin();
-  route.cost = params.getCost();
-  route.flags = params.getFlags();
-
-  NFD_LOG_DEBUG(route << " for " << params.getName() << " has expired");
-  unregisterEntry(request, params);
 }
 
 void
@@ -340,28 +332,36 @@
 {
   ndn::nfd::RibUnregisterCommand command;
 
-  //passing all parameters gives error in validation.
-  //so passing only the required arguments.
+  // 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;
+  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)
-    {
-      parameters.setFaceId(request->getIncomingFaceId());
-    }
+  if (isSelfRegistration) {
+    parameters.setFaceId(request->getIncomingFaceId());
+  }
+
+  // Respond since command is valid and authorized
+  sendSuccessResponse(request, parameters);
 
   Route route;
   route.faceId = parameters.getFaceId();
@@ -370,9 +370,14 @@
   NFD_LOG_INFO("Removing route " << parameters.getName() << " nexthop=" << route.faceId
                                                          << " origin=" << route.origin);
 
-  m_managedRib.erase(parameters.getName(), route);
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(parameters.getName())
+        .setRoute(route);
 
-  sendUpdatesToFib(request, parameters);
+  m_managedRib.beginApplyUpdate(update,
+                                bind(&RibManager::onRibUpdateSuccess, this, update),
+                                bind(&RibManager::onRibUpdateFailure, this, update, _1, _2));
 }
 
 void
@@ -380,8 +385,10 @@
                                       const std::string& failureInfo)
 {
   NFD_LOG_DEBUG("RibRequestValidationFailed: " << failureInfo);
-  if (static_cast<bool>(request))
+
+  if (static_cast<bool>(request)) {
     sendResponse(request->getName(), 403, failureInfo);
+  }
 }
 
 
@@ -389,15 +396,13 @@
 RibManager::extractParameters(const Name::Component& parameterComponent,
                               ControlParameters& extractedParameters)
 {
-  try
-    {
-      Block rawParameters = parameterComponent.blockFromValue();
-      extractedParameters.wireDecode(rawParameters);
-    }
-  catch (const tlv::Error&)
-    {
-      return false;
-    }
+  try {
+    Block rawParameters = parameterComponent.blockFromValue();
+    extractedParameters.wireDecode(rawParameters);
+  }
+  catch (const tlv::Error&) {
+    return false;
+  }
 
   NFD_LOG_DEBUG("Parameters parsed OK");
   return true;
@@ -407,14 +412,12 @@
 RibManager::validateParameters(const ControlCommand& command,
                                ControlParameters& parameters)
 {
-  try
-    {
-      command.validateRequest(parameters);
-    }
-  catch (const ControlCommand::ArgumentError&)
-    {
-      return false;
-    }
+  try {
+    command.validateRequest(parameters);
+  }
+  catch (const ControlCommand::ArgumentError&) {
+    return false;
+  }
 
   command.applyDefaultsToRequest(parameters);
 
@@ -430,21 +433,20 @@
 
   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 (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))
+  if (static_cast<bool>(request)) {
     sendResponse(request->getName(), response);
+  }
 }
 
 void
@@ -460,8 +462,9 @@
 
   NFD_LOG_TRACE("onRegSuccess: registered " << route);
 
-  if (static_cast<bool>(request))
+  if (static_cast<bool>(request)) {
     sendResponse(request->getName(), response);
+  }
 }
 
 
@@ -478,18 +481,18 @@
 
   NFD_LOG_TRACE("onUnRegSuccess: unregistered " << route);
 
-  if (static_cast<bool>(request))
+  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;
-    }
+  if (!static_cast<bool>(request)) {
+    return;
+  }
 
   ControlResponse response;
 
@@ -497,8 +500,9 @@
   response.setText("Success");
   response.setBody(parameters.wireEncode());
 
-  if (static_cast<bool>(request))
+  if (static_cast<bool>(request)) {
     sendResponse(request->getName(), response);
+  }
 }
 
 void
@@ -507,28 +511,42 @@
 {
   NFD_LOG_ERROR("NFD returned an error: " << error << " (code: " << code << ")");
 
-  if (!static_cast<bool>(request))
-    {
-      return;
-    }
+  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 (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))
+  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
@@ -547,7 +565,6 @@
   m_managedRib.insert(prefix, route);
 
   m_registeredFaces.insert(route.faceId);
-  m_managedRib.clearFibUpdates();
 }
 
 void
@@ -556,92 +573,6 @@
   throw Error("Error in setting interest filter (" + name.toUri() + "): " + msg);
 }
 
-bool
-RibManager::isTransactionComplete(const TransactionId transactionId)
-{
-  FibTransactionTable::iterator it = m_pendingFibTransactions.find(transactionId);
-
-  if (it != m_pendingFibTransactions.end())
-    {
-      int& updatesLeft = it->second;
-
-      updatesLeft--;
-
-      // All of the updates have been applied successfully
-      if (updatesLeft == 0)
-        {
-          m_pendingFibTransactions.erase(it);
-          return true;
-        }
-    }
-
-    return false;
-}
-
-void
-RibManager::invalidateTransaction(const TransactionId transactionId)
-{
-  FibTransactionTable::iterator it = m_pendingFibTransactions.find(transactionId);
-
-  if (it != m_pendingFibTransactions.end())
-    {
-      m_pendingFibTransactions.erase(it);
-    }
-}
-
-void
-RibManager::onAddNextHopSuccess(const shared_ptr<const Interest>& request,
-                                const ControlParameters& parameters,
-                                const TransactionId transactionId,
-                                const bool shouldSendResponse)
-{
-  if (isTransactionComplete(transactionId) && shouldSendResponse)
-    {
-      sendSuccessResponse(request, parameters);
-    }
-}
-
-void
-RibManager::onAddNextHopError(uint32_t code, const std::string& error,
-                              const shared_ptr<const Interest>& request,
-                              const TransactionId transactionId, const bool shouldSendResponse)
-{
-  invalidateTransaction(transactionId);
-
-  if (shouldSendResponse)
-  {
-    sendErrorResponse(code, error, request);
-  }
-
-  // Since the FIB rejected the update, clean up the invalid face
-  scheduleActiveFaceFetch(time::seconds(1));
-}
-
-void
-RibManager::onRemoveNextHopSuccess(const shared_ptr<const Interest>& request,
-                                   const ControlParameters& parameters,
-                                   const TransactionId transactionId,
-                                   const bool shouldSendResponse)
-{
-  if (isTransactionComplete(transactionId) && shouldSendResponse)
-    {
-      sendSuccessResponse(request, parameters);
-    }
-}
-
-void
-RibManager::onRemoveNextHopError(uint32_t code, const std::string& error,
-                                 const shared_ptr<const Interest>& request,
-                                 const TransactionId transactionId, const bool shouldSendResponse)
-{
-  invalidateTransaction(transactionId);
-
-  if (shouldSendResponse)
-  {
-    sendErrorResponse(code, error, request);
-  }
-}
-
 void
 RibManager::onControlHeaderSuccess()
 {
@@ -672,111 +603,19 @@
 {
   NFD_LOG_TRACE("onNotification: " << notification);
 
-  if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED)
-    {
-      NFD_LOG_DEBUG("Received notification for destroyed faceId: " << notification.getFaceId());
+  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::processErasureAfterNotification, this,
-                               notification.getFaceId()));
-    }
+    scheduler::schedule(time::seconds(0),
+                        bind(&RibManager::onFaceDestroyedEvent, this, notification.getFaceId()));
+  }
 }
 
 void
-RibManager::processErasureAfterNotification(uint64_t faceId)
+RibManager::onFaceDestroyedEvent(uint64_t faceId)
 {
-  m_managedRib.erase(faceId);
+  m_managedRib.beginRemoveFace(faceId);
   m_registeredFaces.erase(faceId);
-
-  sendUpdatesToFibAfterFaceDestroyEvent();
-}
-
-void
-RibManager::sendUpdatesToFib(const shared_ptr<const Interest>& request,
-                             const ControlParameters& parameters)
-{
-  const Rib::FibUpdateList& updates = m_managedRib.getFibUpdates();
-
-  // If no updates were generated, consider the operation a success
-  if (updates.empty())
-    {
-      sendSuccessResponse(request, parameters);
-      return;
-    }
-
-  bool shouldWaitToRespond = false;
-
-  // An application request should wait for all FIB updates to be applied
-  // successfully before sending a response
-  if (parameters.getOrigin() == ndn::nfd::ROUTE_ORIGIN_APP)
-    {
-      shouldWaitToRespond = true;
-    }
-  else // Respond immediately
-    {
-      sendSuccessResponse(request, parameters);
-    }
-
-  std::string updateString = (updates.size() == 1) ? " update" : " updates";
-  NFD_LOG_DEBUG("Applying " << updates.size() << updateString << " to FIB");
-
-  // Assign an ID to this FIB transaction
-  TransactionId currentTransactionId = ++m_lastTransactionId;
-
-  // Add this transaction to the transaction table
-  m_pendingFibTransactions[currentTransactionId] = updates.size();
-
-  for (Rib::FibUpdateList::const_iterator it = updates.begin(); it != updates.end(); ++it)
-    {
-      shared_ptr<const FibUpdate> update(*it);
-      NFD_LOG_DEBUG("Sending FIB update: " << *update);
-
-      if (update->action == FibUpdate::ADD_NEXTHOP)
-        {
-          Route route;
-          route.faceId = update->faceId;
-          route.cost = update->cost;
-
-          m_nfdController.start<ndn::nfd::FibAddNextHopCommand>(
-            ControlParameters()
-              .setName(update->name)
-              .setFaceId(route.faceId)
-              .setCost(route.cost),
-            bind(&RibManager::onAddNextHopSuccess, this, request,
-                                                         parameters,
-                                                         currentTransactionId,
-                                                         shouldWaitToRespond),
-            bind(&RibManager::onAddNextHopError, this, _1, _2, request, currentTransactionId,
-                                                                        shouldWaitToRespond));
-        }
-      else if (update->action == FibUpdate::REMOVE_NEXTHOP)
-        {
-          Route route;
-          route.faceId = update->faceId;
-
-          m_nfdController.start<ndn::nfd::FibRemoveNextHopCommand>(
-            ControlParameters()
-              .setName(update->name)
-              .setFaceId(route.faceId),
-            bind(&RibManager::onRemoveNextHopSuccess, this, request,
-                                                            parameters,
-                                                            currentTransactionId,
-                                                            shouldWaitToRespond),
-            bind(&RibManager::onRemoveNextHopError, this, _1, _2, request, currentTransactionId,
-                                                                           shouldWaitToRespond));
-        }
-    }
-
-  m_managedRib.clearFibUpdates();
-}
-
-void
-RibManager::sendUpdatesToFibAfterFaceDestroyEvent()
-{
-  ControlParameters parameters;
-  parameters.setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC);
-
-  sendUpdatesToFib(shared_ptr<const Interest>(), parameters);
 }
 
 void
@@ -785,13 +624,12 @@
   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;
-    }
+  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();
 }
@@ -830,16 +668,15 @@
   uint64_t currentSegment = data.getName().get(-1).toSegment();
 
   const name::Component& finalBlockId = data.getMetaInfo().getFinalBlockId();
-  if (finalBlockId.empty() || finalBlockId.toSegment() > currentSegment)
-    {
-      m_face.expressInterest(data.getName().getPrefix(-1).appendSegment(currentSegment+1),
-                             bind(&RibManager::fetchSegments, this, _2, buffer),
-                             bind(&RibManager::onFetchFaceStatusTimeout, this));
-    }
-  else
-    {
-      removeInvalidFaces(buffer);
-    }
+
+  if (finalBlockId.empty() || finalBlockId.toSegment() > currentSegment) {
+    m_face.expressInterest(data.getName().getPrefix(-1).appendSegment(currentSegment+1),
+                           bind(&RibManager::fetchSegments, this, _2, buffer),
+                           bind(&RibManager::onFetchFaceStatusTimeout, this));
+  }
+  else {
+    removeInvalidFaces(buffer);
+  }
 }
 
 void
@@ -869,15 +706,14 @@
 
   // Look for face IDs that were registered but not active to find missed
   // face destroyed events
-  for (FaceIdSet::iterator it = m_registeredFaces.begin(); it != m_registeredFaces.end(); ++it)
-    {
-      if (activeFaces.find(*it) == activeFaces.end())
-        {
-          NFD_LOG_DEBUG("Removing invalid face ID: " << *it);
-          scheduler::schedule(time::seconds(0),
-                              bind(&RibManager::processErasureAfterNotification, this, *it));
-        }
+  for (uint64_t faceId : m_registeredFaces) {
+    if (activeFaces.find(faceId) == activeFaces.end()) {
+      NFD_LOG_DEBUG("Removing invalid face ID: " << faceId);
+
+      scheduler::schedule(time::seconds(0),
+                          bind(&RibManager::onFaceDestroyedEvent, this, faceId));
     }
+  }
 
   // Reschedule the check for future clean up
   scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
diff --git a/rib/rib-manager.hpp b/rib/rib-manager.hpp
index 3819d5a..8d90103 100644
--- a/rib/rib-manager.hpp
+++ b/rib/rib-manager.hpp
@@ -30,6 +30,7 @@
 #include "core/config-file.hpp"
 #include "rib-status-publisher.hpp"
 #include "remote-registrator.hpp"
+#include "fib-updater.hpp"
 
 #include <ndn-cxx/security/validator-config.hpp>
 #include <ndn-cxx/management/nfd-face-monitor.hpp>
@@ -74,10 +75,14 @@
   void
   setConfigFile(ConfigFile& configFile);
 
-private:
-  typedef uint32_t TransactionId;
+  void
+  onRibUpdateSuccess(const RibUpdate& update);
 
   void
+  onRibUpdateFailure(const RibUpdate& update, uint32_t code, const std::string& error);
+
+private:
+  void
   onConfig(const ConfigSection& configSection,
            bool isDryRun,
            const std::string& filename);
@@ -116,9 +121,7 @@
   unregisterEntry(const shared_ptr<const Interest>& request,
                   ControlParameters& parameters);
 
-  void
-  expireEntry(const shared_ptr<const Interest>& request, ControlParameters& params);
-
+private:
   void
   onCommandValidated(const shared_ptr<const Interest>& request);
 
@@ -152,28 +155,6 @@
   onNrdCommandPrefixAddNextHopError(const Name& name, const std::string& msg);
 
   void
-  onAddNextHopSuccess(const shared_ptr<const Interest>& request,
-                      const ControlParameters& parameters,
-                      const TransactionId transactionId,
-                      const bool shouldSendResponse);
-
-  void
-  onAddNextHopError(uint32_t code, const std::string& error,
-                    const shared_ptr<const Interest>& request,
-                    const TransactionId transactionId, const bool shouldSendResponse);
-
-  void
-  onRemoveNextHopSuccess(const shared_ptr<const Interest>& request,
-                         const ControlParameters& parameters,
-                         const TransactionId transactionId,
-                         const bool shouldSendResponse);
-
-  void
-  onRemoveNextHopError(uint32_t code, const std::string& error,
-                       const shared_ptr<const Interest>& request,
-                       const TransactionId transactionId, const bool shouldSendResponse);
-
-  void
   onControlHeaderSuccess();
 
   void
@@ -190,27 +171,11 @@
   void
   onNotification(const FaceEventNotification& notification);
 
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   void
-  processErasureAfterNotification(uint64_t faceId);
+  onFaceDestroyedEvent(uint64_t faceId);
 
-  void
-  sendUpdatesToFib(const shared_ptr<const Interest>& request,
-                   const ControlParameters& parameters);
-
-  void
-  sendUpdatesToFibAfterFaceDestroyEvent();
-
-  /** \brief Checks if the transaction has received all of the expected responses
-   *         from the FIB.
-   *  \return{ True if the transaction with the passed transactionId has applied
-   *           all of its FIB updates successfully; otherwise, false }
-   */
-  bool
-  isTransactionComplete(const TransactionId transactionId);
-
-  void
-  invalidateTransaction(const TransactionId transactionId);
-
+private:
   void
   listEntries(const Interest& request);
 
@@ -247,22 +212,10 @@
 
   RibStatusPublisher m_ribStatusPublisher;
 
-  /** \brief The last transaction ID for FIB update response messages.
-   *         Each group of FIB updates applied to the FIB is assigned an incrementing
-   *         ID that is used to track the number of successfully applied updates.
-   */
-  TransactionId m_lastTransactionId;
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  FibUpdater m_fibUpdater;
 
-  /// table of FIB update transactions => count of pending FIB updates
-  typedef std::map<TransactionId, int> FibTransactionTable;
-
-  /** \brief Table used to track the number of FIB updates that have not yet received
-   *         a response from the FIB.
-   *         The table maps a transaction ID to the number of updates remaining for that
-   *         specific transaction.
-   */
-  FibTransactionTable m_pendingFibTransactions;
-
+private:
   typedef function<void(RibManager*,
                         const shared_ptr<const Interest>& request,
                         ControlParameters& parameters)> SignedVerbProcessor;
diff --git a/rib/rib-update-batch.cpp b/rib/rib-update-batch.cpp
new file mode 100644
index 0000000..e639ae3
--- /dev/null
+++ b/rib/rib-update-batch.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-update-batch.hpp"
+
+namespace nfd {
+namespace rib {
+
+RibUpdateBatch::RibUpdateBatch(uint64_t faceId)
+  : m_faceId(faceId)
+{
+}
+
+void
+RibUpdateBatch::add(const RibUpdate& update)
+{
+  BOOST_ASSERT(m_faceId == update.getRoute().faceId);
+
+  m_updates.push_back(update);
+}
+
+RibUpdateBatch::const_iterator
+RibUpdateBatch::begin() const
+{
+  return m_updates.begin();
+}
+
+RibUpdateBatch::const_iterator
+RibUpdateBatch::end() const
+{
+  return m_updates.end();
+}
+
+size_t
+RibUpdateBatch::size() const
+{
+  return m_updates.size();
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/rib-update-batch.hpp b/rib/rib-update-batch.hpp
new file mode 100644
index 0000000..15e9ecd
--- /dev/null
+++ b/rib/rib-update-batch.hpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_RIB_RIB_UPDATE_BATCH_HPP
+#define NFD_RIB_RIB_UPDATE_BATCH_HPP
+
+#include "common.hpp"
+#include "rib-update.hpp"
+
+namespace nfd {
+namespace rib {
+
+typedef std::list<RibUpdate> RibUpdateList;
+
+/** \brief represents a collection of RibUpdates to be applied to a single FaceId
+ */
+class RibUpdateBatch
+{
+public:
+  typedef RibUpdateList::const_iterator const_iterator;
+
+  explicit
+  RibUpdateBatch(uint64_t faceId);
+
+  uint64_t
+  getFaceId() const;
+
+  void
+  add(const RibUpdate& update);
+
+  const_iterator
+  begin() const;
+
+  const_iterator
+  end() const;
+
+  size_t
+  size() const;
+
+private:
+  uint64_t m_faceId;
+  RibUpdateList m_updates;
+};
+
+inline uint64_t
+RibUpdateBatch::getFaceId() const
+{
+  return m_faceId;
+}
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_RIB_RIB_UPDATE_BATCH_HPP
diff --git a/rib/rib-update.cpp b/rib/rib-update.cpp
new file mode 100644
index 0000000..8efe327
--- /dev/null
+++ b/rib/rib-update.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-update.hpp"
+
+namespace nfd {
+namespace rib {
+
+RibUpdate::RibUpdate()
+{
+
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate::Action action)
+{
+  switch (action) {
+  case RibUpdate::REGISTER:
+    os << "REGISTER";
+    break;
+  case RibUpdate::UNREGISTER:
+    os << "UNREGISTER";
+    break;
+  case RibUpdate::REMOVE_FACE:
+    os << "REMOVE_FACE";
+    break;
+  }
+
+  return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate& update)
+{
+  os << "RibUpdate {\n";
+  os << "  Name: " << update.getName() << "\n";
+  os << "  Action: " << update.getAction() << "\n";
+  os << "  " << update.getRoute() << "\n";
+  os << "}";
+
+  return os;
+}
+
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/rib-update.hpp b/rib/rib-update.hpp
new file mode 100644
index 0000000..4b9d5dd
--- /dev/null
+++ b/rib/rib-update.hpp
@@ -0,0 +1,128 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_RIB_RIB_UPDATE_HPP
+#define NFD_RIB_RIB_UPDATE_HPP
+
+#include "common.hpp"
+#include "route.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** RibUpdate
+ *  \brief represents a route that will be added to or removed from a namespace
+ *
+ *  \note This type is copyable so that it can be stored in STL containers.
+ */
+class RibUpdate
+{
+public:
+  enum Action {
+    REGISTER     = 0,
+    UNREGISTER   = 1,
+
+    /** \brief An update triggered by a face destruction notification
+     *
+     *  \note indicates a Route needs to be removed after a face is destroyed
+     */
+    REMOVE_FACE = 2
+  };
+
+  RibUpdate();
+
+  RibUpdate&
+  setAction(Action action);
+
+  Action
+  getAction() const;
+
+  RibUpdate&
+  setName(const Name& name);
+
+  const Name&
+  getName() const;
+
+  RibUpdate&
+  setRoute(const Route& route);
+
+  const Route&
+  getRoute() const;
+
+private:
+  Action m_action;
+  Name m_name;
+  Route m_route;
+};
+
+inline RibUpdate&
+RibUpdate::setAction(Action action)
+{
+  m_action = action;
+  return *this;
+}
+
+inline RibUpdate::Action
+RibUpdate::getAction() const
+{
+  return m_action;
+}
+
+inline RibUpdate&
+RibUpdate::setName(const Name& name)
+{
+  m_name = name;
+  return *this;
+}
+
+inline const Name&
+RibUpdate::getName() const
+{
+  return m_name;
+}
+
+inline RibUpdate&
+RibUpdate::setRoute(const Route& route)
+{
+  m_route = route;
+  return *this;
+}
+
+inline const Route&
+RibUpdate::getRoute() const
+{
+  return m_route;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate::Action action);
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate& update);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_RIB_RIB_UPDATE_HPP
diff --git a/rib/rib.cpp b/rib/rib.cpp
index 5d578ab..4c4d3c5 100644
--- a/rib/rib.cpp
+++ b/rib/rib.cpp
@@ -25,6 +25,7 @@
 
 #include "rib.hpp"
 
+#include "fib-updater.hpp"
 #include "core/logger.hpp"
 
 NFD_LOG_INIT("Rib");
@@ -38,35 +39,22 @@
   return lhs.faceId < rhs.faceId;
 }
 
-static inline bool
-isChildInheritFlagSet(uint64_t flags)
-{
-  return flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
-}
-
-static inline bool
-isCaptureFlagSet(uint64_t flags)
-{
-  return flags & ndn::nfd::ROUTE_FLAG_CAPTURE;
-}
-
-static inline bool
-isAnyFlagSet(uint64_t flags)
-{
-  return isChildInheritFlagSet(flags) || isCaptureFlagSet(flags);
-}
-
-static inline bool
-areBothFlagsSet(uint64_t flags)
-{
-  return isChildInheritFlagSet(flags) && isCaptureFlagSet(flags);
-}
-
 Rib::Rib()
   : m_nItems(0)
+  , m_isUpdateInProgress(false)
 {
 }
 
+Rib::~Rib()
+{
+}
+
+void
+Rib::setFibUpdater(FibUpdater* updater)
+{
+  m_fibUpdater = updater;
+}
+
 Rib::const_iterator
 Rib::find(const Name& prefix) const
 {
@@ -79,17 +67,15 @@
   RibTable::const_iterator ribIt = m_rib.find(prefix);
 
   // Name prefix exists
-  if (ribIt != m_rib.end())
-    {
-      shared_ptr<RibEntry> entry = ribIt->second;
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry = ribIt->second;
 
-      RibEntry::iterator routeIt = entry->findRoute(route);
+    RibEntry::iterator routeIt = entry->findRoute(route);
 
-      if (routeIt != entry->end())
-        {
-          return &((*routeIt));
-        }
+    if (routeIt != entry->end()) {
+      return &((*routeIt));
     }
+  }
 
   return nullptr;
 }
@@ -100,96 +86,73 @@
   RibTable::iterator ribIt = m_rib.find(prefix);
 
   // Name prefix exists
-  if (ribIt != m_rib.end())
-    {
-      shared_ptr<RibEntry> entry(ribIt->second);
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry(ribIt->second);
 
-      RibEntry::iterator routeIt = entry->findRoute(route);
+    RibEntry::iterator routeIt = entry->findRoute(route);
 
-      if (routeIt == entry->end())
-        {
-          // Will the new route change the namespace's capture flag?
-          bool captureWasTurnedOn = (entry->hasCapture() == false && isCaptureFlagSet(route.flags));
-
-          // New route
-          entry->insertRoute(route);
-          m_nItems++;
-
-          // Register with face lookup table
-          m_faceMap[route.faceId].push_back(entry);
-
-          createFibUpdatesForNewRoute(*entry, route, captureWasTurnedOn);
-        }
-      else // Route exists, update fields
-        {
-          // First cancel old scheduled event, if any, then set the EventId to new one
-          if (static_cast<bool>(routeIt->getExpirationEvent()))
-            {
-              NFD_LOG_TRACE("Cancelling expiration event for " << entry->getName() << " "
-                                                               << *routeIt);
-              scheduler::cancel(routeIt->getExpirationEvent());
-            }
-
-          // No checks are required here as the iterator needs to be updated in all cases.
-          routeIt->setExpirationEvent(route.getExpirationEvent());
-
-          // Save flags for update processing
-          uint64_t previousFlags = routeIt->flags;
-
-          // If the route's cost didn't change and child inherit is not set,
-          // no need to traverse subtree.
-          uint64_t previousCost = routeIt->cost;
-
-          routeIt->flags = route.flags;
-          routeIt->cost = route.cost;
-          routeIt->expires = route.expires;
-
-          createFibUpdatesForUpdatedRoute(*entry, route, previousFlags, previousCost);
-        }
-    }
-  else // New name prefix
-    {
-      shared_ptr<RibEntry> entry(make_shared<RibEntry>(RibEntry()));
-
-      m_rib[prefix] = entry;
-      m_nItems++;
-
-      entry->setName(prefix);
+    if (routeIt == entry->end()) {
+      // New route
       entry->insertRoute(route);
-
-      // Find prefix's parent
-      shared_ptr<RibEntry> parent = findParent(prefix);
-
-      // Add self to parent's children
-      if (static_cast<bool>(parent))
-        {
-          parent->addChild(entry);
-        }
-
-      RibEntryList children = findDescendants(prefix);
-
-      for (std::list<shared_ptr<RibEntry> >::iterator child = children.begin();
-           child != children.end(); ++child)
-        {
-          if ((*child)->getParent() == parent)
-            {
-              // Remove child from parent and inherit parent's child
-              if (static_cast<bool>(parent))
-                {
-                  parent->removeChild((*child));
-                }
-              entry->addChild((*child));
-            }
-        }
+      m_nItems++;
 
       // Register with face lookup table
       m_faceMap[route.faceId].push_back(entry);
-
-      createFibUpdatesForNewRibEntry(*entry, route);
-
-      // do something after inserting an entry
-      afterInsertEntry(prefix);
     }
+    else {
+      // Route exists, update fields
+      // First cancel old scheduled event, if any, then set the EventId to new one
+      if (static_cast<bool>(routeIt->getExpirationEvent())) {
+        NFD_LOG_TRACE("Cancelling expiration event for " << entry->getName() << " "
+                                                         << *routeIt);
+        scheduler::cancel(routeIt->getExpirationEvent());
+      }
+
+      // No checks are required here as the iterator needs to be updated in all cases.
+      routeIt->setExpirationEvent(route.getExpirationEvent());
+
+      routeIt->flags = route.flags;
+      routeIt->cost = route.cost;
+      routeIt->expires = route.expires;
+    }
+  }
+  else {
+    // New name prefix
+    shared_ptr<RibEntry> entry(make_shared<RibEntry>(RibEntry()));
+
+    m_rib[prefix] = entry;
+    m_nItems++;
+
+    entry->setName(prefix);
+    entry->insertRoute(route);
+
+    // Find prefix's parent
+    shared_ptr<RibEntry> parent = findParent(prefix);
+
+    // Add self to parent's children
+    if (parent != nullptr) {
+      parent->addChild(entry);
+    }
+
+    RibEntryList children = findDescendants(prefix);
+
+    for (const auto& child : children) {
+      if (child->getParent() == parent) {
+        // Remove child from parent and inherit parent's child
+        if (parent != nullptr) {
+          parent->removeChild(child);
+        }
+
+        entry->addChild(child);
+      }
+    }
+
+    // Register with face lookup table
+    m_faceMap[route.faceId].push_back(entry);
+
+    // do something after inserting an entry
+    afterInsertEntry(prefix);
+  }
 }
 
 void
@@ -198,110 +161,51 @@
   RibTable::iterator ribIt = m_rib.find(prefix);
 
   // Name prefix exists
-  if (ribIt != m_rib.end())
-    {
-      shared_ptr<RibEntry> entry(ribIt->second);
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry(ribIt->second);
 
-      const bool hadCapture = entry->hasCapture();
+    RibEntry::iterator routeIt = entry->findRoute(route);
 
-      // Need to copy route to do FIB updates with correct cost and flags since nfdc does not
-      // pass flags or cost
-      RibEntry::iterator routeIt = entry->findRoute(route);
+    if (routeIt != entry->end()) {
+      entry->eraseRoute(routeIt);
+      m_nItems--;
 
-      if (routeIt != entry->end())
-        {
-          Route routeToErase = *routeIt;
-          routeToErase.flags = routeIt->flags;
-          routeToErase.cost = routeIt->cost;
+      // If this RibEntry no longer has this faceId, unregister from face lookup table
+      if (!entry->hasFaceId(route.faceId)) {
+        m_faceMap[route.faceId].remove(entry);
+      }
 
-          entry->eraseRoute(routeIt);
-
-          m_nItems--;
-
-          const bool captureWasTurnedOff = (hadCapture && !entry->hasCapture());
-
-          createFibUpdatesForErasedRoute(*entry, routeToErase, captureWasTurnedOff);
-
-          // If this RibEntry no longer has this faceId, unregister from face lookup table
-          if (!entry->hasFaceId(route.faceId))
-            {
-              m_faceMap[route.faceId].remove(entry);
-            }
-          else
-            {
-              // The RibEntry still has the face ID; need to update FIB
-              // with lowest cost for the same route instead of removing the route from the FIB
-              shared_ptr<Route> lowCostRoute = entry->getRouteWithLowestCostByFaceId(route.faceId);
-
-              BOOST_ASSERT(static_cast<bool>(lowCostRoute));
-
-              createFibUpdatesForNewRoute(*entry, *lowCostRoute, false);
-            }
-
-          // If a RibEntry's route list is empty, remove it from the tree
-          if (entry->getRoutes().size() == 0)
-            {
-              eraseEntry(ribIt);
-            }
-        }
+      // If a RibEntry's route list is empty, remove it from the tree
+      if (entry->getRoutes().size() == 0) {
+        eraseEntry(ribIt);
+      }
     }
+  }
 }
 
 void
-Rib::erase(const uint64_t faceId)
+Rib::onRouteExpiration(const Name& prefix, const Route& route)
 {
-  FaceLookupTable::iterator lookupIt = m_faceMap.find(faceId);
+  NFD_LOG_DEBUG(route << " for " << prefix << " has expired");
 
-  // No RIB entries have this face
-  if (lookupIt == m_faceMap.end())
-    {
-      return;
-    }
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(prefix)
+        .setRoute(route);
 
-  RibEntryList& ribEntries = lookupIt->second;
-
-  // For each RIB entry that has faceId, remove the face from the entry
-  for (shared_ptr<RibEntry>& entry : ribEntries)
-    {
-      const bool hadCapture = entry->hasCapture();
-
-      // Find the routes in the entry
-      for (RibEntry::iterator routeIt = entry->begin(); routeIt != entry->end(); ++routeIt)
-        {
-          if (routeIt->faceId == faceId)
-            {
-              Route copy = *routeIt;
-
-              routeIt = entry->eraseRoute(routeIt);
-              m_nItems--;
-
-              const bool captureWasTurnedOff = (hadCapture && !entry->hasCapture());
-              createFibUpdatesForErasedRoute(*entry, copy, captureWasTurnedOff);
-            }
-        }
-
-        // If a RibEntry's route list is empty, remove it from the tree
-        if (entry->getRoutes().size() == 0)
-          {
-            eraseEntry(m_rib.find(entry->getName()));
-          }
-    }
-
-  // Face no longer exists, remove from face lookup table
-  m_faceMap.erase(lookupIt);
+  beginApplyUpdate(update, nullptr, nullptr);
 }
 
 shared_ptr<RibEntry>
 Rib::findParent(const Name& prefix) const
 {
-  for (int i = prefix.size() - 1; i >= 0; i--)
-    {
-      RibTable::const_iterator it = m_rib.find(prefix.getPrefix(i));
-      if (it != m_rib.end())
-        {
-          return (it->second);
-        }
+  for (int i = prefix.size() - 1; i >= 0; i--) {
+    RibTable::const_iterator it = m_rib.find(prefix.getPrefix(i));
+
+    if (it != m_rib.end()) {
+      return (it->second);
     }
+  }
 
   return shared_ptr<RibEntry>();
 }
@@ -313,21 +217,31 @@
 
   RibTable::const_iterator it = m_rib.find(prefix);
 
-  if (it != m_rib.end())
-    {
-      ++it;
-      for (; it != m_rib.end(); ++it)
-        {
-          if (prefix.isPrefixOf(it->first))
-            {
-              children.push_back((it->second));
-            }
-          else
-            {
-              break;
-            }
-        }
+  if (it != m_rib.end()) {
+    ++it;
+    for (; it != m_rib.end(); ++it) {
+      if (prefix.isPrefixOf(it->first)) {
+        children.push_back((it->second));
+      }
+      else {
+        break;
+      }
     }
+  }
+
+  return children;
+}
+
+std::list<shared_ptr<RibEntry>>
+Rib::findDescendantsForNonInsertedName(const Name& prefix) const
+{
+  std::list<shared_ptr<RibEntry>> children;
+
+  for (std::pair<Name, shared_ptr<RibEntry>> pair : m_rib) {
+    if (prefix.isPrefixOf(pair.first)) {
+      children.push_back(pair.second);
+    }
+  }
 
   return children;
 }
@@ -336,43 +250,35 @@
 Rib::eraseEntry(RibTable::iterator it)
 {
   // Entry does not exist
-  if (it == m_rib.end())
-    {
-      return m_rib.end();
-    }
+  if (it == m_rib.end()) {
+    return m_rib.end();
+  }
 
   shared_ptr<RibEntry> entry(it->second);
 
-  // Remove inherited routes from namespace
-  createFibUpdatesForErasedRibEntry(*entry);
-
   shared_ptr<RibEntry> parent = entry->getParent();
 
   // Remove self from parent's children
-  if (static_cast<bool>(parent))
-    {
-      parent->removeChild(entry);
+  if (parent != nullptr) {
+    parent->removeChild(entry);
+  }
+
+  for (auto childIt = entry->getChildren().begin(); childIt != entry->getChildren().end(); ) {
+    shared_ptr<RibEntry> child = *childIt;
+
+    // Advance iterator so it is not invalidated by removal
+    ++childIt;
+
+    // Remove children from self
+    entry->removeChild(child);
+
+    // Update parent's children
+    if (parent != nullptr) {
+      parent->addChild(child);
     }
+  }
 
-  std::list<shared_ptr<RibEntry> > children = entry->getChildren();
-
-  for (RibEntryList::iterator child = children.begin(); child != children.end(); ++child)
-    {
-      // Remove children from self
-      entry->removeChild(*child);
-
-      // Update parent's children
-      if (static_cast<bool>(parent))
-        {
-          parent->addChild(*child);
-        }
-    }
-
-  // Must save and advance iterator to return a valid iterator
-  RibTable::iterator nextIt = it;
-  nextIt++;
-
-  m_rib.erase(it);
+  RibTable::iterator nextIt = m_rib.erase(it);
 
   // do something after erasing an entry.
   afterEraseEntry(entry->getName());
@@ -380,325 +286,6 @@
   return nextIt;
 }
 
-bool
-compareFibUpdates(const shared_ptr<const FibUpdate> lhs, const shared_ptr<const FibUpdate> rhs)
-{
-  return ((lhs->name == rhs->name) &&
-          (lhs->faceId == rhs->faceId));
-}
-
-void
-Rib::insertFibUpdate(shared_ptr<FibUpdate> update)
-{
-  // If an update with the same name and Face ID already exists, replace it
-  FibUpdateList::iterator it = std::find_if(m_fibUpdateList.begin(), m_fibUpdateList.end(),
-                                            bind(&compareFibUpdates, _1, update));
-
-  if (it != m_fibUpdateList.end())
-    {
-      // Get rid of the const to alter the action, prevents copying or removal and
-      // insertion
-      FibUpdate& entry = const_cast<FibUpdate&>(*(*it));
-      entry.action = update->action;
-      entry.cost = update->cost;
-    }
-  else
-    {
-      m_fibUpdateList.push_back(update);
-    }
-}
-
-void
-Rib::createFibUpdatesForNewRibEntry(RibEntry& entry, const Route& route)
-{
-  // Create FIB update for new entry
-  insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
-
-  // No flags are set
-  if (!isAnyFlagSet(route.flags))
-    {
-      // Add ancestor routes to self
-      addInheritedRoutesToEntry(entry, getAncestorRoutes(entry));
-    }
-  else if (areBothFlagsSet(route.flags))
-    {
-      // Add route to children
-      RouteSet routesToAdd;
-      routesToAdd.insert(route);
-
-      // Remove routes blocked by capture and add self to children
-      modifyChildrensInheritedRoutes(entry, routesToAdd, getAncestorRoutes(entry));
-    }
-  else if (isChildInheritFlagSet(route.flags))
-    {
-      RouteSet ancestorRoutes = getAncestorRoutes(entry);
-
-      // Add ancestor routes to self
-      addInheritedRoutesToEntry(entry, ancestorRoutes);
-
-      // If there is an ancestor route with the same Face ID as the new route, replace it
-      // with the new route
-      RouteSet::iterator it = ancestorRoutes.find(route);
-
-      // There is a route that needs to be overwritten, erase and then replace
-      if (it != ancestorRoutes.end())
-        {
-          ancestorRoutes.erase(it);
-        }
-
-      // Add new route to ancestor list so it can be added to children
-      ancestorRoutes.insert(route);
-
-      // Add ancestor routes to children
-      modifyChildrensInheritedRoutes(entry, ancestorRoutes, RouteSet());
-    }
-  else if (isCaptureFlagSet(route.flags))
-    {
-      // Remove routes blocked by capture
-      modifyChildrensInheritedRoutes(entry, RouteSet(), getAncestorRoutes(entry));
-    }
-}
-
-void
-Rib::createFibUpdatesForNewRoute(RibEntry& entry, const Route& route, bool captureWasTurnedOn)
-{
-  // Only update if the new route has a lower cost than a previously installed route
-  shared_ptr<Route> prevRoute = entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
-
-  RouteSet routesToAdd;
-  if (isChildInheritFlagSet(route.flags))
-    {
-      // Add to children if this new route doesn't override a previous lower cost, or
-      // add to children if this new route is lower cost than a previous route.
-      // Less than equal, since entry may find this route
-      if (prevRoute == nullptr || route.cost <= prevRoute->cost)
-        {
-          // Add self to children
-          routesToAdd.insert(route);
-        }
-    }
-
-  RouteSet routesToRemove;
-  if (captureWasTurnedOn)
-    {
-      // Capture flag on
-      routesToRemove = getAncestorRoutes(entry);
-
-      // Remove ancestor routes from self
-      removeInheritedRoutesFromEntry(entry, routesToRemove);
-    }
-
-  modifyChildrensInheritedRoutes(entry, routesToAdd, routesToRemove);
-
-  // If another route with same faceId and lower cost, don't update.
-  // Must be done last so that add updates replace removal updates
-  // Create FIB update for new entry
-  if (route.cost <= entry.getRouteWithLowestCostByFaceId(route.faceId)->cost)
-    {
-      insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
-    }
-}
-
-void
-Rib::createFibUpdatesForUpdatedRoute(RibEntry& entry, const Route& route,
-                                     const uint64_t previousFlags, const uint64_t previousCost)
-{
-  const bool costDidChange = (route.cost != previousCost);
-
-  // Look for the installed route with the lowest cost and child inherit set
-  shared_ptr<Route> prevRoute = entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
-
-  // No flags changed and cost didn't change, no change in FIB
-  if (route.flags == previousFlags && !costDidChange)
-    {
-      return;
-    }
-
-  // Cost changed so create update for the entry itself
-  if (costDidChange)
-    {
-      // Create update if this route's cost is lower than other routes
-       if (route.cost <= entry.getRouteWithLowestCostByFaceId(route.faceId)->cost)
-        {
-          // Create FIB update for the updated entry
-         insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
-        }
-      else if (previousCost < entry.getRouteWithLowestCostByFaceId(route.faceId)->cost)
-        {
-          // Create update if this route used to be the lowest cost route but is no longer
-          // the lowest cost route.
-          insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), prevRoute->faceId,
-                                                                      prevRoute->cost));
-        }
-
-      // If another route with same faceId and lower cost and ChildInherit exists,
-      // don't update children.
-      if (prevRoute == nullptr || route.cost <= prevRoute->cost)
-        {
-          // If no flags changed but child inheritance is set, need to update children
-          // with new cost
-          if ((route.flags == previousFlags) && isChildInheritFlagSet(route.flags))
-          {
-            // Add self to children
-            RouteSet routesToAdd;
-            routesToAdd.insert(route);
-            modifyChildrensInheritedRoutes(entry, routesToAdd, RouteSet());
-
-            return;
-          }
-        }
-    }
-
-  // Child inherit was turned on
-  if (!isChildInheritFlagSet(previousFlags) && isChildInheritFlagSet(route.flags))
-    {
-      // If another route with same faceId and lower cost and ChildInherit exists,
-      // don't update children.
-      if (prevRoute == nullptr || route.cost <= prevRoute->cost)
-        {
-          // Add self to children
-          RouteSet routesToAdd;
-          routesToAdd.insert(route);
-          modifyChildrensInheritedRoutes(entry, routesToAdd, RouteSet());
-        }
-
-    } // Child inherit was turned off
-  else if (isChildInheritFlagSet(previousFlags) && !isChildInheritFlagSet(route.flags))
-    {
-      // Remove self from children
-      RouteSet routesToRemove;
-      routesToRemove.insert(route);
-
-      RouteSet routesToAdd;
-      // If another route with same faceId and ChildInherit exists, update children with this route.
-      if (prevRoute != nullptr)
-        {
-          routesToAdd.insert(*prevRoute);
-        }
-      else
-        {
-          // Look for an ancestor that was blocked previously
-          const RouteSet ancestorRoutes = getAncestorRoutes(entry);
-          RouteSet::iterator it = ancestorRoutes.find(route);
-
-          // If an ancestor is found, add it to children
-          if (it != ancestorRoutes.end())
-            {
-              routesToAdd.insert(*it);
-            }
-        }
-
-      modifyChildrensInheritedRoutes(entry, routesToAdd, routesToRemove);
-    }
-
-  // Capture was turned on
-  if (!isCaptureFlagSet(previousFlags) && isCaptureFlagSet(route.flags))
-    {
-      RouteSet ancestorRoutes = getAncestorRoutes(entry);
-
-      // Remove ancestor routes from self
-      removeInheritedRoutesFromEntry(entry, ancestorRoutes);
-
-      // Remove ancestor routes from children
-      modifyChildrensInheritedRoutes(entry, RouteSet(), ancestorRoutes);
-    }  // Capture was turned off
-  else if (isCaptureFlagSet(previousFlags) && !isCaptureFlagSet(route.flags))
-    {
-      RouteSet ancestorRoutes = getAncestorRoutes(entry);
-
-      // Add ancestor routes to self
-      addInheritedRoutesToEntry(entry, ancestorRoutes);
-
-      // Add ancestor routes to children
-      modifyChildrensInheritedRoutes(entry, ancestorRoutes, RouteSet());
-    }
-}
-
-void
-Rib::createFibUpdatesForErasedRoute(RibEntry& entry, const Route& route,
-                                    const bool captureWasTurnedOff)
-{
-  insertFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
-
-  if (areBothFlagsSet(route.flags))
-    {
-      // Remove self from children
-      RouteSet routesToRemove;
-      routesToRemove.insert(route);
-
-      // If capture is turned off for the route, need to add ancestors
-      // to self and children
-      RouteSet routesToAdd;
-      if (captureWasTurnedOff)
-        {
-          // Look for ancestors that were blocked previously
-          routesToAdd = getAncestorRoutes(entry);
-
-          // Add ancestor routes to self
-          addInheritedRoutesToEntry(entry, routesToAdd);
-        }
-
-      modifyChildrensInheritedRoutes(entry, routesToAdd, routesToRemove);
-    }
-  else if (isChildInheritFlagSet(route.flags))
-    {
-      // If not blocked by capture, add inherited routes to children
-      RouteSet routesToAdd;
-      if (!entry.hasCapture())
-        {
-          routesToAdd = getAncestorRoutes(entry);
-        }
-
-      RouteSet routesToRemove;
-      routesToRemove.insert(route);
-
-      // Add ancestor routes to children
-      modifyChildrensInheritedRoutes(entry, routesToAdd, routesToRemove);
-    }
-  else if (isCaptureFlagSet(route.flags))
-    {
-      // If capture is turned off for the route, need to add ancestors
-      // to self and children
-      RouteSet routesToAdd;
-      if (captureWasTurnedOff)
-        {
-          // Look for an ancestors that were blocked previously
-          routesToAdd = getAncestorRoutes(entry);
-
-          // Add ancestor routes to self
-          addInheritedRoutesToEntry(entry, routesToAdd);
-        }
-
-      modifyChildrensInheritedRoutes(entry, routesToAdd, RouteSet());
-    }
-
-  // Need to check if the removed route was blocking an inherited route
-  RouteSet ancestorRoutes = getAncestorRoutes(entry);
-
-  if (!entry.hasCapture())
-  {
-    // If there is an ancestor route with the same Face ID as the erased route, add that route
-    // to the current entry
-    RouteSet::iterator it = ancestorRoutes.find(route);
-
-    if (it != ancestorRoutes.end())
-      {
-        entry.addInheritedRoute(*it);
-        insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), it->faceId, it->cost));
-      }
-  }
-}
-
-void
-Rib::createFibUpdatesForErasedRibEntry(RibEntry& entry)
-{
-  for (RibEntry::RouteList::iterator it = entry.getInheritedRoutes().begin();
-       it != entry.getInheritedRoutes().end(); ++it)
-    {
-      insertFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), it->faceId));
-    }
-}
-
 Rib::RouteSet
 Rib::getAncestorRoutes(const RibEntry& entry) const
 {
@@ -706,145 +293,216 @@
 
   shared_ptr<RibEntry> parent = entry.getParent();
 
-  while (static_cast<bool>(parent))
-    {
-      for (RibEntry::iterator it = parent->getRoutes().begin();
-           it != parent->getRoutes().end(); ++it)
-        {
-          if (isChildInheritFlagSet(it->flags))
-            {
-              ancestorRoutes.insert(*it);
-            }
-        }
-
-      if (parent->hasCapture())
-        {
-          break;
-        }
-
-      parent = parent->getParent();
-    }
-
-    return ancestorRoutes;
-}
-
-void
-Rib::addInheritedRoutesToEntry(RibEntry& entry, const Rib::RouteSet& routesToAdd)
-{
-  for (RouteSet::const_iterator it = routesToAdd.begin(); it != routesToAdd.end(); ++it)
-    {
-      // Don't add an ancestor route if the namespace has a route with that Face ID
-      if (!entry.hasFaceId(it->faceId))
-        {
-          entry.addInheritedRoute(*it);
-          insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), it->faceId, it->cost));
-        }
-    }
-}
-
-void
-Rib::removeInheritedRoutesFromEntry(RibEntry& entry, const Rib::RouteSet& routesToRemove)
-{
-  for (RouteSet::const_iterator it = routesToRemove.begin(); it != routesToRemove.end(); ++it)
-    {
-      // Only remove if the route has been inherited
-      if (entry.hasInheritedRoute(*it))
-        {
-          entry.removeInheritedRoute(*it);
-          insertFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), it->faceId));
-        }
-    }
-}
-
-void
-Rib::modifyChildrensInheritedRoutes(RibEntry& entry, const Rib::RouteSet& routesToAdd,
-                                                     const Rib::RouteSet& routesToRemove)
-{
-  RibEntryList children = entry.getChildren();
-
-  for (RibEntryList::iterator child = children.begin(); child != children.end(); ++child)
-    {
-      traverseSubTree(*(*child), routesToAdd, routesToRemove);
-    }
-}
-
-void
-Rib::traverseSubTree(RibEntry& entry, Rib::RouteSet routesToAdd, Rib::RouteSet routesToRemove)
-{
-  // If a route on the namespace has the capture flag set, ignore self and children
-  if (entry.hasCapture())
-    {
-      return;
-    }
-
-  // Remove inherited routes from current namespace
-  for (Rib::RouteSet::const_iterator removeIt = routesToRemove.begin();
-       removeIt != routesToRemove.end(); )
-    {
-      // If a route on the namespace has the same face and child inheritance set, ignore this route
-      if (entry.hasChildInheritOnFaceId(removeIt->faceId))
-        {
-          routesToRemove.erase(removeIt++);
-          continue;
-        }
-
-      // Only remove route if it removes an existing inherited route
-      if (entry.hasInheritedRoute(*removeIt))
-        {
-          entry.removeInheritedRoute(*removeIt);
-          insertFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), removeIt->faceId));
-        }
-
-      ++removeIt;
-    }
-
-  // Add inherited routes to current namespace
-  for (Rib::RouteSet::const_iterator addIt = routesToAdd.begin(); addIt != routesToAdd.end(); )
-    {
-      // If a route on the namespace has the same face and child inherit set, ignore this route
-      if (entry.hasChildInheritOnFaceId(addIt->faceId))
-      {
-        routesToAdd.erase(addIt++);
-        continue;
+  while (parent != nullptr) {
+    for (const Route& route : parent->getRoutes()) {
+      if (route.isChildInherit()) {
+        ancestorRoutes.insert(route);
       }
-
-      // Only add route if it does not override an existing route
-      if (!entry.hasFaceId(addIt->faceId))
-        {
-          RibEntry::RouteList::iterator routeIt = entry.findInheritedRoute(*addIt);
-
-          // If the entry already has the inherited route, just update the route
-          if (routeIt != entry.getInheritedRoutes().end())
-            {
-              routeIt->cost = addIt->cost;
-            }
-          else // Otherwise, this is a newly inherited route
-            {
-              entry.addInheritedRoute(*addIt);
-            }
-
-          insertFibUpdate(FibUpdate::createAddUpdate(entry.getName(), addIt->faceId, addIt->cost));
-        }
-
-      ++addIt;
     }
 
-  Rib::RibEntryList children = entry.getChildren();
-
-  // Apply route operations to current namespace's children
-  for (Rib::RibEntryList::iterator child = children.begin(); child != children.end(); ++child)
-    {
-      traverseSubTree(*(*child), routesToAdd, routesToRemove);
+    if (parent->hasCapture()) {
+      break;
     }
+
+    parent = parent->getParent();
+  }
+
+  return ancestorRoutes;
+}
+
+Rib::RouteSet
+Rib::getAncestorRoutes(const Name& name) const
+{
+  RouteSet ancestorRoutes(&sortRoutes);
+
+  shared_ptr<RibEntry> parent = findParent(name);
+
+  while (parent != nullptr) {
+    for (const Route& route : parent->getRoutes()) {
+      if (route.isChildInherit()) {
+        ancestorRoutes.insert(route);
+      }
+    }
+
+    if (parent->hasCapture()) {
+      break;
+    }
+
+    parent = parent->getParent();
+  }
+
+  return ancestorRoutes;
+}
+
+void
+Rib::beginApplyUpdate(const RibUpdate& update,
+                      const Rib::UpdateSuccessCallback& onSuccess,
+                      const Rib::UpdateFailureCallback& onFailure)
+{
+  BOOST_ASSERT(m_fibUpdater != nullptr);
+
+  addUpdateToQueue(update, onSuccess, onFailure);
+
+  sendBatchFromQueue();
+}
+
+void
+Rib::beginRemoveFace(uint64_t faceId)
+{
+  for (const auto& nameAndRoute : findRoutesWithFaceId(faceId)) {
+    RibUpdate update;
+    update.setAction(RibUpdate::REMOVE_FACE)
+          .setName(nameAndRoute.first)
+          .setRoute(nameAndRoute.second);
+
+    addUpdateToQueue(update, nullptr, nullptr);
+  }
+
+  sendBatchFromQueue();
+}
+
+void
+Rib::addUpdateToQueue(const RibUpdate& update,
+                      const Rib::UpdateSuccessCallback& onSuccess,
+                      const Rib::UpdateFailureCallback& onFailure)
+{
+  RibUpdateBatch batch(update.getRoute().faceId);
+  batch.add(update);
+
+  UpdateQueueItem item{batch, onSuccess, onFailure};
+  m_updateBatches.push_back(std::move(item));
+}
+
+void
+Rib::sendBatchFromQueue()
+{
+  if (m_updateBatches.empty() || m_isUpdateInProgress) {
+    return;
+  }
+
+  m_isUpdateInProgress = true;
+
+  UpdateQueueItem item = std::move(m_updateBatches.front());
+  m_updateBatches.pop_front();
+
+  RibUpdateBatch& batch = item.batch;
+
+  // Until task #1698, each RibUpdateBatch contains exactly one RIB update
+  BOOST_ASSERT(batch.size() == 1);
+
+  const Rib::UpdateSuccessCallback& managerSuccessCallback = item.managerSuccessCallback;
+  const Rib::UpdateFailureCallback& managerFailureCallback = item.managerFailureCallback;
+
+  m_fibUpdater->computeAndSendFibUpdates(batch,
+                                         bind(&Rib::onFibUpdateSuccess, this,
+                                              batch, _1, managerSuccessCallback),
+                                         bind(&Rib::onFibUpdateFailure, this,
+                                              managerFailureCallback, _1, _2));
+
+  if (m_onSendBatchFromQueue != nullptr) {
+    m_onSendBatchFromQueue(batch);
+  }
+}
+
+void
+Rib::onFibUpdateSuccess(const RibUpdateBatch& batch,
+                        const RibUpdateList& inheritedRoutes,
+                        const Rib::UpdateSuccessCallback& onSuccess)
+{
+  for (const RibUpdate& update : batch) {
+    switch (update.getAction()) {
+    case RibUpdate::REGISTER:
+      insert(update.getName(), update.getRoute());
+      break;
+    case RibUpdate::UNREGISTER:
+    case RibUpdate::REMOVE_FACE:
+      erase(update.getName(), update.getRoute());
+      break;
+    }
+  }
+
+  // Add and remove precalculated inherited routes to RibEntries
+  modifyInheritedRoutes(inheritedRoutes);
+
+  m_isUpdateInProgress = false;
+
+  if (onSuccess != nullptr) {
+    onSuccess();
+  }
+
+  // Try to advance the batch queue
+  sendBatchFromQueue();
+}
+
+void
+Rib::onFibUpdateFailure(const Rib::UpdateFailureCallback& onFailure,
+                        uint32_t code, const std::string& error)
+{
+  m_isUpdateInProgress = false;
+
+  if (onFailure != nullptr) {
+    onFailure(code, error);
+  }
+
+  // Try to advance the batch queue
+  sendBatchFromQueue();
+}
+
+void
+Rib::modifyInheritedRoutes(const RibUpdateList& inheritedRoutes)
+{
+  for (const RibUpdate& update : inheritedRoutes) {
+    RibTable::iterator ribIt = m_rib.find(update.getName());
+
+    BOOST_ASSERT(ribIt != m_rib.end());
+    shared_ptr<RibEntry> entry(ribIt->second);
+
+    switch (update.getAction()) {
+    case RibUpdate::REGISTER:
+      entry->addInheritedRoute(update.getRoute());
+      break;
+    case RibUpdate::UNREGISTER:
+      entry->removeInheritedRoute(update.getRoute());
+      break;
+    case RibUpdate::REMOVE_FACE:
+      break;
+    }
+  }
+}
+
+std::list<Rib::NameAndRoute>
+Rib::findRoutesWithFaceId(uint64_t faceId)
+{
+  std::list<NameAndRoute> routes;
+
+  FaceLookupTable::iterator lookupIt = m_faceMap.find(faceId);
+
+  // No RIB entries have this face
+  if (lookupIt == m_faceMap.end()) {
+    return routes;
+  }
+
+  RibEntryList& ribEntries = lookupIt->second;
+
+  // For each RIB entry that has faceId
+  for (const shared_ptr<RibEntry>& entry : ribEntries) {
+    // Find the routes in the entry
+    for (const Route& route : *entry) {
+      if (route.faceId == faceId) {
+        routes.push_back(NameAndRoute(entry->getName(), route));
+      }
+    }
+  }
+
+  return routes;
 }
 
 std::ostream&
 operator<<(std::ostream& os, const Rib& rib)
 {
-  for (Rib::RibTable::const_iterator it = rib.begin(); it != rib.end(); ++it)
-    {
-      os << *(it->second) << "\n";
-    }
+  for (const auto& item : rib) {
+    os << item.second << "\n";
+  }
 
   return os;
 }
diff --git a/rib/rib.hpp b/rib/rib.hpp
index dabf69f..2e13692 100644
--- a/rib/rib.hpp
+++ b/rib/rib.hpp
@@ -26,15 +26,20 @@
 #ifndef NFD_RIB_RIB_HPP
 #define NFD_RIB_RIB_HPP
 
-#include "rib-entry.hpp"
-#include "fib-update.hpp"
 #include "common.hpp"
-#include <ndn-cxx/management/nfd-control-command.hpp>
-#include <ndn-cxx/util/signal.hpp>
+
+#include "rib-entry.hpp"
+#include "rib-update-batch.hpp"
+
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
 
 namespace nfd {
 namespace rib {
 
+using ndn::nfd::ControlParameters;
+
+class FibUpdater;
+
 /** \brief represents the RIB
  */
 class Rib : noncopyable
@@ -46,25 +51,20 @@
   typedef std::map<uint64_t, std::list<shared_ptr<RibEntry>>> FaceLookupTable;
   typedef bool (*RouteComparePredicate)(const Route&, const Route&);
   typedef std::set<Route, RouteComparePredicate> RouteSet;
-  typedef std::list<shared_ptr<const FibUpdate>> FibUpdateList;
 
   Rib();
 
+  ~Rib();
+
+  void
+  setFibUpdater(FibUpdater* updater);
+
   const_iterator
   find(const Name& prefix) const;
 
   Route*
   find(const Name& prefix, const Route& route) const;
 
-  void
-  insert(const Name& prefix, const Route& route);
-
-  void
-  erase(const Name& prefix, const Route& route);
-
-  void
-  erase(const uint64_t faceId);
-
   const_iterator
   begin() const;
 
@@ -86,56 +86,123 @@
   std::list<shared_ptr<RibEntry>>
   findDescendants(const Name& prefix) const;
 
-  const std::list<shared_ptr<const FibUpdate>>&
-  getFibUpdates() const;
+  /** \brief finds namespaces under the passed prefix
+   *
+   *  \note Unlike findDescendants, needs to find where prefix would fit in tree
+   *  before collecting list of descendant prefixes
+   *
+   *  \return{ a list of entries which would be under the passed prefix if the prefix
+   *  existed in the RIB }
+   */
+  std::list<shared_ptr<RibEntry>>
+  findDescendantsForNonInsertedName(const Name& prefix) const;
+
+public:
+  typedef function<void()> UpdateSuccessCallback;
+  typedef function<void(uint32_t code, const std::string& error)> UpdateFailureCallback;
+
+  /** \brief passes the provided RibUpdateBatch to FibUpdater to calculate and send FibUpdates.
+   *
+   *  If the FIB is updated successfully, onFibUpdateSuccess() will be called, and the
+   *  RIB will be updated
+   *
+   *  If the FIB update fails, onFibUpdateFailure() will be called, and the RIB will not
+   *  be updated.
+   */
+  void
+  beginApplyUpdate(const RibUpdate& update,
+                   const UpdateSuccessCallback& onSuccess,
+                   const UpdateFailureCallback& onFailure);
+
+  /** \brief starts the FIB update process when a face has been destroyed
+   */
+  void
+  beginRemoveFace(uint64_t faceId);
 
   void
-  clearFibUpdates();
+  onFibUpdateSuccess(const RibUpdateBatch& batch,
+                     const RibUpdateList& inheritedRoutes,
+                     const Rib::UpdateSuccessCallback& onSuccess);
+
+  void
+  onFibUpdateFailure(const Rib::UpdateFailureCallback& onFailure,
+                     uint32_t code, const std::string& error);
+
+  void
+  onRouteExpiration(const Name& prefix, const Route& route);
+
+private:
+  /** \brief adds the passed update to a RibUpdateBatch and adds the batch to
+  *          the end of the update queue.
+  *
+  *   If an update is not in progress, the front update batch in the queue will be
+  *   processed by the RIB.
+  *
+  *   If an update is in progress, the added update will eventually be processed
+  *   when it reaches the front of the queue; after other update batches are
+  *   processed, the queue is advanced.
+  */
+  void
+  addUpdateToQueue(const RibUpdate& update,
+                   const Rib::UpdateSuccessCallback& onSuccess,
+                   const Rib::UpdateFailureCallback& onFailure);
+
+  /** \brief Attempts to send the front update batch in the queue.
+  *
+  *   If an update is not in progress, the front update batch in the queue will be
+  *   sent to the RIB for processing.
+  *
+  *   If an update is in progress, nothing will be done.
+  */
+  void
+  sendBatchFromQueue();
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  // Used by RibManager unit-tests to get sent batch to simulate successful FIB update
+  function<void(RibUpdateBatch)> m_onSendBatchFromQueue;
+
+public:
+  void
+  insert(const Name& prefix, const Route& route);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  void
+  erase(const Name& prefix, const Route& route);
 
 private:
   RibTable::iterator
   eraseEntry(RibTable::iterator it);
 
   void
-  insertFibUpdate(shared_ptr<FibUpdate> update);
+  updateRib(const RibUpdateBatch& batch);
 
-  void
-  createFibUpdatesForNewRibEntry(RibEntry& entry, const Route& route);
-
-  void
-  createFibUpdatesForNewRoute(RibEntry& entry, const Route& route,
-                              const bool captureWasTurnedOn);
-
-  void
-  createFibUpdatesForUpdatedRoute(RibEntry& entry, const Route& route,
-                                  const uint64_t previousFlags, const uint64_t previousCost);
-  void
-  createFibUpdatesForErasedRoute(RibEntry& entry, const Route& route,
-                                 const bool captureWasTurnedOff);
-
-  void
-  createFibUpdatesForErasedRibEntry(RibEntry& entry);
-
+  /** \brief returns routes inherited from the entry's ancestors
+   *
+   *  \return{ a list of inherited routes }
+   */
   RouteSet
   getAncestorRoutes(const RibEntry& entry) const;
 
-  void
-  modifyChildrensInheritedRoutes(RibEntry& entry, const Rib::RouteSet& routesToAdd,
-                                                  const Rib::RouteSet& routesToRemove);
+  /** \brief returns routes inherited from the parent of the name and the parent's ancestors
+   *
+   *  \note A parent is first found for the passed name before inherited routes are collected
+   *
+   *  \return{ a list of inherited routes }
+   */
+  RouteSet
+  getAncestorRoutes(const Name& name) const;
 
-  void
-  traverseSubTree(RibEntry& entry, Rib::RouteSet routesToAdd,
-                                   Rib::RouteSet routesToRemove);
-
-  /** \brief Adds passed routes to the entry's inherited routes list
+  /** \brief applies the passed inheritedRoutes and their actions to the corresponding RibEntries'
+   *  inheritedRoutes lists
    */
   void
-  addInheritedRoutesToEntry(RibEntry& entry, const Rib::RouteSet& routesToAdd);
+  modifyInheritedRoutes(const RibUpdateList& inheritedRoutes);
 
-  /** \brief Removes passed routes from the entry's inherited routes list
-   */
-  void
-  removeInheritedRoutesFromEntry(RibEntry& entry, const Rib::RouteSet& routesToRemove);
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  typedef std::pair<const Name&,const Route&> NameAndRoute;
+
+  std::list<NameAndRoute>
+  findRoutesWithFaceId(uint64_t faceId);
 
 public:
   ndn::util::signal::Signal<Rib, Name> afterInsertEntry;
@@ -144,9 +211,26 @@
 private:
   RibTable m_rib;
   FaceLookupTable m_faceMap;
-  FibUpdateList m_fibUpdateList;
+  FibUpdater* m_fibUpdater;
 
   size_t m_nItems;
+
+  friend class FibUpdater;
+
+private:
+  struct UpdateQueueItem
+  {
+    RibUpdateBatch batch;
+    const Rib::UpdateSuccessCallback managerSuccessCallback;
+    const Rib::UpdateFailureCallback managerFailureCallback;
+  };
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  typedef std::list<UpdateQueueItem> UpdateQueue;
+  UpdateQueue m_updateBatches;
+
+private:
+  bool m_isUpdateInProgress;
 };
 
 inline Rib::const_iterator
@@ -173,18 +257,6 @@
   return m_rib.empty();
 }
 
-inline const Rib::FibUpdateList&
-Rib::getFibUpdates() const
-{
-  return m_fibUpdateList;
-}
-
-inline void
-Rib::clearFibUpdates()
-{
-  m_fibUpdateList.clear();
-}
-
 std::ostream&
 operator<<(std::ostream& os, const Rib& rib);
 
diff --git a/rib/route.cpp b/rib/route.cpp
new file mode 100644
index 0000000..7ee9353
--- /dev/null
+++ b/rib/route.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "route.hpp"
+
+namespace nfd {
+namespace rib {
+
+bool
+Route::operator==(const Route& other) const
+{
+  return (this->faceId == other.faceId &&
+          this->origin == other.origin &&
+          this->flags == other.flags &&
+          this->cost == other.cost &&
+          this->expires == other.expires);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Route& route)
+{
+  os << "Route("
+     << "faceid: " << route.faceId
+     << ", origin: " << route.origin
+     << ", cost: " << route.cost
+     << ", flags: " << route.flags;
+  if (route.expires != time::steady_clock::TimePoint::max()) {
+    os << ", expires in: " << (route.expires - time::steady_clock::now());
+  }
+  else {
+    os << ", never expires";
+  }
+  os << ")";
+
+  return os;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/rib/route.hpp b/rib/route.hpp
index 68769ef..d6a8b6e 100644
--- a/rib/route.hpp
+++ b/rib/route.hpp
@@ -47,6 +47,9 @@
   {
   }
 
+  bool
+  operator==(const Route& other) const;
+
 public:
   void
   setExpirationEvent(const scheduler::EventId eid)
@@ -60,6 +63,18 @@
     return m_expirationEvent;
   }
 
+  bool
+  isChildInherit() const
+  {
+    return flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
+  }
+
+  bool
+  isCapture() const
+  {
+    return flags & ndn::nfd::ROUTE_FLAG_CAPTURE;
+  }
+
 public:
   uint64_t faceId;
   uint64_t origin;
@@ -83,7 +98,6 @@
   return (route.faceId == faceId);
 }
 
-// Method definition in rib-entry.cpp
 std::ostream&
 operator<<(std::ostream& os, const Route& route);