poke: code modernization and cleanup

Change-Id: Ia0d5f0b1bc495636f8fac127bec735cc35af66a7
diff --git a/tools/peek/ndnpoke/main.cpp b/tools/peek/ndnpoke/main.cpp
index 2086a2b..33d91ea 100644
--- a/tools/peek/ndnpoke/main.cpp
+++ b/tools/peek/ndnpoke/main.cpp
@@ -28,47 +28,40 @@
 #include "ndnpoke.hpp"
 #include "core/version.hpp"
 
-#include <ndn-cxx/util/io.hpp>
-
-#include <sstream>
-
 namespace ndn {
 namespace peek {
 
 namespace po = boost::program_options;
 
 static void
-usage(std::ostream& os, const po::options_description& options)
+usage(std::ostream& os, const std::string& program, const po::options_description& options)
 {
-  os << "Usage: ndnpoke [options] ndn:/name\n"
-        "\n"
-        "Reads payload from stdin and sends it to the local NDN forwarder as a single Data packet\n"
-        "\n"
+  os << "Usage: " << program << " [options] /name\n"
+     << "\n"
+     << "Reads a payload from the standard input and sends it as a single Data packet.\n"
      << options;
 }
 
 static int
 main(int argc, char* argv[])
 {
+  std::string progName(argv[0]);
   PokeOptions options;
-  bool wantDigestSha256;
+  bool wantDigestSha256 = false;
 
   po::options_description visibleOptDesc;
   visibleOptDesc.add_options()
-    ("help,h", "print help and exit")
-    ("version,V", "print version and exit")
-    ("force,f", po::bool_switch(&options.wantForceData),
-        "for, send Data without waiting for Interest")
-    ("digest,D", po::bool_switch(&wantDigestSha256),
-        "use DigestSha256 signing method instead of SignatureSha256WithRsa")
-    ("identity,i", po::value<std::string>(),
-        "set identity to be used for signing")
-    ("final,F", po::bool_switch(&options.wantLastAsFinalBlockId),
-        "set FinalBlockId to the last component of Name")
-    ("freshness,x", po::value<int>(),
-        "set FreshnessPeriod in milliseconds")
-    ("timeout,w", po::value<int>(),
-        "set Timeout in milliseconds")
+    ("help,h",      "print help and exit")
+    ("force,f",     po::bool_switch(&options.wantForceData),
+                    "send the Data packet without waiting for an incoming Interest")
+    ("final,F",     po::bool_switch(&options.wantFinalBlockId),
+                    "set FinalBlockId to the last component of the Data name")
+    ("freshness,x", po::value<int>(), "set FreshnessPeriod (in milliseconds)")
+    ("identity,i",  po::value<std::string>(), "use the specified identity for signing")
+    ("digest,D",    po::bool_switch(&wantDigestSha256),
+                    "use DigestSha256 signing method instead of SignatureSha256WithRsa")
+    ("timeout,w",   po::value<int>(), "set timeout (in milliseconds)")
+    ("version,V",   "print version and exit")
   ;
 
   po::options_description hiddenOptDesc;
@@ -91,11 +84,8 @@
     return 2;
   }
 
-  // We store timeout here, instead of PokeOptions, because processEvents is called outside the NdnPoke class
-  time::milliseconds timeout = 10_s;
-
   if (vm.count("help") > 0) {
-    usage(std::cout, visibleOptDesc);
+    usage(std::cout, progName, visibleOptDesc);
     return 0;
   }
 
@@ -104,78 +94,75 @@
     return 0;
   }
 
-  if (vm.count("name") > 0) {
-    options.prefixName = vm["name"].as<std::string>();
-  }
-  else {
-    std::cerr << "ERROR: Data name is missing" << std::endl;
-    usage(std::cerr, visibleOptDesc);
+  if (vm.count("name") == 0) {
+    std::cerr << "ERROR: missing name\n\n";
+    usage(std::cerr, progName, visibleOptDesc);
     return 2;
   }
 
+  try {
+    options.name = vm["name"].as<std::string>();
+  }
+  catch (const Name::Error& e) {
+    std::cerr << "ERROR: invalid name: " << e.what() << std::endl;
+    return 2;
+  }
+
+  if (options.name.empty()) {
+    std::cerr << "ERROR: name cannot have zero components" << std::endl;
+    return 2;
+  }
+
+  if (vm.count("freshness") > 0) {
+    if (vm["freshness"].as<int>() < 0) {
+      std::cerr << "ERROR: freshness cannot be negative" << std::endl;
+      return 2;
+    }
+    options.freshnessPeriod = time::milliseconds(vm["freshness"].as<int>());
+  }
+
+  if (vm.count("identity") > 0) {
+    if (wantDigestSha256) {
+      std::cerr << "ERROR: conflicting '--digest' and '--identity' options specified" << std::endl;
+      return 2;
+    }
+    try {
+      options.signingInfo.setSigningIdentity(vm["identity"].as<std::string>());
+    }
+    catch (const Name::Error& e) {
+      std::cerr << "ERROR: invalid identity name: " << e.what() << std::endl;
+      return 2;
+    }
+  }
+
   if (wantDigestSha256) {
     options.signingInfo.setSha256Signing();
   }
 
-  if (vm.count("identity") > 0) {
-    if (wantDigestSha256) {
-      std::cerr << "ERROR: Signing identity cannot be specified when using DigestSha256 signing method" << std::endl;
-      usage(std::cerr, visibleOptDesc);
-      return 2;
-    }
-    options.signingInfo.setSigningIdentity(vm["identity"].as<std::string>());
-  }
-
-  if (vm.count("final") > 0) {
-    if (!options.prefixName.empty()) {
-      options.wantLastAsFinalBlockId = true;
-    }
-    else {
-      std::cerr << "The provided Name must have 1 or more components to be used with FinalBlockId option" << std::endl;
-      usage(std::cerr, visibleOptDesc);
-      return 1;
-    }
-  }
-
-  if (vm.count("freshness") > 0) {
-    if (vm["freshness"].as<int>() >= 0) {
-      options.freshnessPeriod = time::milliseconds(vm["freshness"].as<int>());
-    }
-    else {
-      std::cerr << "ERROR: FreshnessPeriod must be a non-negative integer" << std::endl;
-      usage(std::cerr, visibleOptDesc);
-      return 2;
-    }
-  }
-
   if (vm.count("timeout") > 0) {
-    if (vm["timeout"].as<int>() > 0) {
-      timeout = time::milliseconds(vm["timeout"].as<int>());
-    }
-    else {
-      std::cerr << "ERROR: Timeout must a positive integer" << std::endl;
-      usage(std::cerr, visibleOptDesc);
+    if (options.wantForceData) {
+      std::cerr << "ERROR: conflicting '--force' and '--timeout' options specified" << std::endl;
       return 2;
     }
+    if (vm["timeout"].as<int>() < 0) {
+      std::cerr << "ERROR: timeout cannot be negative" << std::endl;
+      return 2;
+    }
+    options.timeout = time::milliseconds(vm["timeout"].as<int>());
   }
 
-  boost::asio::io_service io;
-  Face face(io);
-  KeyChain keyChain;
-  NdnPoke program(face, keyChain, std::cin, options);
   try {
+    Face face;
+    KeyChain keyChain;
+    NdnPoke program(face, keyChain, std::cin, options);
+
     program.start();
-    face.processEvents(timeout);
+    face.processEvents();
+
+    return program.didSendData() ? 0 : 1;
   }
   catch (const std::exception& e) {
-    std::cerr << "ERROR: " << e.what() << "\n" << std::endl;
-    return 1;
-  }
-
-  if (program.wasDataSent()) {
-    return 0;
-  }
-  else {
+    std::cerr << "ERROR: " << e.what() << std::endl;
     return 1;
   }
 }
diff --git a/tools/peek/ndnpoke/ndnpoke.cpp b/tools/peek/ndnpoke/ndnpoke.cpp
index 40ec247..2934404 100644
--- a/tools/peek/ndnpoke/ndnpoke.cpp
+++ b/tools/peek/ndnpoke/ndnpoke.cpp
@@ -27,79 +27,66 @@
 
 #include "ndnpoke.hpp"
 
-#include <ndn-cxx/security/signing-helpers.hpp>
-
-#include <sstream>
+#include <ndn-cxx/encoding/buffer-stream.hpp>
 
 namespace ndn {
 namespace peek {
 
-NdnPoke::NdnPoke(Face& face, KeyChain& keyChain, std::istream& inStream, const PokeOptions& options)
-  : m_face(face)
+NdnPoke::NdnPoke(Face& face, KeyChain& keyChain, std::istream& input, const PokeOptions& options)
+  : m_options(options)
+  , m_face(face)
   , m_keyChain(keyChain)
-  , m_inStream(inStream)
-  , m_options(options)
-  , m_wasDataSent(false)
+  , m_input(input)
+  , m_scheduler(m_face.getIoService())
 {
 }
 
 void
 NdnPoke::start()
 {
-  shared_ptr<Data> dataPacket = createDataPacket();
+  auto data = createData();
+
   if (m_options.wantForceData) {
-    m_face.put(*dataPacket);
-    m_wasDataSent = true;
+    m_face.put(*data);
+    m_didSendData = true;
+    return;
   }
-  else {
-    m_registeredPrefix = m_face.setInterestFilter(m_options.prefixName,
-                                                  bind(&NdnPoke::onInterest, this, _1, _2, dataPacket),
-                                                  nullptr,
-                                                  bind(&NdnPoke::onRegisterFailed, this, _1, _2));
-  }
+
+  m_registeredPrefix = m_face.setInterestFilter(m_options.name,
+    [this, data] (auto&&...) {
+      m_timeoutEvent.cancel();
+      m_face.put(*data);
+      m_didSendData = true;
+      m_registeredPrefix.cancel();
+    },
+    [this] (auto&&) {
+      m_timeoutEvent = m_scheduler.schedule(m_options.timeout, [this] {
+        m_registeredPrefix.cancel();
+      });
+    },
+    [] (auto&&, const auto& reason) {
+      std::cerr << "Prefix registration failure (" << reason << ")\n";
+    });
 }
 
 shared_ptr<Data>
-NdnPoke::createDataPacket()
+NdnPoke::createData() const
 {
-  auto dataPacket = make_shared<Data>(m_options.prefixName);
-
-  std::stringstream payloadStream;
-  payloadStream << m_inStream.rdbuf();
-  std::string payload = payloadStream.str();
-  dataPacket->setContent(reinterpret_cast<const uint8_t*>(payload.c_str()), payload.length());
-
+  auto data = make_shared<Data>(m_options.name);
   if (m_options.freshnessPeriod) {
-    dataPacket->setFreshnessPeriod(*m_options.freshnessPeriod);
+    data->setFreshnessPeriod(*m_options.freshnessPeriod);
+  }
+  if (m_options.wantFinalBlockId) {
+    data->setFinalBlock(m_options.name.at(-1));
   }
 
-  if (m_options.wantLastAsFinalBlockId) {
-    dataPacket->setFinalBlock(m_options.prefixName.get(-1));
-  }
+  OBufferStream os;
+  os << m_input.rdbuf();
+  data->setContent(os.buf());
 
-  m_keyChain.sign(*dataPacket, m_options.signingInfo);
+  m_keyChain.sign(*data, m_options.signingInfo);
 
-  return dataPacket;
-}
-
-void
-NdnPoke::onInterest(const Name& name, const Interest& interest, const shared_ptr<Data>& data)
-{
-  try {
-    m_face.put(*data);
-    m_wasDataSent = true;
-  }
-  catch (const Face::OversizedPacketError& e) {
-    std::cerr << "Data exceeded maximum packet size" << std::endl;
-  }
-
-  m_registeredPrefix.cancel();
-}
-
-void
-NdnPoke::onRegisterFailed(const Name& prefix, const std::string& reason)
-{
-  std::cerr << "Prefix Registration Failure. Reason = " << reason << std::endl;
+  return data;
 }
 
 } // namespace peek
diff --git a/tools/peek/ndnpoke/ndnpoke.hpp b/tools/peek/ndnpoke/ndnpoke.hpp
index fdad5bb..af5ad34 100644
--- a/tools/peek/ndnpoke/ndnpoke.hpp
+++ b/tools/peek/ndnpoke/ndnpoke.hpp
@@ -30,6 +30,8 @@
 
 #include "core/common.hpp"
 
+#include <ndn-cxx/util/scheduler.hpp>
+
 namespace ndn {
 namespace peek {
 
@@ -38,45 +40,44 @@
  */
 struct PokeOptions
 {
-  Name prefixName;
-  bool wantForceData = false;
+  // Data construction options
+  Name name;
+  optional<time::milliseconds> freshnessPeriod;
+  bool wantFinalBlockId = false;
   security::SigningInfo signingInfo;
-  bool wantLastAsFinalBlockId = false;
-  optional<time::milliseconds> freshnessPeriod = {};
+
+  // program behavior options
+  bool wantForceData = false;
+  time::milliseconds timeout = 10_s;
 };
 
 class NdnPoke : boost::noncopyable
 {
 public:
-  NdnPoke(Face& face, KeyChain& keyChain, std::istream& inStream, const PokeOptions& options);
+  NdnPoke(Face& face, KeyChain& keyChain, std::istream& input, const PokeOptions& options);
 
   void
   start();
 
   bool
-  wasDataSent() const
+  didSendData() const
   {
-    return m_wasDataSent;
+    return m_didSendData;
   }
 
 private:
   shared_ptr<Data>
-  createDataPacket();
-
-  void
-  onInterest(const Name& name, const Interest& interest, const shared_ptr<Data>& data);
-
-  void
-  onRegisterFailed(const Name& prefix, const std::string& reason);
+  createData() const;
 
 private:
+  const PokeOptions m_options;
   Face& m_face;
   KeyChain& m_keyChain;
-  std::istream& m_inStream;
-  const PokeOptions& m_options;
-
-  RegisteredPrefixHandle m_registeredPrefix;
-  bool m_wasDataSent;
+  std::istream& m_input;
+  Scheduler m_scheduler;
+  ScopedRegisteredPrefixHandle m_registeredPrefix;
+  scheduler::ScopedEventId m_timeoutEvent;
+  bool m_didSendData = false;
 };
 
 } // namespace peek