fw: forward Interest/Data to ad hoc incoming face

Change-Id: Ia2eacf2a54d65ea4bffb607a709e1d1263547fd7
Refs: #3968
diff --git a/daemon/fw/asf-strategy.cpp b/daemon/fw/asf-strategy.cpp
index 871ff2f..baf8230 100644
--- a/daemon/fw/asf-strategy.cpp
+++ b/daemon/fw/asf-strategy.cpp
@@ -60,7 +60,7 @@
 const Name&
 AsfStrategy::getStrategyName()
 {
-  static Name strategyName("/localhost/nfd/strategy/asf/%FD%01");
+  static Name strategyName("/localhost/nfd/strategy/asf/%FD%02");
   return strategyName;
 }
 
@@ -246,7 +246,8 @@
   for (const fib::NextHop& hop : fibEntry.getNextHops()) {
     Face& hopFace = hop.getFace();
 
-    if (hopFace.getId() == inFace.getId() || wouldViolateScope(inFace, interest, hopFace)) {
+    if ((hopFace.getId() == inFace.getId() && hopFace.getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) ||
+         wouldViolateScope(inFace, interest, hopFace)) {
       continue;
     }
 
diff --git a/daemon/fw/best-route-strategy2.cpp b/daemon/fw/best-route-strategy2.cpp
index 1666e81..11add67 100644
--- a/daemon/fw/best-route-strategy2.cpp
+++ b/daemon/fw/best-route-strategy2.cpp
@@ -57,7 +57,7 @@
 const Name&
 BestRouteStrategy2::getStrategyName()
 {
-  static Name strategyName("/localhost/nfd/strategy/best-route/%FD%04");
+  static Name strategyName("/localhost/nfd/strategy/best-route/%FD%05");
   return strategyName;
 }
 
@@ -78,8 +78,8 @@
 {
   const Face& outFace = nexthop.getFace();
 
-  // do not forward back to the same face
-  if (&outFace == &inFace)
+  // do not forward back to the same face, unless it is ad hoc
+  if (outFace.getId() == inFace.getId() && outFace.getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC)
     return false;
 
   // forwarding would violate scope
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index 052258a..41ab2e2 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -375,7 +375,8 @@
 
   // foreach pending downstream
   for (Face* pendingDownstream : pendingDownstreams) {
-    if (pendingDownstream == &inFace) {
+    if (pendingDownstream->getId() == inFace.getId() &&
+        pendingDownstream->getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) {
       continue;
     }
     // goto outgoing Data pipeline
@@ -595,7 +596,7 @@
   }
 
   // Dead Nonce List insert
-  if (upstream == 0) {
+  if (upstream == nullptr) {
     // insert all outgoing Nonces
     const pit::OutRecordCollection& outRecords = pitEntry.getOutRecords();
     std::for_each(outRecords.begin(), outRecords.end(),
diff --git a/daemon/fw/multicast-strategy.cpp b/daemon/fw/multicast-strategy.cpp
index 9543e6f..7b38bc1 100644
--- a/daemon/fw/multicast-strategy.cpp
+++ b/daemon/fw/multicast-strategy.cpp
@@ -58,7 +58,7 @@
 const Name&
 MulticastStrategy::getStrategyName()
 {
-  static Name strategyName("/localhost/nfd/strategy/multicast/%FD%02");
+  static Name strategyName("/localhost/nfd/strategy/multicast/%FD%03");
   return strategyName;
 }
 
@@ -85,12 +85,15 @@
   for (const auto& nexthop : nexthops) {
     Face& outFace = nexthop.getFace();
 
-    if (&outFace != &inFace && !wouldViolateScope(inFace, interest, outFace)) {
-      this->sendInterest(pitEntry, outFace, interest);
-      NFD_LOG_DEBUG(interest << " from=" << inFace.getId()
-                             << " pitEntry-to=" << outFace.getId());
-      ++nEligibleNextHops;
+    if ((outFace.getId() == inFace.getId() && outFace.getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) ||
+        wouldViolateScope(inFace, interest, outFace)) {
+      continue;
     }
+
+    this->sendInterest(pitEntry, outFace, interest);
+    NFD_LOG_DEBUG(interest << " from=" << inFace.getId()
+                           << " pitEntry-to=" << outFace.getId());
+    ++nEligibleNextHops;
   }
 
   if (nEligibleNextHops == 0) {
diff --git a/tests/daemon/fw/ad-hoc-forwarding.t.cpp b/tests/daemon/fw/ad-hoc-forwarding.t.cpp
new file mode 100644
index 0000000..7e81893
--- /dev/null
+++ b/tests/daemon/fw/ad-hoc-forwarding.t.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  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/>.
+ */
+
+/** \file
+ *  This test suite checks that forwarding can relay Interest and Data via ad hoc face.
+ */
+
+// Strategies that can forward Interest to an ad hoc face even if it's the downstream,
+// sorted alphabetically.
+#include "fw/asf-strategy.hpp"
+#include "fw/best-route-strategy2.hpp"
+#include "fw/multicast-strategy.hpp"
+
+#include "tests/test-common.hpp"
+#include "topology-tester.hpp"
+#include <boost/mpl/vector.hpp>
+
+namespace nfd {
+namespace fw {
+namespace tests {
+
+using namespace nfd::tests;
+
+template<typename S>
+class AdHocForwardingFixture : public UnitTestTimeFixture
+{
+protected:
+  AdHocForwardingFixture()
+  {
+    nodeA = topo.addForwarder("A");
+    nodeB = topo.addForwarder("B");
+    nodeC = topo.addForwarder("C");
+
+    for (TopologyNode node : {nodeA, nodeB, nodeC}) {
+      topo.setStrategy<S>(node);
+    }
+
+    auto wireless = topo.addLink("ABC", time::milliseconds(10), {nodeA, nodeB, nodeC},
+                                 ndn::nfd::LINK_TYPE_AD_HOC);
+    wireless->block(nodeA, nodeC);
+    wireless->block(nodeC, nodeA);
+    faceA = &wireless->getFace(nodeA);
+    faceB = &wireless->getFace(nodeB);
+    faceC = &wireless->getFace(nodeC);
+
+    appA = topo.addAppFace("consumer", nodeA);
+    topo.registerPrefix(nodeA, *faceA, "/P");
+    appC = topo.addAppFace("producer", nodeC, "/P");
+    topo.addEchoProducer(appC->getClientFace(), "/P");
+  }
+
+protected:
+  TopologyTester topo;
+  TopologyNode nodeA;
+  TopologyNode nodeB;
+  TopologyNode nodeC;
+  Face* faceA;
+  Face* faceB;
+  Face* faceC;
+  shared_ptr<TopologyAppLink> appA;
+  shared_ptr<TopologyAppLink> appC;
+};
+
+BOOST_AUTO_TEST_SUITE(Fw)
+BOOST_FIXTURE_TEST_SUITE(TestAdHocForwarding, BaseFixture)
+
+using Strategies = boost::mpl::vector<
+  AsfStrategy,
+  BestRouteStrategy2,
+  MulticastStrategy
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SingleNexthop, S, Strategies,
+                                 AdHocForwardingFixture<S>)
+{
+  // +---+---+
+  // A   B   C
+  //
+  // A is the consumer. C is the producer.
+  // B should relay Interest/Data between A and C.
+
+  this->topo.registerPrefix(this->nodeB, *this->faceB, "/P");
+  this->topo.addIntervalConsumer(this->appA->getClientFace(), "/P", time::milliseconds(100), 10);
+  this->advanceClocks(time::milliseconds(5), time::milliseconds(1200));
+
+  // Consumer should receive Data, and B should be relaying.
+  BOOST_CHECK_EQUAL(this->faceB->getCounters().nInInterests, 10);
+  BOOST_CHECK_EQUAL(this->faceB->getCounters().nOutInterests, 10);
+  BOOST_CHECK_EQUAL(this->appC->getForwarderFace().getCounters().nOutInterests, 10);
+  BOOST_CHECK_EQUAL(this->faceB->getCounters().nInData, 10);
+  BOOST_CHECK_EQUAL(this->faceB->getCounters().nOutData, 10);
+  BOOST_CHECK_EQUAL(this->appA->getForwarderFace().getCounters().nOutData, 10);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SecondNexthop, S, Strategies,
+                                 AdHocForwardingFixture<S>)
+{
+  // +---+---+
+  // A   B   C
+  //     |
+  //     D
+  //
+  // A is the consumer. C is the producer.
+  // B's first nexthop is D, but B-D link has failed, so B should relay Interest/Data between A and C.
+
+  TopologyNode nodeD = this->topo.addForwarder("D");
+  shared_ptr<TopologyLink> linkBD = this->topo.addLink("BD", time::milliseconds(5), {this->nodeB, nodeD});
+  this->topo.registerPrefix(this->nodeB, linkBD->getFace(this->nodeB), "/P", 5);
+  linkBD->fail();
+  this->topo.registerPrefix(this->nodeB, *this->faceB, "/P", 10);
+
+  // Two interval consumers are expressing Interests with same name 40ms apart,
+  // so that Interests from the second interval consumer are considered retransmission.
+  this->topo.addIntervalConsumer(this->appA->getClientFace(), "/P", time::milliseconds(100), 50, 1);
+  this->advanceClocks(time::milliseconds(5), time::milliseconds(40));
+  this->topo.addIntervalConsumer(this->appA->getClientFace(), "/P", time::milliseconds(100), 50, 1);
+  this->advanceClocks(time::milliseconds(5), time::milliseconds(5400));
+
+  // Consumer should receive Data, and B should be relaying at least some Interest/Data.
+  BOOST_CHECK_GE(this->faceB->getCounters().nInInterests, 50);
+  BOOST_CHECK_GE(this->faceB->getCounters().nOutInterests, 25);
+  BOOST_CHECK_GE(this->appC->getForwarderFace().getCounters().nOutInterests, 25);
+  BOOST_CHECK_GE(this->faceB->getCounters().nInData, 25);
+  BOOST_CHECK_GE(this->faceB->getCounters().nOutData, 25);
+  BOOST_CHECK_GE(this->appA->getForwarderFace().getCounters().nOutData, 25);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestAdHocForwarding
+BOOST_AUTO_TEST_SUITE_END() // Fw
+
+} // namespace tests
+} // namespace fw
+} // namespace nfd
diff --git a/tests/daemon/fw/strategy-instantiation.t.cpp b/tests/daemon/fw/strategy-instantiation.t.cpp
index 0e012b4..391c761 100644
--- a/tests/daemon/fw/strategy-instantiation.t.cpp
+++ b/tests/daemon/fw/strategy-instantiation.t.cpp
@@ -75,11 +75,11 @@
 
 using Tests = boost::mpl::vector<
   Test<AccessStrategy, false, 1>,
-  Test<AsfStrategy, true, 1>,
+  Test<AsfStrategy, true, 2>,
   Test<BestRouteStrategy, false, 1>,
-  Test<BestRouteStrategy2, false, 4>,
+  Test<BestRouteStrategy2, false, 5>,
   Test<ClientControlStrategy, false, 2>,
-  Test<MulticastStrategy, false, 2>,
+  Test<MulticastStrategy, false, 3>,
   Test<NccStrategy, false, 1>
 >;
 
diff --git a/tests/daemon/fw/topology-tester.cpp b/tests/daemon/fw/topology-tester.cpp
index ad68447..fef3080 100644
--- a/tests/daemon/fw/topology-tester.cpp
+++ b/tests/daemon/fw/topology-tester.cpp
@@ -36,36 +36,43 @@
 using face::InternalClientTransport;
 using face::GenericLinkService;
 
-TopologyLink::TopologyLink(const time::nanoseconds& delay)
+TopologyLink::TopologyLink(time::nanoseconds delay)
   : m_isUp(true)
 {
   this->setDelay(delay);
 }
 
 void
-TopologyLink::setDelay(const time::nanoseconds& delay)
+TopologyLink::block(TopologyNode i, TopologyNode j)
 {
-  BOOST_ASSERT(delay > time::nanoseconds::zero());
-  // zero delay does not work on OSX
+  m_transports.at(i).blockedDestinations.insert(j);
+}
 
+void
+TopologyLink::unblock(TopologyNode i, TopologyNode j)
+{
+  m_transports.at(i).blockedDestinations.erase(j);
+}
+
+void
+TopologyLink::setDelay(time::nanoseconds delay)
+{
+  BOOST_ASSERT(delay > time::nanoseconds::zero()); // zero delay does not work on macOS
   m_delay = delay;
 }
 
 void
 TopologyLink::addFace(TopologyNode i, shared_ptr<Face> face)
 {
-  this->attachTransport(i, dynamic_cast<InternalTransportBase*>(face->getTransport()));
-  m_faces[i] = face;
-}
-
-void
-TopologyLink::attachTransport(TopologyNode i, InternalTransportBase* transport)
-{
-  BOOST_ASSERT(transport != nullptr);
   BOOST_ASSERT(m_transports.count(i) == 0);
+  auto& nodeTransport = m_transports[i];
 
-  m_transports[i] = transport;
-  transport->afterSend.connect([this, i] (const Block& packet) { this->transmit(i, packet); });
+  nodeTransport.face = face;
+
+  nodeTransport.transport = dynamic_cast<InternalTransportBase*>(face->getTransport());
+  BOOST_ASSERT(nodeTransport.transport != nullptr);
+  nodeTransport.transport->afterSend.connect(
+    [this, i] (const Block& packet) { this->transmit(i, packet); });
 }
 
 void
@@ -75,12 +82,14 @@
     return;
   }
 
+  const auto& blockedDestinations = m_transports.at(i).blockedDestinations;
+
   for (const auto& p : m_transports) {
-    if (p.first == i) {
+    if (p.first == i || blockedDestinations.count(p.first) > 0) {
       continue;
     }
 
-    InternalTransportBase* recipient = p.second;
+    InternalTransportBase* recipient = p.second.transport;
     this->scheduleReceive(recipient, packet);
   }
 }
@@ -156,15 +165,17 @@
 }
 
 shared_ptr<TopologyLink>
-TopologyTester::addLink(const std::string& label, const time::nanoseconds& delay,
+TopologyTester::addLink(const std::string& label, time::nanoseconds delay,
                         std::initializer_list<TopologyNode> forwarders,
-                        bool forceMultiAccessFace)
+                        ndn::nfd::LinkType linkType)
 {
   auto link = std::make_shared<TopologyLink>(delay);
   FaceUri remoteUri("topology://link/" + label);
-  ndn::nfd::LinkType linkType = (forceMultiAccessFace || forwarders.size() > 2) ?
-                                ndn::nfd::LINK_TYPE_MULTI_ACCESS :
-                                ndn::nfd::LINK_TYPE_POINT_TO_POINT;
+  if (linkType == ndn::nfd::LINK_TYPE_NONE) {
+    linkType = forwarders.size() > 2 ? ndn::nfd::LINK_TYPE_MULTI_ACCESS :
+                                       ndn::nfd::LINK_TYPE_POINT_TO_POINT;
+  }
+  BOOST_ASSERT(forwarders.size() <= 2 || linkType != ndn::nfd::LINK_TYPE_POINT_TO_POINT);
 
   for (TopologyNode i : forwarders) {
     Forwarder& forwarder = this->getForwarder(i);
@@ -244,16 +255,23 @@
 
 void
 TopologyTester::addIntervalConsumer(ndn::Face& face, const Name& prefix,
-                                    const time::nanoseconds& interval, size_t n)
+                                    time::nanoseconds interval, size_t n, int seq)
 {
   Name name(prefix);
-  name.appendTimestamp();
+  if (seq >= 0) {
+    name.appendSequenceNumber(seq);
+    ++seq;
+  }
+  else {
+    name.appendTimestamp();
+  }
+
   shared_ptr<Interest> interest = makeInterest(name);
   face.expressInterest(*interest, nullptr, nullptr, nullptr);
 
   if (n > 1) {
     scheduler::schedule(interval, bind(&TopologyTester::addIntervalConsumer, this,
-                                       ref(face), prefix, interval, n - 1));
+                                       ref(face), prefix, interval, n - 1, seq));
   }
 }
 
diff --git a/tests/daemon/fw/topology-tester.hpp b/tests/daemon/fw/topology-tester.hpp
index 9f97413..892dc01 100644
--- a/tests/daemon/fw/topology-tester.hpp
+++ b/tests/daemon/fw/topology-tester.hpp
@@ -54,7 +54,7 @@
 {
 public:
   explicit
-  TopologyLink(const time::nanoseconds& delay);
+  TopologyLink(time::nanoseconds delay);
 
   /** \brief fail the link, cause packets to be dropped silently
    */
@@ -72,11 +72,24 @@
     m_isUp = true;
   }
 
+  /** \brief block transmission from i to j
+   *
+   *  Packets transmitted by i would not be delivered to j. Packets from j to i are unaffected.
+   *  This can be used to simulate a wireless channel.
+   */
+  void
+  block(TopologyNode i, TopologyNode j);
+
+  /** \brief unblock transmission from i to j
+   */
+  void
+  unblock(TopologyNode i, TopologyNode j);
+
   /** \brief change the link delay
    *  \param delay link delay, must be positive
    */
   void
-  setDelay(const time::nanoseconds& delay);
+  setDelay(time::nanoseconds delay);
 
   /** \brief attach a face to the link
    *  \param i forwarder index
@@ -90,15 +103,9 @@
   Face&
   getFace(TopologyNode i)
   {
-    return *m_faces.at(i);
+    return *m_transports.at(i).face;
   }
 
-protected:
-  /** \brief attach a Transport onto this link
-   */
-  void
-  attachTransport(TopologyNode i, face::InternalTransportBase* transport);
-
 private:
   void
   transmit(TopologyNode i, const Block& packet);
@@ -109,8 +116,14 @@
 private:
   bool m_isUp;
   time::nanoseconds m_delay;
-  std::unordered_map<TopologyNode, face::InternalTransportBase*> m_transports;
-  std::unordered_map<TopologyNode, shared_ptr<Face>> m_faces;
+
+  struct NodeTransport
+  {
+    face::InternalTransportBase* transport;
+    shared_ptr<Face> face;
+    std::set<TopologyNode> blockedDestinations;
+  };
+  std::unordered_map<TopologyNode, NodeTransport> m_transports;
 };
 
 /** \brief represents a link to a local application
@@ -203,15 +216,18 @@
   }
 
   /** \brief makes a link that interconnects two or more forwarders
+   *  \brief linkType desired link type; LINK_TYPE_NONE to use point-to-point for two forwarders
+   *                  and multi-access for more than two forwarders; it's an error to specify
+   *                  point-to-point when there are more than two forwarders
    *
    *  A face is created on each of \p forwarders .
    *  When a packet is sent onto one of the faces on this link,
    *  this packet will be received by all other faces on this link after \p delay .
    */
   shared_ptr<TopologyLink>
-  addLink(const std::string& label, const time::nanoseconds& delay,
+  addLink(const std::string& label, time::nanoseconds delay,
           std::initializer_list<TopologyNode> forwarders,
-          bool forceMultiAccessFace = false);
+          ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_NONE);
 
   /** \brief makes a link to local application
    */
@@ -246,10 +262,11 @@
 
   /** \brief creates a consumer application that sends \p n Interests under \p prefix
    *         at \p interval fixed rate.
+   *  \param seq if non-negative, append sequence number instead of timestamp
    */
   void
-  addIntervalConsumer(ndn::Face& face, const Name& prefix,
-                      const time::nanoseconds& interval, size_t n);
+  addIntervalConsumer(ndn::Face& face, const Name& prefix, time::nanoseconds interval,
+                      size_t n, int seq = -1);
 
 private:
   bool m_wantPcap = false;