fw: Forwarder processes Link for mobility

This commit also adds NetworkRegionTable to store producer region names,
and checks packets are well-formed before entering forwarding pipelines.

refs #3034, #3155

Change-Id: I8de4c482e8a289b922be70139675bc4661f9a301
diff --git a/common.hpp b/common.hpp
index 5e6724a..bd8f10b 100644
--- a/common.hpp
+++ b/common.hpp
@@ -98,6 +98,7 @@
 using ndn::Data;
 using ndn::Name;
 using ndn::Exclude;
+using ndn::Link;
 using ndn::Block;
 using ndn::util::FaceUri;
 
diff --git a/daemon/fw/face-table.cpp b/daemon/fw/face-table.cpp
index 0f78f74..f256d20 100644
--- a/daemon/fw/face-table.cpp
+++ b/daemon/fw/face-table.cpp
@@ -85,8 +85,10 @@
   NFD_LOG_INFO("Added face id=" << faceId << " remote=" << face->getRemoteUri()
                                           << " local=" << face->getLocalUri());
 
-  face->onReceiveInterest.connect(bind(&Forwarder::onInterest, &m_forwarder, ref(*face), _1));
-  face->onReceiveData.connect(bind(&Forwarder::onData, &m_forwarder, ref(*face), _1));
+  face->onReceiveInterest.connect(bind(&Forwarder::startProcessInterest,
+                                       &m_forwarder, ref(*face), _1));
+  face->onReceiveData.connect(bind(&Forwarder::startProcessData,
+                                   &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 7e5d883..27bfa38 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -49,7 +49,34 @@
 
 Forwarder::~Forwarder()
 {
+}
 
+void
+Forwarder::startProcessInterest(Face& face, const Interest& interest)
+{
+  // check fields used by forwarding are well-formed
+  try {
+    if (interest.hasLink()) {
+      interest.getLink();
+    }
+  }
+  catch (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
+    return;
+  }
+
+  this->onIncomingInterest(face, interest);
+}
+
+void
+Forwarder::startProcessData(Face& face, const Data& data)
+{
+  // check fields used by forwarding are well-formed
+  // (none needed)
+
+  this->onIncomingData(face, data);
 }
 
 void
@@ -101,6 +128,16 @@
 }
 
 void
+Forwarder::onInterestLoop(Face& inFace, const Interest& interest,
+                          shared_ptr<pit::Entry> pitEntry)
+{
+  NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() <<
+                " interest=" << interest.getName());
+
+  // (drop)
+}
+
+void
 Forwarder::onContentStoreMiss(const Face& inFace,
                               shared_ptr<pit::Entry> pitEntry,
                               const Interest& interest)
@@ -114,10 +151,55 @@
   // set PIT unsatisfy timer
   this->setUnsatisfyTimer(pitEntry);
 
-  // FIB lookup
-  shared_ptr<fib::Entry> fibEntry = m_fib.findLongestPrefixMatch(*pitEntry);
+  shared_ptr<fib::Entry> fibEntry;
+  // has Link object?
+  if (!interest.hasLink()) {
+    // FIB lookup with Interest name
+    fibEntry = m_fib.findLongestPrefixMatch(*pitEntry);
+    NFD_LOG_TRACE("onContentStoreMiss noLinkObject");
+  }
+  else {
+    const Link& link = interest.getLink();
+
+    // in producer region?
+    if (m_networkRegionTable.isInProducerRegion(link)) {
+      // FIB lookup with Interest name
+      fibEntry = m_fib.findLongestPrefixMatch(*pitEntry);
+      NFD_LOG_TRACE("onContentStoreMiss inProducerRegion");
+    }
+    // has SelectedDelegation?
+    else if (interest.hasSelectedDelegation()) {
+      // FIB lookup with SelectedDelegation
+      fibEntry = m_fib.findLongestPrefixMatch(interest.getSelectedDelegation());
+      NFD_LOG_TRACE("onContentStoreMiss hasSelectedDelegation=" << interest.getSelectedDelegation());
+    }
+    else {
+      // FIB lookup with first delegation Name
+      fibEntry = m_fib.findLongestPrefixMatch(link.getDelegations().begin()->second);
+
+      // in default-free zone?
+      bool isDefaultFreeZone = !(fibEntry->getPrefix().size() == 0 && fibEntry->hasNextHops());
+      if (isDefaultFreeZone) {
+        // choose and set SelectedDelegation
+        for (const std::pair<uint32_t, Name>& delegation : link.getDelegations()) {
+          const Name& delegationName = delegation.second;
+          fibEntry = m_fib.findLongestPrefixMatch(delegationName);
+          if (fibEntry->hasNextHops()) {
+            const_cast<Interest&>(interest).setSelectedDelegation(delegationName);
+            NFD_LOG_TRACE("onContentStoreMiss enterDefaultFreeZone"
+                          << " setSelectedDelegation=" << delegationName);
+            break;
+          }
+        }
+      }
+      else {
+        NFD_LOG_TRACE("onContentStoreMiss inConsumerRegion");
+      }
+    }
+  }
 
   // dispatch to strategy
+  BOOST_ASSERT(fibEntry != nullptr);
   this->dispatchToStrategy(pitEntry, bind(&Strategy::afterReceiveInterest, _1,
                                           cref(inFace), cref(interest), fibEntry, pitEntry));
 }
@@ -140,16 +222,6 @@
   this->onOutgoingData(data, *const_pointer_cast<Face>(inFace.shared_from_this()));
 }
 
-void
-Forwarder::onInterestLoop(Face& inFace, const Interest& interest,
-                          shared_ptr<pit::Entry> pitEntry)
-{
-  NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() <<
-                " interest=" << interest.getName());
-
-  // (drop)
-}
-
 /** \brief compare two InRecords for picking outgoing Interest
  *  \return true if b is preferred over a
  *
diff --git a/daemon/fw/forwarder.hpp b/daemon/fw/forwarder.hpp
index aa5d19d..155ec4a 100644
--- a/daemon/fw/forwarder.hpp
+++ b/daemon/fw/forwarder.hpp
@@ -36,6 +36,7 @@
 #include "table/measurements.hpp"
 #include "table/strategy-choice.hpp"
 #include "table/dead-nonce-list.hpp"
+#include "table/network-region-table.hpp"
 
 namespace nfd {
 
@@ -77,11 +78,17 @@
   addFace(shared_ptr<Face> face);
 
 public: // forwarding entrypoints and tables
+  /** \brief start incoming Interest processing
+   *  \param interest the incoming Interest, must be created with make_shared
+   */
   void
-  onInterest(Face& face, const Interest& interest);
+  startProcessInterest(Face& face, const Interest& interest);
 
+  /** \brief start incoming Data processing
+   *  \param data the incoming Data, must be created with make_shared
+   */
   void
-  onData(Face& face, const Data& data);
+  startProcessData(Face& face, const Data& data);
 
   NameTree&
   getNameTree();
@@ -104,12 +111,21 @@
   DeadNonceList&
   getDeadNonceList();
 
+  NetworkRegionTable&
+  getNetworkRegionTable();
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE: // pipelines
   /** \brief incoming Interest pipeline
    */
   VIRTUAL_WITH_TESTS void
   onIncomingInterest(Face& inFace, const Interest& interest);
 
+  /** \brief Interest loop pipeline
+   */
+  VIRTUAL_WITH_TESTS void
+  onInterestLoop(Face& inFace, const Interest& interest,
+                 shared_ptr<pit::Entry> pitEntry);
+
   /** \brief Content Store miss pipeline
   */
   void
@@ -121,12 +137,6 @@
   onContentStoreHit(const Face& inFace, shared_ptr<pit::Entry> pitEntry,
                     const Interest& interest, const Data& data);
 
-  /** \brief Interest loop pipeline
-   */
-  VIRTUAL_WITH_TESTS void
-  onInterestLoop(Face& inFace, const Interest& interest,
-                 shared_ptr<pit::Entry> pitEntry);
-
   /** \brief outgoing Interest pipeline
    */
   VIRTUAL_WITH_TESTS void
@@ -202,13 +212,14 @@
   FaceTable m_faceTable;
 
   // tables
-  NameTree       m_nameTree;
-  Fib            m_fib;
-  Pit            m_pit;
-  Cs             m_cs;
-  Measurements   m_measurements;
-  StrategyChoice m_strategyChoice;
-  DeadNonceList  m_deadNonceList;
+  NameTree           m_nameTree;
+  Fib                m_fib;
+  Pit                m_pit;
+  Cs                 m_cs;
+  Measurements       m_measurements;
+  StrategyChoice     m_strategyChoice;
+  DeadNonceList      m_deadNonceList;
+  NetworkRegionTable m_networkRegionTable;
 
   static const Name LOCALHOST_NAME;
 
@@ -240,18 +251,6 @@
   m_faceTable.add(face);
 }
 
-inline void
-Forwarder::onInterest(Face& face, const Interest& interest)
-{
-  this->onIncomingInterest(face, interest);
-}
-
-inline void
-Forwarder::onData(Face& face, const Data& data)
-{
-  this->onIncomingData(face, data);
-}
-
 inline NameTree&
 Forwarder::getNameTree()
 {
@@ -294,6 +293,12 @@
   return m_deadNonceList;
 }
 
+inline NetworkRegionTable&
+Forwarder::getNetworkRegionTable()
+{
+  return m_networkRegionTable;
+}
+
 #ifdef WITH_TESTS
 inline void
 Forwarder::dispatchToStrategy(shared_ptr<pit::Entry> pitEntry, function<void(fw::Strategy*)> trigger)
diff --git a/daemon/table/network-region-table.cpp b/daemon/table/network-region-table.cpp
new file mode 100644
index 0000000..df6ca58
--- /dev/null
+++ b/daemon/table/network-region-table.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * 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.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * 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 "network-region-table.hpp"
+#include <boost/range/adaptor/map.hpp>
+
+namespace nfd {
+
+bool
+NetworkRegionTable::isInProducerRegion(const Link& link) const
+{
+  for (const Name& regionName : *this) {
+    for (const Name& delegationName : boost::adaptors::values(link.getDelegations())) {
+      if (delegationName.isPrefixOf(regionName)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+} // namespace nfd
diff --git a/daemon/table/network-region-table.hpp b/daemon/table/network-region-table.hpp
new file mode 100644
index 0000000..499c356
--- /dev/null
+++ b/daemon/table/network-region-table.hpp
@@ -0,0 +1,55 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * 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.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * 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_NETWORK_REGION_TABLE_HPP
+#define NFD_DAEMON_TABLE_NETWORK_REGION_TABLE_HPP
+
+#include "common.hpp"
+
+namespace nfd {
+
+/** \brief stores a collection of producer region names
+ *
+ *  This table is used in forwarding to process Interests with Link objects.
+ */
+class NetworkRegionTable : public std::set<Name>
+{
+public:
+  /** \brief determines whether an Interest has reached a producer region
+   *  \param link the Link object on an Interest
+   *  \retval true the Interest has reached a producer region
+   *  \retval false the Interest has not reached a producer region
+   *
+   *  If any delegation names in the Link object is a prefix of any region name,
+   *  the Interest has reached the producer region and should be forwarded according to ‎its Name;
+   *  otherwise, the Interest should be forwarded according to the delegations.
+   */
+  bool
+  isInProducerRegion(const Link& link) const;
+};
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_TABLE_NETWORK_REGION_TABLE_HPP
diff --git a/tests/daemon/fw/dummy-strategy.hpp b/tests/daemon/fw/dummy-strategy.hpp
index 50de93e..d078051 100644
--- a/tests/daemon/fw/dummy-strategy.hpp
+++ b/tests/daemon/fw/dummy-strategy.hpp
@@ -40,6 +40,10 @@
 public:
   DummyStrategy(Forwarder& forwarder, const Name& name)
     : Strategy(forwarder, name)
+    , afterReceiveInterest_count(0)
+    , wantAfterReceiveInterestCalls(false)
+    , beforeSatisfyInterest_count(0)
+    , beforeExpirePendingInterest_count(0)
   {
   }
 
@@ -49,10 +53,14 @@
                        shared_ptr<fib::Entry> fibEntry,
                        shared_ptr<pit::Entry> pitEntry) DECL_OVERRIDE
   {
-    ++m_afterReceiveInterest_count;
+    ++afterReceiveInterest_count;
+    if (wantAfterReceiveInterestCalls) {
+      afterReceiveInterestCalls.push_back(std::make_tuple(inFace.getId(),
+        interest, fibEntry, pitEntry));
+    }
 
-    if (static_cast<bool>(m_interestOutFace)) {
-      this->sendInterest(pitEntry, m_interestOutFace);
+    if (static_cast<bool>(interestOutFace)) {
+      this->sendInterest(pitEntry, interestOutFace);
     }
     else {
       this->rejectPendingInterest(pitEntry);
@@ -63,22 +71,25 @@
   beforeSatisfyInterest(shared_ptr<pit::Entry> pitEntry,
                         const Face& inFace, const Data& data) DECL_OVERRIDE
   {
-    ++m_beforeSatisfyInterest_count;
+    ++beforeSatisfyInterest_count;
   }
 
   virtual void
   beforeExpirePendingInterest(shared_ptr<pit::Entry> pitEntry) DECL_OVERRIDE
   {
-    ++m_beforeExpirePendingInterest_count;
+    ++beforeExpirePendingInterest_count;
   }
 
 public:
-  int m_afterReceiveInterest_count;
-  int m_beforeSatisfyInterest_count;
-  int m_beforeExpirePendingInterest_count;
+  int afterReceiveInterest_count;
+  bool wantAfterReceiveInterestCalls;
+  std::vector<std::tuple<FaceId, Interest, shared_ptr<fib::Entry>,
+              shared_ptr<pit::Entry>>> afterReceiveInterestCalls;
+  int beforeSatisfyInterest_count;
+  int beforeExpirePendingInterest_count;
 
   /// outFace to use in afterReceiveInterest, nullptr to reject
-  shared_ptr<Face> m_interestOutFace;
+  shared_ptr<Face> interestOutFace;
 };
 
 } // namespace tests
diff --git a/tests/daemon/fw/forwarder.t.cpp b/tests/daemon/fw/forwarder.t.cpp
index d707a17..d9d50d6 100644
--- a/tests/daemon/fw/forwarder.t.cpp
+++ b/tests/daemon/fw/forwarder.t.cpp
@@ -33,7 +33,8 @@
 namespace nfd {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(FwForwarder, BaseFixture)
+BOOST_AUTO_TEST_SUITE(Fw)
+BOOST_FIXTURE_TEST_SUITE(TestForwarder, BaseFixture)
 
 BOOST_AUTO_TEST_CASE(SimpleExchange)
 {
@@ -129,21 +130,21 @@
   }
 
   virtual void
-  onDataUnsolicited(Face& inFace, const Data& data)
+  onDataUnsolicited(Face& inFace, const Data& data) DECL_OVERRIDE
   {
-    ++m_onDataUnsolicited_count;
+    ++onDataUnsolicited_count;
   }
 
 protected:
   virtual void
-  dispatchToStrategy(shared_ptr<pit::Entry> pitEntry, function<void(fw::Strategy*)> f)
+  dispatchToStrategy(shared_ptr<pit::Entry> pitEntry, function<void(fw::Strategy*)> f) DECL_OVERRIDE
   {
-    ++m_dispatchToStrategy_count;
+    ++dispatchToStrategy_count;
   }
 
 public:
-  int m_dispatchToStrategy_count;
-  int m_onDataUnsolicited_count;
+  int dispatchToStrategy_count;
+  int onDataUnsolicited_count;
 };
 
 BOOST_AUTO_TEST_CASE(ScopeLocalhostIncoming)
@@ -155,52 +156,52 @@
   forwarder.addFace(face2);
 
   // local face, /localhost: OK
-  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.dispatchToStrategy_count = 0;
   shared_ptr<Interest> i1 = makeInterest("/localhost/A1");
   forwarder.onIncomingInterest(*face1, *i1);
-  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.dispatchToStrategy_count, 1);
 
   // non-local face, /localhost: violate
-  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.dispatchToStrategy_count = 0;
   shared_ptr<Interest> i2 = makeInterest("/localhost/A2");
   forwarder.onIncomingInterest(*face2, *i2);
-  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 0);
+  BOOST_CHECK_EQUAL(forwarder.dispatchToStrategy_count, 0);
 
   // local face, non-/localhost: OK
-  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.dispatchToStrategy_count = 0;
   shared_ptr<Interest> i3 = makeInterest("/A3");
   forwarder.onIncomingInterest(*face1, *i3);
-  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.dispatchToStrategy_count, 1);
 
   // non-local face, non-/localhost: OK
-  forwarder.m_dispatchToStrategy_count = 0;
+  forwarder.dispatchToStrategy_count = 0;
   shared_ptr<Interest> i4 = makeInterest("/A4");
   forwarder.onIncomingInterest(*face2, *i4);
-  BOOST_CHECK_EQUAL(forwarder.m_dispatchToStrategy_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.dispatchToStrategy_count, 1);
 
   // local face, /localhost: OK
-  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onDataUnsolicited_count = 0;
   shared_ptr<Data> d1 = makeData("/localhost/B1");
   forwarder.onIncomingData(*face1, *d1);
-  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.onDataUnsolicited_count, 1);
 
   // non-local face, /localhost: OK
-  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onDataUnsolicited_count = 0;
   shared_ptr<Data> d2 = makeData("/localhost/B2");
   forwarder.onIncomingData(*face2, *d2);
-  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 0);
+  BOOST_CHECK_EQUAL(forwarder.onDataUnsolicited_count, 0);
 
   // local face, non-/localhost: OK
-  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onDataUnsolicited_count = 0;
   shared_ptr<Data> d3 = makeData("/B3");
   forwarder.onIncomingData(*face1, *d3);
-  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.onDataUnsolicited_count, 1);
 
   // non-local face, non-/localhost: OK
-  forwarder.m_onDataUnsolicited_count = 0;
+  forwarder.onDataUnsolicited_count = 0;
   shared_ptr<Data> d4 = makeData("/B4");
   forwarder.onIncomingData(*face2, *d4);
-  BOOST_CHECK_EQUAL(forwarder.m_onDataUnsolicited_count, 1);
+  BOOST_CHECK_EQUAL(forwarder.onDataUnsolicited_count, 1);
 }
 
 BOOST_AUTO_TEST_CASE(ScopeLocalhostOutgoing)
@@ -331,7 +332,7 @@
   BOOST_CHECK_EQUAL(face4->m_sentInterests.size(), 1);
 }
 
-BOOST_AUTO_TEST_CASE(StrategyDispatch)
+BOOST_AUTO_TEST_CASE(IncomingInterestStrategyDispatch)
 {
   LimitedIo limitedIo;
   Forwarder forwarder;
@@ -351,41 +352,41 @@
   strategyChoice.insert("ndn:/B", strategyQ->getName());
 
   shared_ptr<Interest> interest1 = makeInterest("ndn:/A/1");
-  strategyP->m_afterReceiveInterest_count = 0;
-  strategyP->m_interestOutFace = face2;
-  forwarder.onInterest(*face1, *interest1);
-  BOOST_CHECK_EQUAL(strategyP->m_afterReceiveInterest_count, 1);
+  strategyP->afterReceiveInterest_count = 0;
+  strategyP->interestOutFace = face2;
+  forwarder.startProcessInterest(*face1, *interest1);
+  BOOST_CHECK_EQUAL(strategyP->afterReceiveInterest_count, 1);
 
   shared_ptr<Interest> interest2 = makeInterest("ndn:/B/2");
-  strategyQ->m_afterReceiveInterest_count = 0;
-  strategyQ->m_interestOutFace = face2;
-  forwarder.onInterest(*face1, *interest2);
-  BOOST_CHECK_EQUAL(strategyQ->m_afterReceiveInterest_count, 1);
+  strategyQ->afterReceiveInterest_count = 0;
+  strategyQ->interestOutFace = face2;
+  forwarder.startProcessInterest(*face1, *interest2);
+  BOOST_CHECK_EQUAL(strategyQ->afterReceiveInterest_count, 1);
 
   limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(5));
 
   shared_ptr<Data> data1 = makeData("ndn:/A/1/a");
-  strategyP->m_beforeSatisfyInterest_count = 0;
-  forwarder.onData(*face2, *data1);
-  BOOST_CHECK_EQUAL(strategyP->m_beforeSatisfyInterest_count, 1);
+  strategyP->beforeSatisfyInterest_count = 0;
+  forwarder.startProcessData(*face2, *data1);
+  BOOST_CHECK_EQUAL(strategyP->beforeSatisfyInterest_count, 1);
 
   shared_ptr<Data> data2 = makeData("ndn:/B/2/b");
-  strategyQ->m_beforeSatisfyInterest_count = 0;
-  forwarder.onData(*face2, *data2);
-  BOOST_CHECK_EQUAL(strategyQ->m_beforeSatisfyInterest_count, 1);
+  strategyQ->beforeSatisfyInterest_count = 0;
+  forwarder.startProcessData(*face2, *data2);
+  BOOST_CHECK_EQUAL(strategyQ->beforeSatisfyInterest_count, 1);
 
   shared_ptr<Interest> interest3 = makeInterest("ndn:/A/3");
   interest3->setInterestLifetime(time::milliseconds(30));
-  forwarder.onInterest(*face1, *interest3);
+  forwarder.startProcessInterest(*face1, *interest3);
   shared_ptr<Interest> interest4 = makeInterest("ndn:/B/4");
   interest4->setInterestLifetime(time::milliseconds(5000));
-  forwarder.onInterest(*face1, *interest4);
+  forwarder.startProcessInterest(*face1, *interest4);
 
-  strategyP->m_beforeExpirePendingInterest_count = 0;
-  strategyQ->m_beforeExpirePendingInterest_count = 0;
+  strategyP->beforeExpirePendingInterest_count = 0;
+  strategyQ->beforeExpirePendingInterest_count = 0;
   limitedIo.run(LimitedIo::UNLIMITED_OPS, time::milliseconds(100));
-  BOOST_CHECK_EQUAL(strategyP->m_beforeExpirePendingInterest_count, 1);
-  BOOST_CHECK_EQUAL(strategyQ->m_beforeExpirePendingInterest_count, 0);
+  BOOST_CHECK_EQUAL(strategyP->beforeExpirePendingInterest_count, 1);
+  BOOST_CHECK_EQUAL(strategyQ->beforeExpirePendingInterest_count, 0);
 }
 
 BOOST_AUTO_TEST_CASE(IncomingData)
@@ -460,6 +461,191 @@
   // an Interest if its Name+Nonce has appeared any point in the past.
 }
 
+BOOST_AUTO_TEST_CASE(LinkDelegation)
+{
+  Forwarder forwarder;
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  forwarder.addFace(face1);
+  forwarder.addFace(face2);
+
+  StrategyChoice& strategyChoice = forwarder.getStrategyChoice();
+  auto strategyP = make_shared<DummyStrategy>(ref(forwarder), "ndn:/strategyP");
+  strategyP->wantAfterReceiveInterestCalls = true;
+  strategyChoice.install(strategyP);
+  strategyChoice.insert("ndn:/" , strategyP->getName());
+
+  Fib& fib = forwarder.getFib();
+  Pit& pit = forwarder.getPit();
+  NetworkRegionTable& nrt = forwarder.getNetworkRegionTable();
+
+  // returns prefix of FIB entry during last afterReceiveInterest trigger
+  auto getLastFibPrefix = [strategyP] () -> Name {
+    BOOST_REQUIRE(!strategyP->afterReceiveInterestCalls.empty());
+    return std::get<2>(strategyP->afterReceiveInterestCalls.back())->getPrefix();
+  };
+
+  shared_ptr<Link> link = makeLink("/net/ndnsim", {{10, "/telia/terabits"}, {20, "/ucla/cs"}});
+
+  // consumer region
+  nrt.clear();
+  nrt.insert("/arizona/cs/avenir");
+  shared_ptr<fib::Entry> fibRoot = fib.insert("/").first;
+  fibRoot->addNextHop(face2, 10);
+
+  auto interest1 = makeInterest("/net/ndnsim/www/1.html");
+  interest1->setLink(link->wireEncode());
+  shared_ptr<pit::Entry> pit1 = pit.insert(*interest1).first;
+  pit1->insertOrUpdateInRecord(face1, *interest1);
+
+  forwarder.onContentStoreMiss(*face1, pit1, *interest1);
+  BOOST_CHECK_EQUAL(getLastFibPrefix(), "/");
+  BOOST_CHECK_EQUAL(interest1->hasSelectedDelegation(), false);
+
+  fibRoot->removeNextHop(face2);
+
+  // first default-free router, both delegations are available
+  nrt.clear();
+  nrt.insert("/arizona/cs/hobo");
+  shared_ptr<fib::Entry> fibTelia = fib.insert("/telia").first;
+  fibTelia->addNextHop(face2, 10);
+  shared_ptr<fib::Entry> fibUcla = fib.insert("/ucla").first;
+  fibUcla->addNextHop(face2, 10);
+
+  auto interest2 = makeInterest("/net/ndnsim/www/2.html");
+  interest2->setLink(link->wireEncode());
+  shared_ptr<pit::Entry> pit2 = pit.insert(*interest2).first;
+  pit2->insertOrUpdateInRecord(face1, *interest2);
+
+  forwarder.onContentStoreMiss(*face1, pit2, *interest2);
+  BOOST_CHECK_EQUAL(getLastFibPrefix(), "/telia");
+  BOOST_REQUIRE_EQUAL(interest2->hasSelectedDelegation(), true);
+  BOOST_CHECK_EQUAL(interest2->getSelectedDelegation(), "/telia/terabits");
+
+  fib.erase(*fibTelia);
+  fib.erase(*fibUcla);
+
+  // first default-free router, only second delegation is available
+  nrt.clear();
+  nrt.insert("/arizona/cs/hobo");
+  fibUcla = fib.insert("/ucla").first;
+  fibUcla->addNextHop(face2, 10);
+
+  auto interest3 = makeInterest("/net/ndnsim/www/3.html");
+  interest3->setLink(link->wireEncode());
+  shared_ptr<pit::Entry> pit3 = pit.insert(*interest3).first;
+  pit3->insertOrUpdateInRecord(face1, *interest3);
+
+  forwarder.onContentStoreMiss(*face1, pit3, *interest3);
+  BOOST_CHECK_EQUAL(getLastFibPrefix(), "/ucla");
+  BOOST_REQUIRE_EQUAL(interest3->hasSelectedDelegation(), true);
+  BOOST_CHECK_EQUAL(interest3->getSelectedDelegation(), "/ucla/cs");
+
+  fib.erase(*fibUcla);
+
+  // default-free router, chosen SelectedDelegation
+  nrt.clear();
+  nrt.insert("/ucsd/caida/click");
+  fibTelia = fib.insert("/telia").first;
+  fibTelia->addNextHop(face2, 10);
+  fibUcla = fib.insert("/ucla").first;
+  fibUcla->addNextHop(face2, 10);
+
+  auto interest4 = makeInterest("/net/ndnsim/www/4.html");
+  interest4->setLink(link->wireEncode());
+  interest4->setSelectedDelegation("/ucla/cs");
+  shared_ptr<pit::Entry> pit4 = pit.insert(*interest4).first;
+  pit4->insertOrUpdateInRecord(face1, *interest4);
+
+  forwarder.onContentStoreMiss(*face1, pit4, *interest4);
+  BOOST_CHECK_EQUAL(getLastFibPrefix(), "/ucla");
+  BOOST_REQUIRE_EQUAL(interest4->hasSelectedDelegation(), true);
+  BOOST_CHECK_EQUAL(interest4->getSelectedDelegation(), "/ucla/cs");
+
+  fib.erase(*fibTelia);
+  fib.erase(*fibUcla);
+
+  // producer region
+  nrt.clear();
+  nrt.insert("/ucla/cs/spurs");
+  fibRoot->addNextHop(face2, 10);
+  fibUcla = fib.insert("/ucla").first;
+  fibUcla->addNextHop(face2, 10);
+  shared_ptr<fib::Entry> fibNdnsim = fib.insert("/net/ndnsim").first;
+  fibNdnsim->addNextHop(face2, 10);
+
+  auto interest5 = makeInterest("/net/ndnsim/www/5.html");
+  interest5->setLink(link->wireEncode());
+  interest5->setSelectedDelegation("/ucla/cs");
+  shared_ptr<pit::Entry> pit5 = pit.insert(*interest5).first;
+  pit5->insertOrUpdateInRecord(face1, *interest5);
+
+  forwarder.onContentStoreMiss(*face1, pit5, *interest5);
+  BOOST_CHECK_EQUAL(getLastFibPrefix(), "/net/ndnsim");
+  BOOST_REQUIRE_EQUAL(interest5->hasSelectedDelegation(), true);
+  BOOST_CHECK_EQUAL(interest5->getSelectedDelegation(), "/ucla/cs");
+
+  fibRoot->removeNextHop(face2);
+  fib.erase(*fibUcla);
+  fib.erase(*fibNdnsim);
+}
+
+
+class MalformedPacketFixture : public UnitTestTimeFixture
+{
+protected:
+  MalformedPacketFixture()
+    : face1(make_shared<DummyFace>())
+    , face2(make_shared<DummyFace>())
+  {
+    forwarder.addFace(face1);
+    forwarder.addFace(face2);
+  }
+
+  void
+  processInterest(shared_ptr<Interest> badInterest)
+  {
+    forwarder.startProcessInterest(*face1, *badInterest);
+    this->continueProcessPacket();
+  }
+
+  // processData
+
+  // processNack
+
+private:
+  void
+  continueProcessPacket()
+  {
+    this->advanceClocks(time::milliseconds(10), time::seconds(6));
+  }
+
+protected:
+  Forwarder forwarder;
+  shared_ptr<DummyFace> face1; // face of incoming bad packet
+  shared_ptr<DummyFace> face2; // another face for setting up states
+};
+
+BOOST_FIXTURE_TEST_SUITE(MalformedPacket, MalformedPacketFixture)
+
+BOOST_AUTO_TEST_CASE(BadLink)
+{
+  shared_ptr<Interest> goodInterest = makeInterest("ndn:/");
+  Block wire = goodInterest->wireEncode();
+  wire.push_back(ndn::encoding::makeEmptyBlock(tlv::Data)); // bad Link
+  wire.encode();
+
+  auto badInterest = make_shared<Interest>();
+  BOOST_REQUIRE_NO_THROW(badInterest->wireDecode(wire));
+  BOOST_REQUIRE(badInterest->hasLink());
+  BOOST_REQUIRE_THROW(badInterest->getLink(), tlv::Error);
+
+  BOOST_CHECK_NO_THROW(this->processInterest(badInterest));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // MalformedPacket
+
+BOOST_AUTO_TEST_SUITE_END()
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
diff --git a/tests/daemon/table/network-region-table.t.cpp b/tests/daemon/table/network-region-table.t.cpp
new file mode 100644
index 0000000..f4ab6e1
--- /dev/null
+++ b/tests/daemon/table/network-region-table.t.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * 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.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * 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 "table/network-region-table.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Table)
+BOOST_FIXTURE_TEST_SUITE(TestNetworkRegionTable, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(InProducerRegion)
+{
+  shared_ptr<Link> link = makeLink("/net/ndnsim", {{10, "/telia/terabits"}, {20, "/ucla/cs"}});
+
+  NetworkRegionTable nrt1;
+  nrt1.insert("/verizon");
+  BOOST_CHECK_EQUAL(nrt1.isInProducerRegion(*link), false);
+
+  NetworkRegionTable nrt2;
+  nrt2.insert("/ucla");
+  BOOST_CHECK_EQUAL(nrt2.isInProducerRegion(*link), false);
+
+  NetworkRegionTable nrt3;
+  nrt3.insert("/ucla/cs");
+  BOOST_CHECK_EQUAL(nrt3.isInProducerRegion(*link), true);
+
+  NetworkRegionTable nrt4;
+  nrt4.insert("/ucla/cs/software");
+  nrt4.insert("/ucla/cs/irl");
+  BOOST_CHECK_EQUAL(nrt4.isInProducerRegion(*link), true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index 247337d..784ee2d 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -141,11 +141,10 @@
 }
 
 inline shared_ptr<Data>
-signData(const shared_ptr<Data>& data)
+signData(shared_ptr<Data> data)
 {
   ndn::SignatureSha256WithRsa fakeSignature;
-  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue,
-                                        static_cast<const uint8_t*>(nullptr), 0));
+  fakeSignature.setValue(ndn::encoding::makeEmptyBlock(tlv::SignatureValue));
   data->setSignature(fakeSignature);
   data->wireEncode();
 
@@ -155,11 +154,17 @@
 inline shared_ptr<Data>
 makeData(const Name& name)
 {
-  shared_ptr<Data> data = make_shared<Data>(name);
-
+  auto data = make_shared<Data>(name);
   return signData(data);
 }
 
+inline shared_ptr<Link>
+makeLink(const Name& name, std::initializer_list<std::pair<uint32_t, Name>> delegations)
+{
+  auto link = make_shared<Link>(name, delegations);
+  signData(link);
+  return link;
+}
 
 } // namespace tests
 } // namespace nfd