fw: add strategy parameters to configure exponential retx suppression

Supported by ASF, BestRoute, and Multicast strategies

Refs: #4924
Change-Id: I215d9212d90b93fa622cc65278703dc5198d0c9d
diff --git a/tests/daemon/fw/asf-strategy.t.cpp b/tests/daemon/fw/asf-strategy.t.cpp
index 1336b3e..f07e72a 100644
--- a/tests/daemon/fw/asf-strategy.t.cpp
+++ b/tests/daemon/fw/asf-strategy.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2021,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -541,39 +541,53 @@
   BOOST_CHECK_EQUAL(linkAC->getFace(nodeA).getCounters().nOutInterests, 1);
 }
 
-BOOST_AUTO_TEST_CASE(InstantiationWithParameters)
+BOOST_AUTO_TEST_CASE(Parameters)
 {
   FaceTable faceTable;
   Forwarder forwarder{faceTable};
 
-  auto checkValidity = [&] (std::string parameters, bool isCorrect) {
-    Name strategyName(Name(AsfStrategy::getStrategyName()).append(std::move(parameters)));
-    if (isCorrect) {
-      BOOST_CHECK_NO_THROW(make_unique<AsfStrategy>(forwarder, strategyName));
+  auto checkValidity = [&] (const std::string& parameters, bool isCorrect) {
+    Name strategyName(Name(AsfStrategy::getStrategyName()).append(parameters));
+    std::unique_ptr<AsfStrategy> strategy;
+    BOOST_TEST_CONTEXT(parameters) {
+      if (isCorrect) {
+        strategy = make_unique<AsfStrategy>(forwarder, strategyName);
+        BOOST_CHECK(strategy->m_retxSuppression != nullptr);
+      }
+      else {
+        BOOST_CHECK_THROW(make_unique<AsfStrategy>(forwarder, strategyName), std::invalid_argument);
+      }
     }
-    else {
-      BOOST_CHECK_THROW(make_unique<AsfStrategy>(forwarder, strategyName), std::invalid_argument);
-    }
+    return strategy;
   };
 
-  checkValidity("/probing-interval~30000/max-timeouts~5", true);
-  checkValidity("/max-timeouts~5/probing-interval~30000", true);
-  checkValidity("/probing-interval~30000", true);
-  checkValidity("/max-timeouts~5", true);
-  checkValidity("", true);
+  auto strategy = checkValidity("", true);
+  BOOST_TEST(strategy->m_probing.getProbingInterval() == 60_s);
+  BOOST_TEST(strategy->m_nMaxTimeouts == 3);
+  strategy = checkValidity("/probing-interval~30000/max-timeouts~5", true);
+  BOOST_TEST(strategy->m_probing.getProbingInterval() == 30_s);
+  BOOST_TEST(strategy->m_nMaxTimeouts == 5);
+  strategy = checkValidity("/max-timeouts~5/probing-interval~30000", true);
+  BOOST_TEST(strategy->m_probing.getProbingInterval() == 30_s);
+  BOOST_TEST(strategy->m_nMaxTimeouts == 5);
+  strategy = checkValidity("/probing-interval~1000", true);
+  BOOST_TEST(strategy->m_probing.getProbingInterval() == 1_s);
+  BOOST_TEST(strategy->m_nMaxTimeouts == 3);
+  strategy = checkValidity("/max-timeouts~0", true);
+  BOOST_TEST(strategy->m_probing.getProbingInterval() == 60_s);
+  BOOST_TEST(strategy->m_nMaxTimeouts == 0);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
 
-  checkValidity("/probing-interval~500", false); // At least 1 seconds
+  checkValidity("/probing-interval~500", false); // minimum is 1 second
   checkValidity("/probing-interval~-5000", false);
-  checkValidity("/max-timeouts~0", false);
-  checkValidity("/max-timeouts~-5", false);
-  checkValidity("/max-timeouts~-5/probing-interval~-30000", false);
-  checkValidity("/max-timeouts", false);
-  checkValidity("/probing-interval~", false);
-  checkValidity("/~1000", false);
+  checkValidity("/max-timeouts~-1", false);
+  checkValidity("/max-timeouts~ -1", false);
+  checkValidity("/max-timeouts~1-0", false);
+  checkValidity("/max-timeouts~1/probing-interval~-30000", false);
   checkValidity("/probing-interval~foo", false);
   checkValidity("/max-timeouts~1~2", false);
-  checkValidity("/foo", false);
-  checkValidity("/foo~42", false);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestAsfStrategy
diff --git a/tests/daemon/fw/best-route-strategy.t.cpp b/tests/daemon/fw/best-route-strategy.t.cpp
index 7293762..d829e98 100644
--- a/tests/daemon/fw/best-route-strategy.t.cpp
+++ b/tests/daemon/fw/best-route-strategy.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2021,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -83,7 +83,7 @@
   shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
 
   const auto TICK = time::duration_cast<time::nanoseconds>(
-                      BestRouteStrategy::RETX_SUPPRESSION_INITIAL * 0.1);
+                      RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL * 0.1);
 
   // first Interest goes to nexthop with lowest FIB cost,
   // however face1 is downstream so it cannot be used
@@ -114,7 +114,7 @@
     retxFrom4Evt = getScheduler().schedule(TICK * 5, periodicalRetxFrom4);
   };
   periodicalRetxFrom4();
-  this->advanceClocks(TICK, BestRouteStrategy::RETX_SUPPRESSION_MAX * 16);
+  this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 16);
   retxFrom4Evt.cancel();
 
   // nexthops for accepted retransmissions: follow FIB cost,
@@ -130,7 +130,7 @@
 
   strategy.sendInterestHistory.clear();
   for (int i = 0; i < 3; ++i) {
-    this->advanceClocks(TICK, BestRouteStrategy::RETX_SUPPRESSION_MAX * 2);
+    this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 2);
     pitEntry->insertOrUpdateInRecord(*face5, *interest);
     strategy.afterReceiveInterest(*interest, FaceEndpoint(*face5, 0), pitEntry);
   }
diff --git a/tests/daemon/fw/multicast-strategy.t.cpp b/tests/daemon/fw/multicast-strategy.t.cpp
index a1656e5..dbb8e52 100644
--- a/tests/daemon/fw/multicast-strategy.t.cpp
+++ b/tests/daemon/fw/multicast-strategy.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2021,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -170,7 +170,8 @@
   BOOST_TEST(didSendInterestTo(*face1));
   BOOST_TEST(didSendInterestTo(*face2));
 
-  const auto TICK = time::duration_cast<time::nanoseconds>(MulticastStrategy::RETX_SUPPRESSION_INITIAL) / 10;
+  const auto TICK = time::duration_cast<time::nanoseconds>(
+                    RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL) / 10;
 
   // downstream retransmits frequently, but the strategy should not send Interests
   // more often than DEFAULT_MIN_RETX_INTERVAL
@@ -195,7 +196,7 @@
     retxFrom4Evt = getScheduler().schedule(TICK * 5, periodicalRetxFrom4);
   };
   periodicalRetxFrom4();
-  this->advanceClocks(TICK, MulticastStrategy::RETX_SUPPRESSION_MAX * 16);
+  this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 16);
   retxFrom4Evt.cancel();
 }
 
@@ -215,7 +216,7 @@
 
 BOOST_AUTO_TEST_CASE(RetxSuppression)
 {
-  const auto suppressPeriod = MulticastStrategy::RETX_SUPPRESSION_INITIAL;
+  const auto suppressPeriod = RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL;
   BOOST_ASSERT(suppressPeriod >= 8_ms);
 
   // Set up the FIB
diff --git a/tests/daemon/fw/retx-suppression.t.cpp b/tests/daemon/fw/retx-suppression.t.cpp
index 3522ace..3a2cf06 100644
--- a/tests/daemon/fw/retx-suppression.t.cpp
+++ b/tests/daemon/fw/retx-suppression.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -95,7 +95,7 @@
   FaceTable faceTable;
   Forwarder forwarder(faceTable);
   Pit& pit = forwarder.getPit();
-  RetxSuppressionExponential rs(10_ms, 3.0, 100_ms);
+  RetxSuppressionExponential rs(10_ms, 100_ms, 3.0);
 
   shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
   shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
@@ -164,7 +164,7 @@
   FaceTable faceTable;
   Forwarder forwarder(faceTable);
   Pit& pit = forwarder.getPit();
-  RetxSuppressionExponential rs(10_ms, 3.0, 100_ms);
+  RetxSuppressionExponential rs(10_ms, 100_ms, 3.0);
 
   shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
   shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
diff --git a/tests/daemon/fw/strategy-instantiation.t.cpp b/tests/daemon/fw/strategy-instantiation.t.cpp
index 64991d3..3ce458a 100644
--- a/tests/daemon/fw/strategy-instantiation.t.cpp
+++ b/tests/daemon/fw/strategy-instantiation.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2021,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -74,8 +74,8 @@
 using Tests = boost::mpl::vector<
   Test<AccessStrategy, false, 1>,
   Test<AsfStrategy, true, 4>,
-  Test<BestRouteStrategy, false, 5>,
-  Test<MulticastStrategy, false, 4>,
+  Test<BestRouteStrategy, true, 5>,
+  Test<MulticastStrategy, true, 4>,
   Test<SelfLearningStrategy, false, 1>,
   Test<RandomStrategy, false, 1>
 >;
@@ -120,6 +120,85 @@
   }
 }
 
+template<typename S>
+class SuppressionParametersFixture
+{
+public:
+  std::unique_ptr<S>
+  checkValidity(const std::string& parameters, bool isCorrect)
+  {
+    Name strategyName(Name(S::getStrategyName()).append(parameters));
+    std::unique_ptr<S> strategy;
+    BOOST_TEST_CONTEXT(parameters) {
+      if (isCorrect) {
+        strategy = make_unique<S>(m_forwarder, strategyName);
+        BOOST_CHECK(strategy->m_retxSuppression != nullptr);
+      }
+      else {
+        BOOST_CHECK_THROW(make_unique<S>(m_forwarder, strategyName), std::invalid_argument);
+      }
+    }
+    return strategy;
+  }
+
+private:
+  FaceTable m_faceTable;
+  Forwarder m_forwarder{m_faceTable};
+};
+
+using StrategiesWithRetxSuppressionExponential = boost::mpl::vector<
+  AsfStrategy,
+  BestRouteStrategy,
+  MulticastStrategy
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SuppressionParameters, S, StrategiesWithRetxSuppressionExponential,
+                                 SuppressionParametersFixture<S>)
+{
+  auto strategy = this->checkValidity("", true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
+
+  strategy = this->checkValidity("/retx-suppression-initial~20", true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 20_ms);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
+  this->checkValidity("/retx-suppression-initial~0", false);
+  this->checkValidity("/retx-suppression-initial~20.5", false);
+  this->checkValidity("/retx-suppression-initial~-10", false);
+  this->checkValidity("/retx-suppression-initial~ -5", false);
+  this->checkValidity("/retx-suppression-initial~NaN", false);
+
+  strategy = this->checkValidity("/retx-suppression-max~1000", true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 1_s);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
+  strategy = this->checkValidity("/retx-suppression-initial~40/retx-suppression-max~500", true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 40_ms);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 500_ms);
+  this->checkValidity("/retx-suppression-initial~20/retx-suppression-max~10", false);
+  this->checkValidity("/retx-suppression-max~ 500", false);
+  this->checkValidity("/retx-suppression-max~521.5", false);
+
+  strategy = this->checkValidity("/retx-suppression-multiplier~2.25", true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == 2.25);
+  this->checkValidity("/retx-suppression-multiplier~0", false);
+  this->checkValidity("/retx-suppression-multiplier~0.9", false);
+  this->checkValidity("/retx-suppression-multiplier~-2.1", false);
+  this->checkValidity("/retx-suppression-multiplier~foo", false);
+
+  strategy = this->checkValidity("/retx-suppression-initial~20/retx-suppression-max~500/retx-suppression-multiplier~3",
+                                 true);
+  BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 20_ms);
+  BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 500_ms);
+  BOOST_TEST(strategy->m_retxSuppression->m_multiplier == 3);
+
+  this->checkValidity("/foo~42", true); // unknown parameters are ignored
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestStrategyInstantiation
 BOOST_AUTO_TEST_SUITE_END() // Fw
 
diff --git a/tests/daemon/fw/strategy.t.cpp b/tests/daemon/fw/strategy.t.cpp
index 6f8e09b..82191f7 100644
--- a/tests/daemon/fw/strategy.t.cpp
+++ b/tests/daemon/fw/strategy.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2022,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -94,13 +94,33 @@
   FaceId id1 = face1->getId();
   FaceId id2 = face2->getId();
 
-  BOOST_CHECK(strategy.getLocalFaces() == std::vector<FaceId>{id2});
+  BOOST_TEST(strategy.getLocalFaces() == std::vector<FaceId>{id2}, boost::test_tools::per_element());
 
   face2->close();
   face1->close();
 
-  BOOST_CHECK((strategy.addedFaces   == std::vector<FaceId>{id1, id2}));
-  BOOST_CHECK((strategy.removedFaces == std::vector<FaceId>{id2, id1}));
+  BOOST_TEST((strategy.addedFaces   == std::vector<FaceId>{id1, id2}), boost::test_tools::per_element());
+  BOOST_TEST((strategy.removedFaces == std::vector<FaceId>{id2, id1}), boost::test_tools::per_element());
+}
+
+BOOST_AUTO_TEST_CASE(ParseParameters)
+{
+  BOOST_TEST(Strategy::parseParameters("").empty());
+  BOOST_TEST(Strategy::parseParameters("/").empty());
+  BOOST_CHECK_THROW(Strategy::parseParameters("/foo"), std::invalid_argument);
+  BOOST_CHECK_THROW(Strategy::parseParameters("/foo~"), std::invalid_argument);
+  BOOST_CHECK_THROW(Strategy::parseParameters("/~bar"), std::invalid_argument);
+  BOOST_CHECK_THROW(Strategy::parseParameters("/~"), std::invalid_argument);
+  BOOST_CHECK_THROW(Strategy::parseParameters("/~~"), std::invalid_argument);
+
+  StrategyParameters expected;
+  expected["foo"] = "bar";
+  BOOST_TEST(Strategy::parseParameters("/foo~bar") == expected);
+  BOOST_CHECK_THROW(Strategy::parseParameters("/foo~bar/42"), std::invalid_argument);
+  expected["the-answer"] = "42";
+  BOOST_TEST(Strategy::parseParameters("/foo~bar/the-answer~42") == expected);
+  expected["foo"] = "foo2";
+  BOOST_TEST(Strategy::parseParameters("/foo~bar/the-answer~42/foo~foo2") == expected);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestStrategy