fw: handle dropped packets in strategies

refs #5128

Change-Id: Ic7f7e61b2dde66004d0291bb41e008961fc7b252
diff --git a/daemon/fw/access-strategy.cpp b/daemon/fw/access-strategy.cpp
index ee2a7a2..c40b12e 100644
--- a/daemon/fw/access-strategy.cpp
+++ b/daemon/fw/access-strategy.cpp
@@ -144,7 +144,9 @@
   NFD_LOG_DEBUG(pitEntry->getInterest() << " interestTo " << mi.lastNexthop
                 << " last-nexthop rto=" << time::duration_cast<time::microseconds>(rto).count());
 
-  this->sendInterest(pitEntry, *outFace, interest);
+  if (!this->sendInterest(pitEntry, *outFace, interest)) {
+    return false;
+  }
 
   // schedule RTO timeout
   PitInfo* pi = pitEntry->insertStrategyInfo<PitInfo>().first;
@@ -198,8 +200,9 @@
       continue;
     }
     NFD_LOG_DEBUG(pitEntry->getInterest() << " interestTo " << outFace.getId() << " multicast");
-    this->sendInterest(pitEntry, outFace, interest);
-    ++nSent;
+    if (this->sendInterest(pitEntry, outFace, interest)) {
+      ++nSent;
+    }
   }
   return nSent;
 }
diff --git a/daemon/fw/algorithm.hpp b/daemon/fw/algorithm.hpp
index 68d9f14..8a6b849 100644
--- a/daemon/fw/algorithm.hpp
+++ b/daemon/fw/algorithm.hpp
@@ -23,8 +23,8 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef NFD_DAEMON_FW_PIT_ALGORITHM_HPP
-#define NFD_DAEMON_FW_PIT_ALGORITHM_HPP
+#ifndef NFD_DAEMON_FW_ALGORITHM_HPP
+#define NFD_DAEMON_FW_ALGORITHM_HPP
 
 #include "fw/scope-prefix.hpp"
 #include "table/fib.hpp"
@@ -110,4 +110,4 @@
 } // namespace fw
 } // namespace nfd
 
-#endif // NFD_DAEMON_FW_PIT_ALGORITHM_HPP
+#endif // NFD_DAEMON_FW_ALGORITHM_HPP
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index bd4d692..671c536 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -240,7 +240,7 @@
     [&] (fw::Strategy& strategy) { strategy.afterContentStoreHit(pitEntry, ingress, data); });
 }
 
-void
+pit::OutRecord*
 Forwarder::onOutgoingInterest(const shared_ptr<pit::Entry>& pitEntry,
                               Face& egress, const Interest& interest)
 {
@@ -249,17 +249,19 @@
     NFD_LOG_DEBUG("onOutgoingInterest out=" << egress.getId() << " interest=" << pitEntry->getName()
                   << " non-local hop-limit=0");
     ++const_cast<PacketCounter&>(egress.getCounters().nOutHopLimitZero);
-    return;
+    return nullptr;
   }
 
   NFD_LOG_DEBUG("onOutgoingInterest out=" << egress.getId() << " interest=" << pitEntry->getName());
 
   // insert out-record
-  pitEntry->insertOrUpdateOutRecord(egress, interest);
+  auto it = pitEntry->insertOrUpdateOutRecord(egress, interest);
+  BOOST_ASSERT(it != pitEntry->out_end());
 
   // send Interest
   egress.sendInterest(interest);
   ++m_counters.nOutInterests;
+  return &*it;
 }
 
 void
@@ -397,12 +399,12 @@
   ++m_counters.nUnsolicitedData;
 }
 
-void
+bool
 Forwarder::onOutgoingData(const Data& data, Face& egress)
 {
   if (egress.getId() == face::INVALID_FACEID) {
     NFD_LOG_WARN("onOutgoingData out=(invalid) data=" << data.getName());
-    return;
+    return false;
   }
   NFD_LOG_DEBUG("onOutgoingData out=" << egress.getId() << " data=" << data.getName());
 
@@ -413,7 +415,7 @@
     NFD_LOG_DEBUG("onOutgoingData out=" << egress.getId() << " data=" << data.getName()
                   << " violates /localhost");
     // (drop)
-    return;
+    return false;
   }
 
   // TODO traffic manager
@@ -421,6 +423,8 @@
   // send Data
   egress.sendData(data);
   ++m_counters.nOutData;
+
+  return true;
 }
 
 void
@@ -480,14 +484,14 @@
     [&] (fw::Strategy& strategy) { strategy.afterReceiveNack(ingress, nack, pitEntry); });
 }
 
-void
+bool
 Forwarder::onOutgoingNack(const shared_ptr<pit::Entry>& pitEntry,
                           Face& egress, const lp::NackHeader& nack)
 {
   if (egress.getId() == face::INVALID_FACEID) {
     NFD_LOG_WARN("onOutgoingNack out=(invalid)"
                  << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason());
-    return;
+    return false;
   }
 
   // has in-record?
@@ -498,7 +502,7 @@
     NFD_LOG_DEBUG("onOutgoingNack out=" << egress.getId()
                   << " nack=" << pitEntry->getInterest().getName()
                   << "~" << nack.getReason() << " no-in-record");
-    return;
+    return false;
   }
 
   // if multi-access or ad hoc face, drop
@@ -506,7 +510,7 @@
     NFD_LOG_DEBUG("onOutgoingNack out=" << egress.getId()
                   << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason()
                   << " link-type=" << egress.getLinkType());
-    return;
+    return false;
   }
 
   NFD_LOG_DEBUG("onOutgoingNack out=" << egress.getId()
@@ -523,6 +527,8 @@
   // send Nack on face
   egress.sendNack(nackPkt);
   ++m_counters.nOutNacks;
+
+  return true;
 }
 
 void
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index a3d4771..f736e0f 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -189,8 +189,9 @@
                     const Interest& interest, const Data& data);
 
   /** \brief outgoing Interest pipeline
+   *  \return A pointer to the out-record created or nullptr if the Interest was dropped
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS pit::OutRecord*
   onOutgoingInterest(const shared_ptr<pit::Entry>& pitEntry,
                      Face& egress, const Interest& interest);
 
@@ -210,8 +211,9 @@
   onDataUnsolicited(const FaceEndpoint& ingress, const Data& data);
 
   /** \brief outgoing Data pipeline
+   *  \return Whether the Data was transmitted (true) or dropped (false)
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS bool
   onOutgoingData(const Data& data, Face& egress);
 
   /** \brief incoming Nack pipeline
@@ -220,8 +222,9 @@
   onIncomingNack(const FaceEndpoint& ingress, const lp::Nack& nack);
 
   /** \brief outgoing Nack pipeline
+   *  \return Whether the Nack was transmitted (true) or dropped (false)
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS bool
   onOutgoingNack(const shared_ptr<pit::Entry>& pitEntry,
                  Face& egress, const lp::NackHeader& nack);
 
diff --git a/daemon/fw/multicast-strategy.cpp b/daemon/fw/multicast-strategy.cpp
index ee921c3..3cb351c 100644
--- a/daemon/fw/multicast-strategy.cpp
+++ b/daemon/fw/multicast-strategy.cpp
@@ -83,10 +83,9 @@
       continue;
     }
 
-    this->sendInterest(pitEntry, outFace, interest);
     NFD_LOG_DEBUG(interest << " from=" << ingress << " pitEntry-to=" << outFace.getId());
-
-    if (suppressResult == RetxSuppressionResult::FORWARD) {
+    bool wasSent = this->sendInterest(pitEntry, outFace, interest) != nullptr;
+    if (wasSent && suppressResult == RetxSuppressionResult::FORWARD) {
       m_retxSuppression.incrementIntervalForOutRecord(*pitEntry->getOutRecord(outFace));
     }
   }
diff --git a/daemon/fw/self-learning-strategy.cpp b/daemon/fw/self-learning-strategy.cpp
index ea32709..59a3f8f 100644
--- a/daemon/fw/self-learning-strategy.cpp
+++ b/daemon/fw/self-learning-strategy.cpp
@@ -149,13 +149,17 @@
 {
   for (auto& outFace : this->getFaceTable() | boost::adaptors::reversed) {
     if ((outFace.getId() == inFace.getId() && outFace.getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) ||
-        wouldViolateScope(inFace, interest, outFace) || outFace.getScope() == ndn::nfd::FACE_SCOPE_LOCAL) {
+        wouldViolateScope(inFace, interest, outFace) ||
+        outFace.getScope() == ndn::nfd::FACE_SCOPE_LOCAL) {
       continue;
     }
-    this->sendInterest(pitEntry, outFace, interest);
-    pitEntry->getOutRecord(outFace)->insertStrategyInfo<OutRecordInfo>().first->isNonDiscoveryInterest = false;
-    NFD_LOG_DEBUG("send discovery Interest=" << interest << " from="
-                  << inFace.getId() << " to=" << outFace.getId());
+
+    NFD_LOG_DEBUG("send discovery Interest=" << interest << " from=" << inFace.getId() <<
+                  " to=" << outFace.getId());
+    auto outRecord = this->sendInterest(pitEntry, outFace, interest);
+    if (outRecord != nullptr) {
+      outRecord->insertStrategyInfo<OutRecordInfo>().first->isNonDiscoveryInterest = false;
+    }
   }
 }
 
@@ -165,15 +169,17 @@
                                         const fib::NextHopList& nexthops)
 {
   for (const auto& nexthop : nexthops) {
-    Face& outFace = nexthop.getFace();
-    if ((outFace.getId() == inFace.getId() && outFace.getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) ||
-        wouldViolateScope(inFace, interest, outFace)) {
+    if (!isNextHopEligible(inFace, interest, nexthop, pitEntry)) {
       continue;
     }
-    this->sendInterest(pitEntry, outFace, interest);
-    pitEntry->getOutRecord(outFace)->insertStrategyInfo<OutRecordInfo>().first->isNonDiscoveryInterest = true;
-    NFD_LOG_DEBUG("send non-discovery Interest=" << interest << " from="
-                  << inFace.getId() << " to=" << outFace.getId());
+
+    Face& outFace = nexthop.getFace();
+    NFD_LOG_DEBUG("send non-discovery Interest=" << interest << " from=" << inFace.getId() <<
+                  " to=" << outFace.getId());
+    auto outRecord = this->sendInterest(pitEntry, outFace, interest);
+    if (outRecord != nullptr) {
+      outRecord->insertStrategyInfo<OutRecordInfo>().first->isNonDiscoveryInterest = true;
+    }
   }
 }
 
diff --git a/daemon/fw/strategy.cpp b/daemon/fw/strategy.cpp
index 785cc8b..3dc9abd 100644
--- a/daemon/fw/strategy.cpp
+++ b/daemon/fw/strategy.cpp
@@ -193,16 +193,15 @@
   NFD_LOG_DEBUG("onDroppedInterest out=" << egress.getId() << " name=" << interest.getName());
 }
 
-void
+pit::OutRecord*
 Strategy::sendInterest(const shared_ptr<pit::Entry>& pitEntry, Face& egress, const Interest& interest)
 {
   if (interest.getTag<lp::PitToken>() != nullptr) {
     Interest interest2 = interest; // make a copy to preserve tag on original packet
     interest2.removeTag<lp::PitToken>();
-    m_forwarder.onOutgoingInterest(pitEntry, egress, interest2);
-    return;
+    return m_forwarder.onOutgoingInterest(pitEntry, egress, interest2);
   }
-  m_forwarder.onOutgoingInterest(pitEntry, egress, interest);
+  return m_forwarder.onOutgoingInterest(pitEntry, egress, interest);
 }
 
 void
@@ -212,7 +211,7 @@
                 << " nexthop=" << nextHop.getFace().getId());
 }
 
-void
+bool
 Strategy::sendData(const shared_ptr<pit::Entry>& pitEntry, const Data& data, Face& egress)
 {
   BOOST_ASSERT(pitEntry->getInterest().matchesData(data));
@@ -230,10 +229,9 @@
   if (pitToken != nullptr) {
     Data data2 = data; // make a copy so each downstream can get a different PIT token
     data2.setTag(pitToken);
-    m_forwarder.onOutgoingData(data2, egress);
-    return;
+    return m_forwarder.onOutgoingData(data2, egress);
   }
-  m_forwarder.onOutgoingData(data, egress);
+  return m_forwarder.onOutgoingData(data, egress);
 }
 
 void
diff --git a/daemon/fw/strategy.hpp b/daemon/fw/strategy.hpp
index 7c93327..37b1553 100644
--- a/daemon/fw/strategy.hpp
+++ b/daemon/fw/strategy.hpp
@@ -255,8 +255,9 @@
    *  \param pitEntry the PIT entry
    *  \param egress face through which to send out the Interest
    *  \param interest the Interest packet
+   *  \return A pointer to the out-record created or nullptr if the Interest was dropped
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS pit::OutRecord*
   sendInterest(const shared_ptr<pit::Entry>& pitEntry, Face& egress,
                const Interest& interest);
 
@@ -264,8 +265,9 @@
    *  \param pitEntry the PIT entry
    *  \param data the Data packet
    *  \param egress face through which to send out the Data
+   *  \return Whether the Data was sent (true) or dropped (false)
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS bool
   sendData(const shared_ptr<pit::Entry>& pitEntry, const Data& data, Face& egress);
 
   /** \brief Send a Data packet to all matched and qualified faces.
@@ -298,12 +300,13 @@
    *  \param pitEntry the PIT entry
    *  \param egress face through which to send out the Nack
    *  \param header the Nack header
+   *  \return Whether the Nack was sent (true) or dropped (false)
    */
-  VIRTUAL_WITH_TESTS void
+  VIRTUAL_WITH_TESTS bool
   sendNack(const shared_ptr<pit::Entry>& pitEntry, Face& egress,
            const lp::NackHeader& header)
   {
-    m_forwarder.onOutgoingNack(pitEntry, egress, header);
+    return m_forwarder.onOutgoingNack(pitEntry, egress, header);
   }
 
   /** \brief Send Nack to every face that has an in-record, except those in \p exceptFaces
diff --git a/tests/daemon/fw/forwarder.t.cpp b/tests/daemon/fw/forwarder.t.cpp
index 5439492..77db2c8 100644
--- a/tests/daemon/fw/forwarder.t.cpp
+++ b/tests/daemon/fw/forwarder.t.cpp
@@ -161,16 +161,20 @@
 
   Pit& pit = forwarder.getPit();
   auto interestA1 = makeInterest("/A", false, nullopt, 8378);
-  shared_ptr<pit::Entry> pitA = pit.insert(*interestA1).first;
+  auto pitA = pit.insert(*interestA1).first;
   pitA->insertOrUpdateInRecord(*face1, *interestA1);
 
   auto interestA2 = makeInterest("/A", false, nullopt, 1698);
-  forwarder.onOutgoingInterest(pitA, *face2, *interestA2);
-
-  auto outA2 = pitA->getOutRecord(*face2);
-  BOOST_REQUIRE(outA2 != pitA->out_end());
+  auto outA2 = forwarder.onOutgoingInterest(pitA, *face2, *interestA2);
+  BOOST_REQUIRE(outA2 != nullptr);
   BOOST_CHECK_EQUAL(outA2->getLastNonce(), 1698);
 
+  // This packet will be dropped because HopLimit=0
+  auto interestA3 = makeInterest("/A", false, nullopt, 9876);
+  interestA3->setHopLimit(0);
+  auto outA3 = forwarder.onOutgoingInterest(pitA, *face2, *interestA3);
+  BOOST_CHECK(outA3 == nullptr);
+
   BOOST_REQUIRE_EQUAL(face2->sentInterests.size(), 1);
   BOOST_CHECK_EQUAL(face2->sentInterests.back().getNonce(), 1698);
 }
@@ -339,8 +343,8 @@
   auto face1 = addFace();
   auto face2 = addFace();
 
-  DummyStrategy& strategyA = choose<DummyStrategy>(forwarder, "/", DummyStrategy::getStrategyName());
-  DummyStrategy& strategyB = choose<DummyStrategy>(forwarder, "/B", DummyStrategy::getStrategyName());
+  auto& strategyA = choose<DummyStrategy>(forwarder, "/", DummyStrategy::getStrategyName());
+  auto& strategyB = choose<DummyStrategy>(forwarder, "/B", DummyStrategy::getStrategyName());
 
   auto interest1 = makeInterest("/A/1");
   strategyA.afterReceiveInterest_count = 0;
@@ -381,14 +385,14 @@
 
   Pit& pit = forwarder.getPit();
   auto interestD = makeInterest("/A/B/C/D");
-  shared_ptr<pit::Entry> pitD = pit.insert(*interestD).first;
+  auto pitD = pit.insert(*interestD).first;
   pitD->insertOrUpdateInRecord(*face1, *interestD);
   auto interestA = makeInterest("/A", true);
-  shared_ptr<pit::Entry> pitA = pit.insert(*interestA).first;
+  auto pitA = pit.insert(*interestA).first;
   pitA->insertOrUpdateInRecord(*face2, *interestA);
   pitA->insertOrUpdateInRecord(*face3, *interestA);
   auto interestC = makeInterest("/A/B/C", true);
-  shared_ptr<pit::Entry> pitC = pit.insert(*interestC).first;
+  auto pitC = pit.insert(*interestC).first;
   pitC->insertOrUpdateInRecord(*face3, *interestC);
   pitC->insertOrUpdateInRecord(*face4, *interestC);
 
@@ -406,6 +410,36 @@
   BOOST_CHECK_EQUAL(forwarder.getCounters().nUnsolicitedData, 0);
 }
 
+BOOST_AUTO_TEST_CASE(OutgoingData)
+{
+  auto face1 = addFace("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
+  auto face2 = addFace("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  auto face3 = addFace();
+  face3->setId(face::INVALID_FACEID);
+
+  auto data = makeData("/QkzAWU6K");
+  auto localData = makeData("/localhost/YH8bqnbv");
+
+  face1->sentData.clear();
+  BOOST_CHECK(forwarder.onOutgoingData(*data, *face1));
+  BOOST_REQUIRE_EQUAL(face1->sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face1->sentData.back().getName(), data->getName());
+
+  // scope control
+  face1->sentData.clear();
+  face2->sentData.clear();
+  BOOST_CHECK(!forwarder.onOutgoingData(*localData, *face2));
+  BOOST_CHECK_EQUAL(face2->sentData.size(), 0);
+  BOOST_CHECK(forwarder.onOutgoingData(*localData, *face1));
+  BOOST_REQUIRE_EQUAL(face1->sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face1->sentData.back().getName(), localData->getName());
+
+  // face with invalid ID
+  face3->sentData.clear();
+  BOOST_CHECK(!forwarder.onOutgoingData(*data, *face3));
+  BOOST_CHECK_EQUAL(face3->sentData.size(), 0);
+}
+
 BOOST_AUTO_TEST_CASE(IncomingNack)
 {
   auto face1 = addFace();
@@ -415,27 +449,27 @@
                        ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                        ndn::nfd::LINK_TYPE_MULTI_ACCESS);
 
-  DummyStrategy& strategyA = choose<DummyStrategy>(forwarder, "/", DummyStrategy::getStrategyName());
-  DummyStrategy& strategyB = choose<DummyStrategy>(forwarder, "/B", DummyStrategy::getStrategyName());
+  auto& strategyA = choose<DummyStrategy>(forwarder, "/", DummyStrategy::getStrategyName());
+  auto& strategyB = choose<DummyStrategy>(forwarder, "/B", DummyStrategy::getStrategyName());
 
   Pit& pit = forwarder.getPit();
 
   // dispatch to the correct strategy
   auto interest1 = makeInterest("/A/AYJqayrzF", false, nullopt, 562);
-  shared_ptr<pit::Entry> pit1 = pit.insert(*interest1).first;
+  auto pit1 = pit.insert(*interest1).first;
   pit1->insertOrUpdateOutRecord(*face1, *interest1);
   auto interest2 = makeInterest("/B/EVyP73ru", false, nullopt, 221);
-  shared_ptr<pit::Entry> pit2 = pit.insert(*interest2).first;
+  auto pit2 = pit.insert(*interest2).first;
   pit2->insertOrUpdateOutRecord(*face1, *interest2);
 
-  lp::Nack nack1 = makeNack(*interest1, lp::NackReason::CONGESTION);
+  auto nack1 = makeNack(*interest1, lp::NackReason::CONGESTION);
   strategyA.afterReceiveNack_count = 0;
   strategyB.afterReceiveNack_count = 0;
   forwarder.onIncomingNack(FaceEndpoint(*face1, 0), nack1);
   BOOST_CHECK_EQUAL(strategyA.afterReceiveNack_count, 1);
   BOOST_CHECK_EQUAL(strategyB.afterReceiveNack_count, 0);
 
-  lp::Nack nack2 = makeNack(*interest2, lp::NackReason::CONGESTION);
+  auto nack2 = makeNack(*interest2, lp::NackReason::CONGESTION);
   strategyA.afterReceiveNack_count = 0;
   strategyB.afterReceiveNack_count = 0;
   forwarder.onIncomingNack(FaceEndpoint(*face1, 0), nack2);
@@ -449,7 +483,7 @@
   BOOST_CHECK_EQUAL(outRecord1->getIncomingNack()->getReason(), lp::NackReason::CONGESTION);
 
   // drop if no PIT entry
-  lp::Nack nack3 = makeNack(*makeInterest("/yEcw5HhdM", false, nullopt, 243), lp::NackReason::CONGESTION);
+  auto nack3 = makeNack(*makeInterest("/yEcw5HhdM", false, nullopt, 243), lp::NackReason::CONGESTION);
   strategyA.afterReceiveNack_count = 0;
   strategyB.afterReceiveNack_count = 0;
   forwarder.onIncomingNack(FaceEndpoint(*face1, 0), nack3);
@@ -458,10 +492,10 @@
 
   // drop if no out-record
   auto interest4 = makeInterest("/Etab4KpY", false, nullopt, 157);
-  shared_ptr<pit::Entry> pit4 = pit.insert(*interest4).first;
+  auto pit4 = pit.insert(*interest4).first;
   pit4->insertOrUpdateOutRecord(*face1, *interest4);
 
-  lp::Nack nack4a = makeNack(*interest4, lp::NackReason::CONGESTION);
+  auto nack4a = makeNack(*interest4, lp::NackReason::CONGESTION);
   strategyA.afterReceiveNack_count = 0;
   strategyB.afterReceiveNack_count = 0;
   forwarder.onIncomingNack(FaceEndpoint(*face2, 0), nack4a);
@@ -469,7 +503,7 @@
   BOOST_CHECK_EQUAL(strategyB.afterReceiveNack_count, 0);
 
   // drop if Nonce does not match out-record
-  lp::Nack nack4b = makeNack(*makeInterest("/Etab4KpY", false, nullopt, 294), lp::NackReason::CONGESTION);
+  auto nack4b = makeNack(*makeInterest("/Etab4KpY", false, nullopt, 294), lp::NackReason::CONGESTION);
   strategyA.afterReceiveNack_count = 0;
   strategyB.afterReceiveNack_count = 0;
   forwarder.onIncomingNack(FaceEndpoint(*face1, 0), nack4b);
@@ -493,6 +527,8 @@
                        ndn::nfd::FACE_SCOPE_NON_LOCAL,
                        ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                        ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+  auto face4 = addFace();
+  face4->setId(face::INVALID_FACEID);
 
   Pit& pit = forwarder.getPit();
 
@@ -501,39 +537,41 @@
 
   // don't send Nack if there's no in-record
   auto interest1 = makeInterest("/fM5IVEtC", false, nullopt, 719);
-  shared_ptr<pit::Entry> pit1 = pit.insert(*interest1).first;
+  auto pit1 = pit.insert(*interest1).first;
   pit1->insertOrUpdateInRecord(*face1, *interest1);
 
   face2->sentNacks.clear();
-  forwarder.onOutgoingNack(pit1, *face2, nackHeader);
+  BOOST_CHECK(!forwarder.onOutgoingNack(pit1, *face2, nackHeader));
   BOOST_CHECK_EQUAL(face2->sentNacks.size(), 0);
 
   // send Nack with correct Nonce
   auto interest2a = makeInterest("/Vi8tRm9MG3", false, nullopt, 152);
-  shared_ptr<pit::Entry> pit2 = pit.insert(*interest2a).first;
+  auto pit2 = pit.insert(*interest2a).first;
   pit2->insertOrUpdateInRecord(*face1, *interest2a);
   auto interest2b = makeInterest("/Vi8tRm9MG3", false, nullopt, 808);
   pit2->insertOrUpdateInRecord(*face2, *interest2b);
-
   face1->sentNacks.clear();
-  forwarder.onOutgoingNack(pit2, *face1, nackHeader);
+  face2->sentNacks.clear();
+
+  BOOST_CHECK(forwarder.onOutgoingNack(pit2, *face1, nackHeader));
   BOOST_REQUIRE_EQUAL(face1->sentNacks.size(), 1);
   BOOST_CHECK_EQUAL(face1->sentNacks.back().getReason(), lp::NackReason::CONGESTION);
   BOOST_CHECK_EQUAL(face1->sentNacks.back().getInterest().getNonce(), 152);
+  BOOST_CHECK_EQUAL(face2->sentNacks.size(), 0);
 
-  // erase in-record
+  // in-record is erased
   auto inRecord2a = pit2->getInRecord(*face1);
   BOOST_CHECK(inRecord2a == pit2->in_end());
 
   // send Nack with correct Nonce
-  face2->sentNacks.clear();
-  forwarder.onOutgoingNack(pit2, *face2, nackHeader);
+  BOOST_CHECK(forwarder.onOutgoingNack(pit2, *face2, nackHeader));
+  BOOST_CHECK_EQUAL(face1->sentNacks.size(), 1);
   BOOST_REQUIRE_EQUAL(face2->sentNacks.size(), 1);
   BOOST_CHECK_EQUAL(face2->sentNacks.back().getReason(), lp::NackReason::CONGESTION);
   BOOST_CHECK_EQUAL(face2->sentNacks.back().getInterest().getNonce(), 808);
 
-  // erase in-record
-  auto inRecord2b = pit2->getInRecord(*face1);
+  // in-record is erased
+  auto inRecord2b = pit2->getInRecord(*face2);
   BOOST_CHECK(inRecord2b == pit2->in_end());
 
   // don't send Nack to multi-access face
@@ -541,8 +579,16 @@
   pit2->insertOrUpdateInRecord(*face3, *interest2c);
 
   face3->sentNacks.clear();
-  forwarder.onOutgoingNack(pit1, *face3, nackHeader);
+  BOOST_CHECK(!forwarder.onOutgoingNack(pit2, *face3, nackHeader));
   BOOST_CHECK_EQUAL(face3->sentNacks.size(), 0);
+
+  // don't send Nack to face with invalid ID
+  auto interest1b = makeInterest("/fM5IVEtC", false, nullopt, 553);
+  pit1->insertOrUpdateInRecord(*face4, *interest1b);
+
+  face4->sentNacks.clear();
+  BOOST_CHECK(!forwarder.onOutgoingNack(pit1, *face4, nackHeader));
+  BOOST_CHECK_EQUAL(face4->sentNacks.size(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(InterestLoopNack)
diff --git a/tests/daemon/fw/strategy-tester.hpp b/tests/daemon/fw/strategy-tester.hpp
index 17806ec..cc40905 100644
--- a/tests/daemon/fw/strategy-tester.hpp
+++ b/tests/daemon/fw/strategy-tester.hpp
@@ -107,13 +107,15 @@
   }
 
 protected:
-  void
+  pit::OutRecord*
   sendInterest(const shared_ptr<pit::Entry>& pitEntry, Face& egress,
                const Interest& interest) override
   {
     sendInterestHistory.push_back({pitEntry->getInterest(), egress.getId(), interest});
-    pitEntry->insertOrUpdateOutRecord(egress, interest);
+    auto it = pitEntry->insertOrUpdateOutRecord(egress, interest);
+    BOOST_ASSERT(it != pitEntry->out_end());
     afterAction();
+    return &*it;
   }
 
   void
@@ -123,13 +125,14 @@
     afterAction();
   }
 
-  void
+  bool
   sendNack(const shared_ptr<pit::Entry>& pitEntry, Face& egress,
            const lp::NackHeader& header) override
   {
     sendNackHistory.push_back({pitEntry->getInterest(), egress.getId(), header});
     pitEntry->deleteInRecord(egress);
     afterAction();
+    return true;
   }
 
 public:
diff --git a/tests/daemon/fw/strategy.t.cpp b/tests/daemon/fw/strategy.t.cpp
index 8b27986..6f8e09b 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-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -43,7 +43,7 @@
 BOOST_AUTO_TEST_SUITE(Fw)
 BOOST_FIXTURE_TEST_SUITE(TestStrategy, GlobalIoFixture)
 
-// Strategy registry is tested in table/strategy-choice.t.cpp and strategy-instantiation.t.cpp
+// Strategy registry is tested in strategy-choice.t.cpp and strategy-instantiation.t.cpp
 
 class FaceTableAccessTestStrategy : public DummyStrategy
 {
@@ -103,8 +103,6 @@
   BOOST_CHECK((strategy.removedFaces == std::vector<FaceId>{id2, id1}));
 }
 
-// LookupFib is tested in Fw/TestLinkForwarding test suite.
-
 BOOST_AUTO_TEST_SUITE_END() // TestStrategy
 BOOST_AUTO_TEST_SUITE_END() // Fw