fw: /localhost scope control for incoming Interest

refs #1230

Change-Id: I86fd071ef144caa506b26a723f14232e2af5e2de
diff --git a/daemon/common.hpp b/daemon/common.hpp
index 18a33c9..76bbab5 100644
--- a/daemon/common.hpp
+++ b/daemon/common.hpp
@@ -9,6 +9,18 @@
 
 #include "config.hpp"
 
+#ifdef WITH_TESTS
+#define VIRTUAL_WITH_TESTS virtual
+#define PUBLIC_WITH_TESTS_ELSE_PROTECTED public
+#define PUBLIC_WITH_TESTS_ELSE_PRIVATE public
+#define PROTECTED_WITH_TESTS_ELSE_PRIVATE protected
+#else
+#define VIRTUAL_WITH_TESTS
+#define PUBLIC_WITH_TESTS_ELSE_PROTECTED protected
+#define PUBLIC_WITH_TESTS_ELSE_PRIVATE private
+#define PROTECTED_WITH_TESTS_ELSE_PRIVATE private
+#endif
+
 #include <ndn-cpp-dev/interest.hpp>
 #include <ndn-cpp-dev/data.hpp>
 
diff --git a/daemon/face/face.cpp b/daemon/face/face.cpp
index d0bde55..b32b57a 100644
--- a/daemon/face/face.cpp
+++ b/daemon/face/face.cpp
@@ -49,6 +49,12 @@
 }
 
 bool
+Face::isLocal() const
+{
+  return false;
+}
+
+bool
 Face::isMultiAccess() const
 {
   return false;
diff --git a/daemon/face/face.hpp b/daemon/face/face.hpp
index e2aa62d..58cccf6 100644
--- a/daemon/face/face.hpp
+++ b/daemon/face/face.hpp
@@ -69,13 +69,15 @@
   virtual void
   close() = 0;
   
-  /** \brief Get whether underlying communicate is up
+  /** \brief Get whether underlying communication is up
+   *
    *  In this base class this property is always true.
    */
   virtual bool
   isUp() const;
   
   /** \brief Set the description
+   *
    *  This is typically invoked by mgmt on set description command
    */
   virtual void
@@ -84,8 +86,16 @@
   /// Get the description
   virtual const std::string&
   getDescription() const;
+
+  /** \brief Get whether face is connected to a local app
+   *
+   *  In this base class this property is always false.
+   */
+  virtual bool
+  isLocal() const;
   
   /** \brief Get whether packets sent this Face may reach multiple peers
+   *
    *  In this base class this property is always false.
    */
   virtual bool
@@ -95,13 +105,6 @@
   virtual bool
   isLocalControlHeaderEnabled() const;
 
-protected:
-  // void
-  // receiveInterest();
-
-  // void
-  // receiveData();
-  
 private:
   void
   setId(FaceId faceId);
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index 67aeba7..6ab6fd4 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -12,6 +12,8 @@
 
 NFD_LOG_INIT("Forwarder");
 
+const Name Forwarder::s_localhostName("ndn:/localhost");
+
 Forwarder::Forwarder(boost::asio::io_service& ioService)
   : m_scheduler(ioService)
   , m_lastFaceId(0)
@@ -75,6 +77,15 @@
   NFD_LOG_DEBUG("onIncomingInterest face=" << inFace.getId() << " interest=" << interest.getName());
   const_cast<Interest&>(interest).setIncomingFaceId(inFace.getId());
   
+  // /localhost scope control
+  bool violatesLocalhost = !inFace.isLocal() &&
+                           s_localhostName.isPrefixOf(interest.getName());
+  if (violatesLocalhost) {
+    NFD_LOG_DEBUG("onIncomingInterest face=" << inFace.getId()
+      << " interest=" << interest.getName() << " violates /localhost");
+    return;
+  }
+  
   // PIT insert
   std::pair<shared_ptr<pit::Entry>, bool>
     pitInsertResult = m_pit.insert(interest);
@@ -123,8 +134,7 @@
   // TODO use Fib::findParent(pitEntry)
   
   // dispatch to strategy
-  m_strategy->afterReceiveInterest(inFace, interest, fibEntry, pitEntry);
-  // TODO dispatch according to fibEntry
+  this->dispatchToStrategy(inFace, interest, fibEntry, pitEntry);
 }
 
 void
@@ -184,6 +194,15 @@
   NFD_LOG_DEBUG("onIncomingData face=" << inFace.getId() << " data=" << data.getName());
   const_cast<Data&>(data).setIncomingFaceId(inFace.getId());
   
+  // /localhost scope control
+  bool violatesLocalhost = !inFace.isLocal() &&
+                           s_localhostName.isPrefixOf(data.getName());
+  if (violatesLocalhost) {
+    NFD_LOG_DEBUG("onIncomingData face=" << inFace.getId()
+      << " interest=" << data.getName() << " violates /localhost");
+    return;
+  }
+  
   // PIT match
   shared_ptr<pit::DataMatchResult> pitMatches = m_pit.findAllDataMatches(data);
   if (pitMatches->begin() == pitMatches->end()) {
@@ -298,6 +317,14 @@
   m_scheduler.cancelEvent(pitEntry->m_stragglerTimer);
 }
 
-
+void
+Forwarder::dispatchToStrategy(const Face& inFace,
+                              const Interest& interest,
+                              shared_ptr<fib::Entry> fibEntry,
+                              shared_ptr<pit::Entry> pitEntry)
+{
+  m_strategy->afterReceiveInterest(inFace, interest, fibEntry, pitEntry);
+  // TODO dispatch according to fibEntry
+}
 
 } // namespace nfd
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index 0ea838b..ae9fb6a 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -25,6 +25,7 @@
 class Forwarder
 {
 public:
+  explicit
   Forwarder(boost::asio::io_service& ioService);
 
   void
@@ -52,57 +53,63 @@
   shared_ptr<Face>
   getFace(FaceId id);
 
-private: // pipelines
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // pipelines
   /** \brief incoming Interest pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onIncomingInterest(Face& inFace, const Interest& interest);
 
   /** \brief Interest loop pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onInterestLoop(Face& inFace, const Interest& interest,
                  shared_ptr<pit::Entry> pitEntry);
   
   /** \brief outgoing Interest pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onOutgoingInterest(shared_ptr<pit::Entry> pitEntry, Face& outFace);
   
   /** \brief Interest rebuff pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onInterestRebuff(shared_ptr<pit::Entry> pitEntry);
   
   /** \brief Interest unsatisfied pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onInterestUnsatisfied(shared_ptr<pit::Entry> pitEntry);
   
   /** \brief incoming Data pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onIncomingData(Face& inFace, const Data& data);
   
   /** \brief Data unsolicited pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onDataUnsolicited(Face& inFace, const Data& data);
   
   /** \brief outgoing Data pipeline
    */
-  void
+  VIRTUAL_WITH_TESTS void
   onOutgoingData(const Data& data, Face& outFace);
 
-private:
-  void
+PROTECTED_WITH_TESTS_ELSE_PRIVATE:
+  VIRTUAL_WITH_TESTS void
   setUnsatisfyTimer(shared_ptr<pit::Entry> pitEntry);
   
-  void
+  VIRTUAL_WITH_TESTS void
   setStragglerTimer(shared_ptr<pit::Entry> pitEntry);
   
-  void
+  VIRTUAL_WITH_TESTS void
   cancelUnsatisfyAndStragglerTimer(shared_ptr<pit::Entry> pitEntry);
+  
+  VIRTUAL_WITH_TESTS void
+  dispatchToStrategy(const Face& inFace,
+                     const Interest& interest,
+                     shared_ptr<fib::Entry> fibEntry,
+                     shared_ptr<pit::Entry> pitEntry);
 
 private:
   Scheduler m_scheduler;
@@ -116,6 +123,8 @@
   /// the active strategy (only one strategy in mock)
   shared_ptr<fw::Strategy> m_strategy;
   
+  static const Name s_localhostName;
+  
   // allow Strategy (base class) to enter pipelines
   friend class fw::Strategy;
 };
diff --git a/tests/fw/forwarder.cpp b/tests/fw/forwarder.cpp
index 27653bc..433c36b 100644
--- a/tests/fw/forwarder.cpp
+++ b/tests/fw/forwarder.cpp
@@ -13,6 +13,7 @@
 
 class ForwarderTestFace : public Face {
 public:
+  explicit
   ForwarderTestFace(boost::asio::io_service& ioService)
     : m_ioService(ioService)
   {
@@ -122,6 +123,105 @@
   BOOST_CHECK_EQUAL(face1->m_sentDatas[0].getIncomingFaceId(), face2->getId());
 }
 
+
+class ForwarderTestLocalFace : public DummyFace {
+public:
+  explicit
+  ForwarderTestLocalFace(bool isLocal)
+    : m_isLocal(isLocal)
+  {
+  }
+
+  virtual bool
+  isLocal() const
+  {
+    return m_isLocal;
+  }
+
+private:
+  bool m_isLocal;
+};
+
+class ScopeLocalhostTestForwarder : public Forwarder
+{
+public:
+  explicit
+  ScopeLocalhostTestForwarder(boost::asio::io_service& ioService)
+    : Forwarder(ioService)
+  {
+  }
+
+  virtual void
+  onDataUnsolicited(Face& inFace, const Data& data)
+  {
+    ++m_onDataUnsolicited_count;
+  }
+
+protected:
+  virtual void
+  dispatchToStrategy(const Face& inFace,
+                     const Interest& interest,
+                     shared_ptr<fib::Entry> fibEntry,
+                     shared_ptr<pit::Entry> pitEntry)
+  {
+    ++m_dispatchToStrategy_count;
+  }
+
+public:
+  int m_dispatchToStrategy_count;
+  int m_onDataUnsolicited_count;
+};
+
+BOOST_AUTO_TEST_CASE(ScopeLocalhost)
+{
+  boost::asio::io_service io;
+  ScopeLocalhostTestForwarder forwarder(io);
+  shared_ptr<ForwarderTestLocalFace> face1 = make_shared<ForwarderTestLocalFace>(true);
+  shared_ptr<ForwarderTestLocalFace> face2 = make_shared<ForwarderTestLocalFace>(false);
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+  
+  // local face, /localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.onIncomingInterest(*face1, Interest(Name("/localhost/A1")));
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  
+  // non-local face, /localhost: violate
+  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.onIncomingInterest(*face2, Interest(Name("/localhost/A2")));
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 0);
+  
+  // local face, non-/localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.onIncomingInterest(*face1, Interest(Name("/A3")));
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  
+  // non-local face, non-/localhost: OK
+  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.onIncomingInterest(*face2, Interest(Name("/A4")));
+  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  
+  // local face, /localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onIncomingData(*face1, Data(Name("/localhost/B1")));
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+
+  // non-local face, /localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onIncomingData(*face2, Data(Name("/localhost/B2")));
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 0);
+  
+  // local face, non-/localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onIncomingData(*face1, Data(Name("/B3")));
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+
+  // non-local face, non-/localhost: OK
+  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onIncomingData(*face2, Data(Name("/B4")));
+  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace nfd
diff --git a/wscript b/wscript
index e2c946b..7f33a8e 100644
--- a/wscript
+++ b/wscript
@@ -46,6 +46,7 @@
     boost_libs='system'
     if conf.options.with_tests:
         conf.env['WITH_TESTS'] = 1
+        conf.define('WITH_TESTS', 1);
         boost_libs+=' unit_test_framework'
 
     conf.check_boost(lib=boost_libs)