fw: access strategy
refs #1999
Change-Id: I5fc284d7ae82ed933bf5937d5c687885e73c3e0a
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