catchunks: add --no-version-discovery option

Refs: #5021
Change-Id: I37bb1f86c1a6d63ab28fbe116e75d1047c110217
diff --git a/tests/chunks/consumer.t.cpp b/tests/chunks/consumer.t.cpp
index 9ae1ad8..671f1ce 100644
--- a/tests/chunks/consumer.t.cpp
+++ b/tests/chunks/consumer.t.cpp
@@ -42,21 +42,16 @@
 BOOST_AUTO_TEST_SUITE(Chunks)
 BOOST_AUTO_TEST_SUITE(TestConsumer)
 
-BOOST_AUTO_TEST_CASE(OutputDataSequential)
+BOOST_AUTO_TEST_CASE(InOrderData)
 {
-  // Test sequential segments in the right order
-  // Segment order: 0 1 2
+  // Segment order: 0 1 2 3
 
-  std::string name("/ndn/chunks/test");
-
-  std::vector<std::string> testStrings {
+  const std::string name("/ndn/chunks/test");
+  const 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 "
@@ -83,24 +78,20 @@
   }
 }
 
-BOOST_AUTO_TEST_CASE(OutputDataUnordered)
+BOOST_AUTO_TEST_CASE(OutOfOrderData)
 {
-  // Test unordered segments
   // Segment order: 1 0 2
 
-  std::string name("/ndn/chunks/test");
-
-  std::vector<std::string> testStrings {
+  const std::string name("/ndn/chunks/test");
+  const 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;
   output_test_stream output("");
@@ -166,17 +157,12 @@
   auto pipeline = make_unique<PipelineInterestsDummy>(face, options);
   auto pipelinePtr = pipeline.get();
 
+  BOOST_CHECK_EQUAL(pipelinePtr->isPipelineRunning, false);
+
   consumer.run(std::move(discover), std::move(pipeline));
+  this->advanceClocks(io, 1_ms);
 
-  this->advanceClocks(io, time::nanoseconds(1));
-  // no discovery interest is issued
-  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
-
-  // this Data packet answers the discovery Interest, so it must end with a version number
-  auto data = makeData(prefix.appendVersion(0));
-  face.receive(*data);
-
-  this->advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0); // no discovery Interests are issued
   BOOST_CHECK_EQUAL(pipelinePtr->isPipelineRunning, true);
 }
 
diff --git a/tests/chunks/discover-version.t.cpp b/tests/chunks/discover-version.t.cpp
index eb94f1d..9c3a844 100644
--- a/tests/chunks/discover-version.t.cpp
+++ b/tests/chunks/discover-version.t.cpp
@@ -44,50 +44,63 @@
   void
   run(const Name& prefix)
   {
-    BOOST_REQUIRE(!prefix.empty());
-
     discover = make_unique<DiscoverVersion>(face, prefix, opt);
     discover->onDiscoverySuccess.connect([this] (const Name& versionedName) {
-      BOOST_REQUIRE(!versionedName.empty() && versionedName[-1].isVersion());
-      discoveredVersion = versionedName[-1].toVersion();
       isDiscoveryFinished = true;
+      discoveredName = versionedName;
+      if (!versionedName.empty() && versionedName[-1].isVersion())
+        discoveredVersion = versionedName[-1].toVersion();
     });
     discover->onDiscoveryFailure.connect([this] (const std::string&) {
       isDiscoveryFinished = true;
     });
 
     discover->run();
-    advanceClocks(io, time::nanoseconds(1));
+    advanceClocks(io, 1_ns);
   }
 
 protected:
+  const Name name = "/ndn/chunks/test";
+  const uint64_t version = 1449227841747;
   boost::asio::io_service io;
   util::DummyClientFace face{io};
-  Name name = "/ndn/chunks/test";
   Options opt;
   unique_ptr<DiscoverVersion> discover;
+  optional<Name> discoveredName;
   optional<uint64_t> discoveredVersion;
   bool isDiscoveryFinished = false;
-  uint64_t version = 1449227841747;
 };
 
 BOOST_AUTO_TEST_SUITE(Chunks)
 BOOST_FIXTURE_TEST_SUITE(TestDiscoverVersion, DiscoverVersionFixture)
 
-BOOST_AUTO_TEST_CASE(VersionNumberIsProvided)
+BOOST_AUTO_TEST_CASE(Disabled)
 {
-  run(Name(name).appendVersion(version));
+  opt.disableVersionDiscovery = true;
+  run(name);
 
-  // no version discovery interest is expressed
+  // no version discovery Interest is expressed
   BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
 
-  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredName.value(), name);
+  BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
+}
+
+BOOST_AUTO_TEST_CASE(NameWithVersion)
+{
+  // start with a name that already contains a version component
+  Name versionedName = Name(name).appendVersion(version);
+  run(versionedName);
+
+  // no version discovery Interest is expressed
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+
+  BOOST_CHECK_EQUAL(discoveredName.value(), versionedName);
   BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
 }
 
-BOOST_AUTO_TEST_CASE(DiscoverySuccess)
+BOOST_AUTO_TEST_CASE(Success)
 {
-  // express a discovery Interest to learn Data version
   run(name);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
@@ -96,61 +109,66 @@
   auto lastInterest = face.sentInterests.back();
   BOOST_CHECK_EQUAL(lastInterest.getName(), discoveryInterest.getName());
 
-  // Send back a metadata packet with a valid versioned name
+  // send back a metadata packet with a valid versioned name
   MetadataObject mobject;
   mobject.setVersionedName(Name(name).appendVersion(version));
   face.receive(mobject.makeData(lastInterest.getName(), m_keyChain));
-  advanceClocks(io, time::nanoseconds(1));
+  advanceClocks(io, 1_ns);
 
-  BOOST_CHECK(isDiscoveryFinished);
   BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
 }
 
 BOOST_AUTO_TEST_CASE(InvalidDiscoveredVersionedName)
 {
-  // issue a discovery Interest to learn Data version
   run(name);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
 
-  // Send back a metadata packet with an invalid versioned name
+  // send back a metadata packet with an invalid versioned name
   MetadataObject mobject;
   mobject.setVersionedName(name);
   face.receive(mobject.makeData(face.sentInterests.back().getName(), m_keyChain));
 
   // finish discovery process without a resolved version number
-  BOOST_CHECK(isDiscoveryFinished);
-  BOOST_CHECK(!discoveredVersion.has_value());
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+  BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
 }
 
 BOOST_AUTO_TEST_CASE(InvalidMetadataPacket)
 {
-  // issue a discovery Interest to learn Data version
   run(name);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
 
-  // Send back an invalid metadata packet
+  // send back an invalid metadata packet
   Data data(face.sentInterests.back().getName());
   data.setFreshnessPeriod(1_s);
   data.setContentType(tlv::ContentType_Key);
   face.receive(signData(data));
 
   // finish discovery process without a resolved version number
-  BOOST_CHECK(isDiscoveryFinished);
-  BOOST_CHECK(!discoveredVersion.has_value());
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+  BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
 }
 
-BOOST_AUTO_TEST_CASE(Timeout1)
+BOOST_AUTO_TEST_CASE(MaxRetriesExceeded)
 {
-  // issue a discovery Interest to learn Data version
+  opt.maxRetriesOnTimeoutOrNack = 3;
   run(name);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
 
-  // timeout discovery Interests
-  for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack; ++retries) {
-    advanceClocks(io, opt.interestLifetime);
+  // timeout or nack discovery Interests
+  for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack * 2; ++retries) {
+    if (retries % 2 == 0) {
+      advanceClocks(io, opt.interestLifetime);
+    }
+    else {
+      face.receive(makeNack(face.sentInterests.back(), lp::NackReason::DUPLICATE));
+      advanceClocks(io, 1_ns);
+    }
 
     BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
     BOOST_REQUIRE_EQUAL(face.sentInterests.size(), retries + 2);
@@ -160,20 +178,27 @@
   advanceClocks(io, opt.interestLifetime);
 
   // finish discovery process without a resolved version number
-  BOOST_CHECK(isDiscoveryFinished);
-  BOOST_CHECK(!discoveredVersion.has_value());
+  BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+  BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+  BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
 }
 
-BOOST_AUTO_TEST_CASE(Timeout2)
+BOOST_AUTO_TEST_CASE(SuccessAfterNackAndTimeout)
 {
-  // issue a discovery Interest to learn Data version
+  opt.maxRetriesOnTimeoutOrNack = 3;
   run(name);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
 
-  // timeout discovery Interests
-  for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack; ++retries) {
-    advanceClocks(io, opt.interestLifetime);
+  // timeout or nack discovery Interests
+  for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack * 2; ++retries) {
+    if (retries % 2 == 0) {
+      advanceClocks(io, opt.interestLifetime);
+    }
+    else {
+      face.receive(makeNack(face.sentInterests.back(), lp::NackReason::DUPLICATE));
+      advanceClocks(io, 1_ns);
+    }
 
     BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
     BOOST_REQUIRE_EQUAL(face.sentInterests.size(), retries + 2);
@@ -183,9 +208,8 @@
   MetadataObject mobject;
   mobject.setVersionedName(Name(name).appendVersion(version));
   face.receive(mobject.makeData(face.sentInterests.back().getName(), m_keyChain));
-  advanceClocks(io, time::nanoseconds(1));
+  advanceClocks(io, 1_ns);
 
-  BOOST_CHECK(isDiscoveryFinished);
   BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
 }
 
diff --git a/tools/chunks/catchunks/discover-version.cpp b/tools/chunks/catchunks/discover-version.cpp
index 6bedd05..1efe3b2 100644
--- a/tools/chunks/catchunks/discover-version.cpp
+++ b/tools/chunks/catchunks/discover-version.cpp
@@ -44,7 +44,7 @@
 void
 DiscoverVersion::run()
 {
-  if (!m_prefix.empty() && m_prefix[-1].isVersion()) {
+  if (m_options.disableVersionDiscovery || (!m_prefix.empty() && m_prefix[-1].isVersion())) {
     onDiscoverySuccess(m_prefix);
     return;
   }
diff --git a/tools/chunks/catchunks/main.cpp b/tools/chunks/catchunks/main.cpp
index 1af58e6..39b808c 100644
--- a/tools/chunks/catchunks/main.cpp
+++ b/tools/chunks/catchunks/main.cpp
@@ -60,11 +60,14 @@
     ("help,h",      "print this help message and exit")
     ("pipeline-type,p", po::value<std::string>(&pipelineType)->default_value(pipelineType),
                         "type of Interest pipeline to use; valid values are: 'fixed', 'aimd', 'cubic'")
-    ("fresh,f",     po::bool_switch(&options.mustBeFresh), "only return fresh content")
+    ("fresh,f",     po::bool_switch(&options.mustBeFresh),
+                    "only return fresh content (set MustBeFresh on all outgoing Interests)")
     ("lifetime,l",  po::value<time::milliseconds::rep>()->default_value(options.interestLifetime.count()),
                     "lifetime of expressed Interests, in milliseconds")
     ("retries,r",   po::value<int>(&options.maxRetriesOnTimeoutOrNack)->default_value(options.maxRetriesOnTimeoutOrNack),
                     "maximum number of retries in case of Nack or timeout (-1 = no limit)")
+    ("no-version-discovery,D", po::bool_switch(&options.disableVersionDiscovery),
+                    "skip version discovery, even if the supplied name does not end with a version component")
     ("quiet,q",     po::bool_switch(&options.isQuiet), "suppress all diagnostic output, except fatal errors")
     ("verbose,v",   po::bool_switch(&options.isVerbose), "turn on verbose output (per segment information")
     ("version,V",   "print program version and exit")
@@ -79,7 +82,7 @@
   po::options_description adaptivePipeDesc("Adaptive pipeline options (AIMD & CUBIC)");
   adaptivePipeDesc.add_options()
     ("ignore-marks", po::bool_switch(&options.ignoreCongMarks),
-                     "do not decrease the window after receiving a congestion mark")
+                     "do not reduce the window after receiving a congestion mark")
     ("disable-cwa",  po::bool_switch(&options.disableCwa),
                      "disable Conservative Window Adaptation, i.e., reduce the window on "
                      "each timeout or congestion mark instead of at most once per RTT")
diff --git a/tools/chunks/catchunks/options.hpp b/tools/chunks/catchunks/options.hpp
index 70bedee..0f432b3 100644
--- a/tools/chunks/catchunks/options.hpp
+++ b/tools/chunks/catchunks/options.hpp
@@ -37,6 +37,7 @@
   // Common options
   time::milliseconds interestLifetime = DEFAULT_INTEREST_LIFETIME;
   int maxRetriesOnTimeoutOrNack = 15;
+  bool disableVersionDiscovery = false;
   bool mustBeFresh = false;
   bool isQuiet = false;
   bool isVerbose = false;
diff --git a/tools/chunks/catchunks/pipeline-interests.cpp b/tools/chunks/catchunks/pipeline-interests.cpp
index d86f155..ad8b818 100644
--- a/tools/chunks/catchunks/pipeline-interests.cpp
+++ b/tools/chunks/catchunks/pipeline-interests.cpp
@@ -51,8 +51,10 @@
 void
 PipelineInterests::run(const Name& versionedName, DataCallback dataCb, FailureCallback failureCb)
 {
-  BOOST_ASSERT(!versionedName.empty() && versionedName[-1].isVersion());
+  BOOST_ASSERT(m_options.disableVersionDiscovery ||
+               (!versionedName.empty() && versionedName[-1].isVersion()));
   BOOST_ASSERT(dataCb != nullptr);
+
   m_prefix = versionedName;
   m_onData = std::move(dataCb);
   m_onFailure = std::move(failureCb);