| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /* |
| * Copyright (c) 2013-2024 Regents of the University of California. |
| * |
| * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
| * |
| * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| * terms of the GNU Lesser General Public License as published by the Free Software |
| * Foundation, either version 3 of the License, or (at your option) any later version. |
| * |
| * ndn-cxx library 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 Lesser General Public License for more details. |
| * |
| * You should have received copies of the GNU General Public License and GNU Lesser |
| * General Public License along with ndn-cxx, 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. |
| */ |
| |
| #include "ndn-cxx/util/segment-fetcher.hpp" |
| |
| #include "ndn-cxx/data.hpp" |
| #include "ndn-cxx/lp/nack.hpp" |
| #include "ndn-cxx/util/dummy-client-face.hpp" |
| |
| #include "tests/test-common.hpp" |
| #include "tests/unit/dummy-validator.hpp" |
| #include "tests/unit/io-key-chain-fixture.hpp" |
| |
| #include <set> |
| |
| namespace ndn::tests { |
| |
| class SegmentFetcherFixture : public IoKeyChainFixture |
| { |
| public: |
| static shared_ptr<Data> |
| makeDataSegment(const Name& baseName, uint64_t segment, bool isFinal) |
| { |
| const uint8_t buffer[] = "Hello, world!"; |
| auto data = makeData(Name(baseName).appendSegment(segment)); |
| data->setContent(buffer); |
| data->setFreshnessPeriod(1_s); |
| if (isFinal) { |
| data->setFinalBlock(data->getName()[-1]); |
| } |
| return data; |
| } |
| |
| void |
| onError(uint32_t errorCode) |
| { |
| ++nErrors; |
| lastError = errorCode; |
| } |
| |
| void |
| onComplete(ConstBufferPtr data) |
| { |
| ++nCompletions; |
| dataSize = data->size(); |
| } |
| |
| void |
| onInOrderComplete() |
| { |
| ++nCompletions; |
| ++nOnInOrderComplete; |
| } |
| |
| void |
| onInOrderData(ConstBufferPtr data) |
| { |
| ++nOnInOrderData; |
| dataSize += data->size(); |
| } |
| |
| void |
| nackLastInterest(lp::NackReason reason) |
| { |
| const Interest& lastInterest = face.sentInterests.back(); |
| face.receive(makeNack(lastInterest, reason)); |
| advanceClocks(10_ms); |
| } |
| |
| void |
| connectSignals(const shared_ptr<SegmentFetcher>& fetcher) |
| { |
| fetcher->onInOrderData.connect(std::bind(&SegmentFetcherFixture::onInOrderData, this, _1)); |
| fetcher->onInOrderComplete.connect(std::bind(&SegmentFetcherFixture::onInOrderComplete, this)); |
| fetcher->onComplete.connect(std::bind(&SegmentFetcherFixture::onComplete, this, _1)); |
| fetcher->onError.connect(std::bind(&SegmentFetcherFixture::onError, this, _1)); |
| |
| fetcher->afterSegmentReceived.connect([this] (const auto&) { ++this->nAfterSegmentReceived; }); |
| fetcher->afterSegmentValidated.connect([this] (const auto &) { ++this->nAfterSegmentValidated; }); |
| fetcher->afterSegmentNacked.connect([this] { ++nAfterSegmentNacked; }); |
| fetcher->afterSegmentTimedOut.connect([this] { ++nAfterSegmentTimedOut; }); |
| } |
| |
| void |
| onInterest(const Interest& interest) |
| { |
| if (interest.getName().get(-1).isSegment()) { |
| if (segmentsToDropOrNack.size() > 0 && |
| interest.getName().get(-1).toSegment() == segmentsToDropOrNack.front()) { |
| segmentsToDropOrNack.pop(); |
| if (sendNackInsteadOfDropping) { |
| lp::Nack nack = makeNack(interest, nackReason); |
| face.receive(nack); |
| } |
| return; |
| } |
| |
| auto data = makeDataSegment("/hello/world/version0", |
| interest.getName().get(-1).toSegment(), |
| interest.getName().get(-1).toSegment() == nSegments - 1); |
| face.receive(*data); |
| |
| uniqSegmentsSent.insert(interest.getName().get(-1).toSegment()); |
| if (uniqSegmentsSent.size() == nSegments) { |
| m_io.stop(); |
| } |
| } |
| else { |
| if (segmentsToDropOrNack.size() > 0 && |
| segmentsToDropOrNack.front() == 0) { |
| segmentsToDropOrNack.pop(); |
| if (sendNackInsteadOfDropping) { |
| lp::Nack nack = makeNack(interest, nackReason); |
| face.receive(nack); |
| } |
| return; |
| } |
| |
| auto data = makeDataSegment("/hello/world/version0", defaultSegmentToSend, nSegments == 1); |
| face.receive(*data); |
| uniqSegmentsSent.insert(defaultSegmentToSend); |
| } |
| } |
| |
| public: |
| DummyClientFace face{m_io, m_keyChain}; |
| std::set<uint64_t> uniqSegmentsSent; |
| |
| int nErrors = 0; |
| uint32_t lastError = 0; |
| int nCompletions = 0; |
| size_t dataSize = 0; |
| size_t nAfterSegmentReceived = 0; |
| size_t nAfterSegmentValidated = 0; |
| size_t nAfterSegmentNacked = 0; |
| size_t nAfterSegmentTimedOut = 0; |
| size_t nOnInOrderData = 0; |
| size_t nOnInOrderComplete = 0; |
| |
| // number of segments in fetched object |
| uint64_t nSegments = 0; |
| std::queue<uint64_t> segmentsToDropOrNack; |
| bool sendNackInsteadOfDropping = false; |
| lp::NackReason nackReason = lp::NackReason::NONE; |
| // segment that is sent in response to an Interest w/o a segment component in its name |
| uint64_t defaultSegmentToSend = 0; |
| }; |
| |
| BOOST_AUTO_TEST_SUITE(Util) |
| BOOST_FIXTURE_TEST_SUITE(TestSegmentFetcher, SegmentFetcherFixture) |
| |
| BOOST_AUTO_TEST_CASE(InvalidOptions) |
| { |
| SegmentFetcher::Options options; |
| options.mdCoef = 1.5; |
| DummyValidator acceptValidator; |
| BOOST_CHECK_THROW(SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options), |
| std::invalid_argument); |
| } |
| |
| BOOST_AUTO_TEST_CASE(ExceedMaxTimeout) |
| { |
| DummyValidator acceptValidator; |
| size_t nAfterSegmentTimedOut = 0; |
| SegmentFetcher::Options options; |
| options.maxTimeout = 100_ms; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator, options); |
| connectSignals(fetcher); |
| fetcher->afterSegmentTimedOut.connect([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }); |
| |
| advanceClocks(1_ms); |
| BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1); |
| |
| const Interest& interest = face.sentInterests[0]; |
| BOOST_CHECK_EQUAL(interest.getName(), "/hello/world"); |
| BOOST_CHECK_EQUAL(interest.getMustBeFresh(), true); |
| BOOST_CHECK_EQUAL(interest.getCanBePrefix(), true); |
| |
| advanceClocks(98_ms); |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| BOOST_CHECK_EQUAL(face.sentData.size(), 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| |
| advanceClocks(1_ms, 2); |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::INTEREST_TIMEOUT)); |
| BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1); |
| BOOST_CHECK_EQUAL(face.sentData.size(), 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 1); |
| } |
| |
| BOOST_AUTO_TEST_CASE(BasicSingleSegment) |
| { |
| DummyValidator acceptValidator; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| connectSignals(fetcher); |
| |
| advanceClocks(10_ms); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, true)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(ConstantCwnd) |
| { |
| SegmentFetcher::Options options; |
| options.useConstantCwnd = true; |
| DummyValidator acceptValidator; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator, options); |
| connectSignals(fetcher); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, false)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2); |
| BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 1); |
| face.receive(*makeDataSegment("/hello/world/version0", 1, false)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 3); |
| BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 2); |
| face.receive(*makeDataSegment("/hello/world/version0", 2, false)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 4); |
| BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 3); |
| face.receive(*makeDataSegment("/hello/world/version0", 3, false)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 5); |
| BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4); |
| nackLastInterest(lp::NackReason::CONGESTION); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 6); |
| BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4); |
| face.receive(*makeDataSegment("/hello/world/version0", 4, true)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(BasicMultipleSegments) |
| { |
| DummyValidator acceptValidator; |
| nSegments = 401; |
| sendNackInsteadOfDropping = false; |
| |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(BasicInOrder) |
| { |
| DummyValidator acceptValidator; |
| SegmentFetcher::Options options; |
| options.inOrder = true; |
| nSegments = 401; |
| sendNackInsteadOfDropping = false; |
| |
| auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nOnInOrderData, 401); |
| BOOST_CHECK_EQUAL(nOnInOrderComplete, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(FirstSegmentNotZero) |
| { |
| DummyValidator acceptValidator; |
| nSegments = 401; |
| sendNackInsteadOfDropping = false; |
| defaultSegmentToSend = 47; |
| |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(FirstSegmentNotZeroInOrder) |
| { |
| DummyValidator acceptValidator; |
| SegmentFetcher::Options options; |
| options.inOrder = true; |
| nSegments = 401; |
| sendNackInsteadOfDropping = false; |
| defaultSegmentToSend = 47; |
| |
| auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nOnInOrderData, 401); |
| BOOST_CHECK_EQUAL(nOnInOrderComplete, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(WindowSize) |
| { |
| DummyValidator acceptValidator; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| connectSignals(fetcher); |
| |
| advanceClocks(10_ms); // T+10ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, std::numeric_limits<double>::max()); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nReceived, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 0); |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), 1); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); |
| |
| double oldCwnd = fetcher->m_cwnd; |
| double oldSsthresh = fetcher->m_ssthresh; |
| uint64_t oldNextSegmentNum = fetcher->m_nextSegmentNum; |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, false)); |
| |
| advanceClocks(10_ms); //T+20ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0); |
| BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0"); |
| // +2 below because m_nextSegmentNum will be incremented in the receive callback if segment 0 is |
| // the first received |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 2); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd + fetcher->m_options.aiStep); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 14); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 1); |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 1 + fetcher->m_cwnd); |
| |
| oldCwnd = fetcher->m_cwnd; |
| oldNextSegmentNum = fetcher->m_nextSegmentNum; |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 2, false)); |
| |
| advanceClocks(10_ms); //T+30ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0); |
| BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0"); |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 28); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 2); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 2); |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 2 + fetcher->m_cwnd); |
| |
| oldCwnd = fetcher->m_cwnd; |
| oldNextSegmentNum = fetcher->m_nextSegmentNum; |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 1, false)); |
| |
| advanceClocks(10_ms); //T+40ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0); |
| BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0"); |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 2); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3); |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 3 + fetcher->m_cwnd); |
| |
| oldCwnd = fetcher->m_cwnd; |
| oldSsthresh = fetcher->m_ssthresh; |
| oldNextSegmentNum = fetcher->m_nextSegmentNum; |
| size_t oldSentInterestsSize = face.sentInterests.size(); |
| |
| nackLastInterest(lp::NackReason::CONGESTION); //T+50ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 20_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1); |
| BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0"); |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 2); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3); |
| // The Nacked segment will remain in pendingSegments, so the size of the structure doesn't change |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize); |
| |
| advanceClocks(10_ms); //T+60ms |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 30_ms); |
| BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1); |
| BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0"); |
| BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum); |
| BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0); |
| BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0); |
| BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42); |
| BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_highData, 2); |
| BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1); |
| BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3); |
| BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize); |
| |
| // Properly end test case |
| lp::Nack nack = makeNack(face.sentInterests[face.sentInterests.size() - 2], lp::NackReason::NO_ROUTE); |
| face.receive(nack); |
| advanceClocks(10_ms); //T+70ms |
| |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::NACK_ERROR)); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MissingSegmentNum) |
| { |
| DummyValidator acceptValidator; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| connectSignals(fetcher); |
| |
| advanceClocks(10_ms); |
| |
| const uint8_t buffer[] = "Hello, world!"; |
| auto data = makeData("/hello/world/version0/no-segment"); |
| data->setFreshnessPeriod(1_s); |
| data->setContent(buffer); |
| |
| face.receive(*data); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::DATA_HAS_NO_SEGMENT)); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MoreSegmentsThanNSegments) |
| { |
| DummyValidator acceptValidator; |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| connectSignals(fetcher); |
| |
| advanceClocks(10_ms); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, false)); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 1, false)); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 2, false)); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 3, false)); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| |
| auto data4 = makeDataSegment("/hello/world/version0", 4, false); |
| data4->setFinalBlock(name::Component::fromSegment(2)); |
| face.receive(*data4); |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 3); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(DuplicateNack) |
| { |
| DummyValidator acceptValidator; |
| nSegments = 401; |
| segmentsToDropOrNack.push(0); |
| segmentsToDropOrNack.push(200); |
| sendNackInsteadOfDropping = true; |
| nackReason = lp::NackReason::DUPLICATE; |
| |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(CongestionNack) |
| { |
| DummyValidator acceptValidator; |
| nSegments = 401; |
| segmentsToDropOrNack.push(0); |
| segmentsToDropOrNack.push(200); |
| sendNackInsteadOfDropping = true; |
| nackReason = lp::NackReason::CONGESTION; |
| |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(dataSize, 14 * 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| } |
| |
| BOOST_AUTO_TEST_CASE(OtherNackReason) |
| { |
| DummyValidator acceptValidator; |
| segmentsToDropOrNack.push(0); |
| sendNackInsteadOfDropping = true; |
| nackReason = lp::NackReason::NO_ROUTE; |
| face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1)); |
| |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| acceptValidator); |
| connectSignals(fetcher); |
| |
| face.processEvents(1_s); |
| |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); |
| } |
| |
| BOOST_AUTO_TEST_CASE(ValidationFailure) |
| { |
| DummyValidator validator; |
| validator.getPolicy().setResultCallback([] (const Name& name) { |
| return name.at(-1).toSegment() % 2 == 0; |
| }); |
| shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"), |
| validator); |
| connectSignals(fetcher); |
| |
| auto data1 = makeDataSegment("/hello/world", 0, false); |
| auto data2 = makeDataSegment("/hello/world", 1, true); |
| |
| advanceClocks(10_ms, 10); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms); |
| |
| face.receive(*data1); |
| |
| advanceClocks(10_ms, 10); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms); |
| |
| face.receive(*data2); |
| |
| advanceClocks(10_ms, 10); |
| |
| BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 200_ms); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 2); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1); |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| } |
| |
| BOOST_AUTO_TEST_CASE(Stop) |
| { |
| DummyValidator acceptValidator; |
| |
| auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator); |
| connectSignals(fetcher); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 2); |
| |
| fetcher->stop(); |
| advanceClocks(10_ms); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 1); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, true)); |
| advanceClocks(10_ms); |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| |
| fetcher.reset(); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 0); |
| |
| // Make sure we can re-assign w/o any complains from ASan |
| fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator); |
| connectSignals(fetcher); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 2); |
| |
| advanceClocks(10_ms); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, true)); |
| |
| advanceClocks(10_ms); |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 1); |
| |
| // Stop from callback |
| bool fetcherStopped = false; |
| |
| fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator); |
| fetcher->afterSegmentReceived.connect([&fetcher, &fetcherStopped] (const Data&) { |
| fetcherStopped = true; |
| fetcher->stop(); |
| }); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 2); |
| |
| advanceClocks(10_ms); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, true)); |
| |
| advanceClocks(10_ms); |
| BOOST_CHECK(fetcherStopped); |
| BOOST_CHECK_EQUAL(fetcher.use_count(), 1); |
| } |
| |
| BOOST_AUTO_TEST_CASE(Lifetime) |
| { |
| // BasicSingleSegment, but with scoped fetcher |
| |
| DummyValidator acceptValidator; |
| |
| weak_ptr<SegmentFetcher> weakFetcher; |
| { |
| auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator); |
| weakFetcher = fetcher; |
| connectSignals(fetcher); |
| } |
| |
| advanceClocks(10_ms); |
| BOOST_CHECK_EQUAL(weakFetcher.expired(), false); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 0, true)); |
| |
| advanceClocks(10_ms); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0); |
| BOOST_CHECK_EQUAL(weakFetcher.expired(), true); |
| } |
| |
| BOOST_AUTO_TEST_CASE(OutOfScopeTimeout) |
| { |
| DummyValidator acceptValidator; |
| SegmentFetcher::Options options; |
| options.maxTimeout = 3000_ms; |
| |
| weak_ptr<SegmentFetcher> weakFetcher; |
| { |
| auto fetcher = SegmentFetcher::start(face, Interest("/localhost/nfd/faces/list"), |
| acceptValidator, options); |
| weakFetcher = fetcher; |
| connectSignals(fetcher); |
| } |
| |
| advanceClocks(500_ms, 7); |
| BOOST_CHECK_EQUAL(weakFetcher.expired(), true); |
| |
| BOOST_CHECK_EQUAL(nErrors, 1); |
| BOOST_CHECK_EQUAL(nCompletions, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0); |
| BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 2); |
| } |
| |
| BOOST_AUTO_TEST_CASE(UncanceledPendingInterest, |
| * ut::description("test for bug #4770")) |
| { |
| DummyValidator acceptValidator; |
| auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator); |
| connectSignals(fetcher); |
| |
| // Fetcher will send the first interest immediately |
| // and the second interest after 1 second by default |
| advanceClocks(1100_ms); |
| |
| // Face will give data to the fetcher twice if the first interest is not canceled. |
| // isFinal=false to keep fetcher alive so that it can receive the second data. |
| face.receive(*makeDataSegment("/hello/world/version0", 0, false)); |
| |
| advanceClocks(1100_ms); |
| |
| face.receive(*makeDataSegment("/hello/world/version0", 1, true)); |
| |
| BOOST_CHECK_EQUAL(nErrors, 0); |
| BOOST_CHECK_EQUAL(nCompletions, 1); |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() // TestSegmentFetcher |
| BOOST_AUTO_TEST_SUITE_END() // Util |
| |
| } // namespace ndn::tests |