catchunks: Implement CUBIC window adaptation

Also increase the RTT estimator multiplier k to 8.

Change-Id: I68c5096ac0da854f071bab5f7519b1c144f20ca1
refs: #4861
diff --git a/tests/chunks/pipeline-interests-aimd.t.cpp b/tests/chunks/pipeline-interests-aimd.t.cpp
new file mode 100644
index 0000000..57022a6
--- /dev/null
+++ b/tests/chunks/pipeline-interests-aimd.t.cpp
@@ -0,0 +1,616 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2016-2019, 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/chunks/catchunks/pipeline-interests-aimd.hpp"
+#include "tools/chunks/catchunks/options.hpp"
+
+#include "pipeline-interests-fixture.hpp"
+
+namespace ndn {
+namespace chunks {
+namespace tests {
+
+using namespace ndn::tests;
+
+class PipelineInterestAimdFixture : public PipelineInterestsFixture
+{
+public:
+  PipelineInterestAimdFixture()
+    : opt(makePipelineOptions())
+    , rttEstimator(makeRttEstimatorOptions())
+  {
+    createPipeline();
+  }
+
+  void
+  createPipeline()
+  {
+    auto pline = make_unique<PipelineInterestsAimd>(face, rttEstimator, opt);
+    pipeline = pline.get();
+    setPipeline(std::move(pline));
+  }
+
+private:
+  static PipelineInterestsAdaptive::Options
+  makePipelineOptions()
+  {
+    PipelineInterestsAdaptive::Options pipelineOptions;
+    pipelineOptions.isQuiet = true;
+    pipelineOptions.isVerbose = false;
+    pipelineOptions.disableCwa = false;
+    pipelineOptions.ignoreCongMarks = false;
+    pipelineOptions.resetCwndToInit = false;
+    pipelineOptions.initCwnd = 1.0;
+    pipelineOptions.aiStep = 1.0;
+    pipelineOptions.mdCoef = 0.5;
+    pipelineOptions.initSsthresh = std::numeric_limits<int>::max();
+    return pipelineOptions;
+  }
+
+  static RttEstimator::Options
+  makeRttEstimatorOptions()
+  {
+    RttEstimator::Options rttOptions;
+    rttOptions.alpha = 0.125;
+    rttOptions.beta = 0.25;
+    rttOptions.k = 4;
+    rttOptions.minRto = Milliseconds(200);
+    rttOptions.maxRto = Milliseconds(4000);
+    return rttOptions;
+  }
+
+protected:
+  PipelineInterestsAdaptive::Options opt;
+  RttEstimator rttEstimator;
+  PipelineInterestsAdaptive* pipeline;
+  static constexpr double MARGIN = 0.01;
+};
+
+constexpr double PipelineInterestAimdFixture::MARGIN;
+
+BOOST_AUTO_TEST_SUITE(Chunks)
+BOOST_FIXTURE_TEST_SUITE(TestPipelineInterestsAimd, PipelineInterestAimdFixture)
+
+BOOST_AUTO_TEST_CASE(SlowStart)
+{
+  nDataSegments = 4;
+  pipeline->m_ssthresh = 8.0;
+  BOOST_REQUIRE_CLOSE(pipeline->m_cwnd, 1, MARGIN);
+
+  double preCwnd = pipeline->m_cwnd;
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  for (uint64_t i = 0; i < nDataSegments - 1; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, 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, 1, MARGIN);
+
+  double preCwnd = pipeline->m_cwnd;
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  for (uint64_t i = 0; i < pipeline->m_ssthresh; ++i) { // slow start
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+    preCwnd = pipeline->m_cwnd;
+  }
+
+  BOOST_CHECK_CLOSE(preCwnd, 4.25, MARGIN);
+
+  for (uint64_t i = pipeline->m_ssthresh; i < nDataSegments - 1; ++i) { // congestion avoidance
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+    BOOST_CHECK_CLOSE(pipeline->m_cwnd - preCwnd, opt.aiStep / floor(pipeline->m_cwnd), 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, 1, MARGIN);
+
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  // receive segment 0, 1, and 2
+  for (uint64_t i = 0; i < 3; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+  }
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 3);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4, MARGIN);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 7); // request for segment 7 has been sent
+
+  advanceClocks(io, time::milliseconds(100));
+
+  // receive segment 4
+  face.receive(*makeDataWithSegment(4));
+  advanceClocks(io, time::nanoseconds(1));
+
+  // receive segment 5
+  face.receive(*makeDataWithSegment(5));
+  advanceClocks(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.5, 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(io, 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.25, 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(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.75, 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, 1, MARGIN);
+
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  // receive segments 0 to 4
+  for (uint64_t i = 0; i < 5; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+  }
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.5, MARGIN);
+
+  // receive segment 5 with congestion mark
+  face.receive(*makeDataWithSegmentAndCongMark(5));
+  advanceClocks(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.25, 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(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.25, 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, 1, MARGIN);
+
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  // receive segments 0 to 4
+  for (uint64_t i = 0; i < 5; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+  }
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 5);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.5, MARGIN);
+
+  // receive segment 5 with congestion mark
+  face.receive(*makeDataWithSegmentAndCongMark(5));
+  advanceClocks(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 2.25, 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(io, 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, 1, MARGIN);
+
+  run(name);
+  advanceClocks(io, time::nanoseconds(1));
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  // receive segments 0 to 5
+  for (uint64_t i = 0; i < 6; ++i) {
+    face.receive(*makeDataWithSegment(i));
+    advanceClocks(io, time::nanoseconds(1));
+  }
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, 6);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 4.75, 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(io, time::nanoseconds(1));
+
+  BOOST_CHECK_EQUAL(pipeline->m_nReceived, nDataSegments);
+  BOOST_CHECK_CLOSE(pipeline->m_cwnd, 5.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(io, time::nanoseconds(1));
+
+  face.receive(*makeDataWithSegment(0));
+  advanceClocks(io, time::nanoseconds(1));
+
+  face.receive(*makeDataWithSegment(1));
+  advanceClocks(io, 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(io, 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(io, 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(io, 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(io, time::nanoseconds(1));
+
+  // receive segment 0 without FinalBlockId
+  face.receive(*makeDataWithSegment(0, false));
+  advanceClocks(io, 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(io, 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(io, time::nanoseconds(1));
+
+  // receive segment 0 without FinalBlockId
+  face.receive(*makeDataWithSegment(0, false));
+  advanceClocks(io, time::nanoseconds(1));
+
+  // receive segment 1 without FinalBlockId
+  face.receive(*makeDataWithSegment(1, false));
+  advanceClocks(io, 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(io, 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(io, 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(io, time::nanoseconds(1));
+
+  // receive segment 0 without FinalBlockId
+  face.receive(*makeDataWithSegment(0, false));
+  advanceClocks(io, time::nanoseconds(1));
+
+  // receive segment 1 without FinalBlockId
+  face.receive(*makeDataWithSegment(1, false));
+  advanceClocks(io, 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(io, 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(io, time::nanoseconds(1));
+
+  // timeout segment 3
+  advanceClocks(io, time::seconds(1));
+
+  // segment 3 is retransmitted
+  BOOST_CHECK_EQUAL(pipeline->m_retxCount[3], 1);
+
+  // receive segment 3
+  face.receive(*makeDataWithSegment(3));
+  advanceClocks(io, 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(io, time::nanoseconds(1));
+
+  // receive segment 0
+  face.receive(*makeDataWithSegment(0));
+  advanceClocks(io, time::nanoseconds(1));
+
+  // receive segment 1
+  face.receive(*makeDataWithSegment(1));
+  advanceClocks(io, 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(io, 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(io, 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(io, 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(io, time::nanoseconds(1));
+
+  face.receive(*makeDataWithSegment(0));
+  advanceClocks(io, 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() // Chunks
+
+} // namespace tests
+} // namespace chunks
+} // namespace ndn