fw: delegate sending Nack-Duplicate to forwarding strategy

This commit introduces a new strategy trigger (onInterestLoop) that
is invoked when a duplicate Interest is received. The default behavior
of the new trigger is to send a Nack as before. We also modify the
MulticastStrategy to override onInterestLoop and skip sending the Nack.

Refs: #5278
Co-authored-by: Davide Pesavento <davidepesa@gmail.com>
Change-Id: I07bb520bd31cbfc8d8f8581e05c49927f8ea35f7
diff --git a/tests/daemon/fw/multicast-strategy.t.cpp b/tests/daemon/fw/multicast-strategy.t.cpp
index 55254da..b51b001 100644
--- a/tests/daemon/fw/multicast-strategy.t.cpp
+++ b/tests/daemon/fw/multicast-strategy.t.cpp
@@ -210,8 +210,28 @@
   pitEntry->insertOrUpdateInRecord(*face1, *interest);
 
   strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
-  BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
-  BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 0);
+  BOOST_TEST(strategy.rejectPendingInterestHistory.size() == 0);
+  BOOST_TEST(strategy.sendInterestHistory.size() == 0);
+}
+
+BOOST_AUTO_TEST_CASE(DuplicateInterest)
+{
+  fib::Entry& fibEntry = *fib.insert(Name()).first;
+  fib.addOrUpdateNextHop(fibEntry, *face3, 0);
+
+  auto interest = makeInterest("ndn:/H0D6i5fc");
+
+  // first interest
+  forwarder.onIncomingInterest(*interest, FaceEndpoint(*face1));
+  BOOST_TEST(forwarder.getCounters().nInInterests == 1);
+  BOOST_TEST(strategy.sendInterestHistory.size() == 1);
+
+  // second interest (duplicate, should enter onInterestLoop)
+  forwarder.onIncomingInterest(*interest, FaceEndpoint(*face2));
+  BOOST_TEST(forwarder.getCounters().nInInterests == 2);
+  BOOST_TEST(strategy.sendInterestHistory.size() == 1);
+  BOOST_TEST(strategy.rejectPendingInterestHistory.size() == 0);
+  BOOST_TEST(strategy.sendNackHistory.size() == 0);
 }
 
 BOOST_AUTO_TEST_CASE(RetxSuppression)
diff --git a/tests/daemon/fw/strategy-instantiation.t.cpp b/tests/daemon/fw/strategy-instantiation.t.cpp
index 535b4b0..ae18eea 100644
--- a/tests/daemon/fw/strategy-instantiation.t.cpp
+++ b/tests/daemon/fw/strategy-instantiation.t.cpp
@@ -64,7 +64,7 @@
   Test<AccessStrategy, false, 1>,
   Test<AsfStrategy, true, 5>,
   Test<BestRouteStrategy, true, 5>,
-  Test<MulticastStrategy, true, 4>,
+  Test<MulticastStrategy, true, 5>,
   Test<SelfLearningStrategy, false, 1>,
   Test<RandomStrategy, false, 1>
 >;
diff --git a/tests/daemon/fw/strategy-tester.hpp b/tests/daemon/fw/strategy-tester.hpp
index e7dcccc..32785b8 100644
--- a/tests/daemon/fw/strategy-tester.hpp
+++ b/tests/daemon/fw/strategy-tester.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2022,  Regents of the University of California,
+ * Copyright (c) 2014-2024,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -99,8 +99,7 @@
 
 protected:
   pit::OutRecord*
-  sendInterest(const Interest& interest, Face& egress,
-               const shared_ptr<pit::Entry>& pitEntry) override
+  sendInterest(const Interest& interest, Face& egress, const shared_ptr<pit::Entry>& pitEntry) override
   {
     sendInterestHistory.push_back({pitEntry->getInterest(), egress.getId(), interest});
     auto it = pitEntry->insertOrUpdateOutRecord(egress, interest);
@@ -117,8 +116,7 @@
   }
 
   bool
-  sendNack(const lp::NackHeader& header, Face& egress,
-           const shared_ptr<pit::Entry>& pitEntry) override
+  sendNack(const lp::NackHeader& header, Face& egress, const shared_ptr<pit::Entry>& pitEntry) override
   {
     sendNackHistory.push_back({pitEntry->getInterest(), egress.getId(), header});
     pitEntry->deleteInRecord(egress);
@@ -126,6 +124,14 @@
     return true;
   }
 
+  bool
+  sendNack(const lp::Nack& nack, Face& egress) override
+  {
+    sendNackHistory.push_back({nack.getInterest(), egress.getId(), nack.getHeader()});
+    afterAction();
+    return true;
+  }
+
 public:
   struct SendInterestArgs
   {