fw: /localhop scope control on outgoing Interests

refs #1253

Change-Id: I359747688568934070d1583995957835ac690f28
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index e6d3438..a723a17 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -15,6 +15,7 @@
 
 const ndn::Milliseconds Forwarder::DEFAULT_INTEREST_LIFETIME(static_cast<ndn::Milliseconds>(4000));
 const Name Forwarder::LOCALHOST_NAME("ndn:/localhost");
+const Name Forwarder::LOCALHOP_NAME("ndn:/localhop");
 
 Forwarder::Forwarder()
   : m_faceTable(*this)
@@ -140,6 +141,16 @@
     return;
   }
 
+  // /localhop scope control
+  bool isViolatingLocalhop = !outFace.isLocal() &&
+                             LOCALHOP_NAME.isPrefixOf(pitEntry->getName()) &&
+                             !pitEntry->hasLocalInRecord();
+  if (isViolatingLocalhop) {
+    NFD_LOG_DEBUG("onOutgoingInterest face=" << outFace.getId() <<
+                  " interest=" << pitEntry->getName() << " violates /localhop");
+    return;
+  }
+
   // pick Interest
   const pit::InRecordCollection& inRecords = pitEntry->getInRecords();
   pit::InRecordCollection::const_iterator pickedInRecord = std::max_element(
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index c6e3529..e229dcf 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -159,6 +159,7 @@
 
   static const ndn::Milliseconds DEFAULT_INTEREST_LIFETIME;
   static const Name LOCALHOST_NAME;
+  static const Name LOCALHOP_NAME;
 
   // allow Strategy (base class) to enter pipelines
   friend class fw::Strategy;
diff --git a/daemon/table/pit-entry.cpp b/daemon/table/pit-entry.cpp
index 2d016ca..7523504 100644
--- a/daemon/table/pit-entry.cpp
+++ b/daemon/table/pit-entry.cpp
@@ -34,6 +34,20 @@
 }
 
 static inline bool
+predicate_InRecord_isLocal(const InRecord& inRecord)
+{
+  return inRecord.getFace()->isLocal();
+}
+
+bool
+Entry::hasLocalInRecord() const
+{
+  InRecordCollection::const_iterator it = std::find_if(
+    m_inRecords.begin(), m_inRecords.end(), &predicate_InRecord_isLocal);
+  return it != m_inRecords.end();
+}
+
+static inline bool
 predicate_FaceRecord_Face(const FaceRecord& faceRecord, shared_ptr<Face> face)
 {
   return faceRecord.getFace() == face;
@@ -57,7 +71,7 @@
   if (hasUnexpiredOutRecord) {
     return false;
   }
-  
+
   InRecordCollection::const_iterator inIt = std::find_if(
     m_inRecords.begin(), m_inRecords.end(),
     bind(&predicate_FaceRecord_ne_Face_and_unexpired, _1, face, time::now()));
@@ -83,7 +97,7 @@
     m_inRecords.push_front(InRecord(face));
     it = m_inRecords.begin();
   }
-  
+
   it->update(interest);
   return it;
 }
@@ -103,7 +117,7 @@
     m_outRecords.push_front(OutRecord(face));
     it = m_outRecords.begin();
   }
-  
+
   it->update(interest);
   m_nonces.insert(interest.getNonce());
   return it;
diff --git a/daemon/table/pit-entry.hpp b/daemon/table/pit-entry.hpp
index aeff8d8..1e342e2 100644
--- a/daemon/table/pit-entry.hpp
+++ b/daemon/table/pit-entry.hpp
@@ -53,6 +53,14 @@
   const OutRecordCollection&
   getOutRecords() const;
 
+  /** \brief determines whether any InRecord is a local Face
+   *
+   *  \return true if any InRecord is a local Face,
+   *          false if all InRecords are non-local Faces
+   */
+  bool
+  hasLocalInRecord() const;
+
   /** \brief decides whether Interest can be forwarded to face
    *
    *  \return true if OutRecord of this face does not exist or has expired,
diff --git a/tests/fw/forwarder.cpp b/tests/fw/forwarder.cpp
index 925ba5b..7ec90a5 100644
--- a/tests/fw/forwarder.cpp
+++ b/tests/fw/forwarder.cpp
@@ -209,6 +209,70 @@
   BOOST_CHECK_EQUAL(face2->m_sentDatas.size(), 1);
 }
 
+BOOST_AUTO_TEST_CASE(ScopeLocalhopOutgoing)
+{
+  Forwarder forwarder;
+  shared_ptr<DummyLocalFace> face1 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face2 = make_shared<DummyFace>();
+  shared_ptr<DummyLocalFace> face3 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face4 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  forwarder.addFace(face3);
+  forwarder.addFace(face4);
+  Pit& pit = forwarder.getPit();
+
+  // from local face, to local face: OK
+  shared_ptr<Interest> interest1 = makeInterest("/localhop/1");
+  shared_ptr<pit::Entry> pit1 = pit.insert(*interest1).first;
+  pit1->insertOrUpdateInRecord(face1, *interest1);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit1, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from non-local face, to local face: OK
+  shared_ptr<Interest> interest2 = makeInterest("/localhop/2");
+  shared_ptr<pit::Entry> pit2 = pit.insert(*interest2).first;
+  pit2->insertOrUpdateInRecord(face2, *interest2);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit2, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from local face, to non-local face: OK
+  shared_ptr<Interest> interest3 = makeInterest("/localhop/3");
+  shared_ptr<pit::Entry> pit3 = pit.insert(*interest3).first;
+  pit3->insertOrUpdateInRecord(face1, *interest3);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit3, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 1);
+
+  // from non-local face, to non-local face: violate
+  shared_ptr<Interest> interest4 = makeInterest("/localhop/4");
+  shared_ptr<pit::Entry> pit4 = pit.insert(*interest4).first;
+  pit4->insertOrUpdateInRecord(face2, *interest4);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit4, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 0);
+
+  // from local face and non-local face, to local face: OK
+  shared_ptr<Interest> interest5 = makeInterest("/localhop/5");
+  shared_ptr<pit::Entry> pit5 = pit.insert(*interest5).first;
+  pit5->insertOrUpdateInRecord(face1, *interest5);
+  pit5->insertOrUpdateInRecord(face2, *interest5);
+  face3->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit5, *face3);
+  BOOST_CHECK_EQUAL(face3->m_sentInterests.size(), 1);
+
+  // from local face and non-local face, to non-local face: OK
+  shared_ptr<Interest> interest6 = makeInterest("/localhop/6");
+  shared_ptr<pit::Entry> pit6 = pit.insert(*interest6).first;
+  pit6->insertOrUpdateInRecord(face1, *interest6);
+  pit6->insertOrUpdateInRecord(face2, *interest6);
+  face4->m_sentInterests.clear();
+  forwarder.onOutgoingInterest(pit6, *face4);
+  BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 1);
+}
+
 BOOST_AUTO_TEST_CASE(StrategyDispatch)
 {
   LimitedIo limitedIo;