fw: abstract Nack processing out of BestRouteStrategy2

refs #3176

Change-Id: Ib220269ff52acc47c65f854a610941afd862ad41
diff --git a/daemon/fw/best-route-strategy2.cpp b/daemon/fw/best-route-strategy2.cpp
index 974567a..1666e81 100644
--- a/daemon/fw/best-route-strategy2.cpp
+++ b/daemon/fw/best-route-strategy2.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -38,6 +38,7 @@
 
 BestRouteStrategy2::BestRouteStrategy2(Forwarder& forwarder, const Name& name)
   : Strategy(forwarder)
+  , ProcessNackTraits(this)
   , m_retxSuppression(RETX_SUPPRESSION_INITIAL,
                       RetxSuppressionExponential::DEFAULT_MULTIPLIER,
                       RETX_SUPPRESSION_MAX)
@@ -183,69 +184,11 @@
   }
 }
 
-/** \return less severe NackReason between x and y
- *
- *  lp::NackReason::NONE is treated as most severe
- */
-inline lp::NackReason
-compareLessSevere(lp::NackReason x, lp::NackReason y)
-{
-  if (x == lp::NackReason::NONE) {
-    return y;
-  }
-  if (y == lp::NackReason::NONE) {
-    return x;
-  }
-  return static_cast<lp::NackReason>(std::min(static_cast<int>(x), static_cast<int>(y)));
-}
-
 void
 BestRouteStrategy2::afterReceiveNack(const Face& inFace, const lp::Nack& nack,
                                      const shared_ptr<pit::Entry>& pitEntry)
 {
-  int nOutRecordsNotNacked = 0;
-  Face* lastFaceNotNacked = nullptr;
-  lp::NackReason leastSevereReason = lp::NackReason::NONE;
-  for (const pit::OutRecord& outR : pitEntry->getOutRecords()) {
-    const lp::NackHeader* inNack = outR.getIncomingNack();
-    if (inNack == nullptr) {
-      ++nOutRecordsNotNacked;
-      lastFaceNotNacked = &outR.getFace();
-      continue;
-    }
-
-    leastSevereReason = compareLessSevere(leastSevereReason, inNack->getReason());
-  }
-
-  lp::NackHeader outNack;
-  outNack.setReason(leastSevereReason);
-
-  if (nOutRecordsNotNacked == 1) {
-    BOOST_ASSERT(lastFaceNotNacked != nullptr);
-    pit::InRecordCollection::iterator inR = pitEntry->getInRecord(*lastFaceNotNacked);
-    if (inR != pitEntry->in_end()) {
-      // one out-record not Nacked, which is also a downstream
-      NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
-                    " nack=" << nack.getReason() <<
-                    " nack-to(bidirectional)=" << lastFaceNotNacked->getId() <<
-                    " out-nack=" << outNack.getReason());
-      this->sendNack(pitEntry, *lastFaceNotNacked, outNack);
-      return;
-    }
-  }
-
-  if (nOutRecordsNotNacked > 0) {
-    NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
-                  " nack=" << nack.getReason() <<
-                  " waiting=" << nOutRecordsNotNacked);
-    // continue waiting
-    return;
-  }
-
-  NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
-                " nack=" << nack.getReason() <<
-                " nack-to=all out-nack=" << outNack.getReason());
-  this->sendNacks(pitEntry, outNack);
+  this->processNack(inFace, nack, pitEntry);
 }
 
 } // namespace fw
diff --git a/daemon/fw/best-route-strategy2.hpp b/daemon/fw/best-route-strategy2.hpp
index 99fc19d..8d1f144 100644
--- a/daemon/fw/best-route-strategy2.hpp
+++ b/daemon/fw/best-route-strategy2.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -27,6 +27,7 @@
 #define NFD_DAEMON_FW_BEST_ROUTE_STRATEGY2_HPP
 
 #include "strategy.hpp"
+#include "process-nack-traits.hpp"
 #include "retx-suppression-exponential.hpp"
 
 namespace nfd {
@@ -50,6 +51,7 @@
  *  The reason of the sent Nack equals the least severe reason among received Nacks.
  */
 class BestRouteStrategy2 : public Strategy
+                         , public ProcessNackTraits<BestRouteStrategy2>
 {
 public:
   explicit
@@ -58,11 +60,11 @@
   static const Name&
   getStrategyName();
 
-  virtual void
+  void
   afterReceiveInterest(const Face& inFace, const Interest& interest,
                        const shared_ptr<pit::Entry>& pitEntry) override;
 
-  virtual void
+  void
   afterReceiveNack(const Face& inFace, const lp::Nack& nack,
                    const shared_ptr<pit::Entry>& pitEntry) override;
 
@@ -70,6 +72,8 @@
   static const time::milliseconds RETX_SUPPRESSION_INITIAL;
   static const time::milliseconds RETX_SUPPRESSION_MAX;
   RetxSuppressionExponential m_retxSuppression;
+
+  friend ProcessNackTraits<BestRouteStrategy2>;
 };
 
 } // namespace fw
diff --git a/daemon/fw/process-nack-traits.cpp b/daemon/fw/process-nack-traits.cpp
new file mode 100644
index 0000000..3167327
--- /dev/null
+++ b/daemon/fw/process-nack-traits.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "process-nack-traits.hpp"
+#include "core/logger.hpp"
+
+namespace nfd {
+namespace fw {
+
+NFD_LOG_INIT("ProcessNackTraits");
+
+/** \brief compare NackReason for severity
+ *
+ *  lp::NackReason::NONE is treated as most severe
+ */
+static bool
+isLessSevere(lp::NackReason x, lp::NackReason y)
+{
+  if (x == lp::NackReason::NONE) {
+    return false;
+  }
+  if (y == lp::NackReason::NONE) {
+    return true;
+  }
+
+  return static_cast<int>(x) < static_cast<int>(y);
+}
+
+void
+ProcessNackTraitsBase::processNack(const Face& inFace, const lp::Nack& nack,
+                                   const shared_ptr<pit::Entry>& pitEntry)
+{
+  int nOutRecordsNotNacked = 0;
+  Face* lastFaceNotNacked = nullptr;
+  lp::NackReason leastSevereReason = lp::NackReason::NONE;
+  for (const pit::OutRecord& outR : pitEntry->getOutRecords()) {
+    const lp::NackHeader* inNack = outR.getIncomingNack();
+    if (inNack == nullptr) {
+      ++nOutRecordsNotNacked;
+      lastFaceNotNacked = &outR.getFace();
+      continue;
+    }
+
+    if (isLessSevere(inNack->getReason(), leastSevereReason)) {
+      leastSevereReason = inNack->getReason();
+    }
+  }
+
+  lp::NackHeader outNack;
+  outNack.setReason(leastSevereReason);
+
+  if (nOutRecordsNotNacked == 1) {
+    BOOST_ASSERT(lastFaceNotNacked != nullptr);
+    pit::InRecordCollection::iterator inR = pitEntry->getInRecord(*lastFaceNotNacked);
+    if (inR != pitEntry->in_end()) {
+      // one out-record not Nacked, which is also a downstream
+      NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
+                    " nack=" << nack.getReason() <<
+                    " nack-to(bidirectional)=" << lastFaceNotNacked->getId() <<
+                    " out-nack=" << outNack.getReason());
+      this->sendNackForProcessNackTraits(pitEntry, *lastFaceNotNacked, outNack);
+      return;
+    }
+  }
+
+  if (nOutRecordsNotNacked > 0) {
+    NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
+                  " nack=" << nack.getReason() <<
+                  " waiting=" << nOutRecordsNotNacked);
+    // continue waiting
+    return;
+  }
+
+  NFD_LOG_DEBUG(nack.getInterest() << " nack-from=" << inFace.getId() <<
+                " nack=" << nack.getReason() <<
+                " nack-to=all out-nack=" << outNack.getReason());
+  this->sendNacksForProcessNackTraits(pitEntry, outNack);
+}
+
+} // namespace fw
+} // namespace nfd
diff --git a/daemon/fw/process-nack-traits.hpp b/daemon/fw/process-nack-traits.hpp
new file mode 100644
index 0000000..fe70183
--- /dev/null
+++ b/daemon/fw/process-nack-traits.hpp
@@ -0,0 +1,101 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FW_PROCESS_NACK_TRAITS_HPP
+#define NFD_DAEMON_FW_PROCESS_NACK_TRAITS_HPP
+
+#include "strategy.hpp"
+
+namespace nfd {
+namespace fw {
+
+/** \brief provides a common procedure for processing Nacks
+ *
+ *  This procedure works as follows:
+ *  1. If Nacks have been received from all upstream faces, return a Nack with least severe reason
+ *     to downstream faces.
+ *  2. If there are more than one upstream face, Nacks have been received from all but one of
+ *     them, and that face is also a downstream, return a Nack to that face. This is to address a
+ *     specific "live deadlock" scenario where two hosts are waiting for each other to return the
+ *     Nack.
+ *  3. Otherwise, wait for the arrival of more Nacks or Data.
+ *
+ *  To use this helper, the strategy should inherit from ProcessNackTraits<MyStrategy>,
+ *  and declare that specialization as a friend class.
+ *  Then, invoke processNack from afterReceiveNack trigger.
+ */
+class ProcessNackTraitsBase : noncopyable
+{
+protected:
+  virtual
+  ~ProcessNackTraitsBase() = default;
+
+  void
+  processNack(const Face& inFace, const lp::Nack& nack,
+              const shared_ptr<pit::Entry>& pitEntry);
+
+private:
+  virtual void
+  sendNackForProcessNackTraits(const shared_ptr<pit::Entry>& pitEntry, const Face& outFace,
+                               const lp::NackHeader& header) = 0;
+
+  virtual void
+  sendNacksForProcessNackTraits(const shared_ptr<pit::Entry>& pitEntry,
+                                const lp::NackHeader& header) = 0;
+};
+
+template<typename S>
+class ProcessNackTraits : public ProcessNackTraitsBase
+{
+protected:
+  explicit
+  ProcessNackTraits(S* strategy)
+    : m_strategy(strategy)
+  {
+  }
+
+private:
+  void
+  sendNackForProcessNackTraits(const shared_ptr<pit::Entry>& pitEntry, const Face& outFace,
+                               const lp::NackHeader& header) override
+  {
+    m_strategy->sendNack(pitEntry, outFace, header);
+  }
+
+  void
+  sendNacksForProcessNackTraits(const shared_ptr<pit::Entry>& pitEntry,
+                                const lp::NackHeader& header) override
+  {
+    m_strategy->sendNacks(pitEntry, header);
+  }
+
+private:
+  S* m_strategy;
+};
+
+} // namespace fw
+} // namespace nfd
+
+#endif // NFD_DAEMON_FW_PROCESS_NACK_TRAITS_HPP