chunks: segmented file transfer

refs #3071

Change-Id: I88e4fc1a8e33a0d61a95e2291cccc7b998647489
diff --git a/AUTHORS.md b/AUTHORS.md
index a4e6eec..93c29e2 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -16,3 +16,6 @@
 * Qi Zhao               <https://www.linkedin.com/pub/qi-zhao/73/835/9a3>
 * Seunghyun Yoo         <http://relue2718.com/>
 * Seungbae Kim          <https://sites.google.com/site/sbkimcv/>
+* Wentao Shang          <http://irl.cs.ucla.edu/~wentao/>
+* Steve DiBenedetto     <https://dibenede.github.io>
+* Andrea Tosatto        <https://linkedin.com/in/tosattoandrea>
diff --git a/README.md b/README.md
index eda9d2b..24de965 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
 Tools in this collection include:
 
 * [peek](tools/peek): transmit a single packet between a consumer and a producer
+* [chunks](tools/chunks): segmented file transfer between a consumer and producer
 * [ping](tools/ping): test reachability between two nodes
 * [dump](tools/dump): analyze traffic on wire
 * [dissect](tools/dissect): inspect TLV structure of NDN packet format
diff --git a/core/common.hpp b/core/common.hpp
index 09d378c..01a30c4 100644
--- a/core/common.hpp
+++ b/core/common.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2015,  Regents of the University of California,
+ * Copyright (c) 2014-2016,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -63,6 +63,7 @@
 #include <ndn-cxx/security/key-chain.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
 #include <ndn-cxx/security/signing-info.hpp>
+#include <ndn-cxx/util/backports.hpp>
 #include <ndn-cxx/util/scheduler.hpp>
 #include <ndn-cxx/util/scheduler-scoped-event-id.hpp>
 #include <ndn-cxx/util/signal.hpp>
diff --git a/tests/chunks/consumer.t.cpp b/tests/chunks/consumer.t.cpp
new file mode 100644
index 0000000..0ce7290
--- /dev/null
+++ b/tests/chunks/consumer.t.cpp
@@ -0,0 +1,142 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "tools/chunks/catchunks/consumer.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/security/validator-null.hpp>
+
+#include <boost/test/output_test_stream.hpp>
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_AUTO_TEST_SUITE(TestConsumer)
+
+BOOST_AUTO_TEST_CASE(OutputDataSequential)
+{
+  // Test sequential segments in the right order
+  // Segment order: 0 1 2
+
+  std::string name("/ndn/chunks/test");
+
+  std::vector<std::string> testStrings {
+      "",
+
+      "a1b2c3%^&(#$&%^$$/><",
+
+      "123456789123456789123456789123456789123456789123456789123456789"
+      "123456789123456789123456789123456789123456789123456789123456789",
+
+      "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. "
+      "Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+      "ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
+      "consequat massa Donec pede justo,"
+  };
+
+  util::DummyClientFace face;
+  ValidatorNull validator;
+  output_test_stream output("");
+  Consumer cons(face, validator, false, output);
+
+  auto interest = makeInterest(name);
+
+  for (size_t i = 0; i < testStrings.size(); ++i) {
+    output.flush();
+
+    auto data = makeData(Name(name).appendVersion(1).appendSegment(i));
+    data->setContent(reinterpret_cast<const uint8_t*>(testStrings[i].data()),
+                     testStrings[i].size());
+
+    cons.m_bufferedData[i] = data;
+    cons.writeInOrderData();
+
+    BOOST_CHECK(output.is_equal(testStrings[i]));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(OutputDataUnordered)
+{
+  // Test unordered segments
+  // Segment order: 1 0 2
+
+  std::string name("/ndn/chunks/test");
+
+  std::vector<std::string> testStrings {
+      "a1b2c3%^&(#$&%^$$/><",
+
+      "123456789123456789123456789123456789123456789123456789123456789"
+      "123456789123456789123456789123456789123456789123456789123456789",
+
+      "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. "
+      "Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+      "ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
+      "consequat massa Donec pede justo,"
+  };;
+
+  util::DummyClientFace face;
+  ValidatorNull validator;
+  output_test_stream output("");
+  Consumer cons(face, validator, false, output);
+
+  auto interest = makeInterest(name);
+  std::vector<shared_ptr<Data>> dataStore;
+
+  for (size_t i = 0; i < testStrings.size(); ++i) {
+    auto data = makeData(Name(name).appendVersion(1).appendSegment(i));
+    data->setContent(reinterpret_cast<const uint8_t*>(testStrings[i].data()),
+                     testStrings[i].size());
+
+    dataStore.push_back(data);
+  }
+
+  output.flush();
+  cons.m_bufferedData[1] = dataStore[1];
+  cons.writeInOrderData();
+  BOOST_CHECK(output.is_equal(""));
+
+  output.flush();
+  cons.m_bufferedData[0] = dataStore[0];
+  cons.writeInOrderData();
+  BOOST_CHECK(output.is_equal(testStrings[0] + testStrings[1]));
+
+  output.flush();
+  cons.m_bufferedData[2] = dataStore[2];
+  cons.writeInOrderData();
+  BOOST_CHECK(output.is_equal(testStrings[2]));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestConsumer
+BOOST_AUTO_TEST_SUITE_END() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
diff --git a/tests/chunks/discover-version-fixed.t.cpp b/tests/chunks/discover-version-fixed.t.cpp
new file mode 100644
index 0000000..a57e5c8
--- /dev/null
+++ b/tests/chunks/discover-version-fixed.t.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "tools/chunks/catchunks/discover-version-fixed.hpp"
+
+#include "discover-version-fixture.hpp"
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+
+class DiscoverVersionFixedFixture : public DiscoverVersionFixture
+{
+public:
+  DiscoverVersionFixedFixture()
+    : DiscoverVersionFixture(makeOptions())
+    , version(1449227841747)
+  {
+    setDiscover(make_unique<DiscoverVersionFixed>(Name(name).appendVersion(version),
+                                                  face, makeOptions()));
+  }
+
+protected:
+  uint64_t version; //Version to find
+};
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_AUTO_TEST_SUITE(TestDiscoverVersionFixed)
+
+BOOST_FIXTURE_TEST_CASE(RequestedVersionAvailable, DiscoverVersionFixedFixture)
+{
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  face.receive(*makeDataWithVersion(version));
+
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredVersion, version);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  auto lastInterest = face.sentInterests.back();
+  BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+  BOOST_CHECK_EQUAL(lastInterest.getMaxSuffixComponents(), 2);
+  BOOST_CHECK_EQUAL(lastInterest.getMinSuffixComponents(), 2);
+  BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+}
+
+BOOST_FIXTURE_TEST_CASE(NoVersionsAvailable, DiscoverVersionFixedFixture)
+{
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  for (int retries = 0; retries < maxRetriesOnTimeoutOrNack; ++retries) {
+    advanceClocks(io, interestLifetime, 1);
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2 + retries);
+  }
+
+  for (const auto& lastInterest : face.sentInterests) {
+    BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+    BOOST_CHECK_EQUAL(lastInterest.getMaxSuffixComponents(), 2);
+    BOOST_CHECK_EQUAL(lastInterest.getMinSuffixComponents(), 2);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(Name(name).appendVersion(version)), true);
+  }
+
+  advanceClocks(io, interestLifetime, 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  // check if discovered version is the default value
+  BOOST_CHECK_EQUAL(discoveredVersion, 0);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), maxRetriesOnTimeoutOrNack + 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(DataNotSegment, DiscoverVersionFixedFixture)
+{
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  std::vector<std::string> randomStrings {
+      "",
+      "abcdefg",
+      "12345",
+      "qr%67a3%4e"
+  };
+
+  Exclude expectedExclude;
+  for (size_t retries = 0; retries < randomStrings.size(); ++retries) {
+    auto data = make_shared<Data>(Name(name).appendVersion(version).append(randomStrings[retries]));
+    data->setFinalBlockId(name::Component::fromSegment(0));
+    data = signData(data);
+
+    face.receive(*data);
+    advanceClocks(io, time::nanoseconds(1), 1);
+
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1 + retries);
+    auto lastInterest = face.sentInterests.back();
+    if (randomStrings[retries] != "")
+      expectedExclude.excludeOne(name::Component::fromEscapedString(randomStrings[retries]));
+    BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+    BOOST_CHECK_EQUAL(lastInterest.getMaxSuffixComponents(), 2);
+    BOOST_CHECK_EQUAL(lastInterest.getMinSuffixComponents(), 2);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+  }
+
+  advanceClocks(io, interestLifetime, maxRetriesOnTimeoutOrNack + 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  // check if discovered version is the default value
+  BOOST_CHECK_EQUAL(discoveredVersion, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDiscoverVersionFixed
+BOOST_AUTO_TEST_SUITE_END() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
diff --git a/tests/chunks/discover-version-fixture.hpp b/tests/chunks/discover-version-fixture.hpp
new file mode 100644
index 0000000..9009301
--- /dev/null
+++ b/tests/chunks/discover-version-fixture.hpp
@@ -0,0 +1,104 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_TESTS_CHUNKS_DISCOVER_VERSION_FIXTURE_HPP
+#define NDN_TOOLS_TESTS_CHUNKS_DISCOVER_VERSION_FIXTURE_HPP
+
+#include "tools/chunks/catchunks/discover-version.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+class DiscoverVersionFixture : public ndn::tests::UnitTestTimeFixture, virtual protected Options
+{
+public:
+  DiscoverVersionFixture(const Options& options)
+    : Options(options)
+    , face(io)
+    , name("/ndn/chunks/test")
+    , isDiscoveryFinished(false)
+    , discoveredVersion(0)
+  {
+  }
+
+protected:
+  void setDiscover(unique_ptr<DiscoverVersion> disc)
+  {
+    discover = std::move(disc);
+    discover->onDiscoverySuccess.connect(bind(&DiscoverVersionFixture::onSuccess, this, _1));
+    discover->onDiscoveryFailure.connect(bind(&DiscoverVersionFixture::onFailure, this, _1));
+  }
+
+  shared_ptr<Data>
+  makeDataWithVersion(uint64_t version)
+  {
+    auto data = make_shared<Data>(Name(name).appendVersion(version).appendSegment(0));
+    data->setFinalBlockId(name::Component::fromSegment(0));
+    return ndn::tests::signData(data);
+  }
+
+  static Options
+  makeOptions()
+  {
+    Options options;
+    options.isVerbose = false;
+    options.interestLifetime = time::seconds(1);
+    options.maxRetriesOnTimeoutOrNack = 3;
+    return options;
+  }
+
+  virtual void
+  onSuccess(const Data& data)
+  {
+    isDiscoveryFinished = true;
+
+    if (data.getName()[name.size()].isVersion())
+      discoveredVersion = data.getName()[name.size()].toVersion();
+  }
+
+  virtual void
+  onFailure(const std::string& reason)
+  {
+    isDiscoveryFinished = true;
+  }
+
+protected:
+  boost::asio::io_service io;
+  util::DummyClientFace face;
+  Name name;
+  unique_ptr<DiscoverVersion> discover;
+  bool isDiscoveryFinished;
+  uint64_t discoveredVersion;
+};
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_TESTS_CHUNKS_DISCOVER_VERSION_FIXTURE_HPP
diff --git a/tests/chunks/discover-version-iterative.t.cpp b/tests/chunks/discover-version-iterative.t.cpp
new file mode 100644
index 0000000..233f36c
--- /dev/null
+++ b/tests/chunks/discover-version-iterative.t.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "tools/chunks/catchunks/discover-version-iterative.hpp"
+
+#include "discover-version-fixture.hpp"
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+
+class DiscoverVersionIterativeFixture : public DiscoverVersionFixture,
+                                        protected DiscoverVersionIterativeOptions
+{
+public:
+  typedef DiscoverVersionIterativeOptions Options;
+
+public:
+  explicit
+  DiscoverVersionIterativeFixture(const Options& opt = makeOptionsIterative())
+    : chunks::Options(opt)
+    , DiscoverVersionFixture(opt)
+    , Options(opt)
+  {
+    setDiscover(make_unique<DiscoverVersionIterative>(Name(name), face, opt));
+  }
+
+protected:
+  static Options
+  makeOptionsIterative()
+  {
+    Options options;
+    options.isVerbose = false;
+    options.maxRetriesOnTimeoutOrNack = 3;
+    options.maxRetriesAfterVersionFound = 1;
+    return options;
+  }
+};
+
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_AUTO_TEST_SUITE(TestDiscoverVersionIterative)
+
+BOOST_FIXTURE_TEST_CASE(SingleVersionAvailable, DiscoverVersionIterativeFixture)
+{
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  uint64_t version = 1449241767037;
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  auto lastInterest = face.sentInterests.back();
+  BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+  BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+  BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+  BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+
+  // Send first segment for the right version
+  face.receive(*makeDataWithVersion(version));
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2);
+  lastInterest = face.sentInterests.back();
+  Exclude expectedExclude;
+  expectedExclude.excludeBefore(name::Component::fromVersion(version));
+  BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+  BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+  BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+  BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+
+  // Generate the timeout
+  for (int retries = 0; retries < maxRetriesAfterVersionFound; ++retries) {
+    advanceClocks(io, interestLifetime, 1);
+
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), retries + 3);
+    lastInterest = face.sentInterests.back();
+    Exclude expectedExclude;
+    expectedExclude.excludeBefore(name::Component::fromVersion(version));
+    BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+    BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+  }
+
+  advanceClocks(io, interestLifetime, 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredVersion, version);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), maxRetriesAfterVersionFound + 2);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(NoVersionsAvailable, DiscoverVersionIterativeFixture)
+{
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  for (int retries = 0; retries < maxRetriesOnTimeoutOrNack; ++retries) {
+    advanceClocks(io, interestLifetime, 1);
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2 + retries);
+  }
+
+  for (auto& lastInterest : face.sentInterests) {
+    BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+    BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+  }
+
+  advanceClocks(io, interestLifetime, 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), maxRetriesOnTimeoutOrNack + 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(MultipleVersionsAvailable, DiscoverVersionIterativeFixture)
+{
+  // nVersions must be positive
+  const uint64_t nVersions = 5;
+
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  auto lastInterest = face.sentInterests.back();
+  BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+  BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+  BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+  BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+
+  for (uint64_t nSentVersions = 0; nSentVersions < nVersions; ++nSentVersions) {
+    face.receive(*makeDataWithVersion(nSentVersions));
+    advanceClocks(io, time::nanoseconds(1), 1);
+
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2 + nSentVersions);
+    lastInterest = face.sentInterests.back();
+    Exclude expectedExclude;
+    expectedExclude.excludeBefore(name::Component::fromVersion(nSentVersions));
+    BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+    BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+  }
+
+  for (int retries = 0; retries < maxRetriesAfterVersionFound; ++retries) {
+    advanceClocks(io, interestLifetime, 1);
+
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2 + retries + nVersions);
+    lastInterest = face.sentInterests.back();
+    Exclude expectedExclude;
+    expectedExclude.excludeBefore(name::Component::fromVersion(nVersions - 1));
+    BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+    BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+  }
+
+  advanceClocks(io, interestLifetime, 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredVersion, nVersions - 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), nVersions + maxRetriesAfterVersionFound + 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(MultipleVersionsAvailableDescendent, DiscoverVersionIterativeFixture)
+{
+  // nVersions must be positive
+  const uint64_t nVersions = 5;
+
+  discover->run();
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  auto lastInterest = face.sentInterests.back();
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+  BOOST_CHECK_EQUAL(lastInterest.getExclude().size(), 0);
+  BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+  BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+  BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+
+  for (uint64_t nVersionsToSend = nVersions; nVersionsToSend > 0; --nVersionsToSend) {
+    face.receive(*makeDataWithVersion(nVersionsToSend - 1));
+    advanceClocks(io, time::nanoseconds(1), 1);
+
+    BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2);
+    lastInterest = face.sentInterests.back();
+    Exclude expectedExclude;
+    expectedExclude.excludeBefore(name::Component::fromVersion(nVersions - 1));
+    BOOST_CHECK_EQUAL(lastInterest.getExclude(), expectedExclude);
+    BOOST_CHECK_EQUAL(lastInterest.getChildSelector(), 1);
+    BOOST_CHECK_EQUAL(lastInterest.getMustBeFresh(), mustBeFresh);
+    BOOST_CHECK_EQUAL(lastInterest.getName().equals(name), true);
+  }
+
+  advanceClocks(io, interestLifetime, maxRetriesAfterVersionFound + 1);
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredVersion, nVersions - 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), maxRetriesAfterVersionFound + 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDiscoverVersionIterative
+BOOST_AUTO_TEST_SUITE_END() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
diff --git a/tests/chunks/pipeline-interests.t.cpp b/tests/chunks/pipeline-interests.t.cpp
new file mode 100644
index 0000000..98abfa4
--- /dev/null
+++ b/tests/chunks/pipeline-interests.t.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "tools/chunks/catchunks/pipeline-interests.hpp"
+#include "tools/chunks/catchunks/data-fetcher.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+
+class PipelineInterestsFixture : public UnitTestTimeFixture
+{
+public:
+  typedef PipelineInterestsOptions Options;
+
+public:
+  PipelineInterestsFixture()
+    : face(io)
+    , opt(makeOptions())
+    , name("/ndn/chunks/test")
+    , pipeline(face, opt)
+    , nDataSegments(0)
+    , nReceivedSegments(0)
+    , hasFailed(false)
+  {
+  }
+
+protected:
+  shared_ptr<Data>
+  makeDataWithSegment(uint64_t segmentNo, bool setFinalBlockId = true)
+  {
+    auto data = make_shared<Data>(Name(name).appendVersion(0).appendSegment(segmentNo));
+    if (setFinalBlockId)
+      data->setFinalBlockId(name::Component::fromSegment(nDataSegments - 1));
+    return signData(data);
+  }
+
+  void
+  runWithData(const Data& data)
+  {
+    pipeline.runWithExcludedSegment(data,
+                                    bind(&PipelineInterestsFixture::onData, this, _1, _2),
+                                    bind(&PipelineInterestsFixture::onFailure, this, _1));
+  }
+
+private:
+  void
+  onData(const Interest& interest, const Data& data)
+  {
+    nReceivedSegments++;
+  }
+
+  void
+  onFailure(const std::string& reason)
+  {
+    hasFailed = true;
+  }
+
+  static Options
+  makeOptions()
+  {
+    Options options;
+    options.isVerbose = false;
+    options.interestLifetime = time::seconds(1);
+    options.maxRetriesOnTimeoutOrNack = 3;
+    options.maxPipelineSize = 5;
+    return options;
+  }
+
+protected:
+  boost::asio::io_service io;
+  util::DummyClientFace face;
+  Options opt;
+  Name name;
+  PipelineInterests pipeline;
+  uint64_t nDataSegments;
+  uint64_t nReceivedSegments;
+  bool hasFailed;
+};
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_AUTO_TEST_SUITE(TestPipelineInterests)
+
+BOOST_FIXTURE_TEST_CASE(FewerSegmentsThanPipelineCapacity, PipelineInterestsFixture)
+{
+  nDataSegments = 3;
+  BOOST_ASSERT(nDataSegments <= opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(0));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), nDataSegments - 1);
+
+  for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1), 1);
+
+    BOOST_CHECK_EQUAL(nReceivedSegments, i);
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), nDataSegments - 1);
+    // check if the interest for the segment i+1 is well formed
+    auto sentInterest = face.sentInterests[i];
+    BOOST_CHECK_EQUAL(sentInterest.getExclude().size(), 0);
+    BOOST_CHECK_EQUAL(sentInterest.getMaxSuffixComponents(), 1);
+    BOOST_CHECK_EQUAL(sentInterest.getMustBeFresh(), opt.mustBeFresh);
+    BOOST_CHECK_EQUAL(Name(name).isPrefixOf(sentInterest.getName()), true);
+    BOOST_CHECK_EQUAL(sentInterest.getName()[-1].toSegment(), i + 1);
+  }
+
+  BOOST_CHECK_EQUAL(hasFailed, false);
+
+  advanceClocks(io, ndn::DEFAULT_INTEREST_LIFETIME, opt.maxRetriesOnTimeoutOrNack + 1);
+  BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_FIXTURE_TEST_CASE(FullPipeline, PipelineInterestsFixture)
+{
+  nDataSegments = 13;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1), 1);
+    BOOST_CHECK_EQUAL(nReceivedSegments, i + 1);
+
+    if (i < nDataSegments - opt.maxPipelineSize - 1) {
+      BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize + i + 1);
+      // check if the interest for the segment i+1 is well formed
+      auto sentInterest = face.sentInterests[i];
+      BOOST_CHECK_EQUAL(sentInterest.getExclude().size(), 0);
+      BOOST_CHECK_EQUAL(sentInterest.getMaxSuffixComponents(), 1);
+      BOOST_CHECK_EQUAL(sentInterest.getMustBeFresh(), opt.mustBeFresh);
+      BOOST_CHECK_EQUAL(Name(name).isPrefixOf(sentInterest.getName()), true);
+      BOOST_CHECK_EQUAL(sentInterest.getName()[-1].toSegment(), i);
+    }
+    else {
+      // all the interests have been sent for all the segments
+      BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments - 1);
+    }
+  }
+
+  BOOST_CHECK_EQUAL(hasFailed, false);
+}
+
+BOOST_FIXTURE_TEST_CASE(TimeoutAllSegments, PipelineInterestsFixture)
+{
+  nDataSegments = 13;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  for (int i = 0; i < opt.maxRetriesOnTimeoutOrNack; ++i) {
+    advanceClocks(io, opt.interestLifetime, 1);
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * (i + 2));
+    BOOST_CHECK_EQUAL(nReceivedSegments, 0);
+
+    // A single retry for every pipeline element
+    for (size_t j = 0; j < opt.maxPipelineSize; ++j) {
+      auto interest = face.sentInterests[(opt.maxPipelineSize * (i + 1)) + j];
+      BOOST_CHECK_EQUAL(static_cast<size_t>(interest.getName()[-1].toSegment()), j);
+    }
+  }
+
+  advanceClocks(io, opt.interestLifetime, 1);
+  BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_FIXTURE_TEST_CASE(TimeoutAfterFinalBlockIdReceived, PipelineInterestsFixture)
+{
+  // the FinalBlockId is sent with the first segment, after the first segment failure the pipeline
+  // should fail
+
+  nDataSegments = 18;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  // send a single segment for each pipeline element but not the first element
+  advanceClocks(io, opt.interestLifetime, 1);
+  for (uint64_t i = 1; i < opt.maxPipelineSize; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1), 1);
+  }
+
+  // send a single data packet for each pipeline element
+  advanceClocks(io, opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack - 1);
+  for (uint64_t i = 0; i < opt.maxPipelineSize - 1; ++i) {
+    face.receive(*makeDataWithSegment(opt.maxPipelineSize + i));
+    advanceClocks(io, time::nanoseconds(1), 1);
+  }
+  advanceClocks(io, opt.interestLifetime, 1);
+
+  size_t interestAfterFailure = face.sentInterests.size();
+
+  BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+  BOOST_CHECK_EQUAL(hasFailed, true);
+
+  // these new segments should not generate new interests
+  advanceClocks(io, opt.interestLifetime, 1);
+  for (uint64_t i = 0; i < opt.maxPipelineSize - 1; ++i) {
+    face.receive(*makeDataWithSegment(opt.maxPipelineSize * 2 + i - 1));
+    advanceClocks(io, time::nanoseconds(1), 1);
+  }
+
+  // no more interests after a failure
+  advanceClocks(io, opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack);
+  BOOST_CHECK_EQUAL(interestAfterFailure, face.sentInterests.size());
+  BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(TimeoutBeforeFinalBlockIdReceived, PipelineInterestsFixture)
+{
+  // the FinalBlockId is sent only with the last segment, all segments are sent except for the
+  // second one (segment #1); all segments are received correctly until the FinalBlockId is received
+
+  nDataSegments = 22;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1, false));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  advanceClocks(io, opt.interestLifetime, 1);
+  for (uint64_t i = 2; i < opt.maxPipelineSize; ++i) {
+    face.receive(*makeDataWithSegment(i, false));
+    advanceClocks(io, time::nanoseconds(1), 1);
+
+    auto lastInterest = face.sentInterests.back();
+    BOOST_CHECK_EQUAL(lastInterest.getName()[-1].toSegment(), opt.maxPipelineSize + i - 2);
+  }
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * 3 - 2);
+
+  // nack for the first pipeline element (segment #0)
+  auto nack = make_shared<lp::Nack>(face.sentInterests[opt.maxPipelineSize]);
+  nack->setReason(lp::NackReason::DUPLICATE);
+  face.receive(*nack);
+
+  // all the pipeline elements are two retries near the timeout error, but not the
+  // second (segment #1) that is only one retry near the timeout
+  advanceClocks(io, opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack - 1);
+  BOOST_CHECK_EQUAL(hasFailed, false);
+
+  // data for the first pipeline element (segment #0)
+  face.receive(*makeDataWithSegment(0, false));
+  BOOST_CHECK_EQUAL(hasFailed, false);
+
+  // data for all the pipeline element, but not the second (segment #1)
+  for (uint64_t i = opt.maxPipelineSize; i < nDataSegments - 1; ++i) {
+    if (i == nDataSegments - 2) {
+      face.receive(*makeDataWithSegment(i, true));
+    }
+    else {
+      face.receive(*makeDataWithSegment(i, false));
+    }
+    advanceClocks(io, time::nanoseconds(1), 1);
+  }
+  // timeout for the second pipeline element (segment #1), this should trigger an error
+  advanceClocks(io, opt.interestLifetime, 1);
+
+  BOOST_CHECK_EQUAL(nReceivedSegments, nDataSegments - 2);
+  BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_FIXTURE_TEST_CASE(SegmentReceivedAfterTimeout, PipelineInterestsFixture)
+{
+  // the FinalBlockId is never sent, all the pipeline elements with a segment number greater than
+  // segment #0 will fail, after this failure also segment #0 fail and this should trigger an error
+
+  nDataSegments = 22;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1, false));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  advanceClocks(io, opt.interestLifetime, 1);
+
+  // nack for the first pipeline element (segment #0)
+  auto nack = make_shared<lp::Nack>(face.sentInterests[opt.maxPipelineSize]);
+  nack->setReason(lp::NackReason::DUPLICATE);
+  face.receive(*nack);
+  BOOST_CHECK_EQUAL(hasFailed, false);
+
+  // timeout for all the pipeline elements, but not the first (segment #0)
+  advanceClocks(io, opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack);
+  BOOST_CHECK_EQUAL(hasFailed, false);
+
+  // data for the first pipeline element (segment #0), this should trigger an error because the
+  // others pipeline elements failed
+  face.receive(*makeDataWithSegment(0, false));
+  advanceClocks(io, time::nanoseconds(1), 1);
+
+  BOOST_CHECK_EQUAL(nReceivedSegments, 1);
+  BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_FIXTURE_TEST_CASE(CongestionAllSegments, PipelineInterestsFixture)
+{
+  nDataSegments = 13;
+  BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+  runWithData(*makeDataWithSegment(nDataSegments - 1));
+  advanceClocks(io, time::nanoseconds(1), 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+  // send nack for all the pipeline elements first interest
+  for (size_t j = 0; j < opt.maxPipelineSize; j++) {
+    auto nack = make_shared<lp::Nack>(face.sentInterests[j]);
+    nack->setReason(lp::NackReason::CONGESTION);
+    face.receive(*nack);
+    advanceClocks(io, time::nanoseconds(1), 1);
+  }
+
+  // send nack for all the pipeline elements interests after the first
+  for (int i = 1; i <= opt.maxRetriesOnTimeoutOrNack; ++i) {
+    time::milliseconds backoffTime(static_cast<uint64_t>(std::pow(2, i)));
+    if (backoffTime > DataFetcher::MAX_CONGESTION_BACKOFF_TIME)
+      backoffTime = DataFetcher::MAX_CONGESTION_BACKOFF_TIME;
+
+    advanceClocks(io, backoffTime, 1);
+    BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * (i +1));
+
+    // A single retry for every pipeline element
+    for (size_t j = 0; j < opt.maxPipelineSize; ++j) {
+      auto interest = face.sentInterests[(opt.maxPipelineSize * i) + j];
+      BOOST_CHECK_LT(static_cast<size_t>(interest.getName()[-1].toSegment()), opt.maxPipelineSize);
+    }
+
+    for (size_t j = 0; j < opt.maxPipelineSize; j++) {
+      auto nack = make_shared<lp::Nack>(face.sentInterests[(opt.maxPipelineSize * i) + j]);
+      nack->setReason(lp::NackReason::CONGESTION);
+      face.receive(*nack);
+      advanceClocks(io, time::nanoseconds(1), 1);
+    }
+  }
+
+  BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPipelineInterests
+BOOST_AUTO_TEST_SUITE_END() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
diff --git a/tests/chunks/producer.t.cpp b/tests/chunks/producer.t.cpp
new file mode 100644
index 0000000..dc5346f
--- /dev/null
+++ b/tests/chunks/producer.t.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "tools/chunks/putchunks/producer.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/security/validator-null.hpp>
+
+#include <cmath>
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_AUTO_TEST_SUITE(TestProducer)
+
+BOOST_AUTO_TEST_CASE(InputData)
+{
+  util::DummyClientFace face;
+  KeyChain keyChain;
+  security::SigningInfo signingInfo;
+  Name prefix("/ndn/chunks/test");
+  int maxSegmentSize = 40;
+  std::vector<std::string> testStrings {
+      "",
+
+      "a1b2c3%^&(#$&%^$$/><",
+
+      "123456789123456789123456789123456789123456789123456789123456789"
+      "123456789123456789123456789123456789123456789123456789123456789",
+
+      "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. "
+      "Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+      "ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
+      "consequat massa Donec pede justo,"
+  };
+
+  for (size_t i = 0; i <  testStrings.size(); ++i) {
+    std::istringstream str(testStrings[i]);
+    Producer prod(prefix, face, keyChain, signingInfo, time::seconds(4), maxSegmentSize, false,
+                  false, str);
+
+    size_t expectedSize = std::ceil(static_cast<double>(testStrings[i].size()) / maxSegmentSize);
+    if (testStrings[i].size() == 0)
+      expectedSize = 1;
+
+    BOOST_CHECK_EQUAL(prod.m_store.size(), expectedSize);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(RequestSegmentUnspecifiedVersion)
+{
+  boost::asio::io_service io;
+  util::DummyClientFace face(io, {true, true});
+  KeyChain keyChain;
+  security::SigningInfo signingInfo;
+  Name prefix("/ndn/chunks/test");
+  time::milliseconds freshnessPeriod(time::seconds(10));
+  size_t maxSegmentSize(40);
+  std::istringstream testString(std::string(
+          "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget "
+          "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, "
+          "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, "
+          "sem. Nulla consequat massa Donec pede justo,"));
+
+  Name keyLocatorName = keyChain.getDefaultCertificateName().getPrefix(-1);
+
+  Producer producer(prefix, face, keyChain, signingInfo, freshnessPeriod, maxSegmentSize,
+                    false, false, testString);
+  io.poll();
+
+  size_t nSegments = std::ceil(static_cast<double>(testString.str().size()) / maxSegmentSize);
+
+  // version request
+  face.receive(*makeInterest(prefix));
+  face.processEvents();
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  auto lastData = face.sentData.back();
+  BOOST_REQUIRE_EQUAL(lastData.getName().size(), prefix.size() + 2);
+  BOOST_CHECK_EQUAL(lastData.getName()[-1].toSegment(), 0);
+  BOOST_REQUIRE(!lastData.getFinalBlockId().empty());
+  BOOST_CHECK_EQUAL(lastData.getFinalBlockId().toSegment(), nSegments - 1);
+  BOOST_CHECK_EQUAL(lastData.getSignature().getKeyLocator().getName(), keyLocatorName);
+
+  // segment request
+  Name nameWithVersion(prefix);
+  nameWithVersion.append(lastData.getName()[-2]);
+  size_t requestSegmentNo = 1;
+
+  face.receive(*makeInterest(nameWithVersion.appendSegment(requestSegmentNo)));
+  face.processEvents();
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
+  lastData = face.sentData.back();
+  BOOST_REQUIRE_EQUAL(lastData.getName().size(), prefix.size() + 2);
+  BOOST_CHECK_EQUAL(lastData.getName()[-1].toSegment(), requestSegmentNo);
+  BOOST_REQUIRE(!lastData.getFinalBlockId().empty());
+  BOOST_CHECK_EQUAL(lastData.getFinalBlockId().toSegment(), nSegments - 1);
+  BOOST_CHECK_EQUAL(lastData.getSignature().getKeyLocator().getName(), keyLocatorName);
+}
+
+BOOST_AUTO_TEST_CASE(RequestSegmentSpecifiedVersion)
+{
+  boost::asio::io_service io;
+  util::DummyClientFace face(io, {true, true});
+  KeyChain keyChain;
+  security::SigningInfo signingInfo;
+  Name prefix("/ndn/chunks/test");
+  time::milliseconds freshnessPeriod(time::seconds(10));
+  size_t maxSegmentSize(40);
+  std::istringstream testString(std::string(
+          "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget "
+          "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, "
+          "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, "
+          "sem. Nulla consequat massa Donec pede justo,"));
+
+  uint64_t version = 1449227841747;
+  Name keyLocatorName = keyChain.getDefaultCertificateName().getPrefix(-1);
+
+  Producer producer(prefix.appendVersion(version), face, keyChain, signingInfo, freshnessPeriod,
+                    maxSegmentSize, false, false, testString);
+  io.poll();
+
+  size_t nSegments = std::ceil(static_cast<double>(testString.str().size()) / maxSegmentSize);
+
+  // version request
+  face.receive(*makeInterest(prefix));
+  face.processEvents();
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  auto lastData = face.sentData.back();
+  BOOST_REQUIRE_EQUAL(lastData.getName().size(), prefix.size() + 1);
+  BOOST_CHECK_EQUAL(lastData.getName()[-2].toVersion(), version);
+  BOOST_CHECK_EQUAL(lastData.getName()[-1].toSegment(), 0);
+  BOOST_REQUIRE(!lastData.getFinalBlockId().empty());
+  BOOST_CHECK_EQUAL(lastData.getFinalBlockId().toSegment(), nSegments - 1);
+  BOOST_CHECK_EQUAL(lastData.getSignature().getKeyLocator().getName(), keyLocatorName);
+
+  // segment request
+  Name nameWithVersion(prefix);
+  size_t requestSegmentNo = 1;
+
+  face.receive(*makeInterest(nameWithVersion.appendSegment(requestSegmentNo)));
+  face.processEvents();
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
+  lastData = face.sentData.back();
+  BOOST_REQUIRE_EQUAL(lastData.getName().size(), prefix.size() + 1);
+  BOOST_CHECK_EQUAL(lastData.getName()[-2].toVersion(), version);
+  BOOST_CHECK_EQUAL(lastData.getName()[-1].toSegment(), requestSegmentNo);
+  BOOST_REQUIRE(!lastData.getFinalBlockId().empty());
+  BOOST_CHECK_EQUAL(lastData.getFinalBlockId().toSegment(), nSegments - 1);
+  BOOST_CHECK_EQUAL(lastData.getSignature().getKeyLocator().getName(), keyLocatorName);
+}
+
+BOOST_AUTO_TEST_CASE(RequestNotExistingSegment)
+{
+  boost::asio::io_service io;
+  util::DummyClientFace face(io, {true, true});
+  KeyChain keyChain;
+  security::SigningInfo signingInfo;
+  Name prefix("/ndn/chunks/test");
+  time::milliseconds freshnessPeriod(time::seconds(10));
+  size_t maxSegmentSize(40);
+  std::istringstream testString(std::string(
+          "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget "
+          "dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, "
+          "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, "
+          "sem. Nulla consequat massa Donec pede justo,"));
+
+  Name keyLocatorName = keyChain.getDefaultCertificateName().getPrefix(-1);
+
+  Producer producer(prefix, face, keyChain, signingInfo, freshnessPeriod, maxSegmentSize,
+                    false, false, testString);
+  io.poll();
+
+  size_t nSegments = std::ceil(static_cast<double>(testString.str().size()) / maxSegmentSize);
+
+  // version request
+  face.receive(*makeInterest(prefix));
+  face.processEvents();
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  auto lastData = face.sentData.back();
+  BOOST_REQUIRE_EQUAL(lastData.getName().size(), prefix.size() + 2);
+  BOOST_CHECK_EQUAL(lastData.getName()[-1].toSegment(), 0);
+  BOOST_REQUIRE(!lastData.getFinalBlockId().empty());
+  BOOST_CHECK_EQUAL(lastData.getFinalBlockId().toSegment(), nSegments - 1);
+  BOOST_CHECK_EQUAL(lastData.getSignature().getKeyLocator().getName(), keyLocatorName);
+
+  // segment request
+  Name nameWithVersion(prefix);
+  nameWithVersion.append(lastData.getName()[-2]);
+  face.receive(*makeInterest(nameWithVersion.appendSegment(nSegments)));
+  face.processEvents();
+
+  // no new data
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestProducer
+BOOST_AUTO_TEST_SUITE_END() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/README.md b/tools/chunks/README.md
new file mode 100644
index 0000000..3f0ffd6
--- /dev/null
+++ b/tools/chunks/README.md
@@ -0,0 +1,73 @@
+# ndncatchunks and ndnputchunks
+
+**ndncatchunks** and **ndnputchunks** are a pair of programs to transfer a file as Data segments.
+
+* **ndnputchunks** is a producer program that reads a file from the standard input, and makes
+  it available as NDN Data segments.  It appends version and segment number components
+  to the specified name, according to the
+  [NDN naming conventions](http://named-data.net/publications/techreports/ndn-tr-22-ndn-memo-naming-conventions/).
+
+* **ndncatchunks** is a consumer program that fetches Data segments of a file, optionally
+  discovering the latest version of the file, and writes the content of the retrieved file to
+  the standard output.
+
+## Version discovery methods
+
+* `fixed`    : ndncatchunks will send an interest attempting to find a data packet with the
+               specified prefix and version number. A version component must be present at the
+               end of the user-specified NDN name.
+
+* `iterative`: ndncatchunks will send a series of interests with ChildSelector set to prefer the
+               rightmost child and Exclude selectors, attempting to find a data packet with the
+               specified prefix and the latest (the largest in the NDN canonical ordering)
+               version number.  The version is declared "latest" after a predefined number of
+               data retrieval timeouts (default: 1).
+
+The default discovery method is `fixed`. Other methods will be implemented in future versions
+of the tool.
+
+
+## Usage examples
+
+### Publishing
+
+The following command will publish the text of the GPL-3 license under the `/localhost/demo/gpl3`
+prefix:
+
+    ndnputchunks ndn:/localhost/demo/gpl3 < /usr/share/common-licenses/GPL-3
+
+To find the published version you have to start ndnputchunks with the `-p` command line option,
+for example:
+
+    ndnputchunks -p ndn:/localhost/demo/gpl3 < /usr/share/common-licenses/GPL-3
+
+This command will print the published version to the standard output.
+
+To publish data with a specific version, you must append a version component to the end of the
+prefix. The version component must follow the aforementioned NDN naming conventions.
+For example, the following command will publish the version `%FD%00%00%01Qc%CF%17v` of the
+`/localhost/demo/gpl3` prefix:
+
+    ndnputchunks ndn:/localhost/demo/gpl3/%FD%00%00%01Qc%CF%17v < /usr/share/common-licenses/GPL-3
+
+If the version component is not valid, a new well-formed version will be generated and appended
+to the supplied NDN name.
+
+
+### Retrieval
+
+To retrieve the latest version of a published file, the following command can be used:
+
+    ndncatchunks -d iterative ndn:/localhost/demo/gpl3
+
+This command will use the iterative method to discover the latest version of the file.
+
+To fetch a specific version of a published file, you can use the `fixed` version discovery method
+(the default). In this case the version needs to be supplied as part of the name.  For example,
+if the version is known to be `%FD%00%00%01Qc%CF%17v`, the following command will fetch that
+exact version of the file:
+
+    ndncatchunks ndn:/localhost/demo/gpl3/%FD%00%00%01Qc%CF%17v
+
+
+For more information, run the programs with `--help` as argument.
diff --git a/tools/chunks/catchunks/consumer.cpp b/tools/chunks/catchunks/consumer.cpp
new file mode 100644
index 0000000..8976f6a
--- /dev/null
+++ b/tools/chunks/catchunks/consumer.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "consumer.hpp"
+#include "discover-version.hpp"
+
+namespace ndn {
+namespace chunks {
+
+Consumer::Consumer(Face& face, Validator& validator, bool isVerbose, std::ostream& os)
+  : m_face(face)
+  , m_validator(validator)
+  , m_pipeline(nullptr)
+  , m_nextToPrint(0)
+  , m_outputStream(os)
+  , m_isVerbose(isVerbose)
+{
+}
+
+void Consumer::run(DiscoverVersion& discover, PipelineInterests& pipeline)
+{
+  m_pipeline = &pipeline;
+  m_nextToPrint = 0;
+
+  discover.onDiscoverySuccess.connect(bind(&Consumer::runWithData, this, _1));
+  discover.onDiscoveryFailure.connect(bind(&Consumer::onFailure, this, _1));
+
+  discover.run();
+  m_face.processEvents();
+}
+
+void Consumer::runWithData(const Data& data)
+{
+  m_validator.validate(data,
+                       bind(&Consumer::onDataValidated, this, _1),
+                       bind(&Consumer::onFailure, this, _2));
+
+  m_pipeline->runWithExcludedSegment(data,
+                                     bind(&Consumer::onData, this, _1, _2),
+                                     bind(&Consumer::onFailure, this, _1));
+
+}
+
+void
+Consumer::onData(const Interest& interest, const Data& data)
+{
+  m_validator.validate(data,
+                       bind(&Consumer::onDataValidated, this, _1),
+                       bind(&Consumer::onFailure, this, _2));
+}
+
+void
+Consumer::onDataValidated(shared_ptr<const Data> data)
+{
+  if (data->getContentType() == ndn::tlv::ContentType_Nack) {
+    if (m_isVerbose)
+      std::cerr << "Application level NACK: " << *data << std::endl;
+
+    m_pipeline->cancel();
+    throw ApplicationNackError(*data);
+  }
+
+  m_bufferedData[data->getName()[-1].toSegment()] = data;
+  writeInOrderData();
+}
+
+void
+Consumer::onFailure(const std::string& reason)
+{
+  throw std::runtime_error(reason);
+}
+
+void
+Consumer::writeInOrderData()
+{
+  for (auto it = m_bufferedData.begin();
+       it != m_bufferedData.end() && it->first == m_nextToPrint;
+       it = m_bufferedData.erase(it), ++m_nextToPrint) {
+
+    const Block& content = it->second->getContent();
+    m_outputStream.write(reinterpret_cast<const char*>(content.value()), content.value_size());
+  }
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/consumer.hpp b/tools/chunks/catchunks/consumer.hpp
new file mode 100644
index 0000000..9428a06
--- /dev/null
+++ b/tools/chunks/catchunks/consumer.hpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_CONSUMER_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_CONSUMER_HPP
+
+#include "pipeline-interests.hpp"
+#include "discover-version.hpp"
+
+#include <ndn-cxx/security/validator.hpp>
+
+namespace ndn {
+namespace chunks {
+
+/**
+ * @brief Segmented version consumer
+ *
+ * Discover the latest version of the data published under a specified prefix, and retrieve all the
+ * segments associated to that version. The segments are fetched in order and written to a
+ * user-specified stream in the same order.
+ */
+class Consumer : noncopyable
+{
+public:
+  class ApplicationNackError : public std::runtime_error
+  {
+  public:
+    explicit
+    ApplicationNackError(const Data& data)
+      : std::runtime_error("Application generated Nack: " + boost::lexical_cast<std::string>(data))
+    {
+    }
+  };
+
+  /**
+   * @brief Create the consumer
+   */
+  Consumer(Face& face, Validator& validator, bool isVerbose, std::ostream& os = std::cout);
+
+  /**
+   * @brief Run the consumer
+   */
+  void
+  run(DiscoverVersion& discover, PipelineInterests& pipeline);
+
+private:
+  void
+  runWithData(const Data& data);
+
+  void
+  onData(const Interest& interest, const Data& data);
+
+  void
+  onDataValidated(shared_ptr<const Data> data);
+
+  void
+  onFailure(const std::string& reason);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  void
+  writeInOrderData();
+
+private:
+  Face& m_face;
+  Validator& m_validator;
+  PipelineInterests* m_pipeline;
+  uint64_t m_nextToPrint;
+  std::ostream& m_outputStream;
+  bool m_isVerbose;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  std::map<uint64_t, shared_ptr<const Data>> m_bufferedData;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_CONSUMER_HPP
diff --git a/tools/chunks/catchunks/data-fetcher.cpp b/tools/chunks/catchunks/data-fetcher.cpp
new file mode 100644
index 0000000..313334f
--- /dev/null
+++ b/tools/chunks/catchunks/data-fetcher.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ * @author Davide Pesavento
+ */
+
+#include "data-fetcher.hpp"
+
+#include <cmath>
+
+namespace ndn {
+namespace chunks {
+
+const int DataFetcher::MAX_RETRIES_INFINITE = -1;
+const time::milliseconds DataFetcher::MAX_CONGESTION_BACKOFF_TIME = time::seconds(10);
+
+shared_ptr<DataFetcher>
+DataFetcher::fetch(Face& face, const Interest& interest, int maxNackRetries, int maxTimeoutRetries,
+                   DataCallback onData, FailureCallback onNack, FailureCallback onTimeout,
+                   bool isVerbose)
+{
+  auto dataFetcher = shared_ptr<DataFetcher>(new DataFetcher(face,
+                                                             maxNackRetries,
+                                                             maxTimeoutRetries,
+                                                             std::move(onData),
+                                                             std::move(onNack),
+                                                             std::move(onTimeout),
+                                                             isVerbose));
+  dataFetcher->expressInterest(interest, dataFetcher);
+  return dataFetcher;
+}
+
+DataFetcher::DataFetcher(Face& face, int maxNackRetries, int maxTimeoutRetries,
+                         DataCallback onData, FailureCallback onNack, FailureCallback onTimeout,
+                         bool isVerbose)
+  : m_face(face)
+  , m_scheduler(m_face.getIoService())
+  , m_onData(std::move(onData))
+  , m_onNack(std::move(onNack))
+  , m_onTimeout(std::move(onTimeout))
+  , m_maxNackRetries(maxNackRetries)
+  , m_maxTimeoutRetries(maxTimeoutRetries)
+  , m_nNacks(0)
+  , m_nTimeouts(0)
+  , m_nCongestionRetries(0)
+  , m_isVerbose(isVerbose)
+  , m_isStopped(false)
+  , m_hasError(false)
+{
+  BOOST_ASSERT(m_onData != nullptr);
+}
+
+void
+DataFetcher::cancel()
+{
+  if (isRunning()) {
+    m_isStopped = true;
+    m_face.removePendingInterest(m_interestId);
+    m_scheduler.cancelAllEvents();
+  }
+}
+
+void
+DataFetcher::expressInterest(const Interest& interest, const shared_ptr<DataFetcher>& self)
+{
+  m_nCongestionRetries = 0;
+  m_interestId = m_face.expressInterest(interest,
+                                        bind(&DataFetcher::handleData, this, _1, _2, self),
+                                        bind(&DataFetcher::handleNack, this, _1, _2, self),
+                                        bind(&DataFetcher::handleTimeout, this, _1, self));
+}
+
+void
+DataFetcher::handleData(const Interest& interest, const Data& data,
+                        const shared_ptr<DataFetcher>& self)
+{
+  if (!isRunning())
+    return;
+
+  m_isStopped = true;
+  m_onData(interest, data);
+}
+
+void
+DataFetcher::handleNack(const Interest& interest, const lp::Nack& nack,
+                        const shared_ptr<DataFetcher>& self)
+{
+  if (!isRunning())
+    return;
+
+  if (m_maxNackRetries != MAX_RETRIES_INFINITE)
+    ++m_nNacks;
+
+  if (m_isVerbose)
+    std::cerr << "Received Nack with reason " << nack.getReason()
+              << " for Interest " << interest << std::endl;
+
+  if (m_nNacks <= m_maxNackRetries || m_maxNackRetries == MAX_RETRIES_INFINITE) {
+    Interest newInterest(interest);
+    newInterest.refreshNonce();
+
+    switch (nack.getReason()) {
+      case lp::NackReason::DUPLICATE: {
+        expressInterest(newInterest, self);
+        break;
+      }
+      case lp::NackReason::CONGESTION: {
+        time::milliseconds backoffTime(static_cast<uint64_t>(std::pow(2, m_nCongestionRetries)));
+        if (backoffTime > MAX_CONGESTION_BACKOFF_TIME)
+          backoffTime = MAX_CONGESTION_BACKOFF_TIME;
+        else
+          m_nCongestionRetries++;
+
+        m_scheduler.scheduleEvent(backoffTime, bind(&DataFetcher::expressInterest, this,
+                                                    newInterest, self));
+        break;
+      }
+      default: {
+        m_hasError = true;
+        if (m_onNack)
+          m_onNack(interest, "Could not retrieve data for " + interest.getName().toUri() +
+                             ", reason: " + boost::lexical_cast<std::string>(nack.getReason()));
+        break;
+      }
+    }
+  }
+  else {
+    m_hasError = true;
+    if (m_onNack)
+      m_onNack(interest, "Reached the maximum number of nack retries (" + to_string(m_maxNackRetries) +
+                         ") while retrieving data for " + interest.getName().toUri());
+  }
+}
+
+void
+DataFetcher::handleTimeout(const Interest& interest, const shared_ptr<DataFetcher>& self)
+{
+  if (!isRunning())
+    return;
+
+  if (m_maxTimeoutRetries != MAX_RETRIES_INFINITE)
+    ++m_nTimeouts;
+
+  if (m_isVerbose)
+    std::cerr << "Timeout for Interest " << interest << std::endl;
+
+  if (m_nTimeouts <= m_maxTimeoutRetries || m_maxTimeoutRetries == MAX_RETRIES_INFINITE) {
+    Interest newInterest(interest);
+    newInterest.refreshNonce();
+    expressInterest(newInterest, self);
+  }
+  else {
+    m_hasError = true;
+    if (m_onTimeout)
+      m_onTimeout(interest, "Reached the maximum number of timeout retries (" + to_string(m_maxTimeoutRetries) +
+                            ") while retrieving data for " + interest.getName().toUri());
+  }
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/data-fetcher.hpp b/tools/chunks/catchunks/data-fetcher.hpp
new file mode 100644
index 0000000..4335ab2
--- /dev/null
+++ b/tools/chunks/catchunks/data-fetcher.hpp
@@ -0,0 +1,132 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ * @author Davide Pesavento
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_DATA_FETCHER_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_DATA_FETCHER_HPP
+
+#include "core/common.hpp"
+
+namespace ndn {
+namespace chunks {
+
+/**
+ * @brief fetch data for a given interest and handle timeout or nack error with retries
+ *
+ * To instantiate a DataFetcher you need to use the static method fetch, this will also express the
+ * interest. After a timeout or nack is received, the same interest with a different nonce will be
+ * requested for a maximum number of time specified by the class user. There are separate retry
+ * counters for timeouts and nacks.
+ *
+ * A specified callback is called after the data matching the expressed interest is received. A
+ * different callback is called in case one of the retries counter reach the maximum. This callback
+ * can be different for timeout and nack. The data callback must be defined but the others callback
+ * are optional.
+ *
+ */
+class DataFetcher
+{
+public:
+  /**
+   * @brief means that there is no maximum number of retries,
+   *        i.e. fetching must be retried indefinitely
+   */
+  static const int MAX_RETRIES_INFINITE;
+
+  /**
+   * @brief ceiling value for backoff time used in congestion handling
+   */
+  static const time::milliseconds MAX_CONGESTION_BACKOFF_TIME;
+
+  typedef function<void(const Interest& interest, const std::string& reason)> FailureCallback;
+
+  /**
+   * @brief instantiate a DataFetcher object and start fetching data
+   *
+   * @param onData callback for segment correctly received, must not be empty
+   */
+  static shared_ptr<DataFetcher>
+  fetch(Face& face, const Interest& interest, int maxNackRetries, int maxTimeoutRetries,
+        DataCallback onData, FailureCallback onTimeout, FailureCallback onNack,
+        bool isVerbose);
+
+  /**
+   * @brief stop data fetching without error and calling any callback
+   */
+  void
+  cancel();
+
+  bool
+  isRunning() const
+  {
+    return !m_isStopped && !m_hasError;
+  }
+
+  bool
+  hasError() const
+  {
+    return m_hasError;
+  }
+
+private:
+  DataFetcher(Face& face, int maxNackRetries, int maxTimeoutRetries,
+              DataCallback onData, FailureCallback onNack, FailureCallback onTimeout,
+              bool isVerbose);
+
+  void
+  expressInterest(const Interest& interest, const shared_ptr<DataFetcher>& self);
+
+  void
+  handleData(const Interest& interest, const Data& data, const shared_ptr<DataFetcher>& self);
+
+  void
+  handleNack(const Interest& interest, const lp::Nack& nack, const shared_ptr<DataFetcher>& self);
+
+  void
+  handleTimeout(const Interest& interest, const shared_ptr<DataFetcher>& self);
+
+private:
+  Face& m_face;
+  Scheduler m_scheduler;
+  const PendingInterestId* m_interestId;
+  DataCallback m_onData;
+  FailureCallback m_onNack;
+  FailureCallback m_onTimeout;
+
+  int m_maxNackRetries;
+  int m_maxTimeoutRetries;
+  int m_nNacks;
+  int m_nTimeouts;
+  uint32_t m_nCongestionRetries;
+
+  bool m_isVerbose;
+  bool m_isStopped;
+  bool m_hasError;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_DATA_FETCHER_HPP
diff --git a/tools/chunks/catchunks/discover-version-fixed.cpp b/tools/chunks/catchunks/discover-version-fixed.cpp
new file mode 100644
index 0000000..6b763de
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version-fixed.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#include "discover-version-fixed.hpp"
+
+#include <cmath>
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace chunks {
+
+DiscoverVersionFixed::DiscoverVersionFixed(const Name& prefix, Face& face, const Options& options)
+  : Options(options)
+  , DiscoverVersion(prefix, face, options)
+  , m_strayExcludes()
+{
+}
+
+void
+DiscoverVersionFixed::run()
+{
+  Interest interest(m_prefix);
+  interest.setInterestLifetime(interestLifetime);
+  interest.setMustBeFresh(mustBeFresh);
+  interest.setMaxSuffixComponents(2);
+  interest.setMinSuffixComponents(2);
+
+  expressInterest(interest, maxRetriesOnTimeoutOrNack, maxRetriesOnTimeoutOrNack);
+}
+
+void
+DiscoverVersionFixed::handleData(const Interest& interest, const Data& data)
+{
+  if (isVerbose)
+    std::cerr << "Data: " << data << std::endl;
+
+  size_t segmentIndex = interest.getName().size();
+  if (data.getName()[segmentIndex].isSegment()) {
+    if (isVerbose)
+      std::cerr << "Found data with the requested version: " << m_prefix[-1] << std::endl;
+
+    this->emitSignal(onDiscoverySuccess, data);
+  }
+  else {
+    // data isn't a valid segment, add to the exclude list
+    m_strayExcludes.excludeOne(data.getName()[segmentIndex]);
+    Interest newInterest(interest);
+    newInterest.refreshNonce();
+    newInterest.setExclude(m_strayExcludes);
+
+    expressInterest(newInterest, maxRetriesOnTimeoutOrNack, maxRetriesOnTimeoutOrNack);
+  }
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/discover-version-fixed.hpp b/tools/chunks/catchunks/discover-version-fixed.hpp
new file mode 100644
index 0000000..31c77db
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version-fixed.hpp
@@ -0,0 +1,73 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_FIXED_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_FIXED_HPP
+
+#include "discover-version.hpp"
+
+namespace ndn {
+namespace chunks {
+
+/**
+ * @brief Service to retrieve a specific version segment. The version is specified in the prefix
+ *
+ * Send a request of a specific version and expect to be answered with one segment.
+ *
+ * The received name component after version can be an invalid segment number, this component will
+ * be excluded in the next interests. In the unlikely case that there are too many excluded
+ * components such that the Interest cannot fit in ndn::MAX_NDN_PACKET_SIZE, the discovery
+ * procedure will throw Face::Error.
+ *
+ * DiscoverVersionFixed's user is notified once after one segment with the user specified version
+ * is found or on failure to find any Data version.
+ */
+class DiscoverVersionFixed : public DiscoverVersion
+{
+
+public:
+  /**
+   * @brief create a DiscoverVersionSpecified service
+   */
+  DiscoverVersionFixed(const Name& prefix, Face& face, const Options& options);
+
+  /**
+   * @brief identify the latest Data version published.
+   */
+  void
+  run() NDN_CXX_DECL_FINAL;
+
+private:
+  void
+  handleData(const Interest& interest, const Data& data) NDN_CXX_DECL_FINAL;
+
+private:
+  Exclude m_strayExcludes;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_FIXED_HPP
diff --git a/tools/chunks/catchunks/discover-version-iterative.cpp b/tools/chunks/catchunks/discover-version-iterative.cpp
new file mode 100644
index 0000000..4f2f543
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version-iterative.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "discover-version-iterative.hpp"
+
+namespace ndn {
+namespace chunks {
+
+DiscoverVersionIterative::DiscoverVersionIterative(const Name& prefix, Face& face,
+                                                   const Options& options)
+  : chunks::Options(options)
+  , DiscoverVersion(prefix, face, options)
+  , Options(options)
+  , m_latestVersion(0)
+  , m_latestVersionData(nullptr)
+  , m_foundVersion(false)
+{
+}
+
+void
+DiscoverVersionIterative::run()
+{
+  m_latestVersion = 0;
+  m_foundVersion = false;
+
+  Interest interest(m_prefix);
+  interest.setInterestLifetime(interestLifetime);
+  interest.setMustBeFresh(mustBeFresh);
+  interest.setMinSuffixComponents(3);
+  interest.setMaxSuffixComponents(3);
+  interest.setChildSelector(1);
+
+  expressInterest(interest, maxRetriesOnTimeoutOrNack, maxRetriesOnTimeoutOrNack);
+}
+
+void
+DiscoverVersionIterative::handleData(const Interest& interest, const Data& data)
+{
+  size_t versionindex = m_prefix.size();
+
+  const Name& name = data.getName();
+  Exclude exclude;
+
+  if (isVerbose)
+    std::cerr << "Data: " << data << std::endl;
+
+  BOOST_ASSERT(name.size() > m_prefix.size());
+  if (name[versionindex].isVersion()) {
+    m_latestVersion = name[versionindex].toVersion();
+    m_latestVersionData = make_shared<Data>(data);
+    m_foundVersion = true;
+
+    exclude.excludeBefore(name[versionindex]);
+
+    if (isVerbose)
+      std::cerr << "Discovered version = " << m_latestVersion << std::endl;
+  }
+  else {
+    // didn't find a version number at expected index.
+    m_strayExcludes.excludeOne(name[versionindex]);
+  }
+
+  for (const auto& i : m_strayExcludes) {
+    exclude.excludeOne(i.first);
+  }
+
+  Interest newInterest(interest);
+  newInterest.refreshNonce();
+  newInterest.setExclude(exclude);
+
+  if (m_foundVersion)
+    expressInterest(newInterest, maxRetriesOnTimeoutOrNack, maxRetriesAfterVersionFound);
+  else
+    expressInterest(interest, maxRetriesOnTimeoutOrNack, maxRetriesOnTimeoutOrNack);
+}
+
+void
+DiscoverVersionIterative::handleTimeout(const Interest& interest, const std::string& reason)
+{
+  if (m_foundVersion) {
+    // a version has been found and after a timeout error this version can be used as the latest.
+    if (isVerbose)
+      std::cerr << "Found data with the latest version: " << m_latestVersion << std::endl;
+
+    // we discovered at least one version. assume what we have is the latest.
+    this->emitSignal(onDiscoverySuccess, *m_latestVersionData);
+  }
+  else {
+    DiscoverVersion::handleTimeout(interest, reason);
+  }
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/discover-version-iterative.hpp b/tools/chunks/catchunks/discover-version-iterative.hpp
new file mode 100644
index 0000000..f772c8f
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version-iterative.hpp
@@ -0,0 +1,107 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_ITERATIVE_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_ITERATIVE_HPP
+
+#include "discover-version.hpp"
+
+namespace ndn {
+namespace chunks {
+
+/**
+ * @brief Options for discover version iterative DiscoverVersionIterative
+ *
+ * The canonical name to use is DiscoverVersionIterative::Options
+ */
+class DiscoverVersionIterativeOptions : public virtual Options
+{
+public:
+  explicit
+  DiscoverVersionIterativeOptions(const Options& opt = Options())
+    : Options(opt)
+    , maxRetriesAfterVersionFound(1)
+  {
+  }
+
+public:
+  int maxRetriesAfterVersionFound;  // used only in timeout handling
+};
+
+/**
+ * @brief Service for discovering the latest Data version in the iterative way
+ *
+ * Identifies the latest retrievable version published under the specified namespace
+ * (as specified by the Version marker).
+ *
+ * DiscoverVersionIterative declares the largest discovered version to be the latest after some
+ * Interest timeouts (i.e. failed retrieval after exclusion and retransmission). The number of
+ * timeouts are specified by the value of maxRetriesAfterVersionFound inside the iterative options.
+ *
+ * The received name component after version can be an invalid segment number, this component will
+ * be excluded in the next interests. In the unlikely case that there are too many excluded
+ * components such that the Interest cannot fit in ndn::MAX_NDN_PACKET_SIZE, the discovery
+ * procedure will throw Face::Error.
+ *
+ * DiscoverVersionIterative's user is notified once after identifying the latest retrievable
+ * version or on failure to find any version Data.
+ */
+class DiscoverVersionIterative : public DiscoverVersion, protected DiscoverVersionIterativeOptions
+{
+public:
+  typedef DiscoverVersionIterativeOptions Options;
+
+public:
+  /**
+   * @brief create a DiscoverVersionIterative service
+   */
+  DiscoverVersionIterative(const Name& prefix, Face& face, const Options& options);
+
+  /**
+   * @brief identify the latest Data version published.
+   */
+  void
+  run() NDN_CXX_DECL_FINAL;
+
+private:
+  void
+  handleData(const Interest& interest, const Data& data) NDN_CXX_DECL_FINAL;
+
+  void
+  handleTimeout(const Interest& interest, const std::string& reason) NDN_CXX_DECL_FINAL;
+
+private:
+  uint64_t m_latestVersion;
+  shared_ptr<const Data> m_latestVersionData;
+  Exclude m_strayExcludes;
+  bool m_foundVersion;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_ITERATIVE_HPP
diff --git a/tools/chunks/catchunks/discover-version.cpp b/tools/chunks/catchunks/discover-version.cpp
new file mode 100644
index 0000000..1d3298e
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "discover-version.hpp"
+#include "data-fetcher.hpp"
+
+namespace ndn {
+namespace chunks {
+
+DiscoverVersion::DiscoverVersion(const Name& prefix, Face& face, const Options& options)
+  : Options(options)
+  , m_prefix(prefix)
+  , m_face(face)
+{
+}
+
+void
+DiscoverVersion::expressInterest(const Interest& interest, int maxRetriesNack,
+                                 int maxRetriesTimeout)
+{
+  fetcher = DataFetcher::fetch(m_face, interest, maxRetriesNack, maxRetriesTimeout,
+                               bind(&DiscoverVersion::handleData, this, _1, _2),
+                               bind(&DiscoverVersion::handleNack, this, _1, _2),
+                               bind(&DiscoverVersion::handleTimeout, this, _1, _2),
+                               isVerbose);
+}
+
+void
+DiscoverVersion::handleData(const Interest& interest, const Data& data)
+{
+  onDiscoverySuccess(data);
+}
+
+void
+DiscoverVersion::handleNack(const Interest& interest, const std::string& reason)
+{
+  onDiscoveryFailure(reason);
+}
+
+void
+DiscoverVersion::handleTimeout(const Interest& interest, const std::string& reason)
+{
+  onDiscoveryFailure(reason);
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/discover-version.hpp b/tools/chunks/catchunks/discover-version.hpp
new file mode 100644
index 0000000..bb6a477
--- /dev/null
+++ b/tools/chunks/catchunks/discover-version.hpp
@@ -0,0 +1,95 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_HPP
+
+#include "core/common.hpp"
+#include "options.hpp"
+
+namespace ndn {
+namespace chunks {
+
+class DataFetcher;
+
+/**
+ * @brief Base class of services for discovering the latest Data version
+ *
+ * DiscoverVersion's user is notified once after identifying the latest retrievable version or
+ * on failure to find any Data version.
+ */
+class DiscoverVersion : virtual protected Options, noncopyable
+{
+public: // signals
+  /**
+   * @brief Signal emited when the first segment of a specific version is found.
+   */
+  signal::Signal<DiscoverVersion, const Data&> onDiscoverySuccess;
+
+  /**
+   * @brief Signal emitted when a failure occurs.
+   */
+  signal::Signal<DiscoverVersion, const std::string&> onDiscoveryFailure;
+
+  DECLARE_SIGNAL_EMIT(onDiscoverySuccess)
+  DECLARE_SIGNAL_EMIT(onDiscoveryFailure)
+
+public:
+  /**
+   * @brief create a DiscoverVersion service
+   */
+  DiscoverVersion(const Name& prefix, Face& face, const Options& options);
+
+  /**
+   * @brief identify the latest Data version published.
+   */
+  virtual void
+  run() = 0;
+
+protected:
+  void
+  expressInterest(const Interest& interest, int maxRetriesNack, int maxRetriesTimeout);
+
+  virtual void
+  handleData(const Interest& interest, const Data& data);
+
+  virtual void
+  handleNack(const Interest& interest, const std::string& reason);
+
+  virtual void
+  handleTimeout(const Interest& interest, const std::string& reason);
+
+protected:
+  const Name m_prefix;
+  Face& m_face;
+  shared_ptr<DataFetcher> fetcher;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_DISCOVER_VERSION_HPP
diff --git a/tools/chunks/catchunks/ndncatchunks.cpp b/tools/chunks/catchunks/ndncatchunks.cpp
new file mode 100644
index 0000000..d2b679f
--- /dev/null
+++ b/tools/chunks/catchunks/ndncatchunks.cpp
@@ -0,0 +1,180 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "core/version.hpp"
+#include "options.hpp"
+#include "consumer.hpp"
+#include "discover-version-fixed.hpp"
+#include "discover-version-iterative.hpp"
+
+#include <ndn-cxx/security/validator-null.hpp>
+
+namespace ndn {
+namespace chunks {
+
+static int
+main(int argc, char** argv)
+{
+  std::string programName(argv[0]);
+  Options options;
+  std::string discoverType("fixed");
+  size_t maxPipelineSize(1);
+  int maxRetriesAfterVersionFound(1);
+  std::string uri;
+
+  namespace po = boost::program_options;
+  po::options_description visibleDesc("Options");
+  visibleDesc.add_options()
+    ("help,h",      "print this help message and exit")
+    ("discover-version,d",  po::value<std::string>(&discoverType)->default_value(discoverType),
+                            "version discovery algorithm to use; valid values are: 'fixed', 'iterative'")
+    ("fresh,f",     po::bool_switch(&options.mustBeFresh), "only return fresh content")
+    ("lifetime,l",  po::value<uint64_t>()->default_value(options.interestLifetime.count()),
+                    "lifetime of expressed Interests, in milliseconds")
+    ("pipeline,p",  po::value<size_t>(&maxPipelineSize)->default_value(maxPipelineSize),
+                    "maximum size of the Interest pipeline")
+    ("retries,r",   po::value<int>(&options.maxRetriesOnTimeoutOrNack)->default_value(options.maxRetriesOnTimeoutOrNack),
+                    "maximum number of retries in case of Nack or timeout (-1 = no limit)")
+    ("retries-iterative,i", po::value<int>(&maxRetriesAfterVersionFound)->default_value(maxRetriesAfterVersionFound),
+                            "number of timeouts that have to occur in order to confirm a discovered Data "
+                            "version as the latest one (only applicable to 'iterative' version discovery)")
+    ("verbose,v",   po::bool_switch(&options.isVerbose), "turn on verbose output")
+    ("version,V",   "print program version and exit")
+    ;
+
+  po::options_description hiddenDesc("Hidden options");
+  hiddenDesc.add_options()
+    ("ndn-name,n", po::value<std::string>(&uri), "NDN name of the requested content");
+
+  po::positional_options_description p;
+  p.add("ndn-name", -1);
+
+  po::options_description optDesc("Allowed options");
+  optDesc.add(visibleDesc).add(hiddenDesc);
+
+  po::variables_map vm;
+  try {
+    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(p).run(), vm);
+    po::notify(vm);
+  }
+  catch (const po::error& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+  catch (const boost::bad_any_cast& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+
+  if (vm.count("help") > 0) {
+    std::cout << "Usage: " << programName << " [options] ndn:/name" << std::endl;
+    std::cout << visibleDesc;
+    return 0;
+  }
+
+  if (vm.count("version") > 0) {
+    std::cout << "ndncatchunks " << tools::VERSION << std::endl;
+    return 0;
+  }
+
+  if (vm.count("ndn-name") == 0) {
+    std::cerr << "Usage: " << programName << " [options] ndn:/name" << std::endl;
+    std::cerr << visibleDesc;
+    return 2;
+  }
+
+  Name prefix(uri);
+  if (discoverType == "fixed" && (prefix.empty() || !prefix[-1].isVersion())) {
+    std::cerr << "ERROR: The specified name must contain a version component when using "
+                 "fixed version discovery" << std::endl;
+    return 2;
+  }
+
+  if (maxPipelineSize < 1 || maxPipelineSize > 1024) {
+    std::cerr << "ERROR: pipeline size must be between 1 and 1024" << std::endl;
+    return 2;
+  }
+
+  if (options.maxRetriesOnTimeoutOrNack < -1 || options.maxRetriesOnTimeoutOrNack > 1024) {
+    std::cerr << "ERROR: retries value must be between -1 and 1024" << std::endl;
+    return 2;
+  }
+
+  if (maxRetriesAfterVersionFound < 0 || maxRetriesAfterVersionFound > 1024) {
+    std::cerr << "ERROR: retries iterative value must be between 0 and 1024" << std::endl;
+    return 2;
+  }
+
+  options.interestLifetime = time::milliseconds(vm["lifetime"].as<uint64_t>());
+
+  try {
+    Face face;
+
+    unique_ptr<DiscoverVersion> discover;
+    if (discoverType == "fixed") {
+      discover = make_unique<DiscoverVersionFixed>(prefix, face, options);
+    }
+    else if (discoverType == "iterative") {
+      DiscoverVersionIterative::Options optionsIterative(options);
+      optionsIterative.maxRetriesAfterVersionFound = maxRetriesAfterVersionFound;
+      discover = make_unique<DiscoverVersionIterative>(prefix, face, optionsIterative);
+    }
+    else {
+      std::cerr << "ERROR: discover version type not valid" << std::endl;
+      return 2;
+    }
+
+    ValidatorNull validator;
+    Consumer consumer(face, validator, options.isVerbose);
+
+    PipelineInterests::Options optionsPipeline(options);
+    optionsPipeline.maxPipelineSize = maxPipelineSize;
+    PipelineInterests pipeline(face, optionsPipeline);
+
+    BOOST_ASSERT(discover != nullptr);
+    consumer.run(*discover, pipeline);
+  }
+  catch (const Consumer::ApplicationNackError& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 3;
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 1;
+  }
+
+  return 0;
+}
+
+} // namespace chunks
+} // namespace ndn
+
+int
+main(int argc, char** argv)
+{
+  return ndn::chunks::main(argc, argv);
+}
diff --git a/tools/chunks/catchunks/options.cpp b/tools/chunks/catchunks/options.cpp
new file mode 100644
index 0000000..4f5f586
--- /dev/null
+++ b/tools/chunks/catchunks/options.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ * @author Davide Pesavento
+ */
+
+#include "options.hpp"
+
+#include <ndn-cxx/interest.hpp>
+
+namespace ndn {
+namespace chunks {
+
+Options::Options()
+  : interestLifetime(ndn::DEFAULT_INTEREST_LIFETIME)
+  , maxRetriesOnTimeoutOrNack(3)
+  , mustBeFresh(false)
+  , isVerbose(false)
+{
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/options.hpp b/tools/chunks/catchunks/options.hpp
new file mode 100644
index 0000000..09a914b
--- /dev/null
+++ b/tools/chunks/catchunks/options.hpp
@@ -0,0 +1,50 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Andrea Tosatto
+ * @author Davide Pesavento
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_OPTIONS_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_OPTIONS_HPP
+
+#include <ndn-cxx/util/time.hpp>
+
+namespace ndn {
+namespace chunks {
+
+class Options
+{
+public:
+  Options();
+
+public:
+  time::milliseconds interestLifetime;
+  int maxRetriesOnTimeoutOrNack;
+  bool mustBeFresh;
+  bool isVerbose;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_OPTIONS_HPP
diff --git a/tools/chunks/catchunks/pipeline-interests.cpp b/tools/chunks/catchunks/pipeline-interests.cpp
new file mode 100644
index 0000000..b07c959
--- /dev/null
+++ b/tools/chunks/catchunks/pipeline-interests.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "pipeline-interests.hpp"
+#include "data-fetcher.hpp"
+
+namespace ndn {
+namespace chunks {
+
+PipelineInterests::PipelineInterests(Face& face, const Options& options)
+  : m_face(face)
+  , m_nextSegmentNo(0)
+  , m_lastSegmentNo(0)
+  , m_excludeSegmentNo(0)
+  , m_options(options)
+  , m_hasFinalBlockId(false)
+  , m_hasError(false)
+  , m_hasFailure(false)
+{
+  m_segmentFetchers.resize(m_options.maxPipelineSize);
+}
+
+PipelineInterests::~PipelineInterests()
+{
+  cancel();
+}
+
+void
+PipelineInterests::runWithExcludedSegment(const Data& data, DataCallback onData,
+                                          FailureCallback onFailure)
+{
+  BOOST_ASSERT(onData != nullptr);
+  m_onData = std::move(onData);
+  m_onFailure = std::move(onFailure);
+
+  Name dataName = data.getName();
+  m_prefix = dataName.getPrefix(-1);
+  m_excludeSegmentNo = dataName[-1].toSegment();
+
+  if (!data.getFinalBlockId().empty()) {
+    m_hasFinalBlockId = true;
+    m_lastSegmentNo = data.getFinalBlockId().toSegment();
+  }
+
+  // if the FinalBlockId is unknown, this could potentially request non-existent segments
+  for (size_t nRequestedSegments = 0; nRequestedSegments < m_options.maxPipelineSize;
+       nRequestedSegments++) {
+    if (!fetchNextSegment(nRequestedSegments)) // all segments have been requested
+      break;
+  }
+}
+
+bool
+PipelineInterests::fetchNextSegment(std::size_t pipeNo)
+{
+  if (m_hasFailure) {
+    fail("Fetching terminated but no final segment number has been found");
+    return false;
+  }
+
+  if (m_nextSegmentNo == m_excludeSegmentNo)
+    m_nextSegmentNo++;
+
+  if (m_hasFinalBlockId && m_nextSegmentNo > m_lastSegmentNo)
+   return false;
+
+  // Send interest for next segment
+  if (m_options.isVerbose)
+    std::cerr << "Requesting segment #" << m_nextSegmentNo << std::endl;
+
+  Interest interest(Name(m_prefix).appendSegment(m_nextSegmentNo));
+  interest.setInterestLifetime(m_options.interestLifetime);
+  interest.setMustBeFresh(m_options.mustBeFresh);
+  interest.setMaxSuffixComponents(1);
+
+  BOOST_ASSERT(!m_segmentFetchers[pipeNo].first || !m_segmentFetchers[pipeNo].first->isRunning());
+
+  auto fetcher = DataFetcher::fetch(m_face, interest,
+                                    m_options.maxRetriesOnTimeoutOrNack,
+                                    m_options.maxRetriesOnTimeoutOrNack,
+                                    bind(&PipelineInterests::handleData, this, _1, _2, pipeNo),
+                                    bind(&PipelineInterests::handleFail, this, _2, pipeNo),
+                                    bind(&PipelineInterests::handleFail, this, _2, pipeNo),
+                                    m_options.isVerbose);
+
+  m_segmentFetchers[pipeNo] = make_pair(fetcher, m_nextSegmentNo);
+
+  m_nextSegmentNo++;
+  return true;
+}
+
+void
+PipelineInterests::cancel()
+{
+  for (auto& fetcher : m_segmentFetchers)
+    if (fetcher.first)
+      fetcher.first->cancel();
+
+  m_segmentFetchers.clear();
+}
+
+void
+PipelineInterests::fail(const std::string& reason)
+{
+  if (!m_hasError) {
+    cancel();
+    m_hasError = true;
+    m_hasFailure = true;
+    if (m_onFailure)
+      m_face.getIoService().post([this, reason] { m_onFailure(reason); });
+  }
+}
+
+void
+PipelineInterests::handleData(const Interest& interest, const Data& data, size_t pipeNo)
+{
+  if (m_hasError)
+    return;
+
+  BOOST_ASSERT(data.getName().equals(interest.getName()));
+
+  if (m_options.isVerbose)
+    std::cerr << "Received segment #" << data.getName()[-1].toSegment() << std::endl;
+
+  m_onData(interest, data);
+
+  if (!m_hasFinalBlockId && !data.getFinalBlockId().empty()) {
+    m_lastSegmentNo = data.getFinalBlockId().toSegment();
+    m_hasFinalBlockId = true;
+
+    for (auto& fetcher : m_segmentFetchers) {
+      if (fetcher.first && fetcher.second > m_lastSegmentNo) {
+        // Stop trying to fetch segments that are not part of the content
+        fetcher.first->cancel();
+      }
+      else if (fetcher.first && fetcher.first->hasError()) { // fetcher.second <= m_lastSegmentNo
+        // there was an error while fetching a segment that is part of the content
+        fail("Failure retriving segment #" + to_string(fetcher.second));
+        return;
+      }
+    }
+  }
+
+  fetchNextSegment(pipeNo);
+}
+
+void PipelineInterests::handleFail(const std::string& reason, std::size_t pipeNo)
+{
+  if (m_hasError)
+    return;
+
+  if (m_hasFinalBlockId && m_segmentFetchers[pipeNo].second <= m_lastSegmentNo) {
+    fail(reason);
+  }
+  else if (!m_hasFinalBlockId) {
+    // don't fetch the following segments
+    bool areAllFetchersStopped = true;
+    for (auto& fetcher : m_segmentFetchers) {
+      if (fetcher.first && fetcher.second > m_segmentFetchers[pipeNo].second) {
+        fetcher.first->cancel();
+      }
+      else if (fetcher.first && fetcher.first->isRunning()) {
+        // fetcher.second <= m_segmentFetchers[pipeNo].second
+        areAllFetchersStopped = false;
+      }
+    }
+    if (areAllFetchersStopped) {
+      if (m_onFailure)
+        fail("Fetching terminated but no final segment number has been found");
+    }
+    else {
+      m_hasFailure = true;
+    }
+  }
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/catchunks/pipeline-interests.hpp b/tools/chunks/catchunks/pipeline-interests.hpp
new file mode 100644
index 0000000..32d5b79
--- /dev/null
+++ b/tools/chunks/catchunks/pipeline-interests.hpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_CATCHUNKS_PIPELINE_INTERESTS_HPP
+#define NDN_TOOLS_CHUNKS_CATCHUNKS_PIPELINE_INTERESTS_HPP
+
+#include "core/common.hpp"
+#include "options.hpp"
+
+namespace ndn {
+namespace chunks {
+
+class DataFetcher;
+
+class PipelineInterestsOptions : public Options
+{
+public:
+  explicit
+  PipelineInterestsOptions(const Options& options = Options())
+    : Options(options)
+    , maxPipelineSize(1)
+  {
+  }
+
+public:
+  size_t maxPipelineSize;
+};
+
+/**
+ * @brief Service for retrieving Data via an Interest pipeline
+ *
+ * Retrieves all segmented Data under the specified prefix by maintaining a pipeline of N Interests
+ * in flight.
+ *
+ * Provides retrieved Data on arrival with no ordering guarantees. Data is delivered to the
+ * PipelineInterests' user via callback immediately upon arrival.
+ */
+class PipelineInterests
+{
+public:
+  typedef PipelineInterestsOptions Options;
+  typedef function<void(const std::string& reason)> FailureCallback;
+
+public:
+  /**
+   * @brief create a PipelineInterests service
+   *
+   * Configures the pipelining service without specifying the retrieval namespace. After this
+   * configuration the method runWithExcludedSegment must be called to run the Pipeline.
+   */
+  explicit
+  PipelineInterests(Face& face, const Options& options = Options());
+
+  ~PipelineInterests();
+
+  /**
+   * @brief fetch all the segments between 0 and lastSegment of the specified prefix
+   *
+   * Starts the pipeline of size defined inside the options. The pipeline retrieves all the segments
+   * until the last segment is received, @p data is excluded from the retrieving.
+   *
+   * @param data a segment of the segmented Data to retrive; data.getName() must end with a segment
+   *        number
+   * @param onData callback for every segment correctly received, must not be empty
+   * @param onfailure callback called if an error occurs
+   */
+  void
+  runWithExcludedSegment(const Data& data, DataCallback onData, FailureCallback onFailure);
+
+  /**
+   * @brief stop all fetch operations
+   */
+  void
+  cancel();
+
+private:
+  /**
+   * @brief fetch the next segment that has not been requested yet
+   *
+   * @return false if there is an error or all the segments have been fetched, true otherwise
+   */
+  bool
+  fetchNextSegment(size_t pipeNo);
+
+  void
+  fail(const std::string& reason);
+
+  void
+  handleData(const Interest& interest, const Data& data, size_t pipeNo);
+
+  void
+  handleFail(const std::string& reason, size_t pipeNo);
+
+private:
+  Name m_prefix;
+  Face& m_face;
+  uint64_t m_nextSegmentNo;
+  uint64_t m_lastSegmentNo;
+  uint64_t m_excludeSegmentNo;
+  DataCallback m_onData;
+  FailureCallback m_onFailure;
+  const Options m_options;
+  std::vector<std::pair<shared_ptr<DataFetcher>, uint64_t>> m_segmentFetchers;
+  bool m_hasFinalBlockId;
+  /**
+   * true if there's a critical error
+   */
+  bool m_hasError;
+  /**
+   * true if one or more segmentFetcher failed, if lastSegmentNo is not set this is usually not a
+   * fatal error for the pipeline
+   */
+  bool m_hasFailure;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_CATCHUNKS_PIPELINE_INTERESTS_HPP
diff --git a/tools/chunks/putchunks/ndnputchunks.cpp b/tools/chunks/putchunks/ndnputchunks.cpp
new file mode 100644
index 0000000..202cd83
--- /dev/null
+++ b/tools/chunks/putchunks/ndnputchunks.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "core/version.hpp"
+#include "producer.hpp"
+
+namespace ndn {
+namespace chunks {
+
+static int
+main(int argc, char** argv)
+{
+  std::string programName = argv[0];
+  uint64_t freshnessPeriod = 10000;
+  bool printVersion = false;
+  size_t maxChunkSize = MAX_NDN_PACKET_SIZE >> 1;
+  std::string signingStr;
+  bool isVerbose = false;
+  std::string prefix;
+
+  namespace po = boost::program_options;
+  po::options_description visibleDesc("Options");
+  visibleDesc.add_options()
+    ("help,h",          "print this help message and exit")
+    ("freshness,f",     po::value<uint64_t>(&freshnessPeriod)->default_value(freshnessPeriod),
+                        "specify FreshnessPeriod, in milliseconds")
+    ("print-data-version,p",  po::bool_switch(&printVersion), "print Data version to the standard output")
+    ("size,s",          po::value<size_t>(&maxChunkSize)->default_value(maxChunkSize),
+                        "maximum chunk size, in bytes")
+    ("signing-info,S",  po::value<std::string>(&signingStr)->default_value(signingStr),
+                        "set signing information")
+    ("verbose,v",       po::bool_switch(&isVerbose), "turn on verbose output")
+    ("version,V",       "print program version and exit")
+    ;
+
+  po::options_description hiddenDesc("Hidden options");
+  hiddenDesc.add_options()
+    ("ndn-name,n", po::value<std::string>(&prefix), "NDN name for the served content");
+
+  po::positional_options_description p;
+  p.add("ndn-name", -1);
+
+  po::options_description optDesc("Allowed options");
+  optDesc.add(visibleDesc).add(hiddenDesc);
+
+  po::variables_map vm;
+  try {
+    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(p).run(), vm);
+    po::notify(vm);
+  }
+  catch (const po::error& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+  catch (const boost::bad_any_cast& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+
+  if (vm.count("help") > 0) {
+    std::cout << "Usage: " << programName << " [options] ndn:/name" << std::endl;
+    std::cout << visibleDesc;
+    return 0;
+  }
+
+  if (vm.count("version") > 0) {
+    std::cout << "ndnputchunks " << tools::VERSION << std::endl;
+    return 0;
+  }
+
+  if (prefix.empty()) {
+    std::cerr << "Usage: " << programName << " [options] ndn:/name" << std::endl;
+    std::cerr << visibleDesc;
+    return 2;
+  }
+
+  if (maxChunkSize < 1 || maxChunkSize > MAX_NDN_PACKET_SIZE) {
+    std::cerr << "ERROR: Maximum chunk size must be between 1 and " << MAX_NDN_PACKET_SIZE << std::endl;
+    return 2;
+  }
+
+  security::SigningInfo signingInfo;
+  try {
+    signingInfo = security::SigningInfo(signingStr);
+  }
+  catch (const std::invalid_argument& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 2;
+  }
+
+  try {
+    Face face;
+    KeyChain keyChain;
+    Producer producer(prefix, face, keyChain, signingInfo, time::milliseconds(freshnessPeriod),
+                      maxChunkSize, isVerbose, printVersion, std::cin);
+    producer.run();
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 1;
+  }
+
+  return 0;
+}
+
+} // namespace chunks
+} // namespace ndn
+
+int
+main(int argc, char** argv)
+{
+  return ndn::chunks::main(argc, argv);
+}
diff --git a/tools/chunks/putchunks/producer.cpp b/tools/chunks/putchunks/producer.cpp
new file mode 100644
index 0000000..141be5b
--- /dev/null
+++ b/tools/chunks/putchunks/producer.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#include "producer.hpp"
+
+namespace ndn {
+namespace chunks {
+
+Producer::Producer(const Name& prefix,
+                   Face& face,
+                   KeyChain& keyChain,
+                   const security::SigningInfo& signingInfo,
+                   time::milliseconds freshnessPeriod,
+                   size_t maxSegmentSize,
+                   bool isVerbose,
+                   bool needToPrintVersion,
+                   std::istream& is)
+  : m_face(face)
+  , m_keyChain(keyChain)
+  , m_signingInfo(signingInfo)
+  , m_freshnessPeriod(freshnessPeriod)
+  , m_maxSegmentSize(maxSegmentSize)
+  , m_isVerbose(isVerbose)
+{
+  if (prefix.size() > 0 && prefix[-1].isVersion()) {
+    m_prefix = prefix.getPrefix(-1);
+    m_versionedPrefix = prefix;
+  }
+  else {
+    m_prefix = prefix;
+    m_versionedPrefix = Name(m_prefix).appendVersion();
+  }
+
+  populateStore(is);
+
+  if (needToPrintVersion)
+    std::cout << m_versionedPrefix[-1] << std::endl;
+
+  m_face.setInterestFilter(m_prefix,
+                           bind(&Producer::onInterest, this, _2),
+                           RegisterPrefixSuccessCallback(),
+                           bind(&Producer::onRegisterFailed, this, _1, _2));
+
+  if (m_isVerbose)
+    std::cerr << "Data published with name: " << m_versionedPrefix << std::endl;
+}
+
+void
+Producer::run()
+{
+  m_face.processEvents();
+}
+
+void
+Producer::onInterest(const Interest& interest)
+{
+  BOOST_ASSERT(m_store.size() > 0);
+
+  if (m_isVerbose)
+    std::cerr << "Interest: " << interest << std::endl;
+
+  const Name& name = interest.getName();
+  shared_ptr<Data> data;
+
+  // is this a discovery Interest or a sequence retrieval?
+  if (name.size() == m_versionedPrefix.size() + 1 && m_versionedPrefix.isPrefixOf(name) &&
+      name[-1].isSegment()) {
+    const auto segmentNo = static_cast<size_t>(interest.getName()[-1].toSegment());
+    // specific segment retrieval
+    if (segmentNo < m_store.size()) {
+      data = m_store[segmentNo];
+    }
+  }
+  else if (interest.matchesData(*m_store[0])) {
+    // Interest has version and is looking for the first segment or has no version
+    data = m_store[0];
+  }
+
+  if (data != nullptr) {
+    if (m_isVerbose)
+      std::cerr << "Data: " << *data << std::endl;
+
+    m_face.put(*data);
+  }
+}
+
+void
+Producer::populateStore(std::istream& is)
+{
+  BOOST_ASSERT(m_store.size() == 0);
+
+  if (m_isVerbose)
+    std::cerr << "Loading input ..." << std::endl;
+
+  std::vector<uint8_t> buffer(m_maxSegmentSize);
+  while (is.good()) {
+    is.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
+    const auto nCharsRead = is.gcount();
+    if (nCharsRead > 0) {
+      auto data = make_shared<Data>(Name(m_versionedPrefix).appendSegment(m_store.size()));
+      data->setFreshnessPeriod(m_freshnessPeriod);
+      data->setContent(&buffer[0], nCharsRead);
+
+      m_store.push_back(data);
+    }
+  }
+
+  if (m_store.empty()) {
+    auto data = make_shared<Data>(Name(m_versionedPrefix).appendSegment(0));
+    data->setFreshnessPeriod(m_freshnessPeriod);
+    m_store.push_back(data);
+  }
+
+  auto finalBlockId = name::Component::fromSegment(m_store.size() - 1);
+  for (const auto& data : m_store) {
+    data->setFinalBlockId(finalBlockId);
+    m_keyChain.sign(*data, m_signingInfo);
+  }
+
+  if (m_isVerbose)
+    std::cerr << "Created " << m_store.size() << " chunks for prefix " << m_prefix << std::endl;
+}
+
+void
+Producer::onRegisterFailed(const Name& prefix, const std::string& reason)
+{
+  std::cerr << "ERROR: Failed to register prefix '"
+            << prefix << "' (" << reason << ")" << std::endl;
+  m_face.shutdown();
+}
+
+} // namespace chunks
+} // namespace ndn
diff --git a/tools/chunks/putchunks/producer.hpp b/tools/chunks/putchunks/producer.hpp
new file mode 100644
index 0000000..061a2bf
--- /dev/null
+++ b/tools/chunks/putchunks/producer.hpp
@@ -0,0 +1,100 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2016,  Regents of the University of California,
+ *                      Colorado State University,
+ *                      University Pierre & Marie Curie, Sorbonne University.
+ *
+ * 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Wentao Shang
+ * @author Steve DiBenedetto
+ * @author Andrea Tosatto
+ */
+
+#ifndef NDN_TOOLS_CHUNKS_PUTCHUNKS_PRODUCER_HPP
+#define NDN_TOOLS_CHUNKS_PUTCHUNKS_PRODUCER_HPP
+
+#include "core/common.hpp"
+
+namespace ndn {
+namespace chunks {
+
+/**
+ * @brief Segmented version Producer
+ *
+ * Packetizes and publishes data from an input stream under /prefix/<version>/<segment number>.
+ * The current time is used as the version number. The store has always at least one element (also
+ * with empty input stream).
+ */
+class Producer : noncopyable
+{
+public:
+  /**
+   * @brief Create the Producer
+   *
+   * @prefix prefix used to publish data, if the last component of prefix is not a version number
+   *         the current time is used as version number.
+   */
+  Producer(const Name& prefix, Face& face, KeyChain& keyChain,
+           const security::SigningInfo& signingInfo, time::milliseconds freshnessPeriod,
+           size_t maxSegmentSize, bool isVerbose = false, bool needToPrintVersion = false,
+           std::istream& is = std::cin);
+
+  /**
+   * @brief Run the Producer
+   */
+  void
+  run();
+
+private:
+  void
+  onInterest(const Interest& interest);
+
+  /**
+   * @brief Split the input stream in data packets and save them to the store
+   *
+   * Create data packets reading all the characters from the input stream until EOF, or an
+   * error occurs. Each data packet has a maximum payload size of m_maxSegmentSize value and is
+   * stored inside the vector m_store. An empty data packet is created and stored if the input
+   * stream is empty.
+   *
+   * @return Number of data packets contained in the store after the operation
+   */
+  void
+  populateStore(std::istream& is);
+
+  void
+  onRegisterFailed(const Name& prefix, const std::string& reason);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  std::vector<shared_ptr<Data>> m_store;
+
+private:
+  Name m_prefix;
+  Name m_versionedPrefix;
+  Face& m_face;
+  KeyChain& m_keyChain;
+  security::SigningInfo m_signingInfo;
+  time::milliseconds m_freshnessPeriod;
+  size_t m_maxSegmentSize;
+  bool m_isVerbose;
+};
+
+} // namespace chunks
+} // namespace ndn
+
+#endif // NDN_TOOLS_CHUNKS_PUTCHUNKS_PRODUCER_HPP
diff --git a/tools/chunks/wscript b/tools/chunks/wscript
new file mode 100644
index 0000000..56ff392
--- /dev/null
+++ b/tools/chunks/wscript
@@ -0,0 +1,30 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+top = '../..'
+
+def build(bld):
+
+    bld(features='cxx',
+        name='ndncatchunks-objects',
+        source=bld.path.ant_glob('catchunks/*.cpp', excl='catchunks/ndncatchunks.cpp'),
+        use='core-objects')
+
+    bld(features='cxx cxxprogram',
+        target='../../bin/ndncatchunks',
+        source='catchunks/ndncatchunks.cpp',
+        use='ndncatchunks-objects')
+
+    bld(features='cxx',
+        name='ndnputchunks-objects',
+        source=bld.path.ant_glob('putchunks/*.cpp', excl='putchunks/ndnputchunks.cpp'),
+        use='core-objects')
+
+    bld(features='cxx cxxprogram',
+        target='../../bin/ndnputchunks',
+        source='putchunks/ndnputchunks.cpp',
+        use='ndnputchunks-objects')
+
+    ## (for unit tests)
+
+    bld(name='chunks-objects',
+        use='ndncatchunks-objects ndnputchunks-objects')
+