helper: ScenarioHelper to simplify writing basic simulation scenarios

Primary use of the ScenarioHelper will be in the unit tests, but it can
also be used to write real simulation scenarios.

Change-Id: I3b8c6370d4e5e35c8b36d91e19aa11d18ab46fdc
diff --git a/helper/ndn-scenario-helper.cpp b/helper/ndn-scenario-helper.cpp
new file mode 100644
index 0000000..1d441d6
--- /dev/null
+++ b/helper/ndn-scenario-helper.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2011-2015  Regents of the University of California.
+ *
+ * This file is part of ndnSIM. See AUTHORS for complete list of ndnSIM authors and
+ * contributors.
+ *
+ * ndnSIM 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.
+ *
+ * ndnSIM 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
+ * ndnSIM, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "ndn-scenario-helper.hpp"
+#include "ndn-fib-helper.hpp"
+#include "ndn-app-helper.hpp"
+
+#include "ns3/ndnSIM/model/ndn-l3-protocol.hpp"
+
+#include "ns3/names.h"
+#include "ns3/point-to-point-helper.h"
+#include "ns3/string.h"
+
+namespace ns3 {
+namespace ndn {
+
+ScenarioHelper::ScenarioHelper()
+  : m_isTopologyInitialized(false)
+{
+}
+
+void
+ScenarioHelper::createTopology(std::initializer_list<std::initializer_list<std::string>/*node clique*/> topology)
+{
+  if (m_isTopologyInitialized) {
+    throw std::logic_error("Topology cannot be created twice");
+  }
+
+  PointToPointHelper p2p;
+
+  for (auto&& clique : topology) {
+    for (auto i = clique.begin(); i != clique.end(); ++i) {
+      auto node1 = getOrCreateNode(*i);
+      for (auto j = i + 1; j != clique.end(); ++j) {
+        auto node2 = getOrCreateNode(*j);
+
+        auto link = p2p.Install(node1, node2);
+        links[*i][*j] = link.Get(0);
+        links[*j][*i] = link.Get(1);
+      }
+    }
+  }
+
+  ndnHelper.InstallAll();
+  m_isTopologyInitialized = true;
+}
+
+void
+ScenarioHelper::addRoutes(std::initializer_list<ScenarioHelper::RouteInfo> routes)
+{
+  for (auto&& route : routes) {
+    FibHelper::AddRoute(getNode(route.node1), route.prefix,
+                        getFace(route.node1, route.node2), route.metric);
+  }
+}
+
+void
+ScenarioHelper::addApps(std::initializer_list<ScenarioHelper::AppInfo> apps)
+{
+  for (auto&& app : apps) {
+    AppHelper appHelper(app.name);
+    for (auto&& param : app.params) {
+      appHelper.SetAttribute(param.first, StringValue(param.second));
+    }
+    auto installedApp = appHelper.Install(getNode(app.node));
+    installedApp.Start(Time(app.start));
+    installedApp.Stop(Time(app.end));
+  }
+}
+
+Ptr<Node>
+ScenarioHelper::getOrCreateNode(const std::string& nodeName)
+{
+  auto node = nodes.find(nodeName);
+  if (node == nodes.end()) {
+    std::tie(node, std::ignore) = nodes.insert(std::make_pair(nodeName, CreateObject<Node>()));
+    Names::Add(nodeName, node->second);
+  }
+  return node->second;
+}
+
+Ptr<Node>
+ScenarioHelper::getNode(const std::string& nodeName)
+{
+  auto node = nodes.find(nodeName);
+  if (node != nodes.end()) {
+    return node->second;
+  }
+
+  throw std::invalid_argument("Node " + nodeName + " does not exist");
+}
+
+shared_ptr<Face>
+ScenarioHelper::getFace(const std::string& node1, const std::string& node2)
+{
+  auto i = links.find(node1);
+  if (i != links.end()) {
+    auto j = i->second.find(node2);
+    if (j != i->second.end()) {
+      return j->second->GetNode()->GetObject<L3Protocol>()->getFaceByNetDevice(j->second);
+    }
+  }
+
+  throw std::invalid_argument("Link between " + node1 + " and " + node2 + " does not exist");
+}
+
+} // namespace ndn
+} // namespace ns3
diff --git a/helper/ndn-scenario-helper.hpp b/helper/ndn-scenario-helper.hpp
new file mode 100644
index 0000000..0085f58
--- /dev/null
+++ b/helper/ndn-scenario-helper.hpp
@@ -0,0 +1,169 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2011-2015  Regents of the University of California.
+ *
+ * This file is part of ndnSIM. See AUTHORS for complete list of ndnSIM authors and
+ * contributors.
+ *
+ * ndnSIM 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.
+ *
+ * ndnSIM 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
+ * ndnSIM, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "ndn-stack-helper.hpp"
+
+#include "ns3/net-device.h"
+#include "ns3/node.h"
+
+#include <ndn-cxx/name.hpp>
+#include <map>
+
+namespace ns3 {
+namespace ndn {
+
+/**
+ * @ingroup ndn-helpers
+ * @brief Helper class to simplify writing basic simulation scenarios
+ *
+ * The following code with scenario helper creates a 3-node topology,
+ * with manual routes between nodes and 2 applications, installed on first and last node
+ * of the topology:
+ *
+ *     ScenarioHelper helper;
+ *     helper.createTopology({
+ *         {"1", "2"},
+ *         {"2", "3"}
+ *       });
+ *
+ *     helper.addRoutes({
+ *         {"1", "2", "/prefix", 1},
+ *         {"2", "3", "/prefix", 1}
+ *       });
+ *
+ *     helper.addApps({
+ *         {"1", "ns3::ndn::ConsumerCbr",
+ *             {{"Prefix", "/prefix"}, {"Frequency", "1"}},
+ *             "0s", "100s"},
+ *         {"3", "ns3::ndn::Producer",
+ *             {{"Prefix", "/prefix"}, {"PayloadSize", "1024"}},
+ *             "0s", "100s"}
+ *       });
+ *
+ */
+class ScenarioHelper
+{
+public:
+  /**
+   * @brief Route information for addRoutes method
+   *
+   * It is preferred to use initializer list to indirectly pass RouteInfo's to addRoutes
+   * method.
+   */
+  struct RouteInfo
+  {
+    std::string node1;
+    std::string node2;
+    Name prefix;
+    int32_t metric;
+  };
+
+  /*
+   * @brief Application information for addApps method
+   *
+   * It is preferred to use initializer list to indirectly pass AppInfo's to addApps
+   * method.
+   */
+  struct AppInfo
+  {
+    std::string node;
+    std::string name;
+    std::initializer_list<std::pair<std::string, std::string>> params;
+    std::string start;
+    std::string end;
+  };
+
+public:
+  ScenarioHelper();
+
+  /**
+   * @brief Create topology
+   * @throw std::logic_error if createTopology is called more than once
+   *
+   * Example:
+   *
+   *     ScenarioHelper helper;
+   *     helper.createTopology({
+   *         {"1", "2"},
+   *         {"2", "3"}
+   *       });
+   */
+  void
+  createTopology(std::initializer_list<std::initializer_list<std::string>/*node clique*/> topology);
+
+  /**
+   * @brief Create routes between topology nodes
+   * @throw std::invalid_argument if the nodes or links between nodes do not exist
+   *
+   * Example:
+   *
+   *     helper.addRoutes({
+   *         {"1", "2", "/prefix", 1},
+   *         {"2", "3", "/prefix", 1}
+   *       });
+   */
+  void
+  addRoutes(std::initializer_list<RouteInfo> routes);
+
+  /**
+   * @brief Create and install application on nodes
+   * @throw std::invalid_argument if the nodes or links between nodes do not exist
+   *
+   * Example:
+   *
+   *     helper.addApps({
+   *         {"1", "ns3::ndn::ConsumerCbr",
+   *             {{"Prefix", "/prefix"}, {"Frequency", "1"}},
+   *             "0s", "100s"},
+   *         {"3", "ns3::ndn::Producer",
+   *             {{"Prefix", "/prefix"}, {"PayloadSize", "1024"}},
+   *             "0s", "100s"}
+   *       });
+   */
+  void
+  addApps(std::initializer_list<AppInfo> apps);
+
+public: // topology accessors
+  /**
+   * @brief Get node
+   * @throw std::invalid_argument if the node does not exist
+   */
+  Ptr<Node>
+  getNode(const std::string& nodeName);
+
+  /**
+   * @brief Get face on the @p node1 pointing towards @p node2
+   * @throw std::invalid_argument if the link does not exist
+   */
+  shared_ptr<Face>
+  getFace(const std::string& node1, const std::string& node2);
+
+private:
+  Ptr<Node>
+  getOrCreateNode(const std::string& nodeName);
+
+private:
+  bool m_isTopologyInitialized;
+  StackHelper ndnHelper;
+  std::map<std::string, std::map<std::string, Ptr<NetDevice>>> links;
+  std::map<std::string, Ptr<Node>> nodes;
+};
+
+} // namespace ndn
+} // namespace ns3
diff --git a/tests/unit-tests/tests-common.hpp b/tests/unit-tests/tests-common.hpp
index 1c51eae..e858141 100644
--- a/tests/unit-tests/tests-common.hpp
+++ b/tests/unit-tests/tests-common.hpp
@@ -21,8 +21,10 @@
 #define NDNSIM_TESTS_UNIT_TESTS_TESTS_COMMON_HPP
 
 #include "ns3/core-module.h"
-#include "boost-test.hpp"
 #include "model/ndn-global-router.hpp"
+#include "helper/ndn-scenario-helper.hpp"
+
+#include "boost-test.hpp"
 
 namespace ns3 {
 namespace ndn {
@@ -38,6 +40,11 @@
   }
 };
 
+class ScenarioHelperWithCleanupFixture : public ScenarioHelper, public CleanupFixture
+{
+public:
+};
+
 } // namespace ndn
 } // namespace ns3