fw: add processing for afterNewNextHop signal

Refs: #4931
Change-Id: I08bddc0ae3ceca0ddb777392ea656876ad6fe701
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index f59963e..5c529a8 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -75,6 +75,10 @@
     cleanupOnFaceRemoval(m_nameTree, m_fib, m_pit, face);
   });
 
+  m_fib.afterNewNextHop.connect([&] (const Name& prefix, const fib::NextHop& nextHop) {
+    this->startProcessNewNextHop(prefix, nextHop);
+  });
+
   m_strategyChoice.setDefaultStrategy(getDefaultStrategyName());
 }
 
@@ -504,6 +508,46 @@
 }
 
 void
+Forwarder::onNewNextHop(const Name& prefix, const fib::NextHop& nextHop)
+{
+  const auto affectedEntries = this->getNameTree().partialEnumerate(prefix,
+    [&] (const name_tree::Entry& nte) -> std::pair<bool, bool> {
+      const fib::Entry* fibEntry = nte.getFibEntry();
+      const fw::Strategy* strategy = nullptr;
+      if (nte.getStrategyChoiceEntry() != nullptr) {
+        strategy = &nte.getStrategyChoiceEntry()->getStrategy();
+      }
+      // current nte has buffered Interests but no fibEntry (except for the root nte) and the strategy
+      // enables new nexthop behavior, we enumerate the current nte and keep visiting its children.
+      if (nte.getName().size() == 0 ||
+          (strategy != nullptr && strategy->wantNewNextHopTrigger() &&
+          fibEntry == nullptr && nte.hasPitEntries())) {
+        return {true, true};
+      }
+      // we don't need the current nte (no pitEntry or strategy doesn't support new nexthop), but
+      // if the current nte has no fibEntry, it's still possible that its children are affected by
+      // the new nexthop.
+      else if (fibEntry == nullptr) {
+        return {false, true};
+      }
+      // if the current nte has a fibEntry, we ignore the current nte and don't visit its
+      // children because they are already covered by the current nte's fibEntry.
+      else {
+        return {false, false};
+      }
+    });
+
+  for (const auto& nte : affectedEntries) {
+    for (const auto& pitEntry : nte.getPitEntries()) {
+      this->dispatchToStrategy(*pitEntry,
+        [&] (fw::Strategy& strategy) {
+          strategy.afterNewNextHop(nextHop, pitEntry);
+        });
+    }
+  }
+}
+
+void
 Forwarder::setExpiryTimer(const shared_ptr<pit::Entry>& pitEntry, time::milliseconds duration)
 {
   BOOST_ASSERT(pitEntry);
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index 3b14a61..2da7c87 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -107,6 +107,16 @@
     this->onIncomingNack(ingress, nack);
   }
 
+  /** \brief start new nexthop processing
+   *  \param prefix the prefix of the FibEntry containing the new nexthop
+   *  \param nextHop the new NextHop
+   */
+  void
+  startProcessNewNextHop(const Name& prefix, const fib::NextHop& nextHop)
+  {
+    this->onNewNextHop(prefix, nextHop);
+  }
+
   NameTree&
   getNameTree()
   {
@@ -218,6 +228,9 @@
   VIRTUAL_WITH_TESTS void
   onDroppedInterest(const FaceEndpoint& egress, const Interest& interest);
 
+  VIRTUAL_WITH_TESTS void
+  onNewNextHop(const Name& prefix, const fib::NextHop& nextHop);
+
 PROTECTED_WITH_TESTS_ELSE_PRIVATE:
   /** \brief set a new expiry timer (now + \p duration) on a PIT entry
    */
diff --git a/daemon/fw/strategy.cpp b/daemon/fw/strategy.cpp
index f9f4106..d33ad9a 100644
--- a/daemon/fw/strategy.cpp
+++ b/daemon/fw/strategy.cpp
@@ -207,6 +207,13 @@
 }
 
 void
+Strategy::afterNewNextHop(const fib::NextHop& nextHop, const shared_ptr<pit::Entry>& pitEntry)
+{
+  NFD_LOG_DEBUG("afterNewNextHop pitEntry=" << pitEntry->getName()
+                << " nexthop=" << nextHop.getFace().getId());
+}
+
+void
 Strategy::sendData(const shared_ptr<pit::Entry>& pitEntry, const Data& data,
                    const FaceEndpoint& egress)
 {
diff --git a/daemon/fw/strategy.hpp b/daemon/fw/strategy.hpp
index 99e4826..37d98bb 100644
--- a/daemon/fw/strategy.hpp
+++ b/daemon/fw/strategy.hpp
@@ -82,7 +82,7 @@
   static std::set<Name>
   listRegistered();
 
-public: // constructor, destructor, strategy name
+public: // constructor, destructor, strategy info
   /** \brief Construct a strategy instance.
    *  \param forwarder a reference to the forwarder, used to enable actions and accessors.
    *  \note Strategy subclass constructor must not retain a reference to \p forwarder.
@@ -114,6 +114,14 @@
     return m_name;
   }
 
+  /** \return whether the afterNewNextHop trigger should be invoked for this strategy.
+   */
+  bool
+  wantNewNextHopTrigger() const
+  {
+    return m_wantNewNextHopTrigger;
+  }
+
 public: // triggers
   /** \brief trigger after Interest is received
    *
@@ -233,6 +241,14 @@
   virtual void
   onDroppedInterest(const FaceEndpoint& egress, const Interest& interest);
 
+  /** \brief trigger after new nexthop is added
+   *
+   *  The strategy should decide whether to send the buffered Interests to the new nexthop.
+   *  In the base class, this method does nothing.
+   */
+  virtual void
+  afterNewNextHop(const fib::NextHop& nextHop, const shared_ptr<pit::Entry>& pitEntry);
+
 protected: // actions
   /** \brief send Interest to egress
    *  \param pitEntry PIT entry
@@ -368,6 +384,15 @@
     m_name = name;
   }
 
+PUBLIC_WITH_TESTS_ELSE_PROTECTED: // setter
+  /** \brief set whether the afterNewNextHop trigger should be invoked for this strategy
+   */
+  void
+  enableNewNextHopTrigger(bool enabled)
+  {
+    m_wantNewNextHopTrigger = enabled;
+  }
+
 private: // registry
   typedef std::function<unique_ptr<Strategy>(Forwarder& forwarder, const Name& strategyName)> CreateFunc;
   typedef std::map<Name, CreateFunc> Registry; // indexed by strategy name
@@ -392,6 +417,8 @@
   Forwarder& m_forwarder;
 
   MeasurementsAccessor m_measurements;
+
+  bool m_wantNewNextHopTrigger = false;
 };
 
 } // namespace fw