Rename ndncatchunks to ndnget
Change-Id: I260e552746e900a73c2ce773bd91d9b6fa384734
diff --git a/tools/get/README.md b/tools/get/README.md
new file mode 100644
index 0000000..90774c9
--- /dev/null
+++ b/tools/get/README.md
@@ -0,0 +1,44 @@
+# ndnget
+
+**ndnget** is a consumer program that fetches the segments (Data packets) of a named object,
+optionally discovering the latest version of the object, and then writes the content of the
+retrieved object to the standard output.
+
+## Version discovery in ndnget
+
+If a version component is present at the end of the user-specified NDN name, the provided version
+number will be used, without any version discovery process. Otherwise, discovery Interest(s) will
+be sent out to fetch metadata of the solicited content from which the Data version will be resolved.
+For more information about the packet format and naming conventions of Interest and Data packets
+used for version discovery in ndnget, please refer to:
+[Realtime Data Retrieval (RDR) protocol](https://redmine.named-data.net/projects/ndn-tlv/wiki/RDR).
+
+## Interest pipeline types in ndnget
+
+* `fixed`: maintains a fixed-size window of Interests in flight; the window size is configurable
+ via a command line option and defaults to 1.
+
+* `aimd` : adjusts the window size via additive-increase/multiplicative-decrease (AIMD).
+ By default, it uses a Conservative Window Adaptation, that is, the congestion window
+ will be decreased at most once per round-trip-time.
+
+* `cubic`: adjusts the window size similar to the TCP CUBIC algorithm.
+ For details about both aimd and cubic please refer to:
+ [A Practical Congestion Control Scheme for Named Data
+ Networking](https://conferences2.sigcomm.org/acm-icn/2016/proceedings/p21-schneider.pdf).
+
+The default Interest pipeline type is `cubic`.
+
+## Usage examples
+
+To retrieve the latest version of a published object, the following command can be used:
+
+ ndnget /localhost/demo/gpl3
+
+To fetch a specific version of a published object, you can specify the version number at the end
+of the name. For example, if the version is known to be 1449078495094, the following command
+will fetch that exact version of the object (without version discovery):
+
+ ndnget -Nt /localhost/demo/gpl3/v=1449078495094
+
+For more information, run the programs with `--help` as argument.
diff --git a/tools/get/consumer.cpp b/tools/get/consumer.cpp
new file mode 100644
index 0000000..10b9d04
--- /dev/null
+++ b/tools/get/consumer.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 <ndn-cxx/util/exception.hpp>
+
+namespace ndn::get {
+
+Consumer::Consumer(security::Validator& validator, std::ostream& os)
+ : m_validator(validator)
+ , m_outputStream(os)
+{
+}
+
+void
+Consumer::run(std::unique_ptr<DiscoverVersion> discover, std::unique_ptr<PipelineInterests> pipeline)
+{
+ m_discover = std::move(discover);
+ m_pipeline = std::move(pipeline);
+ m_nextToPrint = 0;
+ m_bufferedData.clear();
+
+ m_discover->onDiscoverySuccess.connect([this] (const Name& versionedName) {
+ m_pipeline->run(versionedName,
+ FORWARD_TO_MEM_FN(handleData),
+ [] (const std::string& msg) { NDN_THROW(std::runtime_error(msg)); });
+ });
+ m_discover->onDiscoveryFailure.connect([] (const std::string& msg) {
+ NDN_THROW(std::runtime_error(msg));
+ });
+ m_discover->run();
+}
+
+void
+Consumer::handleData(const Data& data)
+{
+ auto dataPtr = data.shared_from_this();
+
+ m_validator.validate(data,
+ [this, dataPtr] (const Data& data) {
+ if (data.getContentType() == ndn::tlv::ContentType_Nack) {
+ NDN_THROW(ApplicationNackError(data));
+ }
+
+ // 'data' passed to callback comes from DataValidationState and was not created with make_shared
+ m_bufferedData[getSegmentFromPacket(data)] = dataPtr;
+ writeInOrderData();
+ },
+ [] (const Data&, const security::ValidationError& error) {
+ NDN_THROW(DataValidationError(error));
+ });
+}
+
+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 ndn::get
diff --git a/tools/get/consumer.hpp b/tools/get/consumer.hpp
new file mode 100644
index 0000000..9e40dba
--- /dev/null
+++ b/tools/get/consumer.hpp
@@ -0,0 +1,106 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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_GET_CONSUMER_HPP
+#define NDN_TOOLS_GET_CONSUMER_HPP
+
+#include "discover-version.hpp"
+#include "pipeline-interests.hpp"
+
+#include <ndn-cxx/security/validation-error.hpp>
+#include <ndn-cxx/security/validator.hpp>
+
+#include <boost/lexical_cast.hpp>
+#include <iostream>
+#include <map>
+
+namespace ndn::get {
+
+/**
+ * @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))
+ {
+ }
+ };
+
+ class DataValidationError : public std::runtime_error
+ {
+ public:
+ explicit
+ DataValidationError(const security::ValidationError& error)
+ : std::runtime_error(boost::lexical_cast<std::string>(error))
+ {
+ }
+ };
+
+ /**
+ * @brief Create the consumer
+ */
+ explicit
+ Consumer(security::Validator& validator, std::ostream& os = std::cout);
+
+ /**
+ * @brief Run the consumer
+ */
+ void
+ run(std::unique_ptr<DiscoverVersion> discover, std::unique_ptr<PipelineInterests> pipeline);
+
+private:
+ void
+ handleData(const Data& data);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ void
+ writeInOrderData();
+
+private:
+ security::Validator& m_validator;
+ std::ostream& m_outputStream;
+ std::unique_ptr<DiscoverVersion> m_discover;
+ std::unique_ptr<PipelineInterests> m_pipeline;
+ uint64_t m_nextToPrint = 0;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ std::map<uint64_t, std::shared_ptr<const Data>> m_bufferedData;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_CONSUMER_HPP
diff --git a/tools/get/data-fetcher.cpp b/tools/get/data-fetcher.cpp
new file mode 100644
index 0000000..1f3506a
--- /dev/null
+++ b/tools/get/data-fetcher.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 <boost/lexical_cast.hpp>
+
+#include <cmath>
+#include <iostream>
+
+namespace ndn::get {
+
+std::shared_ptr<DataFetcher>
+DataFetcher::fetch(Face& face, const Interest& interest, int maxNackRetries, int maxTimeoutRetries,
+ DataCallback onData, FailureCallback onNack, FailureCallback onTimeout,
+ bool isVerbose)
+{
+ auto dataFetcher = std::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.getIoContext())
+ , m_onData(std::move(onData))
+ , m_onNack(std::move(onNack))
+ , m_onTimeout(std::move(onTimeout))
+ , m_maxNackRetries(maxNackRetries)
+ , m_maxTimeoutRetries(maxTimeoutRetries)
+ , m_isVerbose(isVerbose)
+{
+ BOOST_ASSERT(m_onData != nullptr);
+}
+
+void
+DataFetcher::cancel()
+{
+ if (isRunning()) {
+ m_isStopped = true;
+ m_pendingInterest.cancel();
+ m_scheduler.cancelAllEvents();
+ }
+}
+
+void
+DataFetcher::expressInterest(const Interest& interest, const std::shared_ptr<DataFetcher>& self)
+{
+ m_nCongestionRetries = 0;
+ m_pendingInterest = m_face.expressInterest(interest,
+ [this, self] (auto&&... args) { handleData(std::forward<decltype(args)>(args)..., self); },
+ [this, self] (auto&&... args) { handleNack(std::forward<decltype(args)>(args)..., self); },
+ [this, self] (auto&&... args) { handleTimeout(std::forward<decltype(args)>(args)..., self); });
+}
+
+void
+DataFetcher::handleData(const Interest& interest, const Data& data,
+ const std::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 std::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 << "\n";
+
+ 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.schedule(backoffTime, [this, newInterest, self] {
+ expressInterest(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 (" + std::to_string(m_maxNackRetries) +
+ ") while retrieving data for " + interest.getName().toUri());
+ }
+}
+
+void
+DataFetcher::handleTimeout(const Interest& interest, const std::shared_ptr<DataFetcher>& self)
+{
+ if (!isRunning())
+ return;
+
+ if (m_maxTimeoutRetries != MAX_RETRIES_INFINITE)
+ ++m_nTimeouts;
+
+ if (m_isVerbose)
+ std::cerr << "Timeout for Interest " << interest << "\n";
+
+ 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 (" + std::to_string(m_maxTimeoutRetries) +
+ ") while retrieving data for " + interest.getName().toUri());
+ }
+}
+
+} // namespace ndn::get
diff --git a/tools/get/data-fetcher.hpp b/tools/get/data-fetcher.hpp
new file mode 100644
index 0000000..fa968a8
--- /dev/null
+++ b/tools/get/data-fetcher.hpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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_GET_DATA_FETCHER_HPP
+#define NDN_TOOLS_GET_DATA_FETCHER_HPP
+
+#include "core/common.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+#include <functional>
+
+namespace ndn::get {
+
+/**
+ * @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 constexpr int MAX_RETRIES_INFINITE = -1;
+
+ /**
+ * @brief ceiling value for backoff time used in congestion handling
+ */
+ static constexpr time::milliseconds MAX_CONGESTION_BACKOFF_TIME = 10_s;
+
+ using FailureCallback = std::function<void(const Interest& interest, const std::string& reason)>;
+
+ /**
+ * @brief instantiate a DataFetcher object and start fetching data
+ *
+ * @param onData callback for segment correctly received, must not be empty
+ */
+ static std::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 std::shared_ptr<DataFetcher>& self);
+
+ void
+ handleData(const Interest& interest, const Data& data, const std::shared_ptr<DataFetcher>& self);
+
+ void
+ handleNack(const Interest& interest, const lp::Nack& nack, const std::shared_ptr<DataFetcher>& self);
+
+ void
+ handleTimeout(const Interest& interest, const std::shared_ptr<DataFetcher>& self);
+
+private:
+ Face& m_face;
+ Scheduler m_scheduler;
+ PendingInterestHandle m_pendingInterest;
+ DataCallback m_onData;
+ FailureCallback m_onNack;
+ FailureCallback m_onTimeout;
+
+ int m_maxNackRetries;
+ int m_maxTimeoutRetries;
+ int m_nNacks = 0;
+ int m_nTimeouts = 0;
+ uint32_t m_nCongestionRetries = 0;
+
+ bool m_isVerbose = false;
+ bool m_isStopped = false;
+ bool m_hasError = false;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_DATA_FETCHER_HPP
diff --git a/tools/get/discover-version.cpp b/tools/get/discover-version.cpp
new file mode 100644
index 0000000..530e469
--- /dev/null
+++ b/tools/get/discover-version.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Chavoosh Ghasemi
+ */
+
+#include "discover-version.hpp"
+#include "data-fetcher.hpp"
+
+#include <ndn-cxx/metadata-object.hpp>
+
+#include <iostream>
+
+namespace ndn::get {
+
+DiscoverVersion::DiscoverVersion(Face& face, const Name& prefix, const Options& options)
+ : m_face(face)
+ , m_prefix(prefix)
+ , m_options(options)
+{
+}
+
+void
+DiscoverVersion::run()
+{
+ if (m_options.disableVersionDiscovery || (!m_prefix.empty() && m_prefix[-1].isVersion())) {
+ onDiscoverySuccess(m_prefix);
+ return;
+ }
+
+ Interest interest = MetadataObject::makeDiscoveryInterest(m_prefix)
+ .setInterestLifetime(m_options.interestLifetime);
+
+ m_fetcher = DataFetcher::fetch(m_face, interest,
+ m_options.maxRetriesOnTimeoutOrNack,
+ m_options.maxRetriesOnTimeoutOrNack,
+ FORWARD_TO_MEM_FN(handleData),
+ [this] (const auto&, const auto& reason) {
+ onDiscoveryFailure(reason);
+ },
+ [this] (const auto&, const auto& reason) {
+ onDiscoveryFailure(reason);
+ },
+ m_options.isVerbose);
+}
+
+void
+DiscoverVersion::handleData(const Interest& interest, const Data& data)
+{
+ if (m_options.isVerbose)
+ std::cerr << "Data: " << data << "\n";
+
+ // make a metadata object from received metadata packet
+ MetadataObject mobject;
+ try {
+ mobject = MetadataObject(data);
+ }
+ catch (const tlv::Error& e) {
+ onDiscoveryFailure("Invalid metadata packet: "s + e.what());
+ return;
+ }
+
+ if (mobject.getVersionedName().empty() || !mobject.getVersionedName()[-1].isVersion()) {
+ onDiscoveryFailure(mobject.getVersionedName().toUri() + " is not a valid versioned name");
+ return;
+ }
+
+ if (m_options.isVerbose) {
+ std::cerr << "Discovered Data version: " << mobject.getVersionedName()[-1] << "\n";
+ }
+
+ onDiscoverySuccess(mobject.getVersionedName());
+}
+
+} // namespace ndn::get
diff --git a/tools/get/discover-version.hpp b/tools/get/discover-version.hpp
new file mode 100644
index 0000000..99eaa18
--- /dev/null
+++ b/tools/get/discover-version.hpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Chavoosh Ghasemi
+ */
+
+#ifndef NDN_TOOLS_GET_DISCOVER_VERSION_HPP
+#define NDN_TOOLS_GET_DISCOVER_VERSION_HPP
+
+#include "options.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/util/signal.hpp>
+
+namespace ndn::get {
+
+class DataFetcher;
+
+/**
+ * @brief Service 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
+{
+public:
+ DiscoverVersion(Face& face, const Name& prefix, const Options& options);
+
+ /**
+ * @brief Signal emitted when the versioned name of Data is found.
+ */
+ signal::Signal<DiscoverVersion, Name> onDiscoverySuccess;
+
+ /**
+ * @brief Signal emitted when a failure occurs.
+ */
+ signal::Signal<DiscoverVersion, std::string> onDiscoveryFailure;
+
+ void
+ run();
+
+private:
+ void
+ handleData(const Interest& interest, const Data& data);
+
+private:
+ Face& m_face;
+ const Name m_prefix;
+ const Options& m_options;
+ std::shared_ptr<DataFetcher> m_fetcher;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_DISCOVER_VERSION_HPP
diff --git a/tools/get/main.cpp b/tools/get/main.cpp
new file mode 100644
index 0000000..bd1a0e8
--- /dev/null
+++ b/tools/get/main.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Davide Pesavento
+ * @author Weiwei Liu
+ * @author Klaus Schneider
+ * @author Chavoosh Ghasemi
+ */
+
+#include "consumer.hpp"
+#include "discover-version.hpp"
+#include "pipeline-interests-aimd.hpp"
+#include "pipeline-interests-cubic.hpp"
+#include "pipeline-interests-fixed.hpp"
+#include "statistics-collector.hpp"
+#include "core/version.hpp"
+
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/util/rtt-estimator.hpp>
+
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/parsers.hpp>
+#include <boost/program_options/variables_map.hpp>
+
+#include <fstream>
+#include <iostream>
+
+namespace ndn::get {
+
+namespace po = boost::program_options;
+
+static int
+main(int argc, char* argv[])
+{
+ const std::string programName(argv[0]);
+
+ Options options;
+ std::string prefix, nameConv, pipelineType("cubic");
+ std::string cwndPath, rttPath;
+ auto rttEstOptions = std::make_shared<util::RttEstimator::Options>();
+ rttEstOptions->k = 8; // increased from the ndn-cxx default of 4
+
+ po::options_description basicDesc("Basic Options");
+ basicDesc.add_options()
+ ("help,h", "print this help message and exit")
+ ("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)")
+ ("pipeline-type,p", po::value<std::string>(&pipelineType)->default_value(pipelineType),
+ "type of Interest pipeline to use; valid values are: 'fixed', 'aimd', 'cubic'")
+ ("no-version-discovery,D", po::bool_switch(&options.disableVersionDiscovery),
+ "skip version discovery even if the name does not end with a version component")
+ ("naming-convention,N", po::value<std::string>(&nameConv),
+ "encoding convention to use for name components, either 'marker' or 'typed'")
+ ("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")
+ ;
+
+ po::options_description fixedPipeDesc("Fixed pipeline options");
+ fixedPipeDesc.add_options()
+ ("pipeline-size,s", po::value<size_t>(&options.maxPipelineSize)->default_value(options.maxPipelineSize),
+ "size of the Interest pipeline")
+ ;
+
+ po::options_description adaptivePipeDesc("Adaptive pipeline options (AIMD & CUBIC)");
+ adaptivePipeDesc.add_options()
+ ("ignore-marks", po::bool_switch(&options.ignoreCongMarks),
+ "do not reduce the window after receiving a congestion mark")
+ ("disable-cwa", po::bool_switch(&options.disableCwa),
+ "disable Conservative Window Adaptation (reduce the window "
+ "on each congestion event instead of at most once per RTT)")
+ ("init-cwnd", po::value<double>(&options.initCwnd)->default_value(options.initCwnd),
+ "initial congestion window in segments")
+ ("init-ssthresh", po::value<double>(&options.initSsthresh),
+ "initial slow start threshold in segments (defaults to infinity)")
+ ("rto-alpha", po::value<double>(&rttEstOptions->alpha)->default_value(rttEstOptions->alpha),
+ "alpha value for RTO calculation")
+ ("rto-beta", po::value<double>(&rttEstOptions->beta)->default_value(rttEstOptions->beta),
+ "beta value for RTO calculation")
+ ("rto-k", po::value<int>(&rttEstOptions->k)->default_value(rttEstOptions->k),
+ "k value for RTO calculation")
+ ("min-rto", po::value<time::milliseconds::rep>()->default_value(
+ time::duration_cast<time::milliseconds>(rttEstOptions->minRto).count()),
+ "minimum RTO value, in milliseconds")
+ ("max-rto", po::value<time::milliseconds::rep>()->default_value(
+ time::duration_cast<time::milliseconds>(rttEstOptions->maxRto).count()),
+ "maximum RTO value, in milliseconds")
+ ("log-cwnd", po::value<std::string>(&cwndPath), "log file for congestion window stats")
+ ("log-rtt", po::value<std::string>(&rttPath), "log file for round-trip time stats")
+ ;
+
+ po::options_description aimdPipeDesc("AIMD pipeline options");
+ aimdPipeDesc.add_options()
+ ("aimd-step", po::value<double>(&options.aiStep)->default_value(options.aiStep),
+ "additive increase step")
+ ("aimd-beta", po::value<double>(&options.mdCoef)->default_value(options.mdCoef),
+ "multiplicative decrease factor")
+ ("reset-cwnd-to-init", po::bool_switch(&options.resetCwndToInit),
+ "after a congestion event, reset the window to the "
+ "initial value instead of resetting to ssthresh")
+ ;
+
+ po::options_description cubicPipeDesc("CUBIC pipeline options");
+ cubicPipeDesc.add_options()
+ ("cubic-beta", po::value<double>(&options.cubicBeta), "window decrease factor (defaults to 0.7)")
+ ("fast-conv", po::bool_switch(&options.enableFastConv), "enable fast convergence")
+ ;
+
+ po::options_description visibleDesc;
+ visibleDesc.add(basicDesc)
+ .add(fixedPipeDesc)
+ .add(adaptivePipeDesc)
+ .add(aimdPipeDesc)
+ .add(cubicPipeDesc);
+
+ po::options_description hiddenDesc;
+ hiddenDesc.add_options()
+ ("name", po::value<std::string>(&prefix), "NDN name of the requested content");
+
+ po::options_description optDesc;
+ optDesc.add(visibleDesc).add(hiddenDesc);
+
+ po::positional_options_description p;
+ p.add("name", -1);
+
+ 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() << "\n";
+ return 2;
+ }
+ catch (const boost::bad_any_cast& e) {
+ std::cerr << "ERROR: " << e.what() << "\n";
+ return 2;
+ }
+
+ if (vm.count("help") > 0) {
+ std::cout << "Usage: " << programName << " [options] ndn:/name\n";
+ std::cout << visibleDesc;
+ return 0;
+ }
+
+ if (vm.count("version") > 0) {
+ std::cout << "ndnget " << tools::VERSION << "\n";
+ return 0;
+ }
+
+ if (prefix.empty()) {
+ std::cerr << "Usage: " << programName << " [options] ndn:/name\n";
+ std::cerr << visibleDesc;
+ return 2;
+ }
+
+ if (nameConv == "marker" || nameConv == "m" || nameConv == "1") {
+ name::setConventionEncoding(name::Convention::MARKER);
+ }
+ else if (nameConv == "typed" || nameConv == "t" || nameConv == "2") {
+ name::setConventionEncoding(name::Convention::TYPED);
+ }
+ else if (!nameConv.empty()) {
+ std::cerr << "ERROR: '" << nameConv << "' is not a valid naming convention\n";
+ return 2;
+ }
+
+ options.interestLifetime = time::milliseconds(vm["lifetime"].as<time::milliseconds::rep>());
+ if (options.interestLifetime < 0_ms) {
+ std::cerr << "ERROR: --lifetime cannot be negative\n";
+ return 2;
+ }
+
+ if (options.maxRetriesOnTimeoutOrNack < -1 || options.maxRetriesOnTimeoutOrNack > 1024) {
+ std::cerr << "ERROR: --retries must be between -1 and 1024\n";
+ return 2;
+ }
+
+ if (options.isQuiet && options.isVerbose) {
+ std::cerr << "ERROR: cannot be quiet and verbose at the same time\n";
+ return 2;
+ }
+
+ if (options.maxPipelineSize < 1 || options.maxPipelineSize > 1024) {
+ std::cerr << "ERROR: --pipeline-size must be between 1 and 1024\n";
+ return 2;
+ }
+
+ if (rttEstOptions->k < 0) {
+ std::cerr << "ERROR: --rto-k cannot be negative\n";
+ return 2;
+ }
+
+ rttEstOptions->minRto = time::milliseconds(vm["min-rto"].as<time::milliseconds::rep>());
+ if (rttEstOptions->minRto < 0_ms) {
+ std::cerr << "ERROR: --min-rto cannot be negative\n";
+ return 2;
+ }
+
+ rttEstOptions->maxRto = time::milliseconds(vm["max-rto"].as<time::milliseconds::rep>());
+ if (rttEstOptions->maxRto < rttEstOptions->minRto) {
+ std::cerr << "ERROR: --max-rto cannot be smaller than --min-rto\n";
+ return 2;
+ }
+
+ try {
+ Face face;
+ auto discover = std::make_unique<DiscoverVersion>(face, Name(prefix), options);
+ std::unique_ptr<PipelineInterests> pipeline;
+ std::unique_ptr<StatisticsCollector> statsCollector;
+ std::unique_ptr<RttEstimatorWithStats> rttEstimator;
+ std::ofstream statsFileCwnd;
+ std::ofstream statsFileRtt;
+
+ if (pipelineType == "fixed") {
+ pipeline = std::make_unique<PipelineInterestsFixed>(face, options);
+ }
+ else if (pipelineType == "aimd" || pipelineType == "cubic") {
+ if (options.isVerbose) {
+ using namespace ndn::time;
+ std::cerr << "RTT estimator parameters:\n"
+ << "\tAlpha = " << rttEstOptions->alpha << "\n"
+ << "\tBeta = " << rttEstOptions->beta << "\n"
+ << "\tK = " << rttEstOptions->k << "\n"
+ << "\tInitial RTO = " << duration_cast<milliseconds>(rttEstOptions->initialRto) << "\n"
+ << "\tMin RTO = " << duration_cast<milliseconds>(rttEstOptions->minRto) << "\n"
+ << "\tMax RTO = " << duration_cast<milliseconds>(rttEstOptions->maxRto) << "\n"
+ << "\tBackoff multiplier = " << rttEstOptions->rtoBackoffMultiplier << "\n";
+ }
+ rttEstimator = std::make_unique<RttEstimatorWithStats>(std::move(rttEstOptions));
+
+ std::unique_ptr<PipelineInterestsAdaptive> adaptivePipeline;
+ if (pipelineType == "aimd") {
+ adaptivePipeline = std::make_unique<PipelineInterestsAimd>(face, *rttEstimator, options);
+ }
+ else {
+ adaptivePipeline = std::make_unique<PipelineInterestsCubic>(face, *rttEstimator, options);
+ }
+
+ if (!cwndPath.empty() || !rttPath.empty()) {
+ if (!cwndPath.empty()) {
+ statsFileCwnd.open(cwndPath);
+ if (statsFileCwnd.fail()) {
+ std::cerr << "ERROR: failed to open '" << cwndPath << "'\n";
+ return 4;
+ }
+ }
+ if (!rttPath.empty()) {
+ statsFileRtt.open(rttPath);
+ if (statsFileRtt.fail()) {
+ std::cerr << "ERROR: failed to open '" << rttPath << "'\n";
+ return 4;
+ }
+ }
+ statsCollector = std::make_unique<StatisticsCollector>(*adaptivePipeline, statsFileCwnd, statsFileRtt);
+ }
+
+ pipeline = std::move(adaptivePipeline);
+ }
+ else {
+ std::cerr << "ERROR: '" << pipelineType << "' is not a valid pipeline type\n";
+ return 2;
+ }
+
+ Consumer consumer(security::getAcceptAllValidator());
+ BOOST_ASSERT(discover != nullptr);
+ BOOST_ASSERT(pipeline != nullptr);
+ consumer.run(std::move(discover), std::move(pipeline));
+ face.processEvents();
+ }
+ catch (const Consumer::ApplicationNackError& e) {
+ std::cerr << "ERROR: " << e.what() << "\n";
+ return 3;
+ }
+ catch (const Consumer::DataValidationError& e) {
+ std::cerr << "ERROR: " << e.what() << "\n";
+ return 5;
+ }
+ catch (const std::exception& e) {
+ std::cerr << "ERROR: " << e.what() << "\n";
+ return 1;
+ }
+
+ return 0;
+}
+
+} // namespace ndn::get
+
+int
+main(int argc, char* argv[])
+{
+ return ndn::get::main(argc, argv);
+}
diff --git a/tools/get/options.hpp b/tools/get/options.hpp
new file mode 100644
index 0000000..5b65540
--- /dev/null
+++ b/tools/get/options.hpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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_GET_OPTIONS_HPP
+#define NDN_TOOLS_GET_OPTIONS_HPP
+
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/util/time.hpp>
+
+#include <limits>
+
+namespace ndn::get {
+
+struct Options
+{
+ // Common options
+ time::milliseconds interestLifetime = DEFAULT_INTEREST_LIFETIME;
+ int maxRetriesOnTimeoutOrNack = 15;
+ bool disableVersionDiscovery = false;
+ bool mustBeFresh = false;
+ bool isQuiet = false;
+ bool isVerbose = false;
+
+ // Fixed pipeline options
+ size_t maxPipelineSize = 1;
+
+ // Adaptive pipeline common options
+ double initCwnd = 2.0; ///< initial congestion window size
+ double initSsthresh = std::numeric_limits<double>::max(); ///< initial slow start threshold
+ time::milliseconds rtoCheckInterval{10}; ///< interval for checking retransmission timer
+ bool ignoreCongMarks = false; ///< disable window decrease after receiving congestion mark
+ bool disableCwa = false; ///< disable conservative window adaptation
+
+ // AIMD pipeline options
+ double aiStep = 1.0; ///< AIMD additive increase step (in segments)
+ double mdCoef = 0.5; ///< AIMD multiplicative decrease factor
+ bool resetCwndToInit = false; ///< reduce cwnd to initCwnd when loss event occurs
+
+ // Cubic pipeline options
+ double cubicBeta = 0.7; ///< cubic multiplicative decrease factor
+ bool enableFastConv = false; ///< use cubic fast convergence
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_OPTIONS_HPP
diff --git a/tools/get/pipeline-interests-adaptive.cpp b/tools/get/pipeline-interests-adaptive.cpp
new file mode 100644
index 0000000..96949ef
--- /dev/null
+++ b/tools/get/pipeline-interests-adaptive.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Shuo Yang
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#include "pipeline-interests-adaptive.hpp"
+#include "data-fetcher.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <iomanip>
+#include <iostream>
+
+namespace ndn::get {
+
+PipelineInterestsAdaptive::PipelineInterestsAdaptive(Face& face,
+ RttEstimatorWithStats& rttEstimator,
+ const Options& opts)
+ : PipelineInterests(face, opts)
+ , m_cwnd(m_options.initCwnd)
+ , m_ssthresh(m_options.initSsthresh)
+ , m_rttEstimator(rttEstimator)
+ , m_scheduler(m_face.getIoContext())
+{
+}
+
+PipelineInterestsAdaptive::~PipelineInterestsAdaptive()
+{
+ cancel();
+}
+
+void
+PipelineInterestsAdaptive::doRun()
+{
+ if (allSegmentsReceived()) {
+ cancel();
+ if (!m_options.isQuiet) {
+ printSummary();
+ }
+ return;
+ }
+
+ // schedule the event to check retransmission timer
+ m_checkRtoEvent = m_scheduler.schedule(m_options.rtoCheckInterval, [this] { checkRto(); });
+
+ schedulePackets();
+}
+
+void
+PipelineInterestsAdaptive::doCancel()
+{
+ m_checkRtoEvent.cancel();
+ m_segmentInfo.clear();
+}
+
+void
+PipelineInterestsAdaptive::checkRto()
+{
+ if (isStopping())
+ return;
+
+ bool hasTimeout = false;
+ uint64_t highTimeoutSeg = 0;
+
+ for (auto& entry : m_segmentInfo) {
+ SegmentInfo& segInfo = entry.second;
+ if (segInfo.state != SegmentState::InRetxQueue) { // skip segments already in the retx queue
+ auto timeElapsed = time::steady_clock::now() - segInfo.timeSent;
+ if (timeElapsed > segInfo.rto) { // timer expired?
+ m_nTimeouts++;
+ hasTimeout = true;
+ highTimeoutSeg = std::max(highTimeoutSeg, entry.first);
+ enqueueForRetransmission(entry.first);
+ }
+ }
+ }
+
+ if (hasTimeout) {
+ recordTimeout(highTimeoutSeg);
+ schedulePackets();
+ }
+
+ // schedule the next check after predefined interval
+ m_checkRtoEvent = m_scheduler.schedule(m_options.rtoCheckInterval, [this] { checkRto(); });
+}
+
+void
+PipelineInterestsAdaptive::sendInterest(uint64_t segNo, bool isRetransmission)
+{
+ if (isStopping())
+ return;
+
+ if (m_hasFinalBlockId && segNo > m_lastSegmentNo)
+ return;
+
+ if (!isRetransmission && m_hasFailure)
+ return;
+
+ if (m_options.isVerbose) {
+ std::cerr << (isRetransmission ? "Retransmitting" : "Requesting")
+ << " segment #" << segNo << "\n";
+ }
+
+ if (isRetransmission) {
+ // keep track of retx count for this segment
+ auto ret = m_retxCount.emplace(segNo, 1);
+ if (!ret.second) { // not the first retransmission
+ m_retxCount[segNo] += 1;
+ if (m_options.maxRetriesOnTimeoutOrNack != DataFetcher::MAX_RETRIES_INFINITE &&
+ m_retxCount[segNo] > m_options.maxRetriesOnTimeoutOrNack) {
+ return handleFail(segNo, "Reached the maximum number of retries (" +
+ std::to_string(m_options.maxRetriesOnTimeoutOrNack) +
+ ") while retrieving segment #" + std::to_string(segNo));
+ }
+
+ if (m_options.isVerbose) {
+ std::cerr << "# of retries for segment #" << segNo
+ << " is " << m_retxCount[segNo] << "\n";
+ }
+ }
+ }
+
+ auto interest = Interest()
+ .setName(Name(m_prefix).appendSegment(segNo))
+ .setMustBeFresh(m_options.mustBeFresh)
+ .setInterestLifetime(m_options.interestLifetime);
+
+ SegmentInfo& segInfo = m_segmentInfo[segNo];
+ segInfo.interestHdl = m_face.expressInterest(interest,
+ FORWARD_TO_MEM_FN(handleData),
+ FORWARD_TO_MEM_FN(handleNack),
+ FORWARD_TO_MEM_FN(handleLifetimeExpiration));
+ segInfo.timeSent = time::steady_clock::now();
+ segInfo.rto = m_rttEstimator.getEstimatedRto();
+
+ m_nInFlight++;
+ m_nSent++;
+
+ if (isRetransmission) {
+ segInfo.state = SegmentState::Retransmitted;
+ m_nRetransmitted++;
+ }
+ else {
+ m_highInterest = segNo;
+ segInfo.state = SegmentState::FirstTimeSent;
+ }
+}
+
+void
+PipelineInterestsAdaptive::schedulePackets()
+{
+ BOOST_ASSERT(m_nInFlight >= 0);
+ auto availableWindowSize = static_cast<int64_t>(m_cwnd) - m_nInFlight;
+
+ while (availableWindowSize > 0) {
+ if (!m_retxQueue.empty()) { // do retransmission first
+ uint64_t retxSegNo = m_retxQueue.front();
+ m_retxQueue.pop();
+ if (m_segmentInfo.count(retxSegNo) == 0) {
+ m_nSkippedRetx++;
+ continue;
+ }
+ // the segment is still in the map, that means it needs to be retransmitted
+ sendInterest(retxSegNo, true);
+ }
+ else { // send next segment
+ sendInterest(getNextSegmentNo(), false);
+ }
+ availableWindowSize--;
+ }
+}
+
+void
+PipelineInterestsAdaptive::handleData(const Interest& interest, const Data& data)
+{
+ if (isStopping())
+ return;
+
+ // Interest was expressed with CanBePrefix=false
+ BOOST_ASSERT(data.getName().equals(interest.getName()));
+
+ if (!m_hasFinalBlockId && data.getFinalBlock()) {
+ m_lastSegmentNo = data.getFinalBlock()->toSegment();
+ m_hasFinalBlockId = true;
+ cancelInFlightSegmentsGreaterThan(m_lastSegmentNo);
+ if (m_hasFailure && m_lastSegmentNo >= m_failedSegNo) {
+ // previously failed segment is part of the content
+ return onFailure(m_failureReason);
+ }
+ else {
+ m_hasFailure = false;
+ }
+ }
+
+ uint64_t recvSegNo = getSegmentFromPacket(data);
+ auto segIt = m_segmentInfo.find(recvSegNo);
+ if (segIt == m_segmentInfo.end()) {
+ return; // ignore already-received segment
+ }
+
+ SegmentInfo& segInfo = segIt->second;
+ time::nanoseconds rtt = time::steady_clock::now() - segInfo.timeSent;
+ if (m_options.isVerbose) {
+ std::cerr << "Received segment #" << recvSegNo
+ << ", rtt=" << rtt.count() / 1e6 << "ms"
+ << ", rto=" << segInfo.rto.count() / 1e6 << "ms\n";
+ }
+
+ m_highData = std::max(m_highData, recvSegNo);
+
+ // for segments in retx queue, we must not decrement m_nInFlight
+ // because it was already decremented when the segment timed out
+ if (segInfo.state != SegmentState::InRetxQueue) {
+ m_nInFlight--;
+ }
+
+ // upon finding congestion mark, decrease the window size
+ // without retransmitting any packet
+ if (data.getCongestionMark() > 0) {
+ m_nCongMarks++;
+ if (!m_options.ignoreCongMarks) {
+ if (m_options.disableCwa || m_highData > m_recPoint) {
+ m_recPoint = m_highInterest; // react to only one congestion event (timeout or congestion mark)
+ // per RTT (conservative window adaptation)
+ m_nMarkDecr++;
+ decreaseWindow();
+
+ if (m_options.isVerbose) {
+ std::cerr << "Received congestion mark, value = " << data.getCongestionMark()
+ << ", new cwnd = " << m_cwnd << "\n";
+ }
+ }
+ }
+ else {
+ increaseWindow();
+ }
+ }
+ else {
+ increaseWindow();
+ }
+
+ onData(data);
+
+ // do not sample RTT for retransmitted segments
+ if ((segInfo.state == SegmentState::FirstTimeSent ||
+ segInfo.state == SegmentState::InRetxQueue) &&
+ m_retxCount.count(recvSegNo) == 0) {
+ auto nExpectedSamples = std::max<int64_t>((m_nInFlight + 1) >> 1, 1);
+ BOOST_ASSERT(nExpectedSamples > 0);
+ m_rttEstimator.addMeasurement(rtt, static_cast<size_t>(nExpectedSamples));
+ afterRttMeasurement({recvSegNo, rtt,
+ m_rttEstimator.getSmoothedRtt(),
+ m_rttEstimator.getRttVariation(),
+ m_rttEstimator.getEstimatedRto()});
+ }
+
+ // remove the entry associated with the received segment
+ m_segmentInfo.erase(segIt);
+
+ if (allSegmentsReceived()) {
+ cancel();
+ if (!m_options.isQuiet) {
+ printSummary();
+ }
+ }
+ else {
+ schedulePackets();
+ }
+}
+
+void
+PipelineInterestsAdaptive::handleNack(const Interest& interest, const lp::Nack& nack)
+{
+ if (isStopping())
+ return;
+
+ if (m_options.isVerbose)
+ std::cerr << "Received Nack with reason " << nack.getReason()
+ << " for Interest " << interest << "\n";
+
+ uint64_t segNo = getSegmentFromPacket(interest);
+
+ switch (nack.getReason()) {
+ case lp::NackReason::DUPLICATE:
+ // ignore duplicates
+ break;
+ case lp::NackReason::CONGESTION:
+ // treated the same as timeout for now
+ enqueueForRetransmission(segNo);
+ recordTimeout(segNo);
+ schedulePackets();
+ break;
+ default:
+ handleFail(segNo, "Could not retrieve data for " + interest.getName().toUri() +
+ ", reason: " + boost::lexical_cast<std::string>(nack.getReason()));
+ break;
+ }
+}
+
+void
+PipelineInterestsAdaptive::handleLifetimeExpiration(const Interest& interest)
+{
+ if (isStopping())
+ return;
+
+ m_nTimeouts++;
+
+ uint64_t segNo = getSegmentFromPacket(interest);
+ enqueueForRetransmission(segNo);
+ recordTimeout(segNo);
+ schedulePackets();
+}
+
+void
+PipelineInterestsAdaptive::recordTimeout(uint64_t segNo)
+{
+ if (m_options.disableCwa || segNo > m_recPoint) {
+ // interests that are still outstanding during a timeout event
+ // should not trigger another window decrease later (bug #5202)
+ m_recPoint = m_highInterest;
+
+ decreaseWindow();
+ m_rttEstimator.backoffRto();
+ m_nLossDecr++;
+
+ if (m_options.isVerbose) {
+ std::cerr << "Packet loss event, new cwnd = " << m_cwnd
+ << ", ssthresh = " << m_ssthresh << "\n";
+ }
+ }
+}
+
+void
+PipelineInterestsAdaptive::enqueueForRetransmission(uint64_t segNo)
+{
+ BOOST_ASSERT(m_nInFlight > 0);
+ m_nInFlight--;
+ m_retxQueue.push(segNo);
+ m_segmentInfo.at(segNo).state = SegmentState::InRetxQueue;
+}
+
+void
+PipelineInterestsAdaptive::handleFail(uint64_t segNo, const std::string& reason)
+{
+ if (isStopping())
+ return;
+
+ // if the failed segment is definitely part of the content, raise a fatal error
+ if (m_hasFinalBlockId && segNo <= m_lastSegmentNo)
+ return onFailure(reason);
+
+ if (!m_hasFinalBlockId) {
+ m_segmentInfo.erase(segNo);
+ m_nInFlight--;
+
+ if (m_segmentInfo.empty()) {
+ onFailure("Fetching terminated but no final segment number has been found");
+ }
+ else {
+ cancelInFlightSegmentsGreaterThan(segNo);
+ m_hasFailure = true;
+ m_failedSegNo = segNo;
+ m_failureReason = reason;
+ }
+ }
+}
+
+void
+PipelineInterestsAdaptive::cancelInFlightSegmentsGreaterThan(uint64_t segNo)
+{
+ for (auto it = m_segmentInfo.begin(); it != m_segmentInfo.end();) {
+ // cancel fetching all segments that follow
+ if (it->first > segNo) {
+ it = m_segmentInfo.erase(it);
+ m_nInFlight--;
+ }
+ else {
+ ++it;
+ }
+ }
+}
+
+void
+PipelineInterestsAdaptive::printOptions() const
+{
+ PipelineInterests::printOptions();
+ std::cerr
+ << "\tInitial congestion window size = " << m_options.initCwnd << "\n"
+ << "\tInitial slow start threshold = " << m_options.initSsthresh << "\n"
+ << "\tAdditive increase step = " << m_options.aiStep << "\n"
+ << "\tMultiplicative decrease factor = " << m_options.mdCoef << "\n"
+ << "\tRTO check interval = " << m_options.rtoCheckInterval << "\n"
+ << "\tReact to congestion marks = " << (m_options.ignoreCongMarks ? "no" : "yes") << "\n"
+ << "\tConservative window adaptation = " << (m_options.disableCwa ? "no" : "yes") << "\n"
+ << "\tResetting window to " << (m_options.resetCwndToInit ?
+ "initial value" : "ssthresh") << " upon loss event\n";
+}
+
+void
+PipelineInterestsAdaptive::printSummary() const
+{
+ PipelineInterests::printSummary();
+ std::cerr << "Congestion marks: " << m_nCongMarks << " (caused " << m_nMarkDecr << " window decreases)\n"
+ << "Timeouts: " << m_nTimeouts << " (caused " << m_nLossDecr << " window decreases)\n"
+ << "Retransmitted segments: " << m_nRetransmitted
+ << " (" << (m_nSent == 0 ? 0 : (m_nRetransmitted * 100.0 / m_nSent)) << "%)"
+ << ", skipped: " << m_nSkippedRetx << "\n"
+ << "RTT ";
+
+ if (m_rttEstimator.getMinRtt() == time::nanoseconds::max() ||
+ m_rttEstimator.getMaxRtt() == time::nanoseconds::min()) {
+ std::cerr << "stats unavailable\n";
+ }
+ else {
+ std::cerr << "min/avg/max = " << std::fixed << std::setprecision(3)
+ << m_rttEstimator.getMinRtt().count() / 1e6 << "/"
+ << m_rttEstimator.getAvgRtt().count() / 1e6 << "/"
+ << m_rttEstimator.getMaxRtt().count() / 1e6 << " ms\n";
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, SegmentState state)
+{
+ switch (state) {
+ case SegmentState::FirstTimeSent:
+ os << "FirstTimeSent";
+ break;
+ case SegmentState::InRetxQueue:
+ os << "InRetxQueue";
+ break;
+ case SegmentState::Retransmitted:
+ os << "Retransmitted";
+ break;
+ }
+ return os;
+}
+
+} // namespace ndn::get
diff --git a/tools/get/pipeline-interests-adaptive.hpp b/tools/get/pipeline-interests-adaptive.hpp
new file mode 100644
index 0000000..23e1138
--- /dev/null
+++ b/tools/get/pipeline-interests-adaptive.hpp
@@ -0,0 +1,231 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Shuo Yang
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#ifndef NDN_TOOLS_GET_PIPELINE_INTERESTS_ADAPTIVE_HPP
+#define NDN_TOOLS_GET_PIPELINE_INTERESTS_ADAPTIVE_HPP
+
+#include "pipeline-interests.hpp"
+
+#include <ndn-cxx/util/rtt-estimator.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+#include <ndn-cxx/util/signal.hpp>
+
+#include <queue>
+#include <unordered_map>
+
+namespace ndn::get {
+
+using util::RttEstimatorWithStats;
+
+/**
+ * @brief indicates the state of the segment
+ */
+enum class SegmentState {
+ FirstTimeSent, ///< segment has been sent for the first time
+ InRetxQueue, ///< segment is in retransmission queue
+ Retransmitted, ///< segment has been retransmitted
+};
+
+std::ostream&
+operator<<(std::ostream& os, SegmentState state);
+
+/**
+ * @brief Wraps up information that's necessary for segment transmission
+ */
+struct SegmentInfo
+{
+ ScopedPendingInterestHandle interestHdl;
+ time::steady_clock::time_point timeSent;
+ time::nanoseconds rto;
+ SegmentState state;
+};
+
+/**
+ * @brief Service for retrieving Data via an Interest pipeline
+ *
+ * Retrieves all segmented Data under the specified prefix by maintaining a dynamic
+ * congestion window combined with a Conservative Loss Adaptation algorithm. For details,
+ * please refer to the description in section "Interest pipeline types in ndnget" of
+ * tools/get/README.md
+ *
+ * Provides retrieved Data on arrival with no ordering guarantees. Data is delivered to the
+ * PipelineInterests' user via callback immediately upon arrival.
+ */
+class PipelineInterestsAdaptive : public PipelineInterests
+{
+public:
+ /**
+ * @brief Constructor.
+ *
+ * Configures the pipelining service without specifying the retrieval namespace. After this
+ * configuration the method run must be called to start the Pipeline.
+ */
+ PipelineInterestsAdaptive(Face& face, RttEstimatorWithStats& rttEstimator, const Options& opts);
+
+ ~PipelineInterestsAdaptive() override;
+
+ /**
+ * @brief Signals when the congestion window changes.
+ *
+ * The callback function should be: `void(nanoseconds age, double cwnd)`, where `age` is the
+ * time since the pipeline started and `cwnd` is the new congestion window size (in segments).
+ */
+ signal::Signal<PipelineInterestsAdaptive, time::nanoseconds, double> afterCwndChange;
+
+ struct RttSample
+ {
+ uint64_t segNum; ///< segment number on which this sample was taken
+ time::nanoseconds rtt; ///< measured RTT
+ time::nanoseconds sRtt; ///< smoothed RTT
+ time::nanoseconds rttVar; ///< RTT variation
+ time::nanoseconds rto; ///< retransmission timeout
+ };
+
+ /**
+ * @brief Signals when a new RTT sample has been taken.
+ */
+ signal::Signal<PipelineInterestsAdaptive, RttSample> afterRttMeasurement;
+
+protected:
+ DECLARE_SIGNAL_EMIT(afterCwndChange)
+
+ void
+ printOptions() const;
+
+private:
+ /**
+ * @brief Increase congestion window.
+ */
+ virtual void
+ increaseWindow() = 0;
+
+ /**
+ * @brief Decrease congestion window.
+ */
+ virtual void
+ decreaseWindow() = 0;
+
+private:
+ /**
+ * @brief Fetch all the segments between 0 and lastSegment of the specified prefix.
+ *
+ * Starts the pipeline with an adaptive window algorithm to control the window size.
+ * The pipeline will fetch every segment until the last segment is successfully received
+ * or an error occurs.
+ */
+ void
+ doRun() final;
+
+ /**
+ * @brief Stop all fetch operations.
+ */
+ void
+ doCancel() final;
+
+ /**
+ * @brief Check RTO for all sent-but-not-acked segments.
+ */
+ void
+ checkRto();
+
+ /**
+ * @param segNo the segment # of the to-be-sent Interest
+ * @param isRetransmission true if this is a retransmission
+ */
+ void
+ sendInterest(uint64_t segNo, bool isRetransmission);
+
+ void
+ schedulePackets();
+
+ void
+ handleData(const Interest& interest, const Data& data);
+
+ void
+ handleNack(const Interest& interest, const lp::Nack& nack);
+
+ void
+ handleLifetimeExpiration(const Interest& interest);
+
+ void
+ recordTimeout(uint64_t segNo);
+
+ void
+ enqueueForRetransmission(uint64_t segNo);
+
+ void
+ handleFail(uint64_t segNo, const std::string& reason);
+
+ void
+ cancelInFlightSegmentsGreaterThan(uint64_t segNo);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ void
+ printSummary() const final;
+
+PUBLIC_WITH_TESTS_ELSE_PROTECTED:
+ static constexpr double MIN_SSTHRESH = 2.0;
+
+ double m_cwnd; ///< current congestion window size (in segments)
+ double m_ssthresh; ///< current slow start threshold
+ RttEstimatorWithStats& m_rttEstimator;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ Scheduler m_scheduler;
+ scheduler::ScopedEventId m_checkRtoEvent;
+
+ uint64_t m_highData = 0; ///< the highest segment number of the Data packet the consumer has received so far
+ uint64_t m_highInterest = 0; ///< the highest segment number of the Interests the consumer has sent so far
+ uint64_t m_recPoint = 0; ///< the value of m_highInterest when a packet loss event occurred,
+ ///< it remains fixed until the next packet loss event happens
+
+ int64_t m_nInFlight = 0; ///< # of segments in flight
+ int64_t m_nLossDecr = 0; ///< # of window decreases caused by packet loss
+ int64_t m_nMarkDecr = 0; ///< # of window decreases caused by congestion marks
+ int64_t m_nTimeouts = 0; ///< # of timed out segments
+ int64_t m_nSkippedRetx = 0; ///< # of segments queued for retransmission but received before the
+ ///< retransmission occurred
+ int64_t m_nRetransmitted = 0; ///< # of retransmitted segments
+ int64_t m_nCongMarks = 0; ///< # of data packets with congestion mark
+ int64_t m_nSent = 0; ///< # of interest packets sent out (including retransmissions)
+
+ std::unordered_map<uint64_t, SegmentInfo> m_segmentInfo; ///< keeps all the internal information
+ ///< on sent but not acked segments
+ std::unordered_map<uint64_t, int> m_retxCount; ///< maps segment number to its retransmission count;
+ ///< if the count reaches to the maximum number of
+ ///< timeout/nack retries, the pipeline will be aborted
+ std::queue<uint64_t> m_retxQueue;
+
+ bool m_hasFailure = false;
+ uint64_t m_failedSegNo = 0;
+ std::string m_failureReason;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_PIPELINE_INTERESTS_ADAPTIVE_HPP
diff --git a/tools/get/pipeline-interests-aimd.cpp b/tools/get/pipeline-interests-aimd.cpp
new file mode 100644
index 0000000..37beb58
--- /dev/null
+++ b/tools/get/pipeline-interests-aimd.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Shuo Yang
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#include "pipeline-interests-aimd.hpp"
+
+#include <cmath>
+
+namespace ndn::get {
+
+PipelineInterestsAimd::PipelineInterestsAimd(Face& face, RttEstimatorWithStats& rttEstimator,
+ const Options& opts)
+ : PipelineInterestsAdaptive(face, rttEstimator, opts)
+{
+ if (m_options.isVerbose) {
+ printOptions();
+ }
+}
+
+void
+PipelineInterestsAimd::increaseWindow()
+{
+ if (m_cwnd < m_ssthresh) {
+ m_cwnd += m_options.aiStep; // additive increase
+ }
+ else {
+ m_cwnd += m_options.aiStep / std::floor(m_cwnd); // congestion avoidance
+ }
+
+ emitSignal(afterCwndChange, time::steady_clock::now() - getStartTime(), m_cwnd);
+}
+
+void
+PipelineInterestsAimd::decreaseWindow()
+{
+ // please refer to RFC 5681, Section 3.1 for the rationale behind it
+ m_ssthresh = std::max(MIN_SSTHRESH, m_cwnd * m_options.mdCoef); // multiplicative decrease
+ m_cwnd = m_options.resetCwndToInit ? m_options.initCwnd : m_ssthresh;
+
+ emitSignal(afterCwndChange, time::steady_clock::now() - getStartTime(), m_cwnd);
+}
+
+} // namespace ndn::get
diff --git a/tools/get/pipeline-interests-aimd.hpp b/tools/get/pipeline-interests-aimd.hpp
new file mode 100644
index 0000000..858db87
--- /dev/null
+++ b/tools/get/pipeline-interests-aimd.hpp
@@ -0,0 +1,54 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Shuo Yang
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#ifndef NDN_TOOLS_GET_PIPELINE_INTERESTS_AIMD_HPP
+#define NDN_TOOLS_GET_PIPELINE_INTERESTS_AIMD_HPP
+
+#include "pipeline-interests-adaptive.hpp"
+
+namespace ndn::get {
+
+/**
+ * @brief Implements AIMD window increase and decrease.
+ */
+class PipelineInterestsAimd final : public PipelineInterestsAdaptive
+{
+public:
+ PipelineInterestsAimd(Face& face, RttEstimatorWithStats& rttEstimator, const Options& opts);
+
+private:
+ void
+ increaseWindow() final;
+
+ void
+ decreaseWindow() final;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_PIPELINE_INTERESTS_AIMD_HPP
diff --git a/tools/get/pipeline-interests-cubic.cpp b/tools/get/pipeline-interests-cubic.cpp
new file mode 100644
index 0000000..a74e602
--- /dev/null
+++ b/tools/get/pipeline-interests-cubic.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Klaus Schneider
+ */
+
+#include "pipeline-interests-cubic.hpp"
+
+#include <cmath>
+#include <iostream>
+
+namespace ndn::get {
+
+constexpr double CUBIC_C = 0.4;
+
+PipelineInterestsCubic::PipelineInterestsCubic(Face& face, RttEstimatorWithStats& rttEstimator,
+ const Options& opts)
+ : PipelineInterestsAdaptive(face, rttEstimator, opts)
+ , m_lastDecrease(time::steady_clock::now())
+{
+ if (m_options.isVerbose) {
+ printOptions();
+ std::cerr << "\tCubic beta = " << m_options.cubicBeta << "\n"
+ << "\tFast convergence = " << (m_options.enableFastConv ? "yes" : "no") << "\n";
+ }
+}
+
+void
+PipelineInterestsCubic::increaseWindow()
+{
+ // Slow start phase
+ if (m_cwnd < m_ssthresh) {
+ m_cwnd += 1.0;
+ }
+ // Congestion avoidance phase
+ else {
+ // If wmax is still 0, set it to the current cwnd. Usually unnecessary,
+ // if m_ssthresh is large enough.
+ if (m_wmax < m_options.initCwnd) {
+ m_wmax = m_cwnd;
+ }
+
+ // 1. Time since last congestion event in seconds
+ const double t = (time::steady_clock::now() - m_lastDecrease).count() / 1e9;
+
+ // 2. Time it takes to increase the window to m_wmax = the cwnd right before the last
+ // window decrease.
+ // K = cubic_root(wmax*(1-beta_cubic)/C) (Eq. 2)
+ const double k = std::cbrt(m_wmax * (1 - m_options.cubicBeta) / CUBIC_C);
+
+ // 3. Target: W_cubic(t) = C*(t-K)^3 + wmax (Eq. 1)
+ const double wCubic = CUBIC_C * std::pow(t - k, 3) + m_wmax;
+
+ // 4. Estimate of Reno Increase (Eq. 4)
+ const double rtt = m_rttEstimator.getSmoothedRtt().count() / 1e9;
+ const double wEst = m_wmax * m_options.cubicBeta +
+ (3 * (1 - m_options.cubicBeta) / (1 + m_options.cubicBeta)) * (t / rtt);
+
+ // Actual adaptation
+ double cubicIncrement = std::max(wCubic, wEst) - m_cwnd;
+ // Cubic increment must be positive
+ // Note: This change is not part of the RFC, but I added it to improve performance.
+ cubicIncrement = std::max(0.0, cubicIncrement);
+
+ m_cwnd += cubicIncrement / m_cwnd;
+ }
+
+ emitSignal(afterCwndChange, time::steady_clock::now() - getStartTime(), m_cwnd);
+}
+
+void
+PipelineInterestsCubic::decreaseWindow()
+{
+ // A flow remembers the last value of wmax,
+ // before it updates wmax for the current congestion event.
+
+ // Current wmax < last_wmax
+ if (m_options.enableFastConv && m_cwnd < m_lastWmax) {
+ m_lastWmax = m_cwnd;
+ m_wmax = m_cwnd * (1.0 + m_options.cubicBeta) / 2.0;
+ }
+ else {
+ // Save old cwnd as wmax
+ m_lastWmax = m_cwnd;
+ m_wmax = m_cwnd;
+ }
+
+ m_ssthresh = std::max(m_options.initCwnd, m_cwnd * m_options.cubicBeta);
+ m_cwnd = m_ssthresh;
+ m_lastDecrease = time::steady_clock::now();
+
+ emitSignal(afterCwndChange, time::steady_clock::now() - getStartTime(), m_cwnd);
+}
+
+} // namespace ndn::get
diff --git a/tools/get/pipeline-interests-cubic.hpp b/tools/get/pipeline-interests-cubic.hpp
new file mode 100644
index 0000000..8451fd3
--- /dev/null
+++ b/tools/get/pipeline-interests-cubic.hpp
@@ -0,0 +1,59 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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 Klaus Schneider
+ */
+
+#ifndef NDN_TOOLS_GET_PIPELINE_INTERESTS_CUBIC_HPP
+#define NDN_TOOLS_GET_PIPELINE_INTERESTS_CUBIC_HPP
+
+#include "pipeline-interests-adaptive.hpp"
+
+namespace ndn::get {
+
+/**
+ * @brief Implements Cubic window increase and decrease.
+ *
+ * This implementation follows the RFC8312 https://tools.ietf.org/html/rfc8312
+ * and the Linux kernel implementation https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_cubic.c
+ */
+class PipelineInterestsCubic final : public PipelineInterestsAdaptive
+{
+public:
+ PipelineInterestsCubic(Face& face, RttEstimatorWithStats& rttEstimator, const Options& opts);
+
+private:
+ void
+ increaseWindow() final;
+
+ void
+ decreaseWindow() final;
+
+private:
+ double m_wmax = 0.0; ///< window size before last window decrease
+ double m_lastWmax = 0.0; ///< last wmax
+ time::steady_clock::time_point m_lastDecrease; ///< time of last window decrease
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_PIPELINE_INTERESTS_CUBIC_HPP
diff --git a/tools/get/pipeline-interests-fixed.cpp b/tools/get/pipeline-interests-fixed.cpp
new file mode 100644
index 0000000..4d26a04
--- /dev/null
+++ b/tools/get/pipeline-interests-fixed.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Davide Pesavento
+ * @author Chavoosh Ghasemi
+ */
+
+#include "pipeline-interests-fixed.hpp"
+#include "data-fetcher.hpp"
+
+#include <iostream>
+
+namespace ndn::get {
+
+PipelineInterestsFixed::PipelineInterestsFixed(Face& face, const Options& opts)
+ : PipelineInterests(face, opts)
+{
+ m_segmentFetchers.resize(m_options.maxPipelineSize);
+
+ if (m_options.isVerbose) {
+ printOptions();
+ std::cerr << "\tPipeline size = " << m_options.maxPipelineSize << "\n";
+ }
+}
+
+PipelineInterestsFixed::~PipelineInterestsFixed()
+{
+ cancel();
+}
+
+void
+PipelineInterestsFixed::doRun()
+{
+ // 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
+PipelineInterestsFixed::fetchNextSegment(std::size_t pipeNo)
+{
+ if (isStopping())
+ return false;
+
+ if (m_hasFailure) {
+ onFailure("Fetching terminated but no final segment number has been found");
+ return false;
+ }
+
+ uint64_t nextSegmentNo = getNextSegmentNo();
+ if (m_hasFinalBlockId && nextSegmentNo > m_lastSegmentNo)
+ return false;
+
+ // send interest for next segment
+ if (m_options.isVerbose)
+ std::cerr << "Requesting segment #" << nextSegmentNo << "\n";
+
+ auto interest = Interest()
+ .setName(Name(m_prefix).appendSegment(nextSegmentNo))
+ .setMustBeFresh(m_options.mustBeFresh)
+ .setInterestLifetime(m_options.interestLifetime);
+
+ auto fetcher = DataFetcher::fetch(m_face, interest,
+ m_options.maxRetriesOnTimeoutOrNack,
+ m_options.maxRetriesOnTimeoutOrNack,
+ [this, pipeNo] (const auto& interest, const auto& data) {
+ handleData(interest, data, pipeNo);
+ },
+ [this, pipeNo] (const auto&, const auto& reason) {
+ handleFail(reason, pipeNo);
+ },
+ [this, pipeNo] (const auto&, const auto& reason) {
+ handleFail(reason, pipeNo);
+ },
+ m_options.isVerbose);
+
+ BOOST_ASSERT(!m_segmentFetchers[pipeNo].first || !m_segmentFetchers[pipeNo].first->isRunning());
+ m_segmentFetchers[pipeNo] = make_pair(fetcher, nextSegmentNo);
+
+ return true;
+}
+
+void
+PipelineInterestsFixed::doCancel()
+{
+ for (auto& fetcher : m_segmentFetchers) {
+ if (fetcher.first)
+ fetcher.first->cancel();
+ }
+
+ m_segmentFetchers.clear();
+}
+
+void
+PipelineInterestsFixed::handleData(const Interest& interest, const Data& data, size_t pipeNo)
+{
+ if (isStopping())
+ return;
+
+ // Interest was expressed with CanBePrefix=false
+ BOOST_ASSERT(data.getName().equals(interest.getName()));
+
+ if (m_options.isVerbose)
+ std::cerr << "Received segment #" << getSegmentFromPacket(data) << "\n";
+
+ onData(data);
+
+ if (!m_hasFinalBlockId && data.getFinalBlock()) {
+ m_lastSegmentNo = data.getFinalBlock()->toSegment();
+ m_hasFinalBlockId = true;
+
+ for (auto& fetcher : m_segmentFetchers) {
+ if (fetcher.first == nullptr)
+ continue;
+
+ if (fetcher.second > m_lastSegmentNo) {
+ // stop trying to fetch segments that are beyond m_lastSegmentNo
+ fetcher.first->cancel();
+ }
+ else if (fetcher.first->hasError()) { // fetcher.second <= m_lastSegmentNo
+ // there was an error while fetching a segment that is part of the content
+ return onFailure("Failure retrieving segment #" + std::to_string(fetcher.second));
+ }
+ }
+ }
+
+ if (allSegmentsReceived()) {
+ if (!m_options.isQuiet) {
+ printSummary();
+ }
+ }
+ else {
+ fetchNextSegment(pipeNo);
+ }
+}
+
+void PipelineInterestsFixed::handleFail(const std::string& reason, std::size_t pipeNo)
+{
+ if (isStopping())
+ return;
+
+ // if the failed segment is definitely part of the content, raise a fatal error
+ if (m_hasFinalBlockId && m_segmentFetchers[pipeNo].second <= m_lastSegmentNo)
+ return onFailure(reason);
+
+ if (!m_hasFinalBlockId) {
+ bool areAllFetchersStopped = true;
+ for (auto& fetcher : m_segmentFetchers) {
+ if (fetcher.first == nullptr)
+ continue;
+
+ // cancel fetching all segments that follow
+ if (fetcher.second > m_segmentFetchers[pipeNo].second) {
+ fetcher.first->cancel();
+ }
+ else if (fetcher.first->isRunning()) { // fetcher.second <= m_segmentFetchers[pipeNo].second
+ areAllFetchersStopped = false;
+ }
+ }
+
+ if (areAllFetchersStopped) {
+ onFailure("Fetching terminated but no final segment number has been found");
+ }
+ else {
+ m_hasFailure = true;
+ }
+ }
+}
+
+} // namespace ndn::get
diff --git a/tools/get/pipeline-interests-fixed.hpp b/tools/get/pipeline-interests-fixed.hpp
new file mode 100644
index 0000000..6325123
--- /dev/null
+++ b/tools/get/pipeline-interests-fixed.hpp
@@ -0,0 +1,97 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Davide Pesavento
+ * @author Chavoosh Ghasemi
+ */
+
+#ifndef NDN_TOOLS_GET_PIPELINE_INTERESTS_FIXED_HPP
+#define NDN_TOOLS_GET_PIPELINE_INTERESTS_FIXED_HPP
+
+#include "pipeline-interests.hpp"
+
+#include <vector>
+
+namespace ndn::get {
+
+class DataFetcher;
+
+/**
+ * @brief Service for retrieving Data via an Interest pipeline
+ *
+ * Retrieves all segments of Data under a given prefix by maintaining a fixed-size window of
+ * N Interests in flight. A user-specified callback function is used to notify the arrival of
+ * each segment of Data.
+ *
+ * No guarantees are made as to the order in which segments are fetched or callbacks are invoked,
+ * i.e. out-of-order delivery is possible.
+ */
+class PipelineInterestsFixed final : public PipelineInterests
+{
+public:
+ PipelineInterestsFixed(Face& face, const Options& opts);
+
+ ~PipelineInterestsFixed() final;
+
+private:
+ /**
+ * @brief fetch all the segments between 0 and m_lastSegmentNo
+ *
+ * Starts a fixed-window pipeline with size equal to m_options.maxPipelineSize. The pipeline
+ * will fetch every segment until the last segment is successfully received or an error occurs.
+ */
+ void
+ doRun() final;
+
+ void
+ doCancel() final;
+
+ /**
+ * @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
+ handleData(const Interest& interest, const Data& data, size_t pipeNo);
+
+ void
+ handleFail(const std::string& reason, size_t pipeNo);
+
+private:
+ std::vector<std::pair<std::shared_ptr<DataFetcher>, uint64_t>> m_segmentFetchers;
+
+ /**
+ * true if one or more segment fetchers encountered an error; if m_hasFinalBlockId
+ * is false, this is usually not a fatal error for the pipeline
+ */
+ bool m_hasFailure = false;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_PIPELINE_INTERESTS_FIXED_HPP
diff --git a/tools/get/pipeline-interests.cpp b/tools/get/pipeline-interests.cpp
new file mode 100644
index 0000000..afcbd0c
--- /dev/null
+++ b/tools/get/pipeline-interests.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Davide Pesavento
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ */
+
+#include "pipeline-interests.hpp"
+#include "data-fetcher.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+
+#include <iostream>
+
+namespace ndn::get {
+
+PipelineInterests::PipelineInterests(Face& face, const Options& opts)
+ : m_options(opts)
+ , m_face(face)
+{
+}
+
+PipelineInterests::~PipelineInterests() = default;
+
+void
+PipelineInterests::run(const Name& versionedName, DataCallback dataCb, FailureCallback failureCb)
+{
+ 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);
+
+ // record the start time of the pipeline
+ m_startTime = time::steady_clock::now();
+
+ doRun();
+}
+
+void
+PipelineInterests::cancel()
+{
+ if (m_isStopping)
+ return;
+
+ m_isStopping = true;
+ doCancel();
+}
+
+bool
+PipelineInterests::allSegmentsReceived() const
+{
+ return m_nReceived > 0 &&
+ m_hasFinalBlockId &&
+ static_cast<uint64_t>(m_nReceived - 1) >= m_lastSegmentNo;
+}
+
+uint64_t
+PipelineInterests::getNextSegmentNo()
+{
+ return m_nextSegmentNo++;
+}
+
+void
+PipelineInterests::onData(const Data& data)
+{
+ m_nReceived++;
+ m_receivedSize += data.getContent().value_size();
+
+ m_onData(data);
+}
+
+void
+PipelineInterests::onFailure(const std::string& reason)
+{
+ if (m_isStopping)
+ return;
+
+ cancel();
+
+ if (m_onFailure) {
+ boost::asio::post(m_face.getIoContext(), [this, reason] { m_onFailure(reason); });
+ }
+}
+
+void
+PipelineInterests::printOptions() const
+{
+ std::cerr << "Pipeline parameters:\n"
+ << "\tRequest fresh content = " << (m_options.mustBeFresh ? "yes" : "no") << "\n"
+ << "\tInterest lifetime = " << m_options.interestLifetime << "\n"
+ << "\tMax retries on timeout or Nack = " <<
+ (m_options.maxRetriesOnTimeoutOrNack == DataFetcher::MAX_RETRIES_INFINITE ?
+ "infinite" : std::to_string(m_options.maxRetriesOnTimeoutOrNack)) << "\n";
+}
+
+void
+PipelineInterests::printSummary() const
+{
+ using namespace ndn::time;
+ duration<double, seconds::period> timeElapsed = steady_clock::now() - getStartTime();
+ double throughput = 8 * m_receivedSize / timeElapsed.count();
+
+ std::cerr << "\n\nAll segments have been received.\n"
+ << "Time elapsed: " << timeElapsed << "\n"
+ << "Segments received: " << m_nReceived << "\n"
+ << "Transferred size: " << m_receivedSize / 1e3 << " kB" << "\n"
+ << "Goodput: " << formatThroughput(throughput) << "\n";
+}
+
+std::string
+PipelineInterests::formatThroughput(double throughput)
+{
+ int pow = 0;
+ while (throughput >= 1000.0 && pow < 4) {
+ throughput /= 1000.0;
+ pow++;
+ }
+ switch (pow) {
+ case 0:
+ return std::to_string(throughput) + " bit/s";
+ case 1:
+ return std::to_string(throughput) + " kbit/s";
+ case 2:
+ return std::to_string(throughput) + " Mbit/s";
+ case 3:
+ return std::to_string(throughput) + " Gbit/s";
+ case 4:
+ return std::to_string(throughput) + " Tbit/s";
+ }
+ return "";
+}
+
+} // namespace ndn::get
diff --git a/tools/get/pipeline-interests.hpp b/tools/get/pipeline-interests.hpp
new file mode 100644
index 0000000..93245c2
--- /dev/null
+++ b/tools/get/pipeline-interests.hpp
@@ -0,0 +1,186 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2025, 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
+ * @author Davide Pesavento
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ */
+
+#ifndef NDN_TOOLS_GET_PIPELINE_INTERESTS_HPP
+#define NDN_TOOLS_GET_PIPELINE_INTERESTS_HPP
+
+#include "core/common.hpp"
+#include "options.hpp"
+
+#include <ndn-cxx/face.hpp>
+
+#include <functional>
+
+namespace ndn::get {
+
+/**
+ * @brief Service for retrieving Data via an Interest pipeline
+ *
+ * Retrieves all segments of Data under a given prefix by maintaining a (variable or fixed-size)
+ * window of N Interests in flight. A user-specified callback function is used to notify
+ * the arrival of each segment of Data.
+ *
+ * No guarantees are made as to the order in which segments are fetched or callbacks are invoked,
+ * i.e. out-of-order delivery is possible.
+ */
+class PipelineInterests : noncopyable
+{
+public:
+ /**
+ * @brief Constructor.
+ *
+ * Configures the pipelining service without specifying the retrieval namespace.
+ * After construction, the method run() must be called in order to start the pipeline.
+ */
+ PipelineInterests(Face& face, const Options& opts);
+
+ virtual
+ ~PipelineInterests();
+
+ using DataCallback = std::function<void(const Data&)>;
+ using FailureCallback = std::function<void(const std::string& reason)>;
+
+ /**
+ * @brief start fetching all the segments of the specified prefix
+ *
+ * @param versionedName the name of the segmented Data ending with a version number
+ * @param onData callback for every segment correctly received, must not be empty
+ * @param onFailure callback if an error occurs, may be empty
+ */
+ void
+ run(const Name& versionedName, DataCallback onData, FailureCallback onFailure);
+
+ /**
+ * @brief stop all fetch operations
+ */
+ void
+ cancel();
+
+protected:
+ time::steady_clock::time_point
+ getStartTime() const
+ {
+ return m_startTime;
+ }
+
+ bool
+ isStopping() const
+ {
+ return m_isStopping;
+ }
+
+ /**
+ * @brief check if the transfer is complete
+ * @return true if all segments have been received, false otherwise
+ */
+ [[nodiscard]] bool
+ allSegmentsReceived() const;
+
+ /**
+ * @return next segment number to retrieve
+ * @post m_nextSegmentNo == return-value + 1
+ */
+ uint64_t
+ getNextSegmentNo();
+
+ /**
+ * @brief subclasses must call this method to notify successful retrieval of a segment
+ */
+ void
+ onData(const Data& data);
+
+ /**
+ * @brief subclasses can call this method to signal an unrecoverable failure
+ */
+ void
+ onFailure(const std::string& reason);
+
+ void
+ printOptions() const;
+
+ /**
+ * @brief print statistics about this fetching session
+ *
+ * Subclasses can override this method to print additional stats or change the summary format
+ */
+ virtual void
+ printSummary() const;
+
+ /**
+ * @param throughput The throughput in bits/s
+ */
+ static std::string
+ formatThroughput(double throughput);
+
+private:
+ /**
+ * @brief perform subclass-specific operations to fetch all the segments
+ *
+ * When overriding this function, at a minimum, the subclass should implement the retrieving
+ * of all the segments. Subclass must guarantee that `onData` is called once for every
+ * segment that is fetched successfully.
+ *
+ * @note m_lastSegmentNo contains a valid value only if m_hasFinalBlockId is true.
+ */
+ virtual void
+ doRun() = 0;
+
+ virtual void
+ doCancel() = 0;
+
+protected:
+ const Options& m_options;
+ Face& m_face;
+ Name m_prefix;
+
+PUBLIC_WITH_TESTS_ELSE_PROTECTED:
+ bool m_hasFinalBlockId = false; ///< true if the last segment number is known
+ uint64_t m_lastSegmentNo = 0; ///< valid only if m_hasFinalBlockId == true
+ int64_t m_nReceived = 0; ///< number of segments received
+ size_t m_receivedSize = 0; ///< size of received data in bytes
+
+private:
+ DataCallback m_onData;
+ FailureCallback m_onFailure;
+ uint64_t m_nextSegmentNo = 0;
+ time::steady_clock::time_point m_startTime;
+ bool m_isStopping = false;
+};
+
+template<typename Packet>
+uint64_t
+getSegmentFromPacket(const Packet& packet)
+{
+ return packet.getName().at(-1).toSegment();
+}
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_PIPELINE_INTERESTS_HPP
diff --git a/tools/get/statistics-collector.cpp b/tools/get/statistics-collector.cpp
new file mode 100644
index 0000000..214402f
--- /dev/null
+++ b/tools/get/statistics-collector.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016-2025, 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 Weiwei Liu
+ */
+
+#include "statistics-collector.hpp"
+
+namespace ndn::get {
+
+StatisticsCollector::StatisticsCollector(PipelineInterestsAdaptive& pipeline,
+ std::ostream& osCwnd, std::ostream& osRtt)
+ : m_osCwnd(osCwnd)
+ , m_osRtt(osRtt)
+{
+ m_osCwnd << "time\tcwndsize\n";
+ m_osRtt << "segment\trtt\trttvar\tsrtt\trto\n";
+
+ pipeline.afterCwndChange.connect([this] (time::nanoseconds timeElapsed, double cwnd) {
+ m_osCwnd << timeElapsed.count() / 1e9 << '\t' << cwnd << '\n';
+ });
+
+ pipeline.afterRttMeasurement.connect([this] (const auto& sample) {
+ m_osRtt << sample.segNum << '\t'
+ << sample.rtt.count() / 1e6 << '\t'
+ << sample.rttVar.count() / 1e6 << '\t'
+ << sample.sRtt.count() / 1e6 << '\t'
+ << sample.rto.count() / 1e6 << '\n';
+ });
+}
+
+} // namespace ndn::get
diff --git a/tools/get/statistics-collector.hpp b/tools/get/statistics-collector.hpp
new file mode 100644
index 0000000..4aba545
--- /dev/null
+++ b/tools/get/statistics-collector.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016-2025, 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 Weiwei Liu
+ */
+
+#ifndef NDN_TOOLS_GET_STATISTICS_COLLECTOR_HPP
+#define NDN_TOOLS_GET_STATISTICS_COLLECTOR_HPP
+
+#include "pipeline-interests-adaptive.hpp"
+
+namespace ndn::get {
+
+/**
+ * @brief Statistics collector for Adaptive pipelines
+ */
+class StatisticsCollector : noncopyable
+{
+public:
+ StatisticsCollector(PipelineInterestsAdaptive& pipeline,
+ std::ostream& osCwnd, std::ostream& osRtt);
+
+private:
+ std::ostream& m_osCwnd;
+ std::ostream& m_osRtt;
+};
+
+} // namespace ndn::get
+
+#endif // NDN_TOOLS_GET_STATISTICS_COLLECTOR_HPP
diff --git a/tools/get/wscript b/tools/get/wscript
new file mode 100644
index 0000000..9a325c4
--- /dev/null
+++ b/tools/get/wscript
@@ -0,0 +1,17 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+top = '../..'
+
+def build(bld):
+ bld.objects(
+ target='get-objects',
+ source=bld.path.ant_glob('*.cpp', excl='main.cpp'),
+ use='core-objects')
+
+ bld.program(
+ target=f'{top}/bin/ndnget',
+ name='ndnget',
+ source='main.cpp',
+ use='get-objects')
+
+ # backward compatibility
+ bld.symlink_as('${BINDIR}/ndncatchunks', 'ndnget')