fw: Nack in pipelines and best-route strategy

* in PIT out-record, add last incoming Nack field
* create incoming Nack pipeline
* create outgoing Nack pipeline
* modify Interest loop pipeline to send Nack upon duplicate Nonce
* in strategy API, add after receive Nack trigger and send Nack action
* in best-route strategy, send Nack-NoRoute before rejecting pending Interest
* in best-route strategy, process incoming Nack

Other changes include:

* Pit::find
* StrategyTester saved arguments structs
* TopologyTester transmit at Transport level

refs #3156

Change-Id: I7868561c0838231083d471261200aeb280cc6e9d
diff --git a/daemon/fw/best-route-strategy2.cpp b/daemon/fw/best-route-strategy2.cpp
index 2ec786c..e0e31dc 100644
--- a/daemon/fw/best-route-strategy2.cpp
+++ b/daemon/fw/best-route-strategy2.cpp
@@ -31,7 +31,7 @@
 
 NFD_LOG_INIT("BestRouteStrategy2");
 
-const Name BestRouteStrategy2::STRATEGY_NAME("ndn:/localhost/nfd/strategy/best-route/%FD%03");
+const Name BestRouteStrategy2::STRATEGY_NAME("ndn:/localhost/nfd/strategy/best-route/%FD%04");
 NFD_REGISTER_STRATEGY(BestRouteStrategy2);
 
 BestRouteStrategy2::BestRouteStrategy2(Forwarder& forwarder, const Name& name)
@@ -104,8 +104,7 @@
   const fib::NextHopList& nexthops = fibEntry->getNextHops();
   fib::NextHopList::const_iterator it = nexthops.end();
 
-  RetxSuppression::Result suppression =
-      m_retxSuppression.decide(inFace, interest, *pitEntry);
+  RetxSuppression::Result suppression = m_retxSuppression.decide(inFace, interest, *pitEntry);
   if (suppression == RetxSuppression::NEW) {
     // forward to nexthop with lowest cost except downstream
     it = std::find_if(nexthops.begin(), nexthops.end(),
@@ -114,6 +113,11 @@
 
     if (it == nexthops.end()) {
       NFD_LOG_DEBUG(interest << " from=" << inFace.getId() << " noNextHop");
+
+      lp::NackHeader nackHeader;
+      nackHeader.setReason(lp::NackReason::NO_ROUTE);
+      this->sendNack(pitEntry, inFace, nackHeader);
+
       this->rejectPendingInterest(pitEntry);
       return;
     }
@@ -156,5 +160,72 @@
   }
 }
 
+/** \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,
+                                     shared_ptr<fib::Entry> fibEntry,
+                                     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().get();
+      continue;
+    }
+
+    leastSevereReason = compareLessSevere(leastSevereReason, inNack->getReason());
+  }
+
+  lp::NackHeader outNack;
+  outNack.setReason(leastSevereReason);
+
+  if (nOutRecordsNotNacked == 1) {
+    BOOST_ASSERT(lastFaceNotNacked != nullptr);
+    pit::InRecordCollection::const_iterator inR = pitEntry->getInRecord(*lastFaceNotNacked);
+    if (inR != pitEntry->getInRecords().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);
+}
+
 } // namespace fw
 } // namespace nfd
diff --git a/daemon/fw/best-route-strategy2.hpp b/daemon/fw/best-route-strategy2.hpp
index bf3eba2..8c19dc0 100644
--- a/daemon/fw/best-route-strategy2.hpp
+++ b/daemon/fw/best-route-strategy2.hpp
@@ -32,13 +32,22 @@
 namespace nfd {
 namespace fw {
 
-/** \brief Best Route strategy version 3
+/** \brief Best Route strategy version 4
  *
  *  This strategy forwards a new Interest to the lowest-cost nexthop (except downstream).
  *  After that, if consumer retransmits the Interest (and is not suppressed according to
  *  exponential backoff algorithm), the strategy forwards the Interest again to
  *  the lowest-cost nexthop (except downstream) that is not previously used.
- *  If all nexthops have been used, the strategy starts over.
+ *  If all nexthops have been used, the strategy starts over with the first nexthop.
+ *
+ *  This strategy returns Nack to all downstreams with reason NoRoute
+ *  if there is no usable nexthop, which may be caused by:
+ *  (a) the FIB entry contains no nexthop;
+ *  (b) the FIB nexthop happens to be the sole downstream;
+ *  (c) the FIB nexthops violate scope.
+ *
+ *  This strategy returns Nack to all downstreams if all upstreams have returned Nacks.
+ *  The reason of the sent Nack equals the least severe reason among received Nacks.
  */
 class BestRouteStrategy2 : public Strategy
 {
@@ -46,11 +55,15 @@
   BestRouteStrategy2(Forwarder& forwarder, const Name& name = STRATEGY_NAME);
 
   virtual void
-  afterReceiveInterest(const Face& inFace,
-                       const Interest& interest,
+  afterReceiveInterest(const Face& inFace, const Interest& interest,
                        shared_ptr<fib::Entry> fibEntry,
                        shared_ptr<pit::Entry> pitEntry) DECL_OVERRIDE;
 
+  virtual void
+  afterReceiveNack(const Face& inFace, const lp::Nack& nack,
+                   shared_ptr<fib::Entry> fibEntry,
+                   shared_ptr<pit::Entry> pitEntry) DECL_OVERRIDE;
+
 public:
   static const Name STRATEGY_NAME;
 
diff --git a/daemon/fw/face-table.cpp b/daemon/fw/face-table.cpp
index f256d20..1884203 100644
--- a/daemon/fw/face-table.cpp
+++ b/daemon/fw/face-table.cpp
@@ -89,6 +89,8 @@
                                        &m_forwarder, ref(*face), _1));
   face->onReceiveData.connect(bind(&Forwarder::startProcessData,
                                    &m_forwarder, ref(*face), _1));
+  face->onReceiveNack.connect(bind(&Forwarder::startProcessNack,
+                                   &m_forwarder, ref(*face), _1));
   face->onFail.connectSingleShot(bind(&FaceTable::remove, this, face, _1));
 
   this->onAdd(face);
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index 27bfa38..20428c5 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -60,7 +60,7 @@
       interest.getLink();
     }
   }
-  catch (tlv::Error&) {
+  catch (const tlv::Error&) {
     NFD_LOG_DEBUG("startProcessInterest face=" << face.getId() <<
                   " interest=" << interest.getName() << " malformed");
     // It's safe to call interest.getName() because Name has been fully parsed
@@ -80,6 +80,25 @@
 }
 
 void
+Forwarder::startProcessNack(Face& face, const lp::Nack& nack)
+{
+  // check fields used by forwarding are well-formed
+  try {
+    if (nack.getInterest().hasLink()) {
+      nack.getInterest().getLink();
+    }
+  }
+  catch (const tlv::Error&) {
+    NFD_LOG_DEBUG("startProcessNack face=" << face.getId() <<
+                  " nack=" << nack.getInterest().getName() <<
+                  "~" << nack.getReason() << " malformed");
+    return;
+  }
+
+  this->onIncomingNack(face, nack);
+}
+
+void
 Forwarder::onIncomingInterest(Face& inFace, const Interest& interest)
 {
   // receive Interest
@@ -131,10 +150,23 @@
 Forwarder::onInterestLoop(Face& inFace, const Interest& interest,
                           shared_ptr<pit::Entry> pitEntry)
 {
-  NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() <<
-                " interest=" << interest.getName());
+  // if multi-access face, drop
+  if (inFace.isMultiAccess()) {
+    NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() <<
+                  " interest=" << interest.getName() <<
+                  " drop");
+    return;
+  }
 
-  // (drop)
+  NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() <<
+                " interest=" << interest.getName() <<
+                " send-Nack-duplicate");
+
+  // send Nack with reason=DUPLICATE
+  // note: Don't enter outgoing Nack pipeline because it needs an in-record.
+  lp::Nack nack(interest);
+  nack.setReason(lp::NackReason::DUPLICATE);
+  inFace.sendNack(nack);
 }
 
 void
@@ -362,7 +394,7 @@
   // CS insert
   m_cs.insert(data);
 
-  std::set<shared_ptr<Face> > pendingDownstreams;
+  std::set<Face*> pendingDownstreams;
   // foreach PitEntry
   for (const shared_ptr<pit::Entry>& pitEntry : pitMatches) {
     NFD_LOG_DEBUG("onIncomingData matching=" << pitEntry->getName());
@@ -372,10 +404,9 @@
 
     // remember pending downstreams
     const pit::InRecordCollection& inRecords = pitEntry->getInRecords();
-    for (pit::InRecordCollection::const_iterator it = inRecords.begin();
-                                                 it != inRecords.end(); ++it) {
-      if (it->getExpiry() > time::steady_clock::now()) {
-        pendingDownstreams.insert(it->getFace());
+    for (const pit::InRecord& inRecord : inRecords) {
+      if (inRecord.getExpiry() > time::steady_clock::now()) {
+        pendingDownstreams.insert(inRecord.getFace().get());
       }
     }
 
@@ -395,10 +426,8 @@
   }
 
   // foreach pending downstream
-  for (std::set<shared_ptr<Face> >::iterator it = pendingDownstreams.begin();
-      it != pendingDownstreams.end(); ++it) {
-    shared_ptr<Face> pendingDownstream = *it;
-    if (pendingDownstream.get() == &inFace) {
+  for (Face* pendingDownstream : pendingDownstreams) {
+    if (pendingDownstream == &inFace) {
       continue;
     }
     // goto outgoing Data pipeline
@@ -447,6 +476,104 @@
   ++m_counters.getNOutDatas();
 }
 
+void
+Forwarder::onIncomingNack(Face& inFace, const lp::Nack& nack)
+{
+  // if multi-access face, drop
+  if (inFace.isMultiAccess()) {
+    NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() <<
+                  " nack=" << nack.getInterest().getName() <<
+                  "~" << nack.getReason() << " face-is-multi-access");
+    return;
+  }
+
+  // PIT match
+  shared_ptr<pit::Entry> pitEntry = m_pit.find(nack.getInterest());
+  // if no PIT entry found, drop
+  if (pitEntry == nullptr) {
+    NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() <<
+                  " nack=" << nack.getInterest().getName() <<
+                  "~" << nack.getReason() << " no-PIT-entry");
+    return;
+  }
+
+  // has out-record?
+  pit::OutRecordCollection::iterator outRecord = pitEntry->getOutRecord(inFace);
+  // if no out-record found, drop
+  if (outRecord == pitEntry->getOutRecords().end()) {
+    NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() <<
+                  " nack=" << nack.getInterest().getName() <<
+                  "~" << nack.getReason() << " no-out-record");
+    return;
+  }
+
+  // if out-record has different Nonce, drop
+  if (nack.getInterest().getNonce() != outRecord->getLastNonce()) {
+    NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() <<
+                  " nack=" << nack.getInterest().getName() <<
+                  "~" << nack.getReason() << " wrong-Nonce " <<
+                  nack.getInterest().getNonce() << "!=" << outRecord->getLastNonce());
+    return;
+  }
+
+  NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() <<
+                " nack=" << nack.getInterest().getName() <<
+                "~" << nack.getReason() << " OK");
+
+  // record Nack on out-record
+  outRecord->setIncomingNack(nack);
+
+  // trigger strategy: after receive NACK
+  shared_ptr<fib::Entry> fibEntry = m_fib.findLongestPrefixMatch(*pitEntry);
+  this->dispatchToStrategy(pitEntry, bind(&Strategy::afterReceiveNack, _1,
+                                          cref(inFace), cref(nack), fibEntry, pitEntry));
+}
+
+void
+Forwarder::onOutgoingNack(shared_ptr<pit::Entry> pitEntry, const Face& outFace,
+                          const lp::NackHeader& nack)
+{
+  if (outFace.getId() == INVALID_FACEID) {
+    NFD_LOG_WARN("onOutgoingNack face=invalid" <<
+                  " nack=" << pitEntry->getInterest().getName() <<
+                  "~" << nack.getReason() << " no-in-record");
+    return;
+  }
+
+  // has in-record?
+  pit::InRecordCollection::const_iterator inRecord = pitEntry->getInRecord(outFace);
+
+  // if no in-record found, drop
+  if (inRecord == pitEntry->getInRecords().end()) {
+    NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() <<
+                  " nack=" << pitEntry->getInterest().getName() <<
+                  "~" << nack.getReason() << " no-in-record");
+    return;
+  }
+
+  // if multi-access face, drop
+  if (outFace.isMultiAccess()) {
+    NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() <<
+                  " nack=" << pitEntry->getInterest().getName() <<
+                  "~" << nack.getReason() << " face-is-multi-access");
+    return;
+  }
+
+  NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() <<
+                " nack=" << pitEntry->getInterest().getName() <<
+                "~" << nack.getReason() << " OK");
+
+  // create Nack packet with the Interest from in-record
+  lp::Nack nackPkt(inRecord->getInterest());
+  nackPkt.setHeader(nack);
+
+  // erase in-record
+  pitEntry->deleteInRecord(outFace);
+
+  // send Nack on face
+  const_cast<Face&>(outFace).sendNack(nackPkt);
+}
+
 static inline bool
 compare_InRecord_expiry(const pit::InRecord& a, const pit::InRecord& b)
 {
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index 155ec4a..ebfaa05 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -90,6 +90,12 @@
   void
   startProcessData(Face& face, const Data& data);
 
+  /** \brief start incoming Nack processing
+   *  \param nack the incoming Nack, must be created with make_shared
+   */
+  void
+  startProcessNack(Face& face, const lp::Nack& nack);
+
   NameTree&
   getNameTree();
 
@@ -128,12 +134,12 @@
 
   /** \brief Content Store miss pipeline
   */
-  void
+  VIRTUAL_WITH_TESTS void
   onContentStoreMiss(const Face& inFace, shared_ptr<pit::Entry> pitEntry, const Interest& interest);
 
   /** \brief Content Store hit pipeline
   */
-  void
+  VIRTUAL_WITH_TESTS void
   onContentStoreHit(const Face& inFace, shared_ptr<pit::Entry> pitEntry,
                     const Interest& interest, const Data& data);
 
@@ -176,6 +182,16 @@
   VIRTUAL_WITH_TESTS void
   onOutgoingData(const Data& data, Face& outFace);
 
+  /** \brief incoming Nack pipeline
+   */
+  VIRTUAL_WITH_TESTS void
+  onIncomingNack(Face& inFace, const lp::Nack& nack);
+
+  /** \brief outgoing Nack pipeline
+   */
+  VIRTUAL_WITH_TESTS void
+  onOutgoingNack(shared_ptr<pit::Entry> pitEntry, const Face& outFace, const lp::NackHeader& nack);
+
 PROTECTED_WITH_TESTS_ELSE_PRIVATE:
   VIRTUAL_WITH_TESTS void
   setUnsatisfyTimer(shared_ptr<pit::Entry> pitEntry);
diff --git a/daemon/fw/strategy.cpp b/daemon/fw/strategy.cpp
index 4f439db..7b0504e 100644
--- a/daemon/fw/strategy.cpp
+++ b/daemon/fw/strategy.cpp
@@ -60,23 +60,36 @@
   NFD_LOG_DEBUG("beforeExpirePendingInterest pitEntry=" << pitEntry->getName());
 }
 
-//void
-//Strategy::afterAddFibEntry(shared_ptr<fib::Entry> fibEntry)
-//{
-//  NFD_LOG_DEBUG("afterAddFibEntry fibEntry=" << fibEntry->getPrefix());
-//}
-//
-//void
-//Strategy::afterUpdateFibEntry(shared_ptr<fib::Entry> fibEntry)
-//{
-//  NFD_LOG_DEBUG("afterUpdateFibEntry fibEntry=" << fibEntry->getPrefix());
-//}
-//
-//void
-//Strategy::beforeRemoveFibEntry(shared_ptr<fib::Entry> fibEntry)
-//{
-//  NFD_LOG_DEBUG("beforeRemoveFibEntry fibEntry=" << fibEntry->getPrefix());
-//}
+void
+Strategy::afterReceiveNack(const Face& inFace, const lp::Nack& nack,
+                           shared_ptr<fib::Entry> fibEntry, shared_ptr<pit::Entry> pitEntry)
+{
+  NFD_LOG_DEBUG("afterReceiveNack inFace=" << inFace.getId() <<
+                " pitEntry=" << pitEntry->getName());
+}
+
+void
+Strategy::sendNacks(shared_ptr<pit::Entry> pitEntry, const lp::NackHeader& header,
+                    std::initializer_list<const Face*> exceptFaces)
+{
+  // populate downstreams with all downstreams faces
+  std::unordered_set<const Face*> downstreams;
+  const pit::InRecordCollection& inRecords = pitEntry->getInRecords();
+  std::transform(inRecords.begin(), inRecords.end(), std::inserter(downstreams, downstreams.end()),
+                 [] (const pit::InRecord& inR) { return inR.getFace().get(); });
+
+  // delete excluded faces
+  // .erase in a loop is more efficient than std::set_difference between that requires sorted range
+  for (const Face* exceptFace : exceptFaces) {
+    downstreams.erase(exceptFace);
+  }
+
+  // send Nacks
+  for (const Face* downstream : downstreams) {
+    this->sendNack(pitEntry, *downstream, header);
+  }
+  // warning: don't loop on pitEntry->getInRecords(), because InRecord is erased when sending Nack
+}
 
 } // namespace fw
 } // namespace nfd
diff --git a/daemon/fw/strategy.hpp b/daemon/fw/strategy.hpp
index 8eca1db..ebbb361 100644
--- a/daemon/fw/strategy.hpp
+++ b/daemon/fw/strategy.hpp
@@ -70,7 +70,7 @@
    *    invoke this->rejectPendingInterest so that PIT entry will be deleted shortly
    *
    *  \note The strategy is permitted to store a weak reference to fibEntry.
-   *        Do not store a shared reference, because PIT entry may be deleted at any moment.
+   *        Do not store a shared reference, because FIB entry may be deleted at any moment.
    *        fibEntry is passed by value to allow obtaining a weak reference from it.
    *  \note The strategy is permitted to store a shared reference to pitEntry.
    *        pitEntry is passed by value to reflect this fact.
@@ -110,11 +110,33 @@
   virtual void
   beforeExpirePendingInterest(shared_ptr<pit::Entry> pitEntry);
 
+  /** \brief trigger after Nack is received
+   *
+   *  This trigger is invoked when an incoming Nack is received in response to
+   *  an forwarded Interest.
+   *  The Nack has been confirmed to be a response to the last Interest forwarded
+   *  to that upstream, i.e. the PIT out-record exists and has a matching Nonce.
+   *  The NackHeader has been recorded in the PIT out-record.
+   *
+   *  In this base class this method does nothing.
+   *
+   *  \note The strategy is permitted to store a weak reference to fibEntry.
+   *        Do not store a shared reference, because PIT entry may be deleted at any moment.
+   *        fibEntry is passed by value to allow obtaining a weak reference from it.
+   *  \note The strategy is permitted to store a shared reference to pitEntry.
+   *        pitEntry is passed by value to reflect this fact.
+   */
+  virtual void
+  afterReceiveNack(const Face& inFace, const lp::Nack& nack,
+                   shared_ptr<fib::Entry> fibEntry, shared_ptr<pit::Entry> pitEntry);
+
 protected: // actions
-  /// send Interest to outFace
+  /** \brief send Interest to outFace
+   *  \param wantNewNonce if true, a new Nonce will be generated,
+   *                      rather than reusing a Nonce from one of the PIT in-records
+   */
   VIRTUAL_WITH_TESTS void
-  sendInterest(shared_ptr<pit::Entry> pitEntry,
-               shared_ptr<Face> outFace,
+  sendInterest(shared_ptr<pit::Entry> pitEntry, shared_ptr<Face> outFace,
                bool wantNewNonce = false);
 
   /** \brief decide that a pending Interest cannot be forwarded
@@ -125,6 +147,22 @@
   VIRTUAL_WITH_TESTS void
   rejectPendingInterest(shared_ptr<pit::Entry> pitEntry);
 
+  /** \brief send Nack to outFace
+   *
+   *  The outFace must have a PIT in-record, otherwise this method has no effect.
+   */
+  VIRTUAL_WITH_TESTS void
+  sendNack(shared_ptr<pit::Entry> pitEntry, const Face& outFace,
+           const lp::NackHeader& header);
+
+  /** \brief send Nack to every face that has an in-record,
+   *         except those in \p exceptFaces
+   *  \note This is not an action, but a helper that invokes the sendNack action.
+   */
+  void
+  sendNacks(shared_ptr<pit::Entry> pitEntry, const lp::NackHeader& header,
+            std::initializer_list<const Face*> exceptFaces = std::initializer_list<const Face*>());
+
 protected: // accessors
   MeasurementsAccessor&
   getMeasurements();
@@ -171,6 +209,13 @@
   m_forwarder.onInterestReject(pitEntry);
 }
 
+inline void
+Strategy::sendNack(shared_ptr<pit::Entry> pitEntry, const Face& outFace,
+                   const lp::NackHeader& header)
+{
+  m_forwarder.onOutgoingNack(pitEntry, outFace, header);
+}
+
 inline MeasurementsAccessor&
 Strategy::getMeasurements()
 {
diff --git a/daemon/table/pit-entry.cpp b/daemon/table/pit-entry.cpp
index da7c6ed..85ea19c 100644
--- a/daemon/table/pit-entry.cpp
+++ b/daemon/table/pit-entry.cpp
@@ -149,6 +149,16 @@
 }
 
 void
+Entry::deleteInRecord(const Face& face)
+{
+  auto it = std::find_if(m_inRecords.begin(), m_inRecords.end(),
+    [&face] (const InRecord& inRecord) { return inRecord.getFace().get() == &face; });
+  if (it != m_inRecords.end()) {
+    m_inRecords.erase(it);
+  }
+}
+
+void
 Entry::deleteInRecords()
 {
   m_inRecords.clear();
@@ -168,8 +178,8 @@
   return it;
 }
 
-OutRecordCollection::const_iterator
-Entry::getOutRecord(const Face& face) const
+OutRecordCollection::iterator
+Entry::getOutRecord(const Face& face)
 {
   return std::find_if(m_outRecords.begin(), m_outRecords.end(),
     [&face] (const OutRecord& outRecord) { return outRecord.getFace().get() == &face; });
diff --git a/daemon/table/pit-entry.hpp b/daemon/table/pit-entry.hpp
index 6d59ce8..3f15598 100644
--- a/daemon/table/pit-entry.hpp
+++ b/daemon/table/pit-entry.hpp
@@ -132,6 +132,10 @@
   InRecordCollection::const_iterator
   getInRecord(const Face& face) const;
 
+  /// deletes one InRecord for face if exists
+  void
+  deleteInRecord(const Face& face);
+
   /// deletes all InRecords
   void
   deleteInRecords();
@@ -151,8 +155,8 @@
   /** \brief get the OutRecord for face
    *  \return an iterator to the OutRecord, or .end if it does not exist
    */
-  OutRecordCollection::const_iterator
-  getOutRecord(const Face& face) const;
+  OutRecordCollection::iterator
+  getOutRecord(const Face& face);
 
   /// deletes one OutRecord for face if exists
   void
diff --git a/daemon/table/pit-out-record.cpp b/daemon/table/pit-out-record.cpp
index 268b67b..1cf303d 100644
--- a/daemon/table/pit-out-record.cpp
+++ b/daemon/table/pit-out-record.cpp
@@ -1,11 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014  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
+ * Copyright (c) 2014-2015,  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.
@@ -20,7 +21,7 @@
  *
  * 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 "pit-out-record.hpp"
 
@@ -32,5 +33,16 @@
 {
 }
 
+bool
+OutRecord::setIncomingNack(const lp::Nack& nack)
+{
+  if (nack.getInterest().getNonce() != this->getLastNonce()) {
+    return false;
+  }
+
+  m_incomingNack.reset(new lp::NackHeader(nack.getHeader()));
+  return true;
+}
+
 } // namespace pit
 } // namespace nfd
diff --git a/daemon/table/pit-out-record.hpp b/daemon/table/pit-out-record.hpp
index c75e005..4717779 100644
--- a/daemon/table/pit-out-record.hpp
+++ b/daemon/table/pit-out-record.hpp
@@ -1,11 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014  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
+ * Copyright (c) 2014-2015,  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.
@@ -20,7 +21,7 @@
  *
  * 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_TABLE_PIT_OUT_RECORD_HPP
 #define NFD_DAEMON_TABLE_PIT_OUT_RECORD_HPP
@@ -30,14 +31,49 @@
 namespace nfd {
 namespace pit {
 
-/** \class OutRecord
- *  \brief contains information about an Interest toward an outgoing face
+/** \brief contains information about an Interest toward an outgoing face
  */
 class OutRecord : public FaceRecord
 {
 public:
   explicit
   OutRecord(shared_ptr<Face> face);
+
+  /** \return last NACK returned by \p getFace()
+   *
+   *  A nullptr return value means the Interest is still pending or has timed out.
+   *  A non-null return value means the last outgoing Interest has been NACKed.
+   */
+  const lp::NackHeader*
+  getIncomingNack() const
+  {
+    return m_incomingNack.get();
+  }
+
+  /** \brief sets a NACK received from \p getFace()
+   *  \return whether incoming NACK is accepted
+   *
+   *  This is invoked in incoming NACK pipeline.
+   *  An incoming NACK is accepted if its Nonce matches \p getLastNonce().
+   *  If accepted, \p nack.getHeader() will be copied,
+   *  and any pointer previously returned by \p .getIncomingNack() .
+   */
+  bool
+  setIncomingNack(const lp::Nack& nack);
+
+  /** \brief clears last NACK
+   *
+   *  This is invoked in outgoing Interest pipeline.
+   *  This invalidates any pointer previously returned by \p .getIncomingNack() .
+   */
+  void
+  clearIncomingNack()
+  {
+    m_incomingNack.reset();
+  }
+
+private:
+  unique_ptr<lp::NackHeader> m_incomingNack;
 };
 
 } // namespace pit
diff --git a/daemon/table/pit.cpp b/daemon/table/pit.cpp
index c21ccdf..959593f 100644
--- a/daemon/table/pit.cpp
+++ b/daemon/table/pit.cpp
@@ -62,7 +62,7 @@
 }
 
 std::pair<shared_ptr<pit::Entry>, bool>
-Pit::insert(const Interest& interest)
+Pit::findOrInsert(const Interest& interest, bool allowInsert)
 {
   // first lookup() the Interest Name in the NameTree, which will creates all
   // the intermedia nodes, starting from the shortest prefix.
@@ -78,13 +78,17 @@
                                   entry->getInterest().getSelectors() == interest.getSelectors();
                          });
   if (it != pitEntries.end()) {
-    return { *it, false };
+    return {*it, false};
+  }
+
+  if (!allowInsert) {
+    return {nullptr, true};
   }
 
   shared_ptr<pit::Entry> entry = make_shared<pit::Entry>(interest);
   nameTreeEntry->insertPitEntry(entry);
   m_nItems++;
-  return { entry, true };
+  return {entry, true};
 }
 
 pit::DataMatchResult
diff --git a/daemon/table/pit.hpp b/daemon/table/pit.hpp
index e9616d5..adab085 100644
--- a/daemon/table/pit.hpp
+++ b/daemon/table/pit.hpp
@@ -58,10 +58,17 @@
   size_t
   size() const;
 
+  /** \brief finds a PIT entry for Interest
+   *  \param interest the Interest
+   *  \return an existing entry with same Name and Selectors; otherwise nullptr
+   */
+  shared_ptr<pit::Entry>
+  find(const Interest& interest) const;
+
   /** \brief inserts a PIT entry for Interest
-   *
-   *  If an entry for exact same name and selectors exists, that entry is returned.
-   *  \return the entry, and true for new entry, false for existing entry
+   *  \param interest the Interest; must be created with make_shared
+   *  \return a new or existing entry with same Name and Selectors,
+   *          and true for new entry, false for existing entry
    */
   std::pair<shared_ptr<pit::Entry>, bool>
   insert(const Interest& interest);
@@ -135,6 +142,18 @@
   };
 
 private:
+  /** \brief finds or inserts a PIT entry for Interest
+   *  \param interest the Interest; must be created with make_shared if allowInsert
+   *  \param allowInsert whether inserting new entry is allowed.
+   *  \return if allowInsert, a new or existing entry with same Name+Selectors,
+   *          and true for new entry, false for existing entry;
+   *          if not allowInsert, an existing entry with same Name+Selectors and false,
+   *          or {nullptr, true} if there's no existing entry
+   */
+  std::pair<shared_ptr<pit::Entry>, bool>
+  findOrInsert(const Interest& interest, bool allowInsert);
+
+private:
   NameTree& m_nameTree;
   size_t m_nItems;
 };
@@ -145,6 +164,18 @@
   return m_nItems;
 }
 
+inline shared_ptr<pit::Entry>
+Pit::find(const Interest& interest) const
+{
+  return const_cast<Pit*>(this)->findOrInsert(interest, false).first;
+}
+
+inline std::pair<shared_ptr<pit::Entry>, bool>
+Pit::insert(const Interest& interest)
+{
+  return this->findOrInsert(interest, true);
+}
+
 inline Pit::const_iterator
 Pit::end() const
 {