fw: access strategy

refs #1999

Change-Id: I5fc284d7ae82ed933bf5937d5c687885e73c3e0a
diff --git a/tests/daemon/fw/access-strategy.cpp b/tests/daemon/fw/access-strategy.cpp
new file mode 100644
index 0000000..934dfcd
--- /dev/null
+++ b/tests/daemon/fw/access-strategy.cpp
@@ -0,0 +1,353 @@
+/* -*- 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 "fw/access-strategy.hpp"
+
+#include "tests/test-common.hpp"
+#include "topology-tester.hpp"
+
+namespace nfd {
+namespace tests {
+
+// This test suite tests AccessStrategy's behavior as a black box,
+// without accessing its internals.
+//
+// Many test assertions are qualitative rather than quantitative.
+// They capture the design highlights of the strategy without requiring a definite value,
+// so that the test suite is not fragile to minor changes in the strategy implementation.
+//
+// Topology graphes in this test suite are shown in ASCII art,
+// in a style similar to ns-3 and ndnSIM examples.
+// They are enclosed in multi-line comments, which is an intentional violation of
+// code style rule 3.25. This is necessary because some lines ends with '\' which
+// would cause "multi-line comment" compiler warning if '//' comments are used.
+
+BOOST_FIXTURE_TEST_SUITE(FwAccessStrategy, UnitTestTimeFixture)
+
+class TwoLaptopsFixture : public UnitTestTimeFixture
+{
+protected:
+  TwoLaptopsFixture()
+  {
+    /*
+     *                  +--------+
+     *           +----->| router |<------+
+     *           |      +--------+       |
+     *      10ms |                       | 20ms
+     *           v                       v
+     *      +---------+             +---------+
+     *      | laptopA |             | laptopB |
+     *      +---------+             +---------+
+     */
+
+    router = topo.addForwarder();
+    laptopA = topo.addForwarder();
+    laptopB = topo.addForwarder();
+
+    topo.setStrategy<fw::AccessStrategy>(router);
+
+    linkA = topo.addLink(time::milliseconds(10), {router, laptopA});
+    linkB = topo.addLink(time::milliseconds(20), {router, laptopB});
+  }
+
+protected:
+  TopologyTester topo;
+
+  TopologyNode router;
+  TopologyNode laptopA;
+  TopologyNode laptopB;
+  shared_ptr<TopologyLink> linkA;
+  shared_ptr<TopologyLink> linkB;
+};
+
+BOOST_FIXTURE_TEST_CASE(OneProducer, TwoLaptopsFixture)
+{
+  /*
+   *             /------------------\
+   *             | intervalConsumer |
+   *             \------------------/
+   *                      ^ v
+   *                      | v /laptops/A
+   *                      |
+   *                      v
+   *      /laptops << +--------+ >> /laptops
+   *           +----->| router |<------+
+   *           |      +--------+       |
+   *      10ms |                       | 20ms
+   *           v                       v
+   *      +---------+             +---------+
+   *      | laptopA |             | laptopB |
+   *      +---------+             +---------+
+   *           ^  v
+   *           |  v /laptops/A
+   *           v
+   *    /--------------\
+   *    | echoProducer |
+   *    \--------------/
+   */
+
+  // two laptops have same prefix in router FIB
+  topo.registerPrefix(router, linkA->getFace(router), "ndn:/laptops");
+  topo.registerPrefix(router, linkB->getFace(router), "ndn:/laptops");
+
+  shared_ptr<TopologyAppLink> producer = topo.addAppFace(laptopA, "ndn:/laptops/A");
+  topo.addEchoProducer(*producer->getClientFace());
+
+  shared_ptr<TopologyAppLink> consumer = topo.addAppFace(router);
+  topo.addIntervalConsumer(*consumer->getClientFace(), "ndn:/laptops/A",
+                           time::milliseconds(100), 100);
+
+  this->advanceClocks(time::milliseconds(5), time::seconds(12));
+
+  // most Interests should be satisfied, and few Interests can go to wrong laptop
+  BOOST_CHECK_GE(consumer->getForwarderFace()->m_sentDatas.size(), 97);
+  BOOST_CHECK_GE(linkA->getFace(router)->m_sentInterests.size(), 97);
+  BOOST_CHECK_LE(linkB->getFace(router)->m_sentInterests.size(), 5);
+}
+
+BOOST_FIXTURE_TEST_CASE(FastSlowProducer, TwoLaptopsFixture)
+{
+  /*
+   *             /------------------\
+   *             | intervalConsumer |
+   *             \------------------/
+   *                      ^ v
+   *                      | v /laptops/BOTH
+   *                      |
+   *                      v
+   *      /laptops << +--------+ >> /laptops
+   *           +----->| router |<------+
+   *           |      +--------+       |
+   *      10ms |                       | 20ms
+   *           v                       v
+   *      +---------+             +---------+
+   *      | laptopA |             | laptopB |
+   *      +---------+             +---------+
+   *           ^  v                    ^  v
+   *           |  v /laptops/BOTH      |  v /laptops/BOTH
+   *           v                       v
+   *    /--------------\        /--------------\
+   *    | echoProducer |        | echoProducer |
+   *    \--------------/        \--------------/
+   */
+
+  // two laptops have same prefix in router FIB
+  topo.registerPrefix(router, linkA->getFace(router), "ndn:/laptops");
+  topo.registerPrefix(router, linkB->getFace(router), "ndn:/laptops");
+
+  shared_ptr<TopologyAppLink> producerA = topo.addAppFace(laptopA, "ndn:/laptops/BOTH");
+  topo.addEchoProducer(*producerA->getClientFace());
+  shared_ptr<TopologyAppLink> producerB = topo.addAppFace(laptopB, "ndn:/laptops/BOTH");
+  topo.addEchoProducer(*producerB->getClientFace());
+
+  shared_ptr<TopologyAppLink> consumer = topo.addAppFace(router);
+  topo.addIntervalConsumer(*consumer->getClientFace(), "ndn:/laptops/BOTH",
+                           time::milliseconds(100), 100);
+
+  this->advanceClocks(time::milliseconds(5), time::seconds(12));
+
+  // most Interests should be satisfied, and few Interests can go to slower laptopB
+  BOOST_CHECK_GE(consumer->getForwarderFace()->m_sentDatas.size(), 97);
+  BOOST_CHECK_GE(linkA->getFace(router)->m_sentInterests.size(), 90);
+  BOOST_CHECK_LE(linkB->getFace(router)->m_sentInterests.size(), 15);
+}
+
+BOOST_FIXTURE_TEST_CASE(ProducerMobility, TwoLaptopsFixture)
+{
+  /*
+   *           /------------------\                              /------------------\
+   *           | intervalConsumer |                              | intervalConsumer |
+   *           \------------------/              A               \------------------/
+   *                    ^ v                      f                        ^ v
+   *                    | v /laptops/M           t                        | v /laptops/M
+   *                    |                        e                        |
+   *                    v                        r                        v
+   *    /laptops << +--------+ >> /laptops                /laptops << +--------+ >> /laptops
+   *         +----->| router |<------+           6             +----->| router |<------+
+   *         |      +--------+       |                         |      +--------+       |
+   *    10ms |                       | 20ms  === s ==>    10ms |                       | 20ms
+   *         v                       v           e             v                       v
+   *    +---------+             +---------+      c        +---------+             +---------+
+   *    | laptopA |             | laptopB |      o        | laptopA |             | laptopB |
+   *    +---------+             +---------+      n        +---------+             +---------+
+   *         ^  v                                d                                  v  ^
+   *         |  v /laptops/M                     s                       /laptops/M v  |
+   *         v                                                                         v
+   *  /--------------\                                                          /--------------\
+   *  | echoProducer |                                                          | echoProducer |
+   *  \--------------/                                                          \--------------/
+   */
+
+  // two laptops have same prefix in router FIB
+  topo.registerPrefix(router, linkA->getFace(router), "ndn:/laptops");
+  topo.registerPrefix(router, linkB->getFace(router), "ndn:/laptops");
+
+  shared_ptr<TopologyAppLink> producerA = topo.addAppFace(laptopA, "ndn:/laptops/M");
+  topo.addEchoProducer(*producerA->getClientFace());
+  shared_ptr<TopologyAppLink> producerB = topo.addAppFace(laptopB, "ndn:/laptops/M");
+  topo.addEchoProducer(*producerB->getClientFace());
+
+  shared_ptr<TopologyAppLink> consumer = topo.addAppFace(router);
+  topo.addIntervalConsumer(*consumer->getClientFace(), "ndn:/laptops/M",
+                           time::milliseconds(100), 100);
+
+  // producer is initially on laptopA
+  producerB->fail();
+  this->advanceClocks(time::milliseconds(5), time::seconds(6));
+
+  // few Interests can go to laptopB
+  BOOST_CHECK_LE(linkB->getFace(router)->m_sentInterests.size(), 5);
+
+  // producer moves to laptopB
+  producerA->fail();
+  producerB->recover();
+  linkA->getFace(router)->m_sentInterests.clear();
+  this->advanceClocks(time::milliseconds(5), time::seconds(6));
+
+  // few additional Interests can go to laptopA
+  BOOST_CHECK_LE(linkA->getFace(router)->m_sentInterests.size(), 5);
+
+  // most Interests should be satisfied
+  BOOST_CHECK_GE(consumer->getForwarderFace()->m_sentDatas.size(), 97);
+}
+
+BOOST_FIXTURE_TEST_CASE(Bidirectional, TwoLaptopsFixture)
+{
+  /*
+   *                         /laptops << +--------+ >> /laptops
+   *                              +----->| router |<------+
+   *                              |      +--------+       |
+   *                      ^  10ms |                       | 20ms  ^
+   *                    / ^       v                       v       ^ /
+   *                         +---------+             +---------+
+   *                  +----->| laptopA |             | laptopB |<------------+
+   *                  |      +---------+             +---------+             |
+   *                  |           ^  v /laptops/A         ^  v /laptops/B    |
+   *               ^  |           |  v                    |  v               |  ^
+   *    /laptops/B ^  v           v                       v                  v  ^ /laptops/A
+   *  /------------------\   /--------------\        /--------------\     /------------------\
+   *  | intervalConsumer |   | echoProducer |        | echoProducer |     | intervalConsumer |
+   *  \------------------/   \--------------/        \--------------/     \------------------/
+   */
+
+  // laptops have default routes toward the router
+  topo.registerPrefix(laptopA, linkA->getFace(laptopA), "ndn:/");
+  topo.registerPrefix(laptopB, linkB->getFace(laptopB), "ndn:/");
+
+  // two laptops have same prefix in router FIB
+  topo.registerPrefix(router, linkA->getFace(router), "ndn:/laptops");
+  topo.registerPrefix(router, linkB->getFace(router), "ndn:/laptops");
+
+  shared_ptr<TopologyAppLink> producerA = topo.addAppFace(laptopA, "ndn:/laptops/A");
+  topo.addEchoProducer(*producerA->getClientFace());
+  shared_ptr<TopologyAppLink> producerB = topo.addAppFace(laptopB, "ndn:/laptops/B");
+  topo.addEchoProducer(*producerB->getClientFace());
+
+  shared_ptr<TopologyAppLink> consumerAB = topo.addAppFace(laptopA);
+  topo.addIntervalConsumer(*consumerAB->getClientFace(), "ndn:/laptops/B",
+                           time::milliseconds(100), 100);
+  shared_ptr<TopologyAppLink> consumerBA = topo.addAppFace(laptopB);
+  topo.addIntervalConsumer(*consumerBA->getClientFace(), "ndn:/laptops/A",
+                           time::milliseconds(100), 100);
+
+  this->advanceClocks(time::milliseconds(5), time::seconds(12));
+
+  // most Interests should be satisfied
+  BOOST_CHECK_GE(consumerAB->getForwarderFace()->m_sentDatas.size(), 97);
+  BOOST_CHECK_GE(consumerBA->getForwarderFace()->m_sentDatas.size(), 97);
+}
+
+BOOST_FIXTURE_TEST_CASE(PacketLoss, TwoLaptopsFixture)
+{
+  /*
+   *   test case Interests
+   *           |
+   *           v
+   *      +--------+
+   *      | router |
+   *      +--------+
+   *           |  v
+   *      10ms |  v /laptops
+   *           v
+   *      +---------+
+   *      | laptopA |
+   *      +---------+
+   *           ^  v
+   *           |  v /laptops/A
+   *           v
+   *    /--------------\
+   *    | echoProducer |
+   *    \--------------/
+   */
+
+  // laptopA has prefix in router FIB; laptopB is unused in this test case
+  topo.registerPrefix(router, linkA->getFace(router), "ndn:/laptops");
+
+  shared_ptr<TopologyAppLink> producerA = topo.addAppFace(laptopA, "ndn:/laptops/A");
+  topo.addEchoProducer(*producerA->getClientFace());
+
+  shared_ptr<TopologyAppLink> consumer = topo.addAppFace(router);
+
+  // Interest 1 completes normally
+  shared_ptr<Interest> interest1 = makeInterest("ndn:/laptops/A/1");
+  bool hasData1 = false;
+  consumer->getClientFace()->expressInterest(*interest1,
+                                             bind([&hasData1] { hasData1 = true; }));
+  this->advanceClocks(time::milliseconds(5), time::seconds(1));
+  BOOST_CHECK_EQUAL(hasData1, true);
+
+  // Interest 2 experiences a packet loss on initial transmission
+  shared_ptr<Interest> interest2a = makeInterest("ndn:/laptops/A/2");
+  bool hasData2a = false, hasTimeout2a = false;
+  consumer->getClientFace()->expressInterest(*interest2a,
+                                             bind([&hasData2a] { hasData2a = true; }),
+                                             bind([&hasTimeout2a] { hasTimeout2a = true; }));
+  producerA->fail();
+  this->advanceClocks(time::milliseconds(5), time::milliseconds(60));
+  BOOST_CHECK_EQUAL(hasData2a, false);
+  BOOST_CHECK_EQUAL(hasTimeout2a, false);
+
+  // Interest 2 retransmission is suppressed
+  shared_ptr<Interest> interest2b = makeInterest("ndn:/laptops/A/2");
+  bool hasData2b = false;
+  consumer->getClientFace()->expressInterest(*interest2b,
+                                             bind([&hasData2b] { hasData2b = true; }));
+  producerA->recover();
+  this->advanceClocks(time::milliseconds(5), time::seconds(1));
+  BOOST_CHECK_EQUAL(hasData2b, false);
+
+  // Interest 2 retransmission gets through, and is answered
+  shared_ptr<Interest> interest2c = makeInterest("ndn:/laptops/A/2");
+  bool hasData2c = false;
+  consumer->getClientFace()->expressInterest(*interest2c,
+                                             bind([&hasData2c] { hasData2c = true; }));
+  this->advanceClocks(time::milliseconds(5), time::seconds(1));
+  BOOST_CHECK_EQUAL(hasData2c, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/fw/topology-tester.hpp b/tests/daemon/fw/topology-tester.hpp
new file mode 100644
index 0000000..f343738
--- /dev/null
+++ b/tests/daemon/fw/topology-tester.hpp
@@ -0,0 +1,366 @@
+/* -*- 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/>.
+ */
+
+/** \file
+ *  \brief allows testing forwarding in a network topology
+ */
+
+#ifndef NFD_TESTS_NFD_FW_TOPOLOGY_TESTER_HPP
+#define NFD_TESTS_NFD_FW_TOPOLOGY_TESTER_HPP
+
+#include <unordered_map>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include "fw/strategy.hpp"
+#include "tests/test-common.hpp"
+#include "../face/dummy-face.hpp"
+
+namespace nfd {
+namespace tests {
+
+using ndn::util::DummyClientFace;
+
+/** \brief identifies a node (forwarder) in the topology
+ */
+typedef size_t TopologyNode;
+
+/** \brief represents a network or local-app link
+ */
+class TopologyLinkBase : noncopyable
+{
+public:
+  TopologyLinkBase()
+    : m_isUp(true)
+  {
+  }
+
+  /** \brief fail the link, cause packets to be dropped silently
+   */
+  void
+  fail()
+  {
+    m_isUp = false;
+  }
+
+  /** \brief recover the link from a failure
+   */
+  void
+  recover()
+  {
+    m_isUp = true;
+  }
+
+protected:
+  bool m_isUp;
+};
+
+/** \brief represents a network link in the topology which connects two or more nodes
+ */
+class TopologyLink : public TopologyLinkBase
+{
+public:
+  /** \return a face of forwarder \p i which is attached to this link
+   */
+  shared_ptr<DummyFace>
+  getFace(TopologyNode i)
+  {
+    return m_faces.at(i)->face;
+  }
+
+private:
+  explicit
+  TopologyLink(const time::nanoseconds& delay)
+    : m_delay(delay)
+  {
+    BOOST_ASSERT(delay >= time::nanoseconds::zero());
+  }
+
+  struct LinkFace
+  {
+    shared_ptr<DummyFace> face;
+  };
+
+  void
+  addFace(TopologyNode i, shared_ptr<DummyFace> face)
+  {
+    BOOST_ASSERT(m_faces.count(i) == 0);
+
+    LinkFace* lf = new LinkFace();
+    lf->face = face;
+    face->onSendInterest.connect(bind(&TopologyLink::transmitInterest, this, i, _1));
+    face->onSendData.connect(bind(&TopologyLink::transmitData, this, i, _1));
+
+    m_faces[i].reset(lf);
+  }
+
+  friend class TopologyTester;
+
+private:
+  void
+  transmitInterest(TopologyNode i, const Interest& interest)
+  {
+    if (!m_isUp) {
+      return;
+    }
+
+    // Interest object cannot be shared between faces because
+    // Forwarder can set different IncomingFaceId.
+    Block wire = interest.wireEncode();
+    for (auto&& p : m_faces) {
+      if (p.first == i) {
+        continue;
+      }
+      shared_ptr<DummyFace> face = p.second->face;
+      scheduler::schedule(m_delay, [wire, face] {
+        auto interest = make_shared<Interest>(wire);
+        face->receiveInterest(*interest);
+      });
+    }
+  }
+
+  void
+  transmitData(TopologyNode i, const Data& data)
+  {
+    if (!m_isUp) {
+      return;
+    }
+
+    // Data object cannot be shared between faces because
+    // Forwarder can set different IncomingFaceId.
+    Block wire = data.wireEncode();
+    for (auto&& p : m_faces) {
+      if (p.first == i) {
+        continue;
+      }
+      shared_ptr<DummyFace> face = p.second->face;
+      scheduler::schedule(m_delay, [wire, face] {
+        auto data = make_shared<Data>(wire);
+        face->receiveData(*data);
+      });
+    }
+  }
+
+private:
+  time::nanoseconds m_delay;
+  std::unordered_map<TopologyNode, unique_ptr<LinkFace>> m_faces;
+};
+
+/** \brief represents a link to a local application
+ */
+class TopologyAppLink : public TopologyLinkBase
+{
+public:
+  /** \return face on forwarder side
+   */
+  shared_ptr<DummyLocalFace>
+  getForwarderFace()
+  {
+    return m_face;
+  }
+
+  /** \return face on application side
+   */
+  shared_ptr<DummyClientFace>
+  getClientFace()
+  {
+    return m_client;
+  }
+
+private:
+  explicit
+  TopologyAppLink(shared_ptr<DummyLocalFace> face)
+    : m_face(face)
+    , m_client(ndn::util::makeDummyClientFace(getGlobalIoService(), {false, false}))
+  {
+    m_client->onSendInterest.connect([this] (const Interest& interest) {
+      if (!m_isUp) {
+        return;
+      }
+      auto interest2 = interest.shared_from_this();
+      getGlobalIoService().post([=] { m_face->receiveInterest(*interest2); });
+    });
+
+    m_client->onSendData.connect([this] (const Data& data) {
+      if (!m_isUp) {
+        return;
+      }
+      auto data2 = data.shared_from_this();
+      getGlobalIoService().post([=] { m_face->receiveData(*data2); });
+    });
+
+    m_face->onSendInterest.connect([this] (const Interest& interest) {
+      if (!m_isUp) {
+        return;
+      }
+      auto interest2 = interest.shared_from_this();
+      getGlobalIoService().post([=] { m_client->receive(*interest2); });
+    });
+
+    m_face->onSendData.connect([this] (const Data& data) {
+      if (!m_isUp) {
+        return;
+      }
+      auto data2 = data.shared_from_this();
+      getGlobalIoService().post([=] { m_client->receive(*data2); });
+    });
+  }
+
+  friend class TopologyTester;
+
+private:
+  shared_ptr<DummyLocalFace> m_face;
+  shared_ptr<DummyClientFace> m_client;
+};
+
+/** \brief builds a topology for forwarding tests
+ */
+class TopologyTester : noncopyable
+{
+public:
+  /** \brief creates a forwarder
+   *  \return index of new forwarder
+   */
+  TopologyNode
+  addForwarder()
+  {
+    size_t i = m_forwarders.size();
+    m_forwarders.push_back(std::move(unique_ptr<Forwarder>(new Forwarder())));
+    return i;
+  }
+
+  /** \return forwarder instance \p i
+   */
+  Forwarder&
+  getForwarder(TopologyNode i)
+  {
+    return *m_forwarders.at(i);
+  }
+
+  /** \brief sets strategy on forwarder \p i
+   *  \tparam the strategy type
+   *  \note Test scenario can also access StrategyChoice table directly.
+   */
+  template<typename S>
+  void
+  setStrategy(TopologyNode i, Name prefix = Name("ndn:/"))
+  {
+    Forwarder& forwarder = this->getForwarder(i);
+    StrategyChoice& strategyChoice = forwarder.getStrategyChoice();
+    shared_ptr<S> strategy = make_shared<S>(ref(forwarder));
+    strategyChoice.install(strategy);
+    strategyChoice.insert(prefix, strategy->getName());
+  }
+
+  /** \brief makes a link that interconnects two or more 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 time::nanoseconds& delay, std::initializer_list<TopologyNode> forwarders)
+  {
+    auto link = shared_ptr<TopologyLink>(new TopologyLink(delay));
+    for (TopologyNode i : forwarders) {
+      Forwarder& forwarder = this->getForwarder(i);
+      shared_ptr<DummyFace> face = make_shared<DummyFace>();
+      forwarder.addFace(face);
+      link->addFace(i, face);
+    }
+    return link;
+  }
+
+  /** \brief makes a link to local application
+   */
+  shared_ptr<TopologyAppLink>
+  addAppFace(TopologyNode i)
+  {
+    Forwarder& forwarder = this->getForwarder(i);
+    auto face = make_shared<DummyLocalFace>();
+    forwarder.addFace(face);
+
+    return shared_ptr<TopologyAppLink>(new TopologyAppLink(face));
+  }
+
+  /** \brief makes a link to local application, and register a prefix
+   */
+  shared_ptr<TopologyAppLink>
+  addAppFace(TopologyNode i, const Name& prefix, uint64_t cost = 0)
+  {
+    shared_ptr<TopologyAppLink> al = this->addAppFace(i);
+    this->registerPrefix(i, al->getForwarderFace(), prefix, cost);
+    return al;
+  }
+
+  /** \brief registers a prefix on a face
+   *  \tparam F either DummyFace or DummyLocalFace
+   */
+  template<typename F>
+  void
+  registerPrefix(TopologyNode i, shared_ptr<F> face, const Name& prefix, uint64_t cost = 0)
+  {
+    Forwarder& forwarder = this->getForwarder(i);
+    Fib& fib = forwarder.getFib();
+    shared_ptr<fib::Entry> fibEntry = fib.insert(prefix).first;
+    fibEntry->addNextHop(face, cost);
+  }
+
+  /** \brief creates a producer application that answers every Interest with Data of same Name
+   */
+  void
+  addEchoProducer(DummyClientFace& face, const Name& prefix = "/")
+  {
+    face.setInterestFilter(prefix,
+        [&face] (const ndn::InterestFilter&, const Interest& interest) {
+          shared_ptr<Data> data = makeData(interest.getName());
+          face.put(*data);
+        });
+  }
+
+  /** \brief creates a consumer application that sends \p n Interests under \p prefix
+   *         at \p interval fixed rate.
+   */
+  void
+  addIntervalConsumer(DummyClientFace& face, const Name& prefix,
+                      const time::nanoseconds& interval, size_t n)
+  {
+    Name name(prefix);
+    name.appendTimestamp();
+    shared_ptr<Interest> interest = makeInterest(name);
+    face.expressInterest(*interest, bind([]{}));
+
+    if (n > 1) {
+      scheduler::schedule(interval, bind(&TopologyTester::addIntervalConsumer, this,
+                                         ref(face), prefix, interval, n - 1));
+    }
+  }
+
+private:
+  std::vector<unique_ptr<Forwarder>> m_forwarders;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_FW_TOPOLOGY_TESTER_HPP