Rename ndncatchunks to ndnget
Change-Id: I260e552746e900a73c2ce773bd91d9b6fa384734
diff --git a/tests/get/consumer.t.cpp b/tests/get/consumer.t.cpp
new file mode 100644
index 0000000..f43eac6
--- /dev/null
+++ b/tests/get/consumer.t.cpp
@@ -0,0 +1,172 @@
+/* -*- 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
+ */
+
+#include "tools/get/consumer.hpp"
+#include "tools/get/discover-version.hpp"
+#include "tools/get/pipeline-interests.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/io-fixture.hpp"
+
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include <boost/test/tools/output_test_stream.hpp>
+
+namespace ndn::tests {
+
+using namespace ndn::get;
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Get)
+BOOST_AUTO_TEST_SUITE(TestConsumer)
+
+BOOST_AUTO_TEST_CASE(InOrderData)
+{
+ // Segment order: 0 1 2 3
+
+ const std::string name("/ndn/chunks/test");
+ const std::vector<std::string> testStrings {
+ "",
+ "a1b2c3%^&(#$&%^$$/><",
+ "123456789123456789123456789123456789123456789123456789123456789"
+ "123456789123456789123456789123456789123456789123456789123456789",
+ "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. "
+ "Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+ "ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
+ "consequat massa Donec pede justo,"
+ };
+
+ DummyClientFace face;
+ output_test_stream output("");
+ Consumer cons(security::getAcceptAllValidator(), output);
+
+ auto interest = makeInterest(name, true);
+
+ for (size_t i = 0; i < testStrings.size(); ++i) {
+ output.flush();
+
+ auto data = makeData(Name(name).appendVersion(1).appendSegment(i));
+ data->setContent(make_span(reinterpret_cast<const uint8_t*>(testStrings[i].data()),
+ testStrings[i].size()));
+
+ cons.m_bufferedData[i] = data;
+ cons.writeInOrderData();
+
+ BOOST_CHECK(output.is_equal(testStrings[i]));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(OutOfOrderData)
+{
+ // Segment order: 1 0 2
+
+ const std::string name("/ndn/chunks/test");
+ const std::vector<std::string> testStrings {
+ "a1b2c3%^&(#$&%^$$/><",
+ "123456789123456789123456789123456789123456789123456789123456789"
+ "123456789123456789123456789123456789123456789123456789123456789",
+ "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. "
+ "Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+ "ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla "
+ "consequat massa Donec pede justo,"
+ };
+
+ DummyClientFace face;
+ output_test_stream output("");
+ Consumer cons(security::getAcceptAllValidator(), output);
+
+ auto interest = makeInterest(name, true);
+ std::vector<std::shared_ptr<Data>> dataStore;
+
+ for (size_t i = 0; i < testStrings.size(); ++i) {
+ auto data = makeData(Name(name).appendVersion(1).appendSegment(i));
+ data->setContent(make_span(reinterpret_cast<const uint8_t*>(testStrings[i].data()),
+ testStrings[i].size()));
+
+ dataStore.push_back(data);
+ }
+
+ output.flush();
+ cons.m_bufferedData[1] = dataStore[1];
+ cons.writeInOrderData();
+ BOOST_CHECK(output.is_equal(""));
+
+ output.flush();
+ cons.m_bufferedData[0] = dataStore[0];
+ cons.writeInOrderData();
+ BOOST_CHECK(output.is_equal(testStrings[0] + testStrings[1]));
+
+ output.flush();
+ cons.m_bufferedData[2] = dataStore[2];
+ cons.writeInOrderData();
+ BOOST_CHECK(output.is_equal(testStrings[2]));
+}
+
+class PipelineInterestsDummy final : public PipelineInterests
+{
+public:
+ using PipelineInterests::PipelineInterests;
+
+private:
+ void
+ doRun() final
+ {
+ isPipelineRunning = true;
+ }
+
+ void
+ doCancel() final
+ {
+ }
+
+public:
+ bool isPipelineRunning = false;
+};
+
+BOOST_FIXTURE_TEST_CASE(RunBasic, IoFixture)
+{
+ DummyClientFace face(m_io);
+ Options options;
+ Consumer consumer(security::getAcceptAllValidator());
+
+ Name prefix = Name("/ndn/chunks/test").appendVersion(1);
+ auto discover = std::make_unique<DiscoverVersion>(face, prefix, options);
+ auto pipeline = std::make_unique<PipelineInterestsDummy>(face, options);
+ auto pipelinePtr = pipeline.get();
+
+ BOOST_CHECK_EQUAL(pipelinePtr->isPipelineRunning, false);
+
+ consumer.run(std::move(discover), std::move(pipeline));
+ this->advanceClocks(1_ms);
+
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 0); // no discovery Interests are issued
+ BOOST_CHECK_EQUAL(pipelinePtr->isPipelineRunning, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestConsumer
+BOOST_AUTO_TEST_SUITE_END() // Get
+
+} // namespace ndn::tests
diff --git a/tests/get/discover-version.t.cpp b/tests/get/discover-version.t.cpp
new file mode 100644
index 0000000..b4cc519
--- /dev/null
+++ b/tests/get/discover-version.t.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 Chavoosh Ghasemi
+ */
+
+#include "tools/get/discover-version.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/io-fixture.hpp"
+#include "tests/key-chain-fixture.hpp"
+
+#include <ndn-cxx/metadata-object.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace ndn::tests {
+
+using namespace ndn::get;
+
+class DiscoverVersionFixture : public IoFixture, public KeyChainFixture
+{
+public:
+ void
+ run(const Name& prefix)
+ {
+ discover = std::make_unique<DiscoverVersion>(face, prefix, opt);
+ discover->onDiscoverySuccess.connect([this] (const Name& versionedName) {
+ isDiscoveryFinished = true;
+ discoveredName = versionedName;
+ if (!versionedName.empty() && versionedName[-1].isVersion())
+ discoveredVersion = versionedName[-1].toVersion();
+ });
+ discover->onDiscoveryFailure.connect([this] (const std::string&) {
+ isDiscoveryFinished = true;
+ });
+
+ discover->run();
+ advanceClocks(1_ns);
+ }
+
+protected:
+ const Name name = "/ndn/chunks/test";
+ const uint64_t version = 1449227841747;
+ DummyClientFace face{m_io};
+ Options opt;
+ std::unique_ptr<DiscoverVersion> discover;
+ std::optional<Name> discoveredName;
+ std::optional<uint64_t> discoveredVersion;
+ bool isDiscoveryFinished = false;
+};
+
+BOOST_AUTO_TEST_SUITE(Get)
+BOOST_FIXTURE_TEST_SUITE(TestDiscoverVersion, DiscoverVersionFixture)
+
+BOOST_AUTO_TEST_CASE(Disabled)
+{
+ opt.disableVersionDiscovery = true;
+ run(name);
+
+ // no version discovery Interest is expressed
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+
+ BOOST_CHECK_EQUAL(discoveredName.value(), name);
+ BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
+}
+
+BOOST_AUTO_TEST_CASE(NameWithVersion)
+{
+ // start with a name that already contains a version component
+ Name versionedName = Name(name).appendVersion(version);
+ run(versionedName);
+
+ // no version discovery Interest is expressed
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+
+ BOOST_CHECK_EQUAL(discoveredName.value(), versionedName);
+ BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
+}
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+ run(name);
+
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ Interest discoveryInterest = MetadataObject::makeDiscoveryInterest(name);
+ auto lastInterest = face.sentInterests.back();
+ BOOST_CHECK_EQUAL(lastInterest.getName(), discoveryInterest.getName());
+
+ // send back a metadata packet with a valid versioned name
+ MetadataObject mobject;
+ mobject.setVersionedName(Name(name).appendVersion(version));
+ face.receive(mobject.makeData(lastInterest.getName(), m_keyChain));
+ advanceClocks(1_ns);
+
+ BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidDiscoveredVersionedName)
+{
+ run(name);
+
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ // send back a metadata packet with an invalid versioned name
+ MetadataObject mobject;
+ mobject.setVersionedName(name);
+ face.receive(mobject.makeData(face.sentInterests.back().getName(), m_keyChain));
+
+ // finish discovery process without a resolved version number
+ BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+ BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+ BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidMetadataPacket)
+{
+ run(name);
+
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ // send back an invalid metadata packet
+ Data data(face.sentInterests.back().getName());
+ data.setFreshnessPeriod(1_s);
+ data.setContentType(tlv::ContentType_Key);
+ face.receive(signData(data));
+
+ // finish discovery process without a resolved version number
+ BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+ BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+ BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
+}
+
+BOOST_AUTO_TEST_CASE(MaxRetriesExceeded)
+{
+ opt.maxRetriesOnTimeoutOrNack = 3;
+ run(name);
+
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ // timeout or nack discovery Interests
+ for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack * 2; ++retries) {
+ if (retries % 2 == 0) {
+ advanceClocks(opt.interestLifetime);
+ }
+ else {
+ face.receive(makeNack(face.sentInterests.back(), lp::NackReason::DUPLICATE));
+ advanceClocks(1_ns);
+ }
+
+ BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), retries + 2);
+ }
+
+ // timeout the last sent Interest
+ advanceClocks(opt.interestLifetime);
+
+ // finish discovery process without a resolved version number
+ BOOST_CHECK_EQUAL(isDiscoveryFinished, true);
+ BOOST_CHECK_EQUAL(discoveredName.has_value(), false);
+ BOOST_CHECK_EQUAL(discoveredVersion.has_value(), false);
+}
+
+BOOST_AUTO_TEST_CASE(SuccessAfterNackAndTimeout)
+{
+ opt.maxRetriesOnTimeoutOrNack = 3;
+ run(name);
+
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ // timeout or nack discovery Interests
+ for (int retries = 0; retries < opt.maxRetriesOnTimeoutOrNack * 2; ++retries) {
+ if (retries % 2 == 0) {
+ advanceClocks(opt.interestLifetime);
+ }
+ else {
+ face.receive(makeNack(face.sentInterests.back(), lp::NackReason::DUPLICATE));
+ advanceClocks(1_ns);
+ }
+
+ BOOST_CHECK_EQUAL(isDiscoveryFinished, false);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), retries + 2);
+ }
+
+ // satisfy the last Interest with a valid metadata packet
+ MetadataObject mobject;
+ mobject.setVersionedName(Name(name).appendVersion(version));
+ face.receive(mobject.makeData(face.sentInterests.back().getName(), m_keyChain));
+ advanceClocks(1_ns);
+
+ BOOST_CHECK_EQUAL(discoveredVersion.value(), version);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDiscoverVersion
+BOOST_AUTO_TEST_SUITE_END() // Get
+
+} // namespace ndn::tests
diff --git a/tests/get/pipeline-interests-aimd.t.cpp b/tests/get/pipeline-interests-aimd.t.cpp
new file mode 100644
index 0000000..b35c01c
--- /dev/null
+++ b/tests/get/pipeline-interests-aimd.t.cpp
@@ -0,0 +1,643 @@
+/* -*- 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 Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#include "tools/get/pipeline-interests-aimd.hpp"
+
+#include "pipeline-interests-fixture.hpp"
+
+#include <cmath>
+
+namespace ndn::tests {
+
+class PipelineInterestAimdFixture : public PipelineInterestsFixture
+{
+protected:
+ PipelineInterestAimdFixture()
+ {
+ opt.isQuiet = true;
+ createPipeline();
+ }
+
+ void
+ createPipeline()
+ {
+ auto pline = std::make_unique<PipelineInterestsAimd>(face, rttEstimator, opt);
+ pipeline = pline.get();
+ setPipeline(std::move(pline));
+ }
+
+private:
+ static std::shared_ptr<RttEstimatorWithStats::Options>
+ makeRttEstimatorOptions()
+ {
+ auto rttOptions = std::make_shared<RttEstimatorWithStats::Options>();
+ rttOptions->alpha = 0.125;
+ rttOptions->beta = 0.25;
+ rttOptions->k = 4;
+ rttOptions->initialRto = 1_s;
+ rttOptions->minRto = 200_ms;
+ rttOptions->maxRto = 4_s;
+ rttOptions->rtoBackoffMultiplier = 2;
+ return rttOptions;
+ }
+
+protected:
+ Options opt;
+ RttEstimatorWithStats rttEstimator{makeRttEstimatorOptions()};
+ PipelineInterestsAdaptive* pipeline;
+ static constexpr double MARGIN = 0.001;
+};
+
+BOOST_AUTO_TEST_SUITE(Get)
+BOOST_FIXTURE_TEST_SUITE(TestPipelineInterestsAimd, PipelineInterestAimdFixture)
+
+BOOST_AUTO_TEST_CASE(SlowStart)
+{
+ nDataSegments = 4;
+ pipeline->m_ssthresh = 8.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ double preCwnd = pipeline->m_cwnd;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd - preCwnd, 1, MARGIN);
+ preCwnd = pipeline->m_cwnd;
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments - 1);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionAvoidance)
+{
+ nDataSegments = 7;
+ pipeline->m_ssthresh = 4.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ double preCwnd = pipeline->m_cwnd;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ for (uint64_t i = 0; i < pipeline->m_ssthresh; ++i) { // slow start
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ preCwnd = pipeline->m_cwnd;
+ }
+
+ BOOST_CHECK_CLOSE(preCwnd, 4.5, MARGIN);
+
+ for (uint64_t i = pipeline->m_ssthresh; i < nDataSegments - 1; ++i) { // congestion avoidance
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd - preCwnd, opt.aiStep / std::floor(preCwnd), MARGIN);
+ preCwnd = pipeline->m_cwnd;
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments -1);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+ nDataSegments = 8;
+ pipeline->m_ssthresh = 4.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segment 0, 1, and 2
+ for (uint64_t i = 0; i < 3; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 3);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.25, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 7); // request for segment 7 has been sent
+
+ advanceClocks(time::milliseconds(100));
+
+ // receive segment 4
+ face.receive(*makeDataWithSegment(4));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 5
+ face.receive(*makeDataWithSegment(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.75, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all the segment requests have been sent
+
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nMarkDecr, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nSkippedRetx, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 0);
+
+ // timeout segment 3 & 6
+ advanceClocks(time::milliseconds(150));
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_nSkippedRetx, 0);
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.375, MARGIN); // window size drop to 1/2 of previous size
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 1);
+
+ // receive segment 6, retransmit 3
+ face.receive(*makeDataWithSegment(6));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.875, MARGIN); // congestion avoidance
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[3], 1);
+
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, pipeline->m_nRetransmitted + pipeline->m_nSkippedRetx);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionMarksWithCwa)
+{
+ nDataSegments = 7;
+ pipeline->m_ssthresh = 4.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 4
+ for (uint64_t i = 0; i < 5; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.75, MARGIN);
+
+ // receive segment 5 with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.375, MARGIN); // window size drops to 1/2 of previous size
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.375, MARGIN); // conservative window adaption (window size should not decrease)
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[5], 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 2);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionMarksWithoutCwa)
+{
+ opt.disableCwa = true;
+ createPipeline();
+
+ nDataSegments = 7;
+ pipeline->m_ssthresh = 4.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 4
+ for (uint64_t i = 0; i < 5; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.75, MARGIN);
+
+ // receive segment 5 with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.375, MARGIN); // window size drops to 1/2 of previous size
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, PipelineInterestsAdaptive::MIN_SSTHRESH,
+ MARGIN); // window size should decrease, as cwa is disabled
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[5], 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 2);
+}
+
+BOOST_AUTO_TEST_CASE(IgnoreCongestionMarks)
+{
+ opt.ignoreCongMarks = true;
+ createPipeline();
+
+ nDataSegments = 7;
+ pipeline->m_ssthresh = 4.0;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 5
+ for (uint64_t i = 0; i < 6; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 5.0, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 5.2, MARGIN); // window size increases
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packet
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+ nDataSegments = 5;
+ pipeline->m_cwnd = 10.0;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 10);
+
+ // receive a nack with NackReason::DUPLICATE for segment 1
+ auto nack1 = makeNack(face.sentInterests[1], lp::NackReason::DUPLICATE);
+ face.receive(nack1);
+ advanceClocks(time::nanoseconds(1));
+
+ // nack1 is ignored
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // receive a nack with NackReason::CONGESTION for segment 2
+ auto nack2 = makeNack(face.sentInterests[2], lp::NackReason::CONGESTION);
+ face.receive(nack2);
+ advanceClocks(time::nanoseconds(1));
+
+ // segment 2 is retransmitted
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[2], 1);
+
+ // receive a nack with NackReason::NONE for segment 3
+ auto nack3 = makeNack(face.sentInterests[3], lp::NackReason::NONE);
+ face.receive(nack3);
+ advanceClocks(time::nanoseconds(1));
+
+ // Other types of Nack will trigger a failure
+ BOOST_CHECK_EQUAL(hasFailed, true);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+}
+
+BOOST_AUTO_TEST_CASE(FinalBlockIdNotSetAtBeginning)
+{
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 5 have been sent
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 6);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, false);
+ // pending interests: segment 1, 2, 3, 4, 5
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 5);
+
+ // receive segment 1 with FinalBlockId
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, true);
+
+ // pending interests for segment 1, 4, 5 haven been removed
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(FailureBeforeFinalBlockIdReceived)
+{
+ // failed to retrieve segNo while the FinalBlockId has not yet been
+ // set, and later received a FinalBlockId >= segNo, i.e. segNo is
+ // part of the content.
+
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1 without FinalBlockId
+ face.receive(*makeDataWithSegment(1, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 7 have been sent
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 8);
+
+ // receive nack with NackReason::NONE for segment 3
+ auto nack = makeNack(face.sentInterests[3], lp::NackReason::NONE);
+ face.receive(nack);
+ advanceClocks(time::nanoseconds(1));
+
+ // error not triggered
+ // pending interests for segment > 3 haven been removed
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 1);
+
+ // receive segment 2 with FinalBlockId
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // error triggered since segment 3 is part of the content
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(SpuriousFailureBeforeFinalBlockIdReceived)
+{
+ // failed to retrieve segNo while the FinalBlockId has not yet been
+ // set, and later received a FinalBlockId < segNo, i.e. segNo is
+ // not part of the content, and it was actually a spurious failure
+
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1 without FinalBlockId
+ face.receive(*makeDataWithSegment(1, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 7 have been sent
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 8);
+
+ // receive nack with NackReason::NONE for segment 4
+ auto nack = makeNack(face.sentInterests[4], lp::NackReason::NONE);
+ face.receive(nack);
+ advanceClocks(time::nanoseconds(1));
+
+ // error not triggered
+ // pending interests for segment > 3 have been removed
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 2);
+
+ // receive segment 2 with FinalBlockId
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // timeout segment 3
+ advanceClocks(time::seconds(1));
+
+ // segment 3 is retransmitted
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[3], 1);
+
+ // receive segment 3
+ face.receive(*makeDataWithSegment(3));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(hasFailed, false);
+}
+
+BOOST_AUTO_TEST_CASE(SegmentInfoMaintenance)
+{
+ // test that m_segmentInfo is properly maintained when
+ // a segment is received after two consecutive timeouts
+
+ nDataSegments = 3;
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
+
+ // check if segment 2's state is FirstTimeSent
+ auto it = pipeline->m_segmentInfo.find(2);
+ BOOST_REQUIRE(it != pipeline->m_segmentInfo.end());
+ BOOST_CHECK(it->second.state == SegmentState::FirstTimeSent);
+
+ // timeout segment 2 twice
+ advanceClocks(time::milliseconds(400), 3);
+
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+
+ // check if segment 2's state is Retransmitted
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_REQUIRE(it != pipeline->m_segmentInfo.end());
+ BOOST_CHECK(it->second.state == SegmentState::Retransmitted);
+
+ // check if segment 2 was retransmitted twice
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount.at(2), 2);
+
+ // receive segment 2 the first time
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // check if segment 2 was erased from m_segmentInfo
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_CHECK(it == pipeline->m_segmentInfo.end());
+
+ auto prevRtt = rttEstimator.getAvgRtt();
+ auto prevRto = rttEstimator.getEstimatedRto();
+
+ // receive segment 2 the second time
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // nothing changed
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_CHECK(it == pipeline->m_segmentInfo.end());
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+ BOOST_CHECK_EQUAL(rttEstimator.getAvgRtt(), prevRtt);
+ BOOST_CHECK_EQUAL(rttEstimator.getEstimatedRto(), prevRto);
+}
+
+BOOST_AUTO_TEST_CASE(Bug5202)
+{
+ // If an interest is pending during a window decrease, it should not trigger
+ // another window decrease when it times out.
+ // This test emulates a network where RTT = 20ms and transmission time = 1ms.
+
+ // adding small sample to RTT estimator. This should set rto = 200ms
+ rttEstimator.addMeasurement(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(rttEstimator.getEstimatedRto(), time::milliseconds(200));
+
+ nDataSegments = 300;
+ pipeline->m_ssthresh = 0;
+ pipeline->m_cwnd = 20;
+
+ run(name);
+
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 20);
+ advanceClocks(time::milliseconds(20));
+
+ // Segment 1 is lost. Receive segment 0 and 2-99
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::milliseconds(1));
+ for (uint64_t i = 2; i <= 99; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::milliseconds(1));
+ }
+
+ // Segment 100 is lost. Receive segment 100 to 181
+ for (uint64_t i = 101; i <= 181; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::milliseconds(1));
+ }
+
+ // 200ms passed after sending segment 1, check for timeout
+ BOOST_CHECK_GT(pipeline->m_cwnd, 27./2);
+ BOOST_CHECK_LT(pipeline->m_cwnd, 28./2);
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 1);
+
+ // Receive segment 182 to 300
+ for (uint64_t i = 182; i <= 300; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::milliseconds(1));
+ }
+
+ // The second packet should timeout now
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 2);
+ // This timeout should NOT trigger another window decrease
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 1);
+}
+
+BOOST_AUTO_TEST_CASE(PrintSummaryWithNoRttMeasurements)
+{
+ // test the console ouptut when no RTT measurement is available,
+ // to make sure a proper message will be printed out
+
+ std::stringstream ss;
+
+ // change the underlying buffer and save the old buffer
+ auto oldBuf = std::cerr.rdbuf(ss.rdbuf());
+
+ pipeline->printSummary();
+ std::string line;
+
+ bool found = false;
+ while (std::getline(ss, line)) {
+ if (line == "RTT stats unavailable") {
+ found = true;
+ break;
+ }
+ }
+ BOOST_CHECK(found);
+ std::cerr.rdbuf(oldBuf); // reset
+}
+
+BOOST_AUTO_TEST_CASE(StopsWhenFileSizeLessThanChunkSize)
+{
+ // test to see if the program doesn't hang,
+ // when transfer is complete, for files less than the chunk size
+ // (i.e. when only one segment is sent/received)
+
+ createPipeline();
+ nDataSegments = 1;
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, true);
+ BOOST_CHECK_EQUAL(pipeline->m_segmentInfo.size(), 0);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPipelineInterestsAimd
+BOOST_AUTO_TEST_SUITE_END() // Get
+
+} // namespace ndn::tests
diff --git a/tests/get/pipeline-interests-cubic.t.cpp b/tests/get/pipeline-interests-cubic.t.cpp
new file mode 100644
index 0000000..c1f3b60
--- /dev/null
+++ b/tests/get/pipeline-interests-cubic.t.cpp
@@ -0,0 +1,556 @@
+/* -*- 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 Weiwei Liu
+ * @author Chavoosh Ghasemi
+ * @author Klaus Schneider
+ */
+
+#include "tools/get/pipeline-interests-cubic.hpp"
+
+#include "pipeline-interests-fixture.hpp"
+
+namespace ndn::tests {
+
+class PipelineInterestCubicFixture : public PipelineInterestsFixture
+{
+protected:
+ PipelineInterestCubicFixture()
+ {
+ opt.isQuiet = true;
+ createPipeline();
+ }
+
+ void
+ createPipeline()
+ {
+ auto pline = std::make_unique<PipelineInterestsCubic>(face, rttEstimator, opt);
+ pipeline = pline.get();
+ setPipeline(std::move(pline));
+ }
+
+private:
+ static std::shared_ptr<RttEstimatorWithStats::Options>
+ makeRttEstimatorOptions()
+ {
+ auto rttOptions = std::make_shared<RttEstimatorWithStats::Options>();
+ rttOptions->alpha = 0.125;
+ rttOptions->beta = 0.25;
+ rttOptions->k = 8;
+ rttOptions->initialRto = 1_s;
+ rttOptions->minRto = 200_ms;
+ rttOptions->maxRto = 4_s;
+ rttOptions->rtoBackoffMultiplier = 2;
+ return rttOptions;
+ }
+
+protected:
+ Options opt;
+ RttEstimatorWithStats rttEstimator{makeRttEstimatorOptions()};
+ PipelineInterestsCubic* pipeline;
+ static constexpr double MARGIN = 0.001;
+};
+
+BOOST_AUTO_TEST_SUITE(Get)
+BOOST_FIXTURE_TEST_SUITE(TestPipelineInterestsCubic, PipelineInterestCubicFixture)
+
+BOOST_AUTO_TEST_CASE(SlowStart)
+{
+ nDataSegments = 4;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ double preCwnd = pipeline->m_cwnd;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd - preCwnd, 1, MARGIN);
+ preCwnd = pipeline->m_cwnd;
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments - 1);
+}
+
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+ nDataSegments = 8;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segment 0, 1, and 2
+ for (uint64_t i = 0; i < 3; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 3);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 5, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 8); // request for segment #7 has been sent
+
+ advanceClocks(time::milliseconds(100));
+
+ // receive segment 4
+ face.receive(*makeDataWithSegment(4));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 5
+ face.receive(*makeDataWithSegment(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 7.0, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all the segment requests have been sent
+
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nMarkDecr, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nSkippedRetx, 0);
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 0);
+
+ // timeout segment 3 & 6
+ advanceClocks(time::milliseconds(150));
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 3);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 3);
+ BOOST_CHECK_EQUAL(pipeline->m_nLossDecr, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_nSkippedRetx, 0);
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.9, MARGIN); // window size drop to 0.7x of previous size
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // receive segment 6, retransmit 3
+ face.receive(*makeDataWithSegment(6));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.9, MARGIN); // congestion avoidance
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[3], 1);
+
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts, 3);
+ BOOST_CHECK_EQUAL(pipeline->m_nRetransmitted, 3);
+ BOOST_CHECK_EQUAL(pipeline->m_nTimeouts,
+ pipeline->m_nRetransmitted + pipeline->m_nSkippedRetx);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionMarksWithCwa)
+{
+ nDataSegments = 7;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 4
+ for (uint64_t i = 0; i < 5; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 7.0, MARGIN);
+
+ // receive segment 5 with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.9, MARGIN); // window size drops to 0.7x of previous size
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.9, MARGIN); // conservative window adaption (window size should not decrease)
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[5], 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 2);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionMarksWithoutCwa)
+{
+ opt.disableCwa = true;
+ createPipeline();
+
+ nDataSegments = 7;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 4
+ for (uint64_t i = 0; i < 5; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 7.0, MARGIN);
+
+ // receive segment 5 with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(5));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.9, MARGIN); // window size drops to 0.7x of previous size
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 3.43, MARGIN); // window size should decrease, as cwa is disabled
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[5], 0);
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 2);
+}
+
+BOOST_AUTO_TEST_CASE(IgnoreCongestionMarks)
+{
+ opt.ignoreCongMarks = true;
+ createPipeline();
+
+ nDataSegments = 7;
+ BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 2, MARGIN);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+
+ // receive segments 0 to 5
+ for (uint64_t i = 0; i < 6; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 8.0, MARGIN);
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments); // all interests have been sent
+
+ // receive the last segment with congestion mark
+ face.receive(*makeDataWithSegmentAndCongMark(nDataSegments - 1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+ BOOST_CHECK_CLOSE(pipeline->m_cwnd, 9.0, MARGIN); // window size increases
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // make sure no interest is retransmitted for marked data packet
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[nDataSegments - 1], 0);
+
+ // check number of received marked data packets
+ BOOST_CHECK_EQUAL(pipeline->m_nCongMarks, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+ nDataSegments = 5;
+ pipeline->m_cwnd = 10.0;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 10);
+
+ // receive a nack with NackReason::DUPLICATE for segment 1
+ auto nack1 = makeNack(face.sentInterests[1], lp::NackReason::DUPLICATE);
+ face.receive(nack1);
+ advanceClocks(time::nanoseconds(1));
+
+ // nack1 is ignored
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_retxQueue.size(), 0);
+
+ // receive a nack with NackReason::CONGESTION for segment 2
+ auto nack2 = makeNack(face.sentInterests[2], lp::NackReason::CONGESTION);
+ face.receive(nack2);
+ advanceClocks(time::nanoseconds(1));
+
+ // segment 2 is retransmitted
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[2], 1);
+
+ // receive a nack with NackReason::NONE for segment 3
+ auto nack3 = makeNack(face.sentInterests[3], lp::NackReason::NONE);
+ face.receive(nack3);
+ advanceClocks(time::nanoseconds(1));
+
+ // Other types of Nack will trigger a failure
+ BOOST_CHECK_EQUAL(hasFailed, true);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+}
+
+BOOST_AUTO_TEST_CASE(FinalBlockIdNotSetAtBeginning)
+{
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 5 have been sent
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 6);
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 1);
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, false);
+ // pending interests: segment 1, 2, 3, 4, 5
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 5);
+
+ // receive segment 1 with FinalBlockId
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 2);
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, true);
+
+ // pending interests for segment 1, 4, 5 haven been removed
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(FailureBeforeFinalBlockIdReceived)
+{
+ // failed to retrieve segNo while the FinalBlockId has not yet been
+ // set, and later received a FinalBlockId >= segNo, i.e. segNo is
+ // part of the content.
+
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1 without FinalBlockId
+ face.receive(*makeDataWithSegment(1, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 7 have been sent
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 8);
+
+ // receive nack with NackReason::NONE for segment 3
+ auto nack = makeNack(face.sentInterests[3], lp::NackReason::NONE);
+ face.receive(nack);
+ advanceClocks(time::nanoseconds(1));
+
+ // error not triggered
+ // pending interests for segment > 3 haven been removed
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 1);
+
+ // receive segment 2 with FinalBlockId
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // error triggered since segment 3 is part of the content
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(SpuriousFailureBeforeFinalBlockIdReceived)
+{
+ // failed to retrieve segNo while the FinalBlockId has not yet been
+ // set, and later received a FinalBlockId < segNo, i.e. segNo is
+ // not part of the content, and it was actually a spurious failure
+
+ nDataSegments = 4;
+ pipeline->m_cwnd = 4;
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0 without FinalBlockId
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1 without FinalBlockId
+ face.receive(*makeDataWithSegment(1, false));
+ advanceClocks(time::nanoseconds(1));
+
+ // interests for segment 0 - 7 have been sent
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 8);
+
+ // receive nack with NackReason::NONE for segment 4
+ auto nack = makeNack(face.sentInterests[4], lp::NackReason::NONE);
+ face.receive(nack);
+ advanceClocks(time::nanoseconds(1));
+
+ // error not triggered
+ // pending interests for segment > 3 have been removed
+ BOOST_CHECK_EQUAL(hasFailed, false);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 2);
+
+ // receive segment 2 with FinalBlockId
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // timeout segment 3
+ advanceClocks(time::seconds(1));
+
+ // segment 3 is retransmitted
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount[3], 1);
+
+ // receive segment 3
+ face.receive(*makeDataWithSegment(3));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(hasFailed, false);
+}
+
+BOOST_AUTO_TEST_CASE(SegmentInfoMaintenance)
+{
+ // test that m_segmentInfo is properly maintained when
+ // a segment is received after two consecutive timeouts
+
+ nDataSegments = 3;
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 0
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ // receive segment 1
+ face.receive(*makeDataWithSegment(1));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
+
+ // check if segment 2's state is FirstTimeSent
+ auto it = pipeline->m_segmentInfo.find(2);
+ BOOST_REQUIRE(it != pipeline->m_segmentInfo.end());
+ BOOST_CHECK(it->second.state == SegmentState::FirstTimeSent);
+
+ // timeout segment 2 twice
+ advanceClocks(time::milliseconds(400), 3);
+
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+
+ // check if segment 2's state is Retransmitted
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_REQUIRE(it != pipeline->m_segmentInfo.end());
+ BOOST_CHECK(it->second.state == SegmentState::Retransmitted);
+
+ // check if segment 2 was retransmitted twice
+ BOOST_CHECK_EQUAL(pipeline->m_retxCount.at(2), 2);
+
+ // receive segment 2 the first time
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // check if segment 2 was erased from m_segmentInfo
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_CHECK(it == pipeline->m_segmentInfo.end());
+
+ auto prevRtt = rttEstimator.getAvgRtt();
+ auto prevRto = rttEstimator.getEstimatedRto();
+
+ // receive segment 2 the second time
+ face.receive(*makeDataWithSegment(2));
+ advanceClocks(time::nanoseconds(1));
+
+ // nothing changed
+ it = pipeline->m_segmentInfo.find(2);
+ BOOST_CHECK(it == pipeline->m_segmentInfo.end());
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+ BOOST_CHECK_EQUAL(rttEstimator.getAvgRtt(), prevRtt);
+ BOOST_CHECK_EQUAL(rttEstimator.getEstimatedRto(), prevRto);
+}
+
+BOOST_AUTO_TEST_CASE(PrintSummaryWithNoRttMeasurements)
+{
+ // test the console ouptut when no RTT measurement is available,
+ // to make sure a proper message will be printed out
+
+ std::stringstream ss;
+
+ // change the underlying buffer and save the old buffer
+ auto oldBuf = std::cerr.rdbuf(ss.rdbuf());
+
+ pipeline->printSummary();
+ std::string line;
+
+ bool found = false;
+ while (std::getline(ss, line)) {
+ if (line == "RTT stats unavailable") {
+ found = true;
+ break;
+ }
+ }
+ BOOST_CHECK(found);
+ std::cerr.rdbuf(oldBuf); // reset
+}
+
+BOOST_AUTO_TEST_CASE(StopsWhenFileSizeLessThanChunkSize)
+{
+ // test to see if the program doesn't hang,
+ // when transfer is complete, for files less than the chunk size
+ // (i.e. when only one segment is sent/received)
+
+ createPipeline();
+ nDataSegments = 1;
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+
+ face.receive(*makeDataWithSegment(0));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_hasFinalBlockId, true);
+ BOOST_CHECK_EQUAL(pipeline->m_segmentInfo.size(), 0);
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPipelineInterestsCubic
+BOOST_AUTO_TEST_SUITE_END() // Get
+
+} // namespace ndn::tests
diff --git a/tests/get/pipeline-interests-fixed.t.cpp b/tests/get/pipeline-interests-fixed.t.cpp
new file mode 100644
index 0000000..c6e5464
--- /dev/null
+++ b/tests/get/pipeline-interests-fixed.t.cpp
@@ -0,0 +1,301 @@
+/* -*- 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 Chavoosh Ghasemi
+ */
+
+#include "tools/get/pipeline-interests-fixed.hpp"
+#include "tools/get/data-fetcher.hpp"
+
+#include "pipeline-interests-fixture.hpp"
+
+#include <cmath>
+
+namespace ndn::tests {
+
+class PipelineInterestFixedFixture : public PipelineInterestsFixture
+{
+public:
+ PipelineInterestFixedFixture()
+ {
+ opt.interestLifetime = 1_s;
+ opt.maxRetriesOnTimeoutOrNack = 3;
+ opt.isQuiet = true;
+ opt.maxPipelineSize = 5;
+ createPipeline();
+ }
+
+ void
+ createPipeline()
+ {
+ auto pline = std::make_unique<PipelineInterestsFixed>(face, opt);
+ pipeline = pline.get();
+ setPipeline(std::move(pline));
+ }
+
+protected:
+ Options opt;
+ PipelineInterestsFixed* pipeline;
+};
+
+BOOST_AUTO_TEST_SUITE(Get)
+BOOST_FIXTURE_TEST_SUITE(TestPipelineInterestsFixed, PipelineInterestFixedFixture)
+
+BOOST_AUTO_TEST_CASE(FullPipeline)
+{
+ nDataSegments = 13;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, i + 1);
+
+ if (i < nDataSegments - opt.maxPipelineSize) {
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize + i + 1);
+ // check if the interest for the segment i is well formed
+ const auto& sentInterest = face.sentInterests[i];
+ BOOST_CHECK_EQUAL(sentInterest.getCanBePrefix(), false);
+ BOOST_CHECK_EQUAL(sentInterest.getMustBeFresh(), opt.mustBeFresh);
+ BOOST_CHECK_EQUAL(Name(name).isPrefixOf(sentInterest.getName()), true);
+ BOOST_CHECK_EQUAL(getSegmentFromPacket(sentInterest), i);
+ }
+ else {
+ // all the interests have been sent for all the segments
+ BOOST_CHECK_EQUAL(face.sentInterests.size(), nDataSegments);
+ }
+ }
+
+ BOOST_CHECK_EQUAL(hasFailed, false);
+
+ advanceClocks(ndn::DEFAULT_INTEREST_LIFETIME, opt.maxRetriesOnTimeoutOrNack + 1);
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(TimeoutAllSegments)
+{
+ nDataSegments = 13;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ for (int i = 0; i < opt.maxRetriesOnTimeoutOrNack; ++i) {
+ advanceClocks(opt.interestLifetime);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * (i + 2));
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 0);
+
+ // A single retry for every pipeline element
+ for (size_t j = 0; j < opt.maxPipelineSize; ++j) {
+ const auto& interest = face.sentInterests[(opt.maxPipelineSize * (i + 1)) + j];
+ BOOST_CHECK_EQUAL(static_cast<size_t>(getSegmentFromPacket(interest)), j);
+ }
+ }
+
+ advanceClocks(opt.interestLifetime);
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(TimeoutAfterFinalBlockIdReceived)
+{
+ // the FinalBlockId is sent with the first segment, after the first segment failure the pipeline
+ // should fail
+
+ nDataSegments = 18;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ // send a single segment for each pipeline element but not the first element
+ advanceClocks(opt.interestLifetime);
+ for (uint64_t i = 1; i < opt.maxPipelineSize; ++i) {
+ face.receive(*makeDataWithSegment(i));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ // send a single data packet for each pipeline element
+ advanceClocks(opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack - 1);
+ for (uint64_t i = 0; i < opt.maxPipelineSize; ++i) {
+ face.receive(*makeDataWithSegment(opt.maxPipelineSize + i));
+ advanceClocks(time::nanoseconds(1));
+ }
+ advanceClocks(opt.interestLifetime);
+
+ size_t interestAfterFailure = face.sentInterests.size();
+
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+ BOOST_CHECK_EQUAL(hasFailed, true);
+
+ // these new segments should not generate new interests
+ advanceClocks(opt.interestLifetime);
+ for (uint64_t i = 0; i < opt.maxPipelineSize; ++i) {
+ face.receive(*makeDataWithSegment(opt.maxPipelineSize * 2 + i - 1));
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ // no more interests after a failure
+ advanceClocks(opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack);
+ BOOST_CHECK_EQUAL(interestAfterFailure, face.sentInterests.size());
+ BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(TimeoutBeforeFinalBlockIdReceived)
+{
+ // the FinalBlockId is sent only with the last segment, all segments are sent except for the
+ // second one (segment #1); all segments are received correctly until the FinalBlockId is received
+
+ nDataSegments = 22;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ advanceClocks(opt.interestLifetime);
+ for (uint64_t i = 2; i < opt.maxPipelineSize; ++i) {
+ face.receive(*makeDataWithSegment(i, false));
+ advanceClocks(time::nanoseconds(1));
+
+ const auto& lastInterest = face.sentInterests.back();
+ BOOST_CHECK_EQUAL(getSegmentFromPacket(lastInterest), opt.maxPipelineSize + i - 2);
+ }
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * 3 - 2);
+
+ // nack for the first pipeline element (segment #0)
+ auto nack = std::make_shared<lp::Nack>(face.sentInterests[opt.maxPipelineSize]);
+ nack->setReason(lp::NackReason::DUPLICATE);
+ face.receive(*nack);
+
+ // all the pipeline elements are two retries near the timeout error, but not the
+ // second (segment #1) that is only one retry near the timeout
+ advanceClocks(opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack - 1);
+ BOOST_CHECK_EQUAL(hasFailed, false);
+
+ // data for the first pipeline element (segment #0)
+ face.receive(*makeDataWithSegment(0, false));
+ BOOST_CHECK_EQUAL(hasFailed, false);
+
+ // data for all the pipeline element, but not the second (segment #1)
+ for (uint64_t i = opt.maxPipelineSize; i < nDataSegments; ++i) {
+ if (i == nDataSegments - 1) {
+ face.receive(*makeDataWithSegment(i, true));
+ }
+ else {
+ face.receive(*makeDataWithSegment(i, false));
+ }
+ advanceClocks(time::nanoseconds(1));
+ }
+ // timeout for the second pipeline element (segment #1), this should trigger an error
+ advanceClocks(opt.interestLifetime);
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments - 1);
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(SegmentReceivedAfterTimeout)
+{
+ // the FinalBlockId is never sent, all the pipeline elements with a segment number greater than
+ // segment #0 will fail, after this failure also segment #0 fail and this should trigger an error
+
+ nDataSegments = 22;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ advanceClocks(opt.interestLifetime);
+
+ // nack for the first pipeline element (segment #0)
+ auto nack = std::make_shared<lp::Nack>(face.sentInterests[opt.maxPipelineSize]);
+ nack->setReason(lp::NackReason::DUPLICATE);
+ face.receive(*nack);
+ BOOST_CHECK_EQUAL(hasFailed, false);
+
+ // timeout for all the pipeline elements, but not the first (segment #0)
+ advanceClocks(opt.interestLifetime, opt.maxRetriesOnTimeoutOrNack);
+ BOOST_CHECK_EQUAL(hasFailed, false);
+
+ // data for the first pipeline element (segment #0), this should trigger an error because the
+ // other pipeline elements failed
+ face.receive(*makeDataWithSegment(0, false));
+ advanceClocks(time::nanoseconds(1));
+
+ BOOST_CHECK_EQUAL(pipeline->m_nReceived, 1);
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionAllSegments)
+{
+ nDataSegments = 13;
+ BOOST_ASSERT(nDataSegments > opt.maxPipelineSize);
+
+ run(name);
+ advanceClocks(time::nanoseconds(1));
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize);
+
+ // send nack for all the pipeline elements first interest
+ for (size_t i = 0; i < opt.maxPipelineSize; i++) {
+ auto nack = std::make_shared<lp::Nack>(face.sentInterests[i]);
+ nack->setReason(lp::NackReason::CONGESTION);
+ face.receive(*nack);
+ advanceClocks(time::nanoseconds(1));
+ }
+
+ // send nack for all the pipeline elements interests after the first
+ for (int i = 1; i <= opt.maxRetriesOnTimeoutOrNack; ++i) {
+ time::milliseconds backoffTime(static_cast<uint64_t>(std::pow(2, i)));
+ if (backoffTime > DataFetcher::MAX_CONGESTION_BACKOFF_TIME)
+ backoffTime = DataFetcher::MAX_CONGESTION_BACKOFF_TIME;
+
+ advanceClocks(backoffTime);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), opt.maxPipelineSize * (i +1));
+
+ // A single retry for every pipeline element
+ for (size_t j = 0; j < opt.maxPipelineSize; ++j) {
+ const auto& interest = face.sentInterests[(opt.maxPipelineSize * i) + j];
+ BOOST_CHECK_LT(static_cast<size_t>(getSegmentFromPacket(interest)), opt.maxPipelineSize);
+ }
+
+ for (size_t j = 0; j < opt.maxPipelineSize; j++) {
+ auto nack = std::make_shared<lp::Nack>(face.sentInterests[(opt.maxPipelineSize * i) + j]);
+ nack->setReason(lp::NackReason::CONGESTION);
+ face.receive(*nack);
+ advanceClocks(time::nanoseconds(1));
+ }
+ }
+
+ BOOST_CHECK_EQUAL(hasFailed, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPipelineInterests
+BOOST_AUTO_TEST_SUITE_END() // Get
+
+} // namespace ndn::tests
diff --git a/tests/get/pipeline-interests-fixture.hpp b/tests/get/pipeline-interests-fixture.hpp
new file mode 100644
index 0000000..543f817
--- /dev/null
+++ b/tests/get/pipeline-interests-fixture.hpp
@@ -0,0 +1,91 @@
+/* -*- 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
+ * @author Weiwei Liu
+ * @author Chavoosh Ghasemi
+ */
+
+#ifndef NDN_TOOLS_TESTS_GET_PIPELINE_INTERESTS_FIXTURE_HPP
+#define NDN_TOOLS_TESTS_GET_PIPELINE_INTERESTS_FIXTURE_HPP
+
+#include "tools/get/pipeline-interests.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/io-fixture.hpp"
+
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace ndn::tests {
+
+using namespace ndn::get;
+
+class PipelineInterestsFixture : public IoFixture
+{
+protected:
+ void
+ setPipeline(std::unique_ptr<PipelineInterests> pline)
+ {
+ m_pipeline = std::move(pline);
+ }
+
+ std::shared_ptr<Data>
+ makeDataWithSegment(uint64_t segmentNo, bool setFinalBlockId = true) const
+ {
+ auto data = std::make_shared<Data>(Name(name).appendVersion(0).appendSegment(segmentNo));
+ if (setFinalBlockId)
+ data->setFinalBlock(name::Component::fromSegment(nDataSegments - 1));
+ return signData(data);
+ }
+
+ std::shared_ptr<Data>
+ makeDataWithSegmentAndCongMark(uint64_t segmentNo,
+ uint64_t congestionMark = 1,
+ bool setFinalBlockId = true) const
+ {
+ auto data = makeDataWithSegment(segmentNo, setFinalBlockId);
+ data->setCongestionMark(congestionMark);
+ return data;
+ }
+
+ void
+ run(const Name& name, uint64_t version = 0)
+ {
+ m_pipeline->run(Name(name).appendVersion(version),
+ [] (const Data&) {},
+ [this] (const std::string&) { hasFailed = true; });
+ }
+
+protected:
+ DummyClientFace face{m_io};
+ Name name{"/ndn/chunks/test"};
+ uint64_t nDataSegments = 0;
+ bool hasFailed = false;
+
+private:
+ std::unique_ptr<PipelineInterests> m_pipeline;
+};
+
+} // namespace ndn::tests
+
+#endif // NDN_TOOLS_TESTS_GET_PIPELINE_INTERESTS_FIXTURE_HPP