tests: add test case for retx suppression in multicast strategy

Refs: #4066
Change-Id: I78953b91b1fc1c6599b83cd1f39c11722261dd98
diff --git a/tests/daemon/fw/multicast-strategy.t.cpp b/tests/daemon/fw/multicast-strategy.t.cpp
index 0ab54ef..a75bf30 100644
--- a/tests/daemon/fw/multicast-strategy.t.cpp
+++ b/tests/daemon/fw/multicast-strategy.t.cpp
@@ -53,6 +53,15 @@
     faceTable.add(face3);
   }
 
+  bool
+  didSendInterestTo(const Face& face) const
+  {
+    auto it = std::find_if(strategy.sendInterestHistory.begin(),
+                           strategy.sendInterestHistory.end(),
+                           [&] (const auto& elem) { return elem.outFaceId == face.getId(); });
+    return it != strategy.sendInterestHistory.end();
+  }
+
 protected:
   FaceTable faceTable;
   Forwarder forwarder{faceTable};
@@ -74,12 +83,13 @@
   fib.addOrUpdateNextHop(fibEntry, *face2, 0);
 
   // Send an Interest from face 1 to face 2
-  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
-  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  auto interest = makeInterest("ndn:/H0D6i5fc");
+  auto pitEntry = pit.insert(*interest).first;
   pitEntry->insertOrUpdateInRecord(*face1, *interest);
 
   strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest, pitEntry);
   BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
+  BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 1);
 
   // Advance more than default suppression
   this->advanceClocks(15_ms);
@@ -89,8 +99,8 @@
   pitEntry->insertOrUpdateInRecord(*face2, *interest);
 
   strategy.afterReceiveInterest(FaceEndpoint(*face2, 0), *interest, pitEntry);
-  // Since the interest is same as the one sent out by face 1 pit should not be rejected
-  // as any data coming back should be able to satisfy original interest from face 1
+  // Since the interest is the same as the one sent out earlier, the PIT entry should not be
+  // rejected, as any data coming back must be able to satisfy the original interest from face 1
   BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
 
   /*
@@ -150,25 +160,17 @@
   fib.addOrUpdateNextHop(fibEntry, *face2, 0);
   fib.addOrUpdateNextHop(fibEntry, *face3, 0);
 
-  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
-  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  auto interest = makeInterest("ndn:/H0D6i5fc");
+  auto pitEntry = pit.insert(*interest).first;
   pitEntry->insertOrUpdateInRecord(*face3, *interest);
 
   strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest, pitEntry);
   BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
   BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 2);
-  std::set<FaceId> sentInterestFaceIds;
-  std::transform(strategy.sendInterestHistory.begin(), strategy.sendInterestHistory.end(),
-                 std::inserter(sentInterestFaceIds, sentInterestFaceIds.end()),
-                 [] (const MulticastStrategyTester::SendInterestArgs& args) {
-                   return args.outFaceId;
-                 });
-  std::set<FaceId> expectedInterestFaceIds{face1->getId(), face2->getId()};
-  BOOST_CHECK_EQUAL_COLLECTIONS(sentInterestFaceIds.begin(), sentInterestFaceIds.end(),
-                                expectedInterestFaceIds.begin(), expectedInterestFaceIds.end());
+  BOOST_TEST(didSendInterestTo(*face1));
+  BOOST_TEST(didSendInterestTo(*face2));
 
-  const time::nanoseconds TICK = time::duration_cast<time::nanoseconds>(
-                                 MulticastStrategy::RETX_SUPPRESSION_INITIAL * 0.1);
+  const auto TICK = time::duration_cast<time::nanoseconds>(MulticastStrategy::RETX_SUPPRESSION_INITIAL) / 10;
 
   // downstream retransmits frequently, but the strategy should not send Interests
   // more often than DEFAULT_MIN_RETX_INTERVAL
@@ -202,8 +204,8 @@
   fib::Entry& fibEntry = *fib.insert(Name()).first;
   fib.addOrUpdateNextHop(fibEntry, *face1, 0);
 
-  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
-  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  auto interest = makeInterest("ndn:/H0D6i5fc");
+  auto pitEntry = pit.insert(*interest).first;
   pitEntry->insertOrUpdateInRecord(*face1, *interest);
 
   strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest, pitEntry);
@@ -211,14 +213,95 @@
   BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 0);
 }
 
-BOOST_AUTO_TEST_CASE(ForwardAsync)
+BOOST_AUTO_TEST_CASE(RetxSuppression)
+{
+  const auto suppressPeriod = MulticastStrategy::RETX_SUPPRESSION_INITIAL;
+  BOOST_ASSERT(suppressPeriod >= 8_ms);
+
+  // Set up the FIB
+  fib::Entry& fibEntry = *fib.insert(Name()).first;
+  fib.addOrUpdateNextHop(fibEntry, *face1, 0);
+  fib.addOrUpdateNextHop(fibEntry, *face2, 0);
+  fib.addOrUpdateNextHop(fibEntry, *face3, 0);
+
+  // Interest arrives from face 1
+  auto interest = makeInterest("/t8ZiSOi3");
+  auto pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(*face1, *interest);
+  strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest, pitEntry);
+
+  // forwarded to faces 2 and 3
+  BOOST_TEST(strategy.sendInterestHistory.size() == 2);
+  BOOST_TEST(didSendInterestTo(*face2));
+  BOOST_TEST(didSendInterestTo(*face3));
+  strategy.sendInterestHistory.clear();
+
+  // still within the initial suppression period for face 2 and 3
+  this->advanceClocks(suppressPeriod - 5_ms);
+
+  // Interest arrives from face 2
+  interest->refreshNonce();
+  pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(*face2, *interest);
+  strategy.afterReceiveInterest(FaceEndpoint(*face2, 0), *interest, pitEntry);
+
+  // forwarded only to face 1, suppressed on face 3
+  BOOST_TEST(strategy.sendInterestHistory.size() == 1);
+  BOOST_TEST(didSendInterestTo(*face1));
+  strategy.sendInterestHistory.clear();
+
+  // faces 2 and 3 no longer inside the suppression window
+  this->advanceClocks(7_ms);
+
+  // Interest arrives from face 3
+  interest->refreshNonce();
+  pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(*face3, *interest);
+  strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest, pitEntry);
+
+  // suppressed on face 1, forwarded on face 2 (and suppression window doubles)
+  BOOST_TEST(strategy.sendInterestHistory.size() == 1);
+  BOOST_TEST(didSendInterestTo(*face2));
+  strategy.sendInterestHistory.clear();
+
+  // face 1 exits the suppression period, face 2 still inside
+  this->advanceClocks(2 * suppressPeriod - 2_ms);
+
+  // Interest arrives from face 3
+  interest->refreshNonce();
+  pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(*face3, *interest);
+  strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest, pitEntry);
+
+  // forwarded only to face 1, suppressed on face 2
+  BOOST_TEST(strategy.sendInterestHistory.size() == 1);
+  BOOST_TEST(didSendInterestTo(*face1));
+  strategy.sendInterestHistory.clear();
+
+  // face 2 exits the suppression period
+  this->advanceClocks(3_ms);
+
+  // Interest arrives from face 1
+  interest->refreshNonce();
+  pitEntry = pit.insert(*interest).first;
+  pitEntry->insertOrUpdateInRecord(*face1, *interest);
+  strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest, pitEntry);
+
+  // forwarded to faces 2 and 3
+  BOOST_TEST(strategy.sendInterestHistory.size() == 2);
+  BOOST_TEST(didSendInterestTo(*face2));
+  BOOST_TEST(didSendInterestTo(*face3));
+  strategy.sendInterestHistory.clear();
+}
+
+BOOST_AUTO_TEST_CASE(NewNextHop)
 {
   fib::Entry& fibEntry = *fib.insert(Name()).first;
   fib.addOrUpdateNextHop(fibEntry, *face1, 0);
   fib.addOrUpdateNextHop(fibEntry, *face2, 0);
 
-  shared_ptr<Interest> interest = makeInterest("ndn:/H0D6i5fc");
-  shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
+  auto interest = makeInterest("ndn:/H0D6i5fc");
+  auto pitEntry = pit.insert(*interest).first;
   pitEntry->insertOrUpdateInRecord(*face1, *interest);
 
   strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest, pitEntry);
@@ -406,13 +489,13 @@
   this->fib.addOrUpdateNextHop(fibEntry, *this->fibFace1, 0);
   this->fib.addOrUpdateNextHop(fibEntry, *this->fibFace2, 0);
 
-  shared_ptr<Interest> interest = makeInterest("ndn:/localhop/H0D6i5fc");
-  shared_ptr<pit::Entry> pitEntry = this->pit.insert(*interest).first;
+  auto interest = makeInterest("ndn:/localhop/H0D6i5fc");
+  auto pitEntry = this->pit.insert(*interest).first;
   pitEntry->insertOrUpdateInRecord(*this->inFace1, *interest);
   this->strategy.afterReceiveInterest(FaceEndpoint(*this->inFace1, 0), *interest, pitEntry);
 
   if (this->inFace2 != nullptr) {
-    shared_ptr<Interest> interest2 = makeInterest("ndn:/localhop/H0D6i5fc");
+    auto interest2 = makeInterest("ndn:/localhop/H0D6i5fc");
     pitEntry->insertOrUpdateInRecord(*this->inFace2, *interest2);
     this->strategy.afterReceiveInterest(FaceEndpoint(*this->inFace2, 0), *interest2, pitEntry);
   }