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/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