【ndnSIM】ndn-cxxで実装したアプリケーションをndnSIMに移植する

# モチベーション
ndn-cxxで実装したアプリケーションを、ノード数を増やした環境で実験に使いたい
→ ndnSIMに移植したい！

# 前提条件
実験環境 : Ubuntu 17.10 
対象読者 : Named Data Networking(NDN)の研究をしている日本人, C++がそこそこに書ける日本人

# 公式にはなんて書いてあるの？
http://ndnsim.net/current/guide-to-simulate-real-apps.html
こちらにndn-cxxで書いたアプリケーションをndnSIMに移植する際のサンプルコードが書いてあります。
このコードは`ndnSIM/ns-3/src/ndnSIM/examples/ndn-cxx-simple/`下と、`ndnSIM/ns-3/src/ndnSIM/examples/ndn-cxx-simple.cpp`に入っています。
(ちなみに、先日の更新前まで上記リンク先で説明されていた`ndnSIM/ns-3/src/ndnSIM/examples/ndn-custom-apps.cpp`は全く使い物になりません)

普段ndnSIMを利用する際には自分で作ったアプリを`scratch/`下に置いて、`ndnSIM/ns-3/`下で

```scratch/hoge.ccを実行する場合
$ ./waf
$ ./build/scratch/hoge 
```

と実行すれば良い""はず""なのですが、上記コード群をそのまま`scratch/`下に持って来て実行しても、何故かうまくいきません。
しかもこのコードはノードが1つであり、実際に実験する際には複数ノードを使う場合がほとんどであるので、複数ノードの場合はどうするのか分からないという問題もあります......
ということで、同じ点で困っている日本人研究者のために記事を書き残しておきます。

# とりあえず2ノード間通信を移植してみる
https://named-data.net/doc/ndn-cxx/current/examples.html
にあるサンプルコードをndnSIMに移植してみます。

まずはTrival Consumerを移植してみます。移植の際には名前空間に注意すること！

```consumer.hpp
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_HPP

#include <ndn-cxx/face.hpp>
#include <ndn-cxx/interest.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/util/scheduler.hpp>

#include <iostream>

namespace app {

class CustomConsumer
{
public:
  CustomConsumer(ndn::KeyChain& keyChain)
    : m_keyChain(keyChain)
    , m_scheduler(m_face.getIoService())
  {
  }

  void
  run()
  {
    ndn::Interest interest(ndn::Name("/example/testApp/randomData"));
    interest.setInterestLifetime(ndn::time::milliseconds(1000));
    interest.setMustBeFresh(true);

    m_face.expressInterest(interest,
                          std::bind([] { std::cout << "Hello!" << std::endl; }),
                          std::bind([] { std::cout << "NACK!" << std::endl; }),
                          std::bind([] { std::cout << "Bye!.." << std::endl; }));

    std::cout << "Sending: " << interest << std::endl;

    m_face.processEvents(); // ok (will not block and do nothing)
    // m_faceConsumer.getIoService().run(); // will crash
  }

private:
  void
  onData(const ndn::Interest& interest, const ndn::Data& data)
  {
    std::cout << data << std::endl;
  }

  void
  onNack(const ndn::Interest& interest, const ndn::lp::Nack& nack)
  {
    std::cout << "received Nack with reason " << nack.getReason() << " for interest" << interest << std::endl;
  }

  void
  onTimeout(const ndn::Interest& interest)
  {
    std::cout << "Timeout " << interest << std::endl;
  }

private:
  ndn::KeyChain& m_keyChain;
  ndn::Face m_face;
  ndn::Scheduler m_scheduler;
};

} // namespace app

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_HPP
```

Consumerの処理をシナリオコードからstartさせるためのコードも必要になります。

```consumer-starter.hpp
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_STARTER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_STARTER_HPP

#include "consumer.hpp"

#include "ns3/ndnSIM/helper/ndn-stack-helper.hpp"
#include "ns3/application.h"

namespace ns3 {

// Class inheriting from ns3::Application
class CustomConsumerStarter : public Application
{
public:
  static TypeId
  GetTypeId()
  {
    static TypeId tid = TypeId("CustomConsumerStarter")
      .SetParent<Application>()
      .AddConstructor<CustomConsumerStarter>();

    return tid;
  }

protected:
  // inherited from Application base class.
  virtual void
  StartApplication()
  {
    // Create an instance of the app, and passing the dummy version of KeyChain (no real signing)
    m_instance.reset(new app::CustomConsumer(ndn::StackHelper::getKeyChain()));
    m_instance->run(); // can be omitted
  }

  virtual void
  StopApplication()
  {
    // Stop and destroy the instance of the app
    m_instance.reset();
  }

private:
  std::unique_ptr<app::CustomConsumer> m_instance;
};

} // namespace ns3

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_STARTER_HPP
```

同様にしてTrival Producerも移植してみます。

```producer.hpp
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_HPP

#include <ndn-cxx/face.hpp>
#include <ndn-cxx/interest.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/util/scheduler.hpp>

#include <iostream>

namespace app {

class CustomProducer
{
public:
  CustomProducer(ndn::KeyChain& keyChain)
    : m_keyChain(keyChain)
    // , m_faceProducer(m_face.getIoService())
    , m_scheduler(m_face.getIoService())
  {
  }

  void
  run()
  {
    m_face.setInterestFilter("/example/testApp",
                              std::bind(&CustomProducer::onInterest, this, _1, _2),
                              ndn::RegisterPrefixSuccessCallback(),
                              std::bind(&CustomProducer::onRegisterFailed, this, _1, _2));

    m_face.processEvents(); // ok (will not block and do nothing)
    // m_faceConsumer.getIoService().run(); // will crash
  }

private:
  void
  onInterest(const ndn::InterestFilter& filter, const ndn::Interest& interest)
  {
    std::cout << "<< I: " << interest << std::endl;

    ndn::Name dataName(interest.getName());
    dataName
      .append("testApp")
      .appendVersion();

    static const std::string content = "hogehogeclub";

    std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>();
    data->setName(dataName);
    data->setFreshnessPeriod(ndn::time::seconds(10));
    data->setContent(reinterpret_cast<const uint8_t*>(content.c_str()), content.size());

    m_keyChain.sign(*data);

    std::cout << ">> D: " << *data << std::endl;
    m_face.put(*data);
  }

  void
  onRegisterFailed(const ndn::Name& prefix, const std::string& reason)
  {
    std::cerr << "ERROR: Failed to register prefix \""
              << prefix <<"\" in local hub's daemon (" << reason << ")"
              << std::endl;

    m_face.shutdown();
  }

private:
  ndn::KeyChain& m_keyChain;
  ndn::Face m_face;
  ndn::Scheduler m_scheduler;
};

} // namespace app

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_HPP
```

```producer-starter.hpp
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_STARTER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_STARTER_HPP

#include "producer.hpp"

#include "ns3/ndnSIM/helper/ndn-stack-helper.hpp"
#include "ns3/application.h"

namespace ns3 {

// Class inheriting from ns3::Application
class CustomProducerStarter : public Application
{
public:
  static TypeId
  GetTypeId()
  {
    static TypeId tid = TypeId("CustomProducerStarter")
      .SetParent<Application>()
      .AddConstructor<CustomProducerStarter>();

    return tid;
  }

protected:
  // inherited from Application base class.
  virtual void
  StartApplication()
  {
    // Create an instance of the app, and passing the dummy version of KeyChain (no real signing)
    m_instance.reset(new app::CustomProducer(ndn::StackHelper::getKeyChain()));
    m_instance->run(); // can be omitted
  }

  virtual void
  StopApplication()
  {
    // Stop and destroy the instance of the app
    m_instance.reset();
  }

private:
  std::unique_ptr<app::CustomProducer> m_instance;
};

} // namespace ns3

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_STARTER_HPP
```

最後に`scratch/`下にシナリオコードを書いてみる。

```ndn-cxx-simple.cc
#include "ns3/sampleapps/consumer.hpp"
#include "ns3/sampleapps/consumer-starter.hpp"

#include "ns3/sampleapps/producer.hpp"
#include "ns3/sampleapps/producer-starter.hpp"

#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/ndnSIM-module.h"
#include "ns3/point-to-point-module.h"

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED(CustomConsumerStarter);
NS_OBJECT_ENSURE_REGISTERED(CustomProducerStarter);

int
main(int argc, char* argv[])
{
  CommandLine cmd;
  cmd.Parse(argc, argv);

  // Ptr<Node> node = CreateObject<Node>();
  NodeContainer nodes;
  nodes.Create(2);

  PointToPointHelper p2p;
  p2p.Install(nodes.Get(0), nodes.Get(1));

  ndn::StackHelper ndnHelper;
  ndnHelper.SetDefaultRoutes(true);
  ndnHelper.InstallAll();

  ndn::AppHelper consumerHelper("CustomConsumerStarter");
  consumerHelper.Install(nodes.Get(0))
    .Start(Seconds(6.5));

  ndn::AppHelper producerHelper("CustomProducerStarter");
  producerHelper.Install(nodes.Get(1))
    .Start(Seconds(6.5));

  Simulator::Stop(Seconds(20.0));

  Simulator::Run();
  Simulator::Destroy();

  return 0;
}

} // namespace ns3

int
main(int argc, char* argv[])
{
  return ns3::main(argc, argv);
}
```

ndn-cxxのコードを元に書き直したhppファイル群は、元々ndnSIMに入っているモジュール群と同じように`build/ns3/`下に置くと`scratch/`下に置いたシナリオコードで上手くincludeすることが出来ます。
`scratch/`下にフォルダを作成してそこにhppファイル群を入れた場合、どう足掻いてもinclude出来ません！！！

今回は`build/ns3/sampleapps/`下にhppファイル群を置いて実行します。


# 実行結果

```
$ ./build/scratch/ndn-cxx-simple
Sending: /example/testApp/randomData?ndn.MustBeFresh=1&ndn.InterestLifetime=1000
<< I: /example/testApp/randomData?ndn.MustBeFresh=1&ndn.InterestLifetime=1000&ndn.Nonce=2956930911
>> D: Name: /example/testApp/randomData/testApp/%FD%19q
MetaInfo: ContentType: 0, FreshnessPeriod: 10000 milliseconds
Content: (size: 12)
Signature: (type: SignatureSha256WithRsa, value_length: 260)

Hello!
```
無事に実行できました。

# 終わりに
ndnSIMのhelperでは出来ることが非常に限られているので、実験の際にはndn-cxxのコードみたいに書いたhppファイル群を作成して、シナリオコードからincludeすると柔軟な条件で実験が出来たりします。
この記事がNDNを研究している日本人研究者の助けになれば幸いです。
質問があればコメント欄にどうぞ。

※ 今回書いたコードはこちらからも見れます https://github.com/wawawanet/ndn-cxx_to_ndnSIM

# 追記
ConsumerとProducerの間にノードを増やしても、ルーティング処理をndnSIMに任せておけばパケットが到達することを確認出来たので追記しておきます。
PointToPointHelperをインストールしてノードを結んでおいて、 ndn::StackHelperをSetDefaultRoutes(true)として全ノードにインストールしておけば、パケットは自動でルーティングされて届くみたいです。
ノードがちゃんと結ばれていないとNackが返ってくるようになっています。
(ルーティングの処理がブラックボックスすぎて、ルータに機能を追加したい場合はどうやるのか分からないですね......)