face: Interest loopback

refs #3979

Change-Id: I6f65b30451939397975a7af40c59002e330afc25
diff --git a/src/detail/face-impl.hpp b/src/detail/face-impl.hpp
index f0c7231..86945ba 100644
--- a/src/detail/face-impl.hpp
+++ b/src/detail/face-impl.hpp
@@ -88,21 +88,26 @@
                        const NackCallback& afterNacked,
                        const TimeoutCallback& afterTimeout)
   {
+    NDN_LOG_DEBUG("<I " << *interest);
     this->ensureConnected(true);
 
     const Interest& interest2 = *interest;
     auto i = m_pendingInterestTable.insert(make_shared<PendingInterest>(
       std::move(interest), afterSatisfied, afterNacked, afterTimeout, ref(m_scheduler))).first;
-    PendingInterest& entry = **i;
-    entry.setDeleter([this, i] { m_pendingInterestTable.erase(i); });
+    // In dispatchInterest, an InterestCallback may respond with Data right away and delete
+    // the PendingInterestTable entry. shared_ptr is retained to ensure PendingInterest instance
+    // remains valid in this case.
+    shared_ptr<PendingInterest> entry = *i;
+    entry->setDeleter([this, i] { m_pendingInterestTable.erase(i); });
 
     lp::Packet lpPacket;
     addFieldFromTag<lp::NextHopFaceIdField, lp::NextHopFaceIdTag>(lpPacket, interest2);
     addFieldFromTag<lp::CongestionMarkField, lp::CongestionMarkTag>(lpPacket, interest2);
 
-    entry.recordForwarding();
+    entry->recordForwarding();
     m_face.m_transport->send(finishEncoding(std::move(lpPacket), interest2.wireEncode(),
                                             'I', interest2.getName()));
+    dispatchInterest(*entry, interest2);
   }
 
   void
@@ -206,16 +211,23 @@
     const Interest& interest2 = *interest;
     auto i = m_pendingInterestTable.insert(make_shared<PendingInterest>(
       std::move(interest), ref(m_scheduler))).first;
-    // InterestCallback may put Data right away and delete the entry from PendingInterestTable.
-    // shared_ptr is retained to ensure PendingInterest instance is valid throughout the loop.
+    // In dispatchInterest, an InterestCallback may respond with Data right away and delete
+    // the PendingInterestTable entry. shared_ptr is retained to ensure PendingInterest instance
+    // remains valid in this case.
     shared_ptr<PendingInterest> entry = *i;
     entry->setDeleter([this, i] { m_pendingInterestTable.erase(i); });
 
+    this->dispatchInterest(*entry, interest2);
+  }
+
+  void
+  dispatchInterest(PendingInterest& entry, const Interest& interest)
+  {
     for (const auto& filter : m_interestFilterTable) {
-      if (filter->doesMatch(interest2.getName())) {
+      if (filter->doesMatch(entry)) {
         NDN_LOG_DEBUG("   matches " << filter->getFilter());
-        entry->recordForwarding();
-        filter->invokeInterestCallback(interest2);
+        entry.recordForwarding();
+        filter->invokeInterestCallback(interest);
       }
     }
   }
@@ -223,6 +235,7 @@
   void
   asyncPutData(const Data& data)
   {
+    NDN_LOG_DEBUG("<D " << data.getName());
     bool shouldSendToForwarder = satisfyPendingInterests(data);
     if (!shouldSendToForwarder) {
       return;
@@ -241,6 +254,7 @@
   void
   asyncPutNack(const lp::Nack& nack)
   {
+    NDN_LOG_DEBUG("<N " << nack.getInterest() << '~' << nack.getHeader().getReason());
     optional<lp::Nack> outNack = nackPendingInterests(nack);
     if (!outNack) {
       return;
diff --git a/src/detail/interest-filter-record.hpp b/src/detail/interest-filter-record.hpp
index f771d16..154112c 100644
--- a/src/detail/interest-filter-record.hpp
+++ b/src/detail/interest-filter-record.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2016 Regents of the University of California.
+/*
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,8 +22,7 @@
 #ifndef NDN_DETAIL_INTEREST_FILTER_RECORD_HPP
 #define NDN_DETAIL_INTEREST_FILTER_RECORD_HPP
 
-#include "../name.hpp"
-#include "../interest.hpp"
+#include "pending-interest.hpp"
 
 namespace ndn {
 
@@ -60,9 +59,10 @@
    * @param name Interest Name
    */
   bool
-  doesMatch(const Name& name) const
+  doesMatch(const PendingInterest& entry) const
   {
-    return m_filter.doesMatch(name);
+    return (entry.getOrigin() == PendingInterestOrigin::FORWARDER || m_filter.allowsLoopback()) &&
+            m_filter.doesMatch(entry.getInterest()->getName());
   }
 
   /**
diff --git a/src/face.cpp b/src/face.cpp
index 92c46ab..fdb6b29 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -182,7 +182,6 @@
 {
   shared_ptr<Interest> interest2 = make_shared<Interest>(interest);
   interest2->getNonce();
-  NDN_LOG_DEBUG("<I " << *interest2);
 
   IO_CAPTURE_WEAK_IMPL(dispatch) {
     impl->asyncExpressInterest(interest2, afterSatisfied, afterNacked, afterTimeout);
@@ -216,7 +215,6 @@
 void
 Face::put(Data data)
 {
-  NDN_LOG_DEBUG("<D " << data.getName());
   IO_CAPTURE_WEAK_IMPL(dispatch) {
     impl->asyncPutData(data);
   } IO_CAPTURE_WEAK_IMPL_END
@@ -225,7 +223,6 @@
 void
 Face::put(lp::Nack nack)
 {
-  NDN_LOG_DEBUG("<N " << nack.getInterest() << '~' << nack.getHeader().getReason());
   IO_CAPTURE_WEAK_IMPL(dispatch) {
     impl->asyncPutNack(nack);
   } IO_CAPTURE_WEAK_IMPL_END
diff --git a/src/interest-filter.hpp b/src/interest-filter.hpp
index d6241a1..f2a09c0 100644
--- a/src/interest-filter.hpp
+++ b/src/interest-filter.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2016 Regents of the University of California.
+/*
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -125,9 +125,30 @@
     return *m_regexFilter;
   }
 
+  /** \brief Get whether Interest loopback is allowed
+   */
+  bool
+  allowsLoopback() const
+  {
+    return m_allowsLoopback;
+  }
+
+  /** \brief Set whether Interest loopback is allowed
+   *  \param wantLoopback if true, this InterestFilter may receive Interests that are expressed
+   *                      locally on the same \p ndn::Face ; if false, this InterestFilter can only
+   *                      receive Interests received from the forwarder. The default is true.
+   */
+  InterestFilter&
+  allowLoopback(bool wantLoopback)
+  {
+    m_allowsLoopback = wantLoopback;
+    return *this;
+  }
+
 private:
   Name m_prefix;
   shared_ptr<RegexPatternListMatcher> m_regexFilter;
+  bool m_allowsLoopback = true;
 };
 
 std::ostream&
diff --git a/tests/unit-tests/face.t.cpp b/tests/unit-tests/face.t.cpp
index ceee9f7..3c903e9 100644
--- a/tests/unit-tests/face.t.cpp
+++ b/tests/unit-tests/face.t.cpp
@@ -325,6 +325,34 @@
   BOOST_CHECK(face.sentData[1].getTag<lp::CongestionMarkTag>() != nullptr);
 }
 
+BOOST_AUTO_TEST_CASE(PutDataLoopback)
+{
+  bool hasInterest1 = false, hasData = false;
+
+  // first InterestFilter allows loopback and should receive Interest
+  face.setInterestFilter("/", [&] (const InterestFilter&, const Interest& interest) {
+    hasInterest1 = true;
+    // do not respond with Data right away, so Face must send Interest to forwarder
+  });
+  // second InterestFilter disallows loopback and should not receive Interest
+  face.setInterestFilter(InterestFilter("/").allowLoopback(false),
+    bind([] { BOOST_ERROR("Unexpected Interest on second InterestFilter"); }));
+
+  face.expressInterest(Interest("/A"),
+                       bind([&] { hasData = true; }),
+                       bind([] { BOOST_FAIL("Unexpected nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(hasInterest1, true); // Interest looped back
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); // Interest sent to forwarder
+  BOOST_CHECK_EQUAL(hasData, false); // waiting for Data
+
+  face.put(*makeData("/A/B")); // first InterestFilter responds with Data
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(hasData, true);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0); // do not spill Data to forwarder
+}
+
 BOOST_AUTO_TEST_CASE(PutMultipleData)
 {
   bool hasInterest1 = false;
@@ -406,6 +434,36 @@
   BOOST_CHECK_EQUAL(face.sentNacks.size(), 1); // additional Nacks are ignored
 }
 
+BOOST_AUTO_TEST_CASE(PutMultipleNackLoopback)
+{
+  bool hasInterest1 = false, hasNack = false;
+
+  // first InterestFilter allows loopback and should receive Interest
+  face.setInterestFilter("/", [&] (const InterestFilter&, const Interest& interest) {
+    hasInterest1 = true;
+    face.put(makeNack(interest, lp::NackReason::CONGESTION));
+  });
+  // second InterestFilter disallows loopback and should not receive Interest
+  face.setInterestFilter(InterestFilter("/").allowLoopback(false),
+    bind([] { BOOST_ERROR("Unexpected Interest on second InterestFilter"); }));
+
+  face.expressInterest(*makeInterest("/A", 28395852),
+                       bind([] { BOOST_FAIL("Unexpected data"); }),
+                       [&] (const Interest&, const lp::Nack& nack) {
+                         hasNack = true;
+                         BOOST_CHECK_EQUAL(nack.getReason(), lp::NackReason::CONGESTION);
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(hasInterest1, true); // Interest looped back
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); // Interest sent to forwarder
+  BOOST_CHECK_EQUAL(hasNack, false); // waiting for Nack from forwarder
+
+  face.receive(makeNack("/A", 28395852, lp::NackReason::NO_ROUTE));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(hasNack, true);
+}
+
 BOOST_AUTO_TEST_CASE(SetUnsetInterestFilter)
 {
   size_t nInterests = 0;
@@ -548,6 +606,7 @@
 
 BOOST_FIXTURE_TEST_CASE(RegisterUnregisterPrefixFail, FacesNoRegistrationReplyFixture)
 {
+  // don't enable registration reply
   size_t nRegFailures = 0;
   face.registerPrefix("/Hello/World",
                       bind([] { BOOST_FAIL("Unexpected registerPrefix success"); }),
diff --git a/tests/unit-tests/interest-filter.t.cpp b/tests/unit-tests/interest-filter.t.cpp
index 26be797..8277c23 100644
--- a/tests/unit-tests/interest-filter.t.cpp
+++ b/tests/unit-tests/interest-filter.t.cpp
@@ -61,6 +61,14 @@
   BOOST_CHECK_THROW(face.receive(Interest("/Hello/World/a/b/c")), InterestFilter::Error);
 }
 
+BOOST_AUTO_TEST_CASE(AllowLoopback)
+{
+  InterestFilter filter("/A");
+  BOOST_CHECK_EQUAL(filter.allowsLoopback(), true);
+  BOOST_CHECK_EQUAL(&filter.allowLoopback(false), &filter);
+  BOOST_CHECK_EQUAL(filter.allowsLoopback(), false);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestInterestFilter
 
 } // namespace tests