peek: Refactor ndnpeek and add unit tests

Change-Id: Ib34c4431bdc268c5a7ef2fccf0946b0d19d69ab6
Refs: #3558
diff --git a/tools/peek/ndn-peek.cpp b/tools/peek/ndn-peek.cpp
deleted file mode 100644
index f1d1d6f..0000000
--- a/tools/peek/ndn-peek.cpp
+++ /dev/null
@@ -1,358 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2016,  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 ndn-tools (Named Data Networking Essential Tools).
- * See AUTHORS.md for complete list of ndn-tools authors and contributors.
- *
- * ndn-tools 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.
- *
- * ndn-tools 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
- * ndn-tools, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-/**
- * Copyright (c) 2014,  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/>.
- *
- * @author Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
- */
-
-#include "core/version.hpp"
-#include "core/common.hpp"
-
-#include <ndn-cxx/util/io.hpp>
-
-namespace ndn {
-namespace peek {
-
-class NdnPeek : boost::noncopyable
-{
-public:
-  explicit
-  NdnPeek(char* programName)
-    : isVerbose(false)
-    , mustBeFresh(false)
-    , wantRightmostChild(false)
-    , wantPayloadOnly(false)
-    , m_programName(programName)
-    , m_minSuffixComponents(-1)
-    , m_maxSuffixComponents(-1)
-    , m_interestLifetime(-1)
-    , m_timeout(-1)
-    , m_prefixName("")
-    , m_didReceiveData(false)
-    , m_didReceiveNack(false)
-  {
-  }
-
-  void
-  usage(std::ostream& os ,const boost::program_options::options_description& options) const
-  {
-    os << "Usage: " << m_programName << " [options] ndn:/name\n"
-          "   Fetch one data item matching the name prefix and write it to standard output\n"
-          "\n"
-       << options;
-  }
-
-  void
-  setMinSuffixComponents(int minSuffixComponents)
-  {
-    if (minSuffixComponents < 0)
-      throw std::out_of_range("'minSuffixComponents' must be a non-negative integer");
-
-    m_minSuffixComponents = minSuffixComponents;
-  }
-
-  void
-  setMaxSuffixComponents(int maxSuffixComponents)
-  {
-    if (maxSuffixComponents < 0)
-      throw std::out_of_range("'maxSuffixComponents' must be a non-negative integer");
-
-    m_maxSuffixComponents = maxSuffixComponents;
-  }
-
-  void
-  setInterestLifetime(int interestLifetime)
-  {
-    if (interestLifetime < 0)
-      throw std::out_of_range("'lifetime' must be a non-negative integer");
-
-    m_interestLifetime = time::milliseconds(interestLifetime);
-  }
-
-  void
-  setTimeout(int timeout)
-  {
-    if (timeout < 0)
-      throw std::out_of_range("'timeout' must be a non-negative integer");
-
-    m_timeout = time::milliseconds(timeout);
-  }
-
-  void
-  setLink(const std::string& file)
-  {
-    m_link = io::load<Link>(file);
-    if (m_link == nullptr)
-      throw std::runtime_error(file + " is either nonreadable or nonparseable");
-  }
-
-  void
-  setPrefixName(const std::string& prefixName)
-  {
-    m_prefixName = prefixName;
-  }
-
-  time::milliseconds
-  getDefaultInterestLifetime()
-  {
-    return time::seconds(4);
-  }
-
-  Interest
-  createInterestPacket()
-  {
-    Name interestName(m_prefixName);
-    Interest interestPacket(interestName);
-
-    if (mustBeFresh)
-      interestPacket.setMustBeFresh(true);
-
-    if (wantRightmostChild)
-      interestPacket.setChildSelector(1);
-
-    if (m_minSuffixComponents >= 0)
-      interestPacket.setMinSuffixComponents(m_minSuffixComponents);
-
-    if (m_maxSuffixComponents >= 0)
-      interestPacket.setMaxSuffixComponents(m_maxSuffixComponents);
-
-    if (m_interestLifetime < time::milliseconds::zero())
-      interestPacket.setInterestLifetime(getDefaultInterestLifetime());
-    else
-      interestPacket.setInterestLifetime(m_interestLifetime);
-
-    if (m_link != nullptr)
-      interestPacket.setLink(m_link->wireEncode());
-
-    if (isVerbose) {
-      std::cerr << "INTEREST: " << interestPacket << std::endl;
-    }
-
-    return interestPacket;
-  }
-
-  void
-  onData(const Interest& interest, const Data& data)
-  {
-    m_didReceiveData = true;
-
-    if (isVerbose) {
-      std::cerr << "DATA, RTT: "
-                << time::duration_cast<time::milliseconds>(time::steady_clock::now() - m_expressInterestTime).count()
-                << "ms" << std::endl;
-    }
-
-    if (wantPayloadOnly) {
-      const Block& block = data.getContent();
-      std::cout.write(reinterpret_cast<const char*>(block.value()), block.value_size());
-    }
-    else {
-      const Block& block = data.wireEncode();
-      std::cout.write(reinterpret_cast<const char*>(block.wire()), block.size());
-    }
-  }
-
-  void
-  onNack(const Interest& interest, const lp::Nack& nack)
-  {
-    m_didReceiveNack = true;
-    lp::NackHeader header = nack.getHeader();
-
-    if (isVerbose) {
-      std::cerr << "NACK, RTT: "
-                << time::duration_cast<time::milliseconds>(time::steady_clock::now() - m_expressInterestTime).count()
-                << "ms" << std::endl;
-    }
-
-    if (wantPayloadOnly) {
-      std::cout << header.getReason() << std::endl;
-    }
-    else {
-      const Block& block = header.wireEncode();
-      std::cout.write(reinterpret_cast<const char*>(block.wire()), block.size());
-    }
-  }
-
-  void
-  onTimeout(const Interest& interest)
-  {
-  }
-
-  int
-  run()
-  {
-    try {
-      m_face.expressInterest(createInterestPacket(),
-                             bind(&NdnPeek::onData, this, _1, _2),
-                             bind(&NdnPeek::onNack, this, _1, _2),
-                             bind(&NdnPeek::onTimeout, this, _1));
-      m_expressInterestTime = time::steady_clock::now();
-      if (m_timeout < time::milliseconds::zero()) {
-        m_timeout = m_interestLifetime < time::milliseconds::zero() ?
-                    getDefaultInterestLifetime() : m_interestLifetime;
-      }
-      m_face.processEvents(m_timeout);
-    }
-    catch (const std::exception& e) {
-      std::cerr << "ERROR: " << e.what() << std::endl;
-      return 1;
-    }
-
-    if (m_didReceiveNack)
-      return 4;
-
-    if (isVerbose && !m_didReceiveData) {
-      std::cerr << "TIMEOUT" << std::endl;
-      return 3;
-    }
-
-    if (!m_didReceiveData)
-      return 3;
-
-    return 0;
-  }
-
-public:
-  bool isVerbose;
-  bool mustBeFresh;
-  bool wantRightmostChild;
-  bool wantPayloadOnly;
-
-private:
-  std::string m_programName;
-  int m_minSuffixComponents;
-  int m_maxSuffixComponents;
-  time::milliseconds m_interestLifetime;
-  time::milliseconds m_timeout;
-  std::string m_prefixName;
-  time::steady_clock::TimePoint m_expressInterestTime;
-  shared_ptr<Link> m_link;
-  bool m_didReceiveData;
-  bool m_didReceiveNack;
-  Face m_face;
-};
-
-int
-main(int argc, char* argv[])
-{
-  NdnPeek program(argv[0]);
-
-  namespace po = boost::program_options;
-
-  po::options_description visibleOptDesc("Allowed options");
-  visibleOptDesc.add_options()
-    ("help,h", "print help and exit")
-    ("version,V", "print version and exit")
-    ("fresh,f", po::bool_switch(&program.mustBeFresh),
-        "set MustBeFresh")
-    ("rightmost,r", po::bool_switch(&program.wantRightmostChild),
-        "set ChildSelector to rightmost")
-    ("minsuffix,m", po::value<int>()->notifier(bind(&NdnPeek::setMinSuffixComponents, &program, _1)),
-        "set MinSuffixComponents")
-    ("maxsuffix,M", po::value<int>()->notifier(bind(&NdnPeek::setMaxSuffixComponents, &program, _1)),
-        "set MaxSuffixComponents")
-    ("lifetime,l", po::value<int>()->notifier(bind(&NdnPeek::setInterestLifetime, &program, _1)),
-        "set InterestLifetime (in milliseconds)")
-    ("payload,p", po::bool_switch(&program.wantPayloadOnly),
-        "print payload only, instead of full packet")
-    ("timeout,w", po::value<int>()->notifier(bind(&NdnPeek::setTimeout, &program, _1)),
-        "set timeout (in milliseconds)")
-    ("verbose,v", po::bool_switch(&program.isVerbose),
-        "turn on verbose output")
-    ("link-file", po::value<std::string>()->notifier(bind(&NdnPeek::setLink, &program, _1)),
-        "set Link from a file")
-  ;
-
-  po::options_description hiddenOptDesc("Hidden options");
-  hiddenOptDesc.add_options()
-    ("prefix", po::value<std::string>(), "Interest name");
-
-  po::options_description optDesc("Allowed options");
-  optDesc.add(visibleOptDesc).add(hiddenOptDesc);
-
-  po::positional_options_description optPos;
-  optPos.add("prefix", -1);
-
-  try {
-    po::variables_map vm;
-    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(optPos).run(), vm);
-    po::notify(vm);
-
-    if (vm.count("help") > 0) {
-      program.usage(std::cout, visibleOptDesc);
-      return 0;
-    }
-
-    if (vm.count("version") > 0) {
-      std::cout << "ndnpeek " << tools::VERSION << std::endl;
-      return 0;
-    }
-
-    if (vm.count("prefix") > 0) {
-      std::string prefixName = vm["prefix"].as<std::string>();
-      program.setPrefixName(prefixName);
-    }
-    else {
-      throw std::runtime_error("Required argument 'prefix' is missing");
-    }
-  }
-  catch (const std::exception& e) {
-    std::cerr << "ERROR: " << e.what() << std::endl;
-    program.usage(std::cerr, visibleOptDesc);
-    return 2;
-  }
-
-  return program.run();
-}
-
-} // namespace peek
-} // namespace ndn
-
-int
-main(int argc, char** argv)
-{
-  return ndn::peek::main(argc, argv);
-}
diff --git a/tools/peek/ndnpeek/main.cpp b/tools/peek/ndnpeek/main.cpp
new file mode 100644
index 0000000..774d759
--- /dev/null
+++ b/tools/peek/ndnpeek/main.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 ndn-tools (Named Data Networking Essential Tools).
+ * See AUTHORS.md for complete list of ndn-tools authors and contributors.
+ *
+ * ndn-tools 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.
+ *
+ * ndn-tools 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
+ * ndn-tools, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
+ * @author Zhuo Li <zhuoli@email.arizona.edu>
+ */
+
+#include "ndnpeek.hpp"
+#include "core/version.hpp"
+
+#include <ndn-cxx/util/io.hpp>
+
+namespace ndn {
+namespace peek {
+
+namespace po = boost::program_options;
+
+static void
+usage(std::ostream& os, const po::options_description& options)
+{
+  os << "Usage: ndnpeek [options] ndn:/name\n"
+        "\n"
+        "Fetch one data item matching the name prefix and write it to standard output.\n"
+        "\n"
+     << options;
+}
+
+static int
+main(int argc, char* argv[])
+{
+  PeekOptions options;
+  options.isVerbose = false;
+  options.mustBeFresh = false;
+  options.wantRightmostChild = false;
+  options.wantPayloadOnly = false;
+  options.minSuffixComponents = -1;
+  options.maxSuffixComponents = -1;
+  options.interestLifetime = time::milliseconds(-1);
+  options.timeout = time::milliseconds(-1);
+
+  po::options_description genericOptDesc("Generic options");
+  genericOptDesc.add_options()
+    ("help,h", "print help and exit")
+    ("payload,p", po::bool_switch(&options.wantPayloadOnly),
+        "print payload only, instead of full packet")
+    ("timeout,w", po::value<int>(),
+        "set timeout (in milliseconds)")
+    ("verbose,v", po::bool_switch(&options.isVerbose),
+        "turn on verbose output")
+    ("version,V", "print version and exit")
+  ;
+
+  po::options_description interestOptDesc("Interest construction");
+  interestOptDesc.add_options()
+    ("fresh,f", po::bool_switch(&options.mustBeFresh),
+        "set MustBeFresh")
+    ("rightmost,r", po::bool_switch(&options.wantRightmostChild),
+        "set ChildSelector to rightmost")
+    ("minsuffix,m", po::value<int>(&options.minSuffixComponents),
+        "set MinSuffixComponents")
+    ("maxsuffix,M", po::value<int>(&options.maxSuffixComponents),
+        "set MaxSuffixComponents")
+    ("lifetime,l", po::value<int>(),
+        "set InterestLifetime (in milliseconds)")
+    ("link-file", po::value<std::string>(),
+        "set Link from a file")
+  ;
+
+  po::options_description visibleOptDesc;
+  visibleOptDesc.add(genericOptDesc).add(interestOptDesc);
+
+  po::options_description hiddenOptDesc;
+  hiddenOptDesc.add_options()
+    ("prefix", po::value<std::string>(), "Interest name");
+
+  po::options_description optDesc;
+  optDesc.add(visibleOptDesc).add(hiddenOptDesc);
+
+  po::positional_options_description optPos;
+  optPos.add("prefix", -1);
+
+  po::variables_map vm;
+  try {
+    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(optPos).run(), vm);
+    po::notify(vm);
+  }
+  catch (const po::error& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+
+  if (vm.count("help") > 0) {
+    usage(std::cout, visibleOptDesc);
+    return 0;
+  }
+
+  if (vm.count("version") > 0) {
+    std::cout << "ndnpeek " << tools::VERSION << std::endl;
+    return 0;
+  }
+
+  if (vm.count("prefix") > 0) {
+    options.prefix = vm["prefix"].as<std::string>();
+  }
+  else {
+    std::cerr << "ERROR: Interest name is missing" << std::endl;
+    usage(std::cerr, visibleOptDesc);
+    return 2;
+  }
+
+  if (vm.count("minsuffix") > 0 && options.minSuffixComponents < 0) {
+    std::cerr << "ERROR: MinSuffixComponents must be a non-negative integer" << std::endl;
+    usage(std::cerr, visibleOptDesc);
+    return 2;
+  }
+
+  if (vm.count("maxsuffix") > 0 && options.maxSuffixComponents < 0) {
+    std::cerr << "ERROR: MaxSuffixComponents must be a non-negative integer" << std::endl;
+    usage(std::cerr, visibleOptDesc);
+    return 2;
+  }
+
+  if (vm.count("lifetime") > 0) {
+    if (vm["lifetime"].as<int>() >= 0) {
+      options.interestLifetime = time::milliseconds(vm["lifetime"].as<int>());
+    }
+    else {
+      std::cerr << "ERROR: InterestLifetime 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) {
+      options.timeout = time::milliseconds(vm["timeout"].as<int>());
+    }
+    else {
+      std::cerr << "ERROR: Timeout must be a positive integer" << std::endl;
+      usage(std::cerr, visibleOptDesc);
+      return 2;
+    }
+  }
+
+  if (vm.count("link-file") > 0) {
+    options.link = io::load<Link>(vm["link-file"].as<std::string>());
+    if (options.link == nullptr) {
+      std::cerr << "ERROR: Cannot read Link object from the specified file" << std::endl;
+      usage(std::cerr, visibleOptDesc);
+      return 2;
+    }
+  }
+
+  Face face;
+  NdnPeek program(face, options);
+
+  try {
+    program.start();
+    face.processEvents(program.getTimeout());
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 1;
+  }
+
+  ResultCode result = program.getResultCode();
+  if (result == ResultCode::TIMEOUT && options.isVerbose) {
+    std::cerr << "TIMEOUT" << std::endl;
+  }
+  return static_cast<int>(result);
+}
+
+} // namespace peek
+} // namespace ndn
+
+int
+main(int argc, char** argv)
+{
+  return ndn::peek::main(argc, argv);
+}
diff --git a/tools/peek/ndnpeek/ndnpeek.cpp b/tools/peek/ndnpeek/ndnpeek.cpp
new file mode 100644
index 0000000..c9f89b2
--- /dev/null
+++ b/tools/peek/ndnpeek/ndnpeek.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 ndn-tools (Named Data Networking Essential Tools).
+ * See AUTHORS.md for complete list of ndn-tools authors and contributors.
+ *
+ * ndn-tools 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.
+ *
+ * ndn-tools 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
+ * ndn-tools, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
+ * @author Zhuo Li <zhuoli@email.arizona.edu>
+ */
+
+#include "ndnpeek.hpp"
+
+namespace ndn {
+namespace peek {
+
+NdnPeek::NdnPeek(Face& face, const PeekOptions& options)
+  : m_face(face)
+  , m_options(options)
+  , m_timeout(options.timeout)
+  , m_resultCode(ResultCode::TIMEOUT)
+{
+  if (m_timeout < time::milliseconds::zero()) {
+    m_timeout = m_options.interestLifetime < time::milliseconds::zero() ?
+                DEFAULT_INTEREST_LIFETIME : m_options.interestLifetime;
+  }
+}
+
+time::milliseconds
+NdnPeek::getTimeout() const
+{
+  return m_timeout;
+}
+
+ResultCode
+NdnPeek::getResultCode() const
+{
+  return m_resultCode;
+}
+
+void
+NdnPeek::start()
+{
+  m_face.expressInterest(createInterest(),
+                         bind(&NdnPeek::onData, this, _2),
+                         bind(&NdnPeek::onNack, this, _2),
+                         nullptr);
+  m_expressInterestTime = time::steady_clock::now();
+}
+
+Interest
+NdnPeek::createInterest() const
+{
+  Interest interest(m_options.prefix);
+
+  if (m_options.minSuffixComponents >= 0)
+    interest.setMinSuffixComponents(m_options.minSuffixComponents);
+
+  if (m_options.maxSuffixComponents >= 0)
+    interest.setMaxSuffixComponents(m_options.maxSuffixComponents);
+
+  if (m_options.interestLifetime >= time::milliseconds::zero())
+    interest.setInterestLifetime(m_options.interestLifetime);
+
+  if (m_options.link != nullptr)
+    interest.setLink(m_options.link->wireEncode());
+
+  if (m_options.mustBeFresh)
+    interest.setMustBeFresh(true);
+
+  if (m_options.wantRightmostChild)
+    interest.setChildSelector(1);
+
+  if (m_options.isVerbose) {
+    std::cerr << "INTEREST: " << interest << std::endl;
+  }
+
+  return interest;
+}
+
+void
+NdnPeek::onData(const Data& data)
+{
+  m_resultCode = ResultCode::DATA;
+
+  if (m_options.isVerbose) {
+    std::cerr << "DATA, RTT: "
+              << time::duration_cast<time::milliseconds>(time::steady_clock::now() - m_expressInterestTime).count()
+              << "ms" << std::endl;
+  }
+
+  if (m_options.wantPayloadOnly) {
+    const Block& block = data.getContent();
+    std::cout.write(reinterpret_cast<const char*>(block.value()), block.value_size());
+  }
+  else {
+    const Block& block = data.wireEncode();
+    std::cout.write(reinterpret_cast<const char*>(block.wire()), block.size());
+  }
+}
+
+void
+NdnPeek::onNack(const lp::Nack& nack)
+{
+  m_resultCode = ResultCode::NACK;
+  lp::NackHeader header = nack.getHeader();
+
+  if (m_options.isVerbose) {
+    std::cerr << "NACK, RTT: "
+              << time::duration_cast<time::milliseconds>(time::steady_clock::now() - m_expressInterestTime).count()
+              << "ms" << std::endl;
+  }
+
+  if (m_options.wantPayloadOnly) {
+    std::cout << header.getReason() << std::endl;
+  }
+  else {
+    const Block& block = header.wireEncode();
+    std::cout.write(reinterpret_cast<const char*>(block.wire()), block.size());
+  }
+}
+
+} // namespace peek
+} // namespace ndn
diff --git a/tools/peek/ndnpeek/ndnpeek.hpp b/tools/peek/ndnpeek/ndnpeek.hpp
new file mode 100644
index 0000000..2b0a85e
--- /dev/null
+++ b/tools/peek/ndnpeek/ndnpeek.hpp
@@ -0,0 +1,112 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 ndn-tools (Named Data Networking Essential Tools).
+ * See AUTHORS.md for complete list of ndn-tools authors and contributors.
+ *
+ * ndn-tools 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.
+ *
+ * ndn-tools 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
+ * ndn-tools, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
+ * @author Zhuo Li <zhuoli@email.arizona.edu>
+ */
+
+#ifndef NDN_TOOLS_NDNPEEK_NDNPEEK_HPP
+#define NDN_TOOLS_NDNPEEK_NDNPEEK_HPP
+
+#include "core/common.hpp"
+
+namespace ndn {
+namespace peek {
+
+/**
+ * @brief options for NdnPeek
+ */
+struct PeekOptions
+{
+  std::string prefix;
+  int minSuffixComponents;
+  int maxSuffixComponents;
+  time::milliseconds interestLifetime;
+  time::milliseconds timeout;
+  shared_ptr<Link> link;
+  bool isVerbose;
+  bool mustBeFresh;
+  bool wantRightmostChild;
+  bool wantPayloadOnly;
+};
+
+enum class ResultCode {
+  NONE = -1,
+  DATA = 0,
+  NACK = 4,
+  TIMEOUT = 3
+};
+
+class NdnPeek : boost::noncopyable
+{
+public:
+  NdnPeek(Face& face, const PeekOptions& options);
+
+  /**
+   * @return the timeout
+   */
+  time::milliseconds
+  getTimeout() const;
+
+  /**
+   * @return the result of Peek execution
+   */
+  ResultCode
+  getResultCode() const;
+
+  /**
+   * @brief express the Interest
+   * @note The caller must invoke face.processEvents() afterwards
+   */
+  void
+  start();
+
+private:
+  Interest
+  createInterest() const;
+
+  /**
+   * @brief called when a Data packet is received
+   */
+  void
+  onData(const Data& data);
+
+  /**
+   * @brief called when a Nack packet is received
+   */
+  void
+  onNack(const lp::Nack& nack);
+
+private:
+  Face& m_face;
+  const PeekOptions& m_options;
+  time::steady_clock::TimePoint m_expressInterestTime;
+  time::milliseconds m_timeout;
+  ResultCode m_resultCode;
+};
+
+} // namespace peek
+} // namespace ndn
+
+#endif // NDN_TOOLS_NDNPEEK_NDNPEEK_HPP
diff --git a/tools/peek/wscript b/tools/peek/wscript
index 45679a2..f701269 100644
--- a/tools/peek/wscript
+++ b/tools/peek/wscript
@@ -2,14 +2,24 @@
 top = '../..'
 
 def build(bld):
-    bld.program(
-        features='cxx',
-        target='../../bin/ndnpeek',
-        source='ndn-peek.cpp',
+
+    bld(features='cxx',
+        name='peek-ndnpeek-objects',
+        source=bld.path.ant_glob('ndnpeek/*.cpp', excl='ndnpeek/main.cpp'),
         use='core-objects')
 
+    bld(features='cxx cxxprogram',
+        target='../../bin/ndnpeek',
+        source='ndnpeek/main.cpp',
+        use='peek-ndnpeek-objects')
+
     bld.program(
         features='cxx',
         target='../../bin/ndnpoke',
         source='ndn-poke.cpp',
         use='core-objects')
+
+    ## (for unit tests)
+
+    bld(name='peek-objects',
+        use='peek-ndnpeek-objects')