helper: Add LFID route calculation

Loop-Free In-port Dependent (LFID) route calculation provides a set of loop-free paths.

Basically porting the existing code from https://github.com/schneiderklaus/ndnSIM-routing

Refs: #4985
Change-Id: I1ab25e729851cf2233c3b99be715ba0159cca0c7
diff --git a/helper/lfid/abstract-fib.cpp b/helper/lfid/abstract-fib.cpp
new file mode 100644
index 0000000..c4d736f
--- /dev/null
+++ b/helper/lfid/abstract-fib.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#include "abstract-fib.hpp"
+
+#include <algorithm>
+
+#include "ns3/names.h"
+#include "ns3/ndnSIM/NFD/daemon/fw/forwarder.hpp"
+#include "ns3/ndnSIM/model/ndn-global-router.hpp"
+#include "ns3/ndnSIM/model/ndn-l3-protocol.hpp"
+#include "ns3/node.h"
+
+namespace ns3 {
+namespace ndn {
+
+using std::set;
+
+AbstractFib::AbstractFib(const Ptr<GlobalRouter> own, int numNodes)
+  : nodeId{static_cast<int>(own->GetObject<ns3::Node>()->GetId())}
+  , nodeName{ns3::Names::FindName(own->GetObject<ns3::Node>())}
+  , numberOfNodes{numNodes}
+  , nodeDegree{static_cast<int>(own->GetIncidencies().size())}
+  , ownRouter{own}
+  , upwardCounter{0}
+  , totalNhCounter{0}
+{
+  checkInputs();
+
+  createEmptyFib();
+}
+
+void
+AbstractFib::checkInputs()
+{
+  if (nodeDegree <= 0) {
+    std::cerr << nodeName << " has a degree of " << nodeDegree << "!\n\n";
+  }
+
+  const auto MAX_SIZE{1e5};
+  NS_ABORT_UNLESS(nodeId >= 0 && nodeId <= MAX_SIZE);
+  NS_ABORT_UNLESS(nodeName.size() > 0 && nodeName.size() <= MAX_SIZE);
+  NS_ABORT_UNLESS(nodeDegree >= 0 && nodeDegree <= MAX_SIZE);
+  NS_ABORT_UNLESS(numberOfNodes > 1 && numberOfNodes <= MAX_SIZE);
+}
+
+void
+AbstractFib::createEmptyFib()
+{
+  // Create empty FIB:
+  for (int dstId = 0; dstId < numberOfNodes; dstId++) {
+    if (dstId == nodeId) {
+      continue;
+    }
+    perDstFib.insert({dstId, {}});
+    upwardPerDstFib.insert({dstId, {}});
+  }
+}
+
+Ptr<GlobalRouter>
+AbstractFib::getGR() const
+{
+  return ownRouter;
+}
+
+// Setters:
+void
+AbstractFib::insert(int dstId, const FibNextHop& nh)
+{
+  NS_ABORT_UNLESS(nh.getType() == NextHopType::DOWNWARD || nh.getType() == NextHopType::UPWARD);
+  NS_ABORT_UNLESS(nh.getNexthopId() != nodeId);
+
+  bool inserted1 = perDstFib.at(dstId).insert(nh).second;
+  BOOST_VERIFY(inserted1); // Check if it didn't exist yet.
+  totalNhCounter++;
+
+  if (nh.getType() == NextHopType::UPWARD) {
+    bool inserted2 = upwardPerDstFib.at(dstId).insert(nh).second;
+    BOOST_VERIFY(inserted2);
+    upwardCounter++;
+  }
+}
+
+size_t
+AbstractFib::erase(int dstId, int nhId)
+{
+  auto& fib{perDstFib.at(dstId)};
+
+  auto fibNh = std::find_if(fib.begin(), fib.end(),
+                            [&](const FibNextHop& item) { return item.getNexthopId() == nhId; });
+
+  // Element doesn't exist:
+  if (fibNh == fib.end()) {
+
+    // TODO: Figure out why this happens.
+    return 0;
+  }
+
+  NS_ABORT_UNLESS(fibNh != perDstFib.at(dstId).end());
+  NS_ABORT_UNLESS(fibNh->getType() == NextHopType::UPWARD);
+  totalNhCounter--;
+
+  fib.erase(fibNh);
+  auto numErased2 = upwardPerDstFib.at(dstId).erase(*fibNh);
+  NS_ABORT_UNLESS(numErased2 == 1);
+  upwardCounter--;
+
+  return numErased2;
+}
+
+// O(1)
+const set<FibNextHop>&
+AbstractFib::getNexthops(int dstId) const
+{
+  NS_ABORT_MSG_IF(dstId == nodeId, "Requested destination id is the same as current nodeId!");
+  NS_ABORT_MSG_IF(perDstFib.count(dstId) == 0,
+                  "Node " << nodeId << " No nexthops for dst: " << dstId << "!");
+  NS_ABORT_UNLESS(perDstFib.count(dstId) != 0);
+  return perDstFib.at(dstId);
+}
+
+const set<FibNextHop>&
+AbstractFib::getUpwardNexthops(int dstId) const
+{
+  NS_ABORT_MSG_IF(dstId == nodeId, "Requested destination id is the same as current nodeId!");
+  NS_ABORT_MSG_IF(perDstFib.count(dstId) == 0,
+                  "Node " << nodeId << " No nexthops for dst: " << dstId << "!");
+  return upwardPerDstFib.at(dstId);
+}
+
+void
+AbstractFib::checkFib() const
+{
+  BOOST_VERIFY(perDstFib.size() > 0);
+
+  for (const auto& fibSet : perDstFib) {
+    const size_t numNhs = fibSet.second.size();
+
+    bool hasDownward{false};
+    std::unordered_set<int> nextHopSet{};
+
+    for (const FibNextHop& nextHop : fibSet.second) {
+      BOOST_VERIFY(nextHop.getCost() > 0 && nextHop.getCost() < FibNextHop::MAX_COST);
+      if (nextHop.getType() == NextHopType::DOWNWARD) {
+        hasDownward = true;
+      }
+
+      // Only one FIB entry per nexthop allowed!
+      BOOST_VERIFY(nextHopSet.count(nextHop.getNexthopId()) == 0);
+      nextHopSet.emplace(nextHop.getNexthopId());
+    }
+    BOOST_VERIFY(hasDownward || numNhs == 0);
+    BOOST_VERIFY(nextHopSet.size() == fibSet.second.size());
+  }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const AbstractFib& fib)
+{
+  for (const auto& entry : fib.perDstFib) {
+    os << "\nFIB node: " << fib.nodeName << fib.nodeId << "\n";
+    os << "Dst: " << entry.first << "\n";
+    for (const auto& nh : entry.second) {
+      os << nh << "\n";
+    }
+  }
+  return os;
+}
+
+} // namespace ndn
+} // namespace ns-3
diff --git a/helper/lfid/abstract-fib.hpp b/helper/lfid/abstract-fib.hpp
new file mode 100644
index 0000000..b5a118f
--- /dev/null
+++ b/helper/lfid/abstract-fib.hpp
@@ -0,0 +1,164 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#ifndef LFID_ABS_FIB_H
+#define LFID_ABS_FIB_H
+
+#include <set>
+#include <unordered_map>
+
+#include "ns3/abort.h"
+#include "ns3/ndnSIM/helper/lfid/fib-nexthop.hpp"
+#include "ns3/ptr.h"
+
+namespace ns3 {
+namespace ndn {
+
+class GlobalRouter;
+
+/**
+ * An abstract, lightweight representation of the FIB.
+ */
+class AbstractFib {
+public:
+  using AllNodeFib = std::unordered_map<int, AbstractFib>;
+
+  /**
+   * @param own The GlobalRouter object representing the current router
+   * @param numNodes The total number of nodes in the network
+   */
+  AbstractFib(const Ptr<GlobalRouter> own, int numNodes);
+
+public:
+  // Getters:
+  /**
+   * @return Return set of nexthops per destination
+   */
+  const std::set<FibNextHop>&
+  getNexthops(int dstId) const;
+
+  /**
+   * @return Return set of upward nexthops per destination
+   */
+  const std::set<FibNextHop>&
+  getUpwardNexthops(int dstId) const;
+
+  /**
+   * Make sure that FIB is consistent (each destination has at least one downward nexthop)
+   */
+  void
+  checkFib() const;
+
+  /**
+   * @return Return number nexthops per destination
+   * @pre Also assure that the destination is not equal to the current nodeId.
+   */
+  int
+  numEnabledNhPerDst(int dstId) const
+  {
+    NS_ABORT_UNLESS(dstId != nodeId);
+    return static_cast<int>(getNexthops(dstId).size());
+  }
+
+  int
+  getNodeId() const
+  {
+    return nodeId;
+  }
+
+  Ptr<GlobalRouter>
+  getGR() const;
+
+  std::string
+  getName() const
+  {
+    return nodeName;
+  }
+
+  int
+  getDegree() const
+  {
+    return nodeDegree;
+  }
+
+  int
+  getNumDsts() const
+  {
+    return static_cast<int>(perDstFib.size());
+  }
+
+  bool
+  contains(int dstId) const
+  {
+    return perDstFib.count(dstId) > 0;
+  }
+
+  // Functions for range-based loop:
+  auto
+  begin() const
+  {
+    return perDstFib.cbegin();
+  }
+
+  auto
+  end() const
+  {
+    return perDstFib.cend();
+  }
+
+  // Setters:
+  void
+  insert(int dstId, const FibNextHop& nh);
+
+  size_t
+  erase(int dstId, int nhId);
+
+private:
+  void
+  checkInputs();
+
+  void
+  createEmptyFib();
+
+private:
+  const int nodeId;           // Own node id
+  const std::string nodeName; // Own node name
+  const int numberOfNodes;
+  const int nodeDegree;
+  const Ptr<GlobalRouter> ownRouter;
+
+  int upwardCounter;
+  int totalNhCounter;
+
+  // DstId -> set<FibNextHop>
+  std::unordered_map<int, std::set<FibNextHop>> perDstFib;
+  std::unordered_map<int, std::set<FibNextHop>> upwardPerDstFib;
+
+  friend std::ostream&
+  operator<<(std::ostream&, const AbstractFib& fib);
+};
+
+std::ostream&
+operator<<(std::ostream& os, const AbstractFib& fib);
+
+} // namespace ndn
+} // namespace ns-3
+
+#endif // LFID_ABS_FIB_H
diff --git a/helper/lfid/fib-nexthop.cpp b/helper/lfid/fib-nexthop.cpp
new file mode 100644
index 0000000..dc3b069
--- /dev/null
+++ b/helper/lfid/fib-nexthop.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#include "fib-nexthop.hpp"
+
+#include <boost/functional/hash.hpp>
+#include <ostream>
+
+namespace ns3 {
+namespace ndn {
+
+constexpr int NODE_ID_LIMIT = 1000;
+
+FibNextHop::FibNextHop(int cost, int nhId, int costDelta, NextHopType type)
+{
+  NS_ABORT_UNLESS(cost > 0 && cost <= MAX_COST);
+  NS_ABORT_UNLESS(nhId >= 0 && nhId <= NODE_ID_LIMIT);
+
+  this->m_nhId = nhId;
+  this->m_cost = cost;
+  this->m_type = type;
+  this->m_costDelta = costDelta;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const NextHopType& type)
+{
+  switch (type) {
+  case NextHopType::DOWNWARD:
+    return os << "DOWNWARD";
+  case NextHopType::UPWARD:
+    return os << "UPWARD";
+  case NextHopType::DISABLED:
+    return os << "DISABLED";
+  }
+  return os << static_cast<int>(type);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const FibNextHop& a)
+{
+  return os << "Id: " << a.getNexthopId() << ", cost: " << a.m_cost << ", type: " << a.m_type;
+}
+
+} // namespace ndn
+} // namespace ns-3
+
+namespace std {
+
+using ns3::ndn::FibNextHop;
+
+template <>
+struct hash<FibNextHop> {
+  size_t
+  operator()(const FibNextHop& k) const
+  {
+    // Combine hash via boost library
+    std::size_t seed = 0;
+    boost::hash_combine(seed, k.getNexthopId());
+    boost::hash_combine(seed, k.getCost());
+
+    return seed;
+  }
+};
+
+}
diff --git a/helper/lfid/fib-nexthop.hpp b/helper/lfid/fib-nexthop.hpp
new file mode 100644
index 0000000..32a5e2a
--- /dev/null
+++ b/helper/lfid/fib-nexthop.hpp
@@ -0,0 +1,145 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#ifndef LFID_FIB_NH_H
+#define LFID_FIB_NH_H
+
+#include <iosfwd>
+#include <unordered_set>
+
+#include "ns3/abort.h"
+
+namespace ns3 {
+namespace ndn {
+
+enum class NextHopType { DOWNWARD,
+                         UPWARD,
+                         DISABLED };
+
+class FibNextHop {
+public:
+  static constexpr int MAX_COST = 1 * 1000 * 1000;
+
+  /**
+   * @param nhId The nexthop id.
+   * @param cost The link cost of a nexthop. has to be larger than 0!
+   * @param costDelta The cost difference (relative to the shortest path nexthop)
+   * @param type The nexthop type @sa NextHopType
+   */
+  FibNextHop(int cost, int nhId, int costDelta = -1, NextHopType type = NextHopType::DISABLED);
+
+  // Getters
+  int
+  getNexthopId() const
+  {
+    return m_nhId;
+  }
+
+  int
+  getCost() const
+  {
+    return m_cost;
+  }
+
+  int
+  getCostDelta() const
+  {
+    return m_costDelta;
+  }
+
+  NextHopType
+  getType() const
+  {
+    return m_type;
+  }
+
+  // Setters:
+  void
+  setType(const NextHopType& newType)
+  {
+    NS_ABORT_UNLESS(newType != NextHopType::DISABLED);
+    this->m_type = newType;
+  }
+
+  // Only used in old fillFib:
+  void
+  setCost(int newCost, int newCostDelta)
+  {
+    NS_ABORT_UNLESS(newCost > 0);
+    NS_ABORT_UNLESS(newCostDelta >= 0);
+    this->m_cost = newCost;
+    this->m_costDelta = newCostDelta;
+  }
+
+private:
+  // Order of FibNexthop:
+  friend bool
+  operator<(const FibNextHop& own, const FibNextHop& other)
+  {
+    NS_ABORT_UNLESS(own.m_nhId != -1);
+
+    return std::tie(own.m_costDelta, own.m_cost, own.m_nhId)
+           < std::tie(other.m_costDelta, other.m_cost, other.m_nhId);
+  }
+
+  friend bool
+  operator==(const FibNextHop& own, const FibNextHop& other)
+  {
+    if (other.m_nhId == own.m_nhId) {
+      // Check that there are no FibNextHop with identical id, but different cost.
+      NS_ABORT_UNLESS(other.m_cost == own.m_cost);
+      NS_ABORT_UNLESS(other.m_costDelta == own.m_costDelta);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  friend bool
+  operator!=(const FibNextHop& own, const FibNextHop& other)
+  {
+    return !(own == other);
+  }
+
+  friend std::ostream&
+  operator<<(std::ostream&, const FibNextHop& fib);
+
+private:
+  int m_cost;
+  int m_nhId;
+  NextHopType m_type;
+  int m_costDelta;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NextHopType& type);
+std::ostream&
+operator<<(std::ostream& os, const FibNextHop& a);
+
+} // namespace ndn
+} // namespace ns-3
+
+namespace std {
+template <>
+struct hash<ns3::ndn::FibNextHop>;
+}
+
+#endif // LFID_FIB_NH_H
diff --git a/helper/lfid/ndn-global-routing-helper-lfid.cpp b/helper/lfid/ndn-global-routing-helper-lfid.cpp
new file mode 100644
index 0000000..13f86ab
--- /dev/null
+++ b/helper/lfid/ndn-global-routing-helper-lfid.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#include "ns3/ndnSIM/helper/ndn-global-routing-helper.hpp"
+
+#include <boost/graph/dijkstra_shortest_paths.hpp>
+#include <boost/graph/graph_utility.hpp>
+
+#include "ns3/ndnSIM/helper/boost-graph-ndn-global-routing-helper.hpp"
+#include "ns3/ndnSIM/helper/lfid/abstract-fib.hpp"
+#include "ns3/ndnSIM/helper/lfid/remove-loops.hpp"
+#include "ns3/ndnSIM/helper/ndn-fib-helper.hpp"
+#include "ns3/ndnSIM/model/ndn-global-router.hpp"
+
+NS_LOG_COMPONENT_DEFINE("ndn.GlobalRoutingHelperLfid");
+
+namespace ns3 {
+namespace ndn {
+
+using std::get;
+using std::unordered_map;
+
+void
+GlobalRoutingHelper::CalculateLfidRoutes()
+{
+  BOOST_CONCEPT_ASSERT((boost::VertexListGraphConcept<boost::NdnGlobalRouterGraph>));
+  BOOST_CONCEPT_ASSERT((boost::IncidenceGraphConcept<boost::NdnGlobalRouterGraph>));
+
+  // Creates graph from nodeList:
+  boost::NdnGlobalRouterGraph graph{};
+
+  AbstractFib::AllNodeFib allNodeFIB;
+
+  // Store mapping nodeId -> faceId -> Ptr<Face>
+  unordered_map<int, unordered_map<nfd::FaceId, shared_ptr<Face>>> faceMap;
+
+  // For all existing nodes:
+  for (auto node = NodeList::Begin(); node != NodeList::End(); node++) {
+
+    int nodeId = static_cast<int>((*node)->GetId());
+    const Ptr<GlobalRouter>& source = (*node)->GetObject<GlobalRouter>();
+
+    AbstractFib nodeFib = AbstractFib{source, static_cast<int>(NodeList::GetNNodes())};
+
+    if (source == nullptr) {
+      NS_LOG_ERROR("Node " << (*node)->GetId() << " does not export GlobalRouter interface");
+      continue;
+    }
+
+    // map: neighborId -> DistancesMap
+    unordered_map<int, boost::DistancesMap> nbSp;
+
+    // map: Destination (GlobalRouter) -> Distance
+    boost::DistancesMap distMap;
+    unordered_map<int, boost::DistancesMap> neighborSpMap;
+
+    dijkstra_shortest_paths(graph, source,
+                            distance_map(boost::ref(distMap))
+                              .distance_inf(boost::WeightInf)
+                              .distance_zero(boost::WeightZero)
+                              .distance_compare(boost::WeightCompare())
+                              .distance_combine(boost::WeightCombine()));
+
+    // 1. Get all neighbors of node.
+    unordered_map<nfd::FaceId, uint64_t> originalMetrics;
+    auto& originalFace = faceMap[nodeId];
+
+    const GlobalRouter::IncidencyList& neighbors = source->GetIncidencies();
+    // Set link weight of all neighbors to infinity
+    for (const auto& neighbor : neighbors) {
+      int nbId = get<2>(neighbor)->GetObject<ns3::Node>()->GetId();
+      NS_ABORT_UNLESS(nbId != nodeId);
+
+      auto& face = get<shared_ptr<Face>>(neighbor);
+      NS_ABORT_UNLESS(face != nullptr);
+
+      originalMetrics[nbId] = face->getMetric();
+      originalFace[nbId] = face; // Is only a copy
+      face->setMetric(get<uint16_t>(boost::WeightInf));
+    }
+
+    // 2. Calculate Dijkstra for neighbors
+    for (const auto& neighbor : neighbors) {
+      const auto& nSource = get<0>(neighbor);
+      const auto& target = get<2>(neighbor);
+
+      int nbId = target->GetObject<ns3::Node>()->GetId();
+      Ptr<GlobalRouter> nbRouter = target;
+
+      NS_ABORT_UNLESS(target != source && nbId != nodeId);
+      NS_ABORT_UNLESS(nSource == source);
+
+      dijkstra_shortest_paths(graph, nbRouter,
+                              distance_map(boost::ref(neighborSpMap[nbId]))
+                                .distance_inf(boost::WeightInf)
+                                .distance_zero(boost::WeightZero)
+                                .distance_compare(boost::WeightCompare())
+                                .distance_combine(boost::WeightCombine()));
+    }
+
+    // 3. Reset link weights
+    for (const auto& neighbor : neighbors) {
+      int nbId = get<2>(neighbor)->GetObject<ns3::Node>()->GetId();
+      NS_ABORT_UNLESS(nbId != nodeId);
+
+      const auto& face = get<shared_ptr<Face>>(neighbor);
+      NS_ABORT_UNLESS(face->getMetric() == get<uint16_t>(boost::WeightInf));
+      face->setMetric(originalMetrics.at(nbId));
+    }
+
+    // 4. Fill Abstract FIB:
+    // For each destination:
+    for (const auto& dstEntry : distMap) {
+      Ptr<GlobalRouter> dstRouter = dstEntry.first;
+      int dstId = dstRouter->GetObject<ns3::Node>()->GetId();
+      if (dstRouter == source)
+        continue; // Skip destination == source.
+
+      int spTotalCost = static_cast<int>(get<uint32_t>(dstEntry.second));
+
+      // For each neighbor:
+      for (const auto& nb : neighborSpMap) {
+        int neighborId = nb.first;
+        const uint32_t nbDist{get<uint32_t>(nb.second.at(dstRouter))};
+
+        int neighborCost = static_cast<int>(nbDist);
+        int neighborTotalCost = neighborCost + static_cast<int>(originalFace.at(neighborId)->getMetric());
+
+        NS_ABORT_UNLESS(neighborTotalCost >= spTotalCost);
+
+        // Skip routers that would loop back
+        if (neighborTotalCost >= static_cast<int>(get<uint16_t>(boost::WeightInf)))
+          continue;
+
+        NextHopType nbType;
+        if (neighborCost < spTotalCost) {
+          nbType = NextHopType::DOWNWARD;
+        }
+        else {
+          nbType = NextHopType::UPWARD;
+        }
+
+        int costDelta = neighborTotalCost - spTotalCost;
+        FibNextHop nh = {neighborTotalCost, neighborId, costDelta, nbType};
+        nodeFib.insert(dstId, nh);
+      }
+
+    } // End for all dsts
+
+    nodeFib.checkFib();
+    allNodeFIB.emplace(nodeId, nodeFib);
+  } // End for all nodes
+
+  ///  4. Remove loops and Deadends ///
+  removeLoops(allNodeFIB, true);
+  removeDeadEnds(allNodeFIB, true);
+
+  // 5. Insert from AbsFIB into real FIB!
+  // For each node in the AbsFIB: Insert into real fib.
+  for (const auto& nodeEntry : allNodeFIB) {
+    int nodeId = nodeEntry.first;
+    const auto& fib = nodeEntry.second;
+
+    // For each destination:
+    for (const auto& dst : fib) {
+      int dstId = dst.first;
+      const auto& dstRouter = allNodeFIB.at(dstId).getGR();
+
+      // Each fibNexthop
+      for (const auto& nh : dst.second) {
+        int neighborId = nh.getNexthopId();
+        int neighborTotalCost = nh.getCost();
+
+        for (const auto& prefix : dstRouter->GetLocalPrefixes()) {
+          Ptr<Node> node = NodeList::GetNode(static_cast<uint32_t>(nodeId));
+          FibHelper::AddRoute(node, *prefix, faceMap.at(nodeId).at(neighborId), neighborTotalCost);
+        }
+      }
+    }
+  }
+}
+
+} // namespace ndn
+} // namespace ns3
diff --git a/helper/lfid/remove-loops.cpp b/helper/lfid/remove-loops.cpp
new file mode 100644
index 0000000..90062d1
--- /dev/null
+++ b/helper/lfid/remove-loops.cpp
@@ -0,0 +1,348 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#include "remove-loops.hpp"
+
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/dijkstra_shortest_paths.hpp>
+#include <boost/graph/properties.hpp>
+#include <boost/property_map/property_map.hpp>
+#include <queue>
+
+#include "ns3/abort.h"
+#include "ns3/ndnSIM/helper/lfid/abstract-fib.hpp"
+
+namespace ns3 {
+namespace ndn {
+
+using std::set;
+using AllNodeFib = AbstractFib::AllNodeFib;
+
+/**
+ * Fill directed graph only with edges existing in the FIB.
+ */
+void
+getDigraphFromFib(DiGraph& dg, const AllNodeFib& allNodeFIB, const int dstId)
+{
+
+  // 1. Erase All Arcs:
+  dg.clear();
+
+  // 2. Add Arcs from FIB
+  for (const auto& node : allNodeFIB) {
+    int nodeId = node.first;
+    if (dstId == nodeId) {
+      continue;
+    }
+
+    for (const auto& fibNh : node.second.getNexthops(dstId)) {
+      NS_ABORT_UNLESS(fibNh.getType() <= NextHopType::UPWARD);
+      boost::add_edge(static_cast<uint64_t>(nodeId), static_cast<uint64_t>(fibNh.getNexthopId()), 1, dg);
+    }
+  }
+}
+
+class NodePrio {
+public:
+  NodePrio(int nodeId, int remainingNh, set<FibNextHop> nhSet)
+    : m_nodeId{nodeId}
+    , m_remainingNh{remainingNh}
+    , m_uwSet{nhSet}
+  {
+    NS_ABORT_UNLESS(remainingNh > 0 && m_uwSet.size() > 0);
+    NS_ABORT_UNLESS(static_cast<int>(m_uwSet.size()) < remainingNh);
+  }
+
+  int
+  getId() const
+  {
+    return m_nodeId;
+  }
+
+  int
+  getRemainingUw() const
+  {
+    return static_cast<int>(m_uwSet.size());
+  }
+
+  /**
+   * Order by Remamining UW NHs, Highest DeltaCost, and then id.
+   */
+  bool
+  operator<(const NodePrio& other) const
+  {
+    return std::make_tuple(m_remainingNh, getHighestCostUw(), m_nodeId)
+           < std::make_tuple(other.m_remainingNh, other.getHighestCostUw(), other.m_nodeId);
+  }
+
+  // Setters:
+  FibNextHop
+  popHighestCostUw()
+  {
+    const FibNextHop& tmp = getHighestCostUw();
+    eraseUw(tmp);
+    return tmp;
+  }
+
+  void
+  reduceRemainingNh()
+  {
+    m_remainingNh--;
+    // Check that remaining nexthops >= remaining uw nexthops.
+    NS_ABORT_UNLESS(m_remainingNh > 0 && m_remainingNh > getRemainingUw());
+  }
+
+private:
+  void
+  eraseUw(FibNextHop nh)
+  {
+    NS_ABORT_UNLESS(m_uwSet.size() > 0);
+    auto success = m_uwSet.erase(nh);
+    NS_ABORT_UNLESS(success == 1);
+  }
+
+  FibNextHop
+  getHighestCostUw() const
+  {
+    NS_ABORT_UNLESS(m_uwSet.size() > 0);
+    NS_ABORT_UNLESS(std::prev(m_uwSet.end()) != m_uwSet.end());
+    return *(std::prev(m_uwSet.end()));
+  }
+
+private:
+  int m_nodeId;
+  int m_remainingNh;
+  set<FibNextHop> m_uwSet;
+
+  friend std::ostream&
+  operator<<(std::ostream&, const NodePrio& node);
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NodePrio& node)
+{
+  return os << "Id: " << node.m_nodeId << ", remaining NH: " << node.m_remainingNh
+            << ", remaining UW: " << node.getRemainingUw() << " ";
+}
+
+int
+removeLoops(AllNodeFib& allNodeFIB, bool printOutput)
+{
+  int removedLoopCounter = 0;
+  int upwardCounter = 0;
+
+  const int NUM_NODES{static_cast<int>(allNodeFIB.size())};
+
+  // Build graph with boost graph library:
+  DiGraph dg{};
+
+  // Add all Arcs that fit into FIB. // O(n)
+  for (int dstId = 0; dstId < NUM_NODES; dstId++) {
+    // 1. Get DiGraph from Fib //
+    getDigraphFromFib(dg, allNodeFIB, dstId);
+
+    // NodeId -> set<UwNexthops>
+    std::priority_queue<NodePrio> q;
+
+    // 2. Put nodes in the queue, ordered by # remaining nexthops, then CostDelta // O(n^2)
+    for (const auto& node : allNodeFIB) {
+      int nodeId{node.first};
+      const AbstractFib& fib{node.second};
+      if (nodeId == dstId) {
+        continue;
+      }
+
+      const auto& uwNhSet = fib.getUpwardNexthops(dstId);
+      if (!uwNhSet.empty()) {
+        upwardCounter += uwNhSet.size();
+
+        int fibSize{fib.numEnabledNhPerDst(dstId)};
+        // NodePrio tmpNode {nodeId, fibSize, uwNhSet};
+        q.emplace(nodeId, fibSize, uwNhSet);
+      }
+    }
+
+    // 3. Iterate PriorityQueue //
+    while (!q.empty()) {
+      NodePrio node = q.top();
+      q.pop();
+
+      int nodeId = node.getId();
+      int nhId = node.popHighestCostUw().getNexthopId();
+
+      // Remove opposite of Uphill link
+      //      int arcId1 {getArcId(arcMap, nhId, nodeId)};
+      auto res = boost::edge(static_cast<uint64_t>(nhId), static_cast<uint64_t>(nodeId), dg);
+
+      auto arc = res.first;
+      bool arcExists = res.second;
+
+      if (arcExists) {
+        boost::remove_edge(arc, dg);
+      }
+
+      // 2. Loop Check: Is the current node still reachable for the uphill nexthop?
+      // Uses BFS:
+      // bool willLoop = bfs(dg).run(dg.nodeFromId(nhId), dg.nodeFromId(nodeId)); // O(m^2n)
+
+      std::vector<int> dists(num_vertices(dg));
+
+      auto weightmap = get(boost::edge_weight, dg);
+
+      const auto& x = boost::edges(dg);
+      for (auto e = x.first; e != x.second; e++) {
+        int weight = get(weightmap, *e);
+        NS_ABORT_UNLESS(weight == 1); // Only use uniform weights.
+      }
+
+      // TODO: Could be replaced by BFS/DFS to improve speed.
+      dijkstra_shortest_paths(dg, static_cast<uint64_t>(nhId),
+                              distance_map(
+                                boost::make_iterator_property_map(dists.begin(), get(boost::vertex_index, dg))));
+
+      bool willLoop = (dists.at(static_cast<size_t>(nodeId)) < (std::numeric_limits<int>::max() - 1));
+
+      // Uphill nexthop loops back to original node
+      if (willLoop) {
+        node.reduceRemainingNh();
+        removedLoopCounter++;
+
+        // Erase FIB entry
+        allNodeFIB.at(node.getId()).erase(dstId, nhId);
+
+        auto res2 = boost::edge(static_cast<uint64_t>(node.getId()), static_cast<uint64_t>(nhId), dg);
+        auto arc2 = res2.first;
+        NS_ABORT_UNLESS(res.second);
+
+        boost::remove_edge(arc2, dg);
+      }
+
+      // Add opposite of UW link back:
+      if (arcExists) {
+        boost::add_edge(static_cast<uint64_t>(nhId), static_cast<uint64_t>(nodeId), 1, dg);
+      }
+
+      // If not has further UW nexthops: Requeue.
+      if (node.getRemainingUw() > 0) {
+        q.push(node);
+      }
+    }
+  }
+
+  if (printOutput) {
+    std::cout << "Found " << upwardCounter << " UW nexthops, Removed " << removedLoopCounter
+              << " Looping UwNhs, Remaining: " << upwardCounter - removedLoopCounter << " NHs\n";
+  }
+  NS_ABORT_UNLESS((upwardCounter - removedLoopCounter) >= 0);
+
+  return removedLoopCounter;
+}
+
+int
+removeDeadEnds(AllNodeFib& allNodeFIB, bool printOutput)
+{
+  int NUM_NODES{static_cast<int>(allNodeFIB.size())};
+  int checkedUwCounter{0};
+  int uwCounter{0};
+  int totalCounter{0};
+  int removedDeadendCounter{0};
+
+  for (int dstId = 0; dstId < NUM_NODES; dstId++) {
+    // NodeId -> FibNexthops (Order important)
+    set<std::pair<int, FibNextHop>> nhSet;
+
+    // 1. Put all uwNexthops in set<NodeId, FibNexhtop>:
+    for (const auto& node : allNodeFIB) {
+      int nodeId{node.first};
+      if (nodeId == dstId) {
+        continue;
+      }
+
+      totalCounter += node.second.getNexthops(dstId).size();
+
+      const auto& uwNhSet = node.second.getUpwardNexthops(dstId);
+      uwCounter += uwNhSet.size();
+      for (const FibNextHop& fibNh : uwNhSet) {
+        nhSet.emplace(nodeId, fibNh);
+      }
+    }
+
+    // FibNexthops ordered by (costDelta, cost, nhId).
+    // Start with nexthop with highest cost:
+    while (!nhSet.empty()) {
+      checkedUwCounter++;
+
+      // Pop from queue:
+      const auto& nhPair = nhSet.begin();
+      NS_ABORT_UNLESS(nhPair != nhSet.end());
+      nhSet.erase(nhPair);
+
+      int nodeId = nhPair->first;
+      const FibNextHop& nh = nhPair->second;
+      AbstractFib& fib = allNodeFIB.at(nodeId);
+
+      if (nh.getNexthopId() == dstId) {
+        continue;
+      }
+
+      int reverseEntries{allNodeFIB.at(nh.getNexthopId()).numEnabledNhPerDst(dstId)};
+
+      // Must have at least one FIB entry.
+      NS_ABORT_UNLESS(reverseEntries > 0);
+
+      // If it has exactly 1 entry -> Is downward back through the upward nexthop!
+      // Higher O-Complexity below:
+      if (reverseEntries <= 1) {
+        removedDeadendCounter++;
+
+        // Erase NhEntry from FIB:
+        fib.erase(dstId, nh.getNexthopId());
+
+        // Push into Queue: All NhEntries that lead to m_nodeId!
+        const auto& nexthops = fib.getNexthops(dstId);
+
+        for (const auto& ownNhs : nexthops) {
+          if (ownNhs.getType() == NextHopType::DOWNWARD && ownNhs.getNexthopId() != dstId) {
+            const auto& reverseNh = allNodeFIB.at(ownNhs.getNexthopId()).getNexthops(dstId);
+
+            for (const auto& y : reverseNh) {
+              if (y.getNexthopId() == nodeId) {
+                NS_ABORT_UNLESS(y.getType() == NextHopType::UPWARD);
+                nhSet.emplace(ownNhs.getNexthopId(), y);
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if (printOutput) {
+    std::cout << "Checked " << checkedUwCounter << " Upward NHs, Removed " << removedDeadendCounter
+              << " Deadend UwNhs, Remaining: " << uwCounter - removedDeadendCounter << " UW NHs, "
+              << totalCounter - removedDeadendCounter << " total nexthops\n";
+  }
+
+  return removedDeadendCounter;
+}
+
+} // namespace ndn
+} // namespace ns3
diff --git a/helper/lfid/remove-loops.hpp b/helper/lfid/remove-loops.hpp
new file mode 100644
index 0000000..ec2a89e
--- /dev/null
+++ b/helper/lfid/remove-loops.hpp
@@ -0,0 +1,49 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2019 Klaus Schneider, The University of Arizona
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Klaus Schneider <klaus@cs.arizona.edu>
+ */
+
+#ifndef LFID_REMOVE_LOOPS_H
+#define LFID_REMOVE_LOOPS_H
+
+#include <boost/graph/adjacency_list.hpp>
+
+#include "ns3/ndnSIM/helper/lfid/abstract-fib.hpp"
+
+namespace ns3 {
+namespace ndn {
+
+// No Vertex Property
+// Edge Property: Weight.
+using DiGraph =
+  boost::adjacency_list<boost::listS, boost::vecS, boost::bidirectionalS, boost::no_property,
+                        boost::property<boost::edge_weight_t, int>>;
+
+void
+getDigraphFromFib(DiGraph& dg, const AbstractFib::AllNodeFib& allNodeFIB, const int dstId);
+
+int
+removeLoops(AbstractFib::AllNodeFib& allNodeFIB, bool printOutput = true);
+
+int
+removeDeadEnds(AbstractFib::AllNodeFib& allNodeFIB, bool printOutput = true);
+
+} // namespace ndn
+} // namespace ns3
+
+#endif //LFID_REMOVE_LOOPS_H