util: optional MustBeFresh in SegmentFetcher's initial Interest
Change-Id: Icc2d029a6a7a0d634d4502a43083b986ee4e803c
diff --git a/ndn-cxx/util/segment-fetcher.cpp b/ndn-cxx/util/segment-fetcher.cpp
index 11cc339..0d75668 100644
--- a/ndn-cxx/util/segment-fetcher.cpp
+++ b/ndn-cxx/util/segment-fetcher.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2013-2023 Regents of the University of California,
+ * Copyright (c) 2013-2024 Regents of the University of California,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University.
*
@@ -107,7 +107,12 @@
{
Interest interest(baseInterest);
interest.setCanBePrefix(true);
- interest.setMustBeFresh(true);
+ if (!interest.getName().empty() && interest.getName()[-1].isVersion()) {
+ interest.setMustBeFresh(false);
+ }
+ else {
+ interest.setMustBeFresh(m_options.probeLatestVersion);
+ }
interest.setInterestLifetime(m_options.interestLifetime);
if (isRetransmission) {
interest.refreshNonce();
@@ -375,15 +380,15 @@
return signalError(INTEREST_TIMEOUT, "Timeout exceeded");
}
- name::Component lastNameComponent = origInterest.getName().get(-1);
+ BOOST_ASSERT(!m_pendingSegments.empty());
+
+ const auto& origName = origInterest.getName();
std::map<uint64_t, PendingSegment>::iterator pendingSegmentIt;
- BOOST_ASSERT(m_pendingSegments.size() > 0);
- if (lastNameComponent.isSegment()) {
- BOOST_ASSERT(m_pendingSegments.count(lastNameComponent.toSegment()) > 0);
- pendingSegmentIt = m_pendingSegments.find(lastNameComponent.toSegment());
+ if (!origName.empty() && origName[-1].isSegment()) {
+ pendingSegmentIt = m_pendingSegments.find(origName[-1].toSegment());
+ BOOST_ASSERT(pendingSegmentIt != m_pendingSegments.end());
}
else { // First Interest
- BOOST_ASSERT(m_pendingSegments.size() > 0);
pendingSegmentIt = m_pendingSegments.begin();
}
@@ -393,7 +398,7 @@
m_rttEstimator.backoffRto();
- if (m_receivedSegments.size() == 0) {
+ if (m_receivedSegments.empty()) {
// Resend first Interest (until maximum receive timeout exceeded)
fetchFirstSegment(origInterest, true);
}
diff --git a/ndn-cxx/util/segment-fetcher.hpp b/ndn-cxx/util/segment-fetcher.hpp
index 6ddf6fe..1312de6 100644
--- a/ndn-cxx/util/segment-fetcher.hpp
+++ b/ndn-cxx/util/segment-fetcher.hpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2024 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -34,22 +34,29 @@
namespace ndn {
/**
- * @brief Utility class to fetch the latest version of a segmented object.
+ * @brief Utility class to fetch a versioned and segmented object.
*
* SegmentFetcher assumes that segments in the object are named `/<prefix>/<version>/<segment>`,
* where:
- * - `<prefix>` is the specified prefix,
- * - `<version>` is an unknown version that needs to be discovered, and
- * - `<segment>` is a segment number (the number of segments in the object is unknown until a Data
- * packet containing the `FinalBlockId` field is received).
+ * - `<prefix>` is an arbitrary name prefix;
+ * - `<version>` is the version number (VersionNameComponent);
+ * - `<segment>` is the segment number (SegmentNameComponent).
+ *
+ * The number of segments in the object is generally unknown until a Data packet containing
+ * a `FinalBlockId` field is received and validated.
+ *
+ * The version can either be provided by the application or be discovered at the beginning
+ * of the fetching process. By default, SegmentFetcher will attempt to probe the latest
+ * version of the object by requesting only "fresh" segments during the initial discovery
+ * phase. This behavior can be turned off by setting Options::probeLatestVersion to false.
*
* SegmentFetcher implements the following logic:
*
- * 1. Express an Interest to discover the latest version of the object:
+ * 1. If the application does not provide a `<version>` component and requires probing the
+ * latest version of the object, an Interest with CanBePrefix and MustBeFresh is sent to
+ * discover a fresh version. Otherwise, only CanBePrefix is set.
*
- * Interest: `/<prefix>?CanBePrefix&MustBeFresh`
- *
- * 2. Infer the latest version of the object: `<version> = Data.getName().get(-2)`.
+ * 2. Infer the version of the object: `version = data.getName().get(-2).toVersion()`.
*
* 3. Keep sending Interests for future segments until an error occurs or the number of segments
* indicated by the FinalBlockId in a received Data packet is reached. This retrieval will start
@@ -108,6 +115,7 @@
public:
time::milliseconds interestLifetime = 4_s; ///< lifetime of sent Interests - independent of Interest timeout
time::milliseconds maxTimeout = 60_s; ///< maximum allowed time between successful receipt of segments
+ bool probeLatestVersion = true; ///< use the first Interest to probe the latest version of the object
bool inOrder = false; ///< true for 'in order' mode, false for 'block' mode
bool useConstantInterestTimeout = false; ///< if true, Interest timeout is kept at `maxTimeout`
bool useConstantCwnd = false; ///< if true, window size is kept at `initCwnd`
@@ -129,11 +137,13 @@
*
* @param face Reference to the Face that should be used to fetch data.
* @param baseInterest Interest for the initial segment of requested data.
- * This interest may include a custom InterestLifetime and parameters that
- * will propagate to all subsequent Interests. The only exception is that the
- * initial Interest will be forced to include the "CanBePrefix=true" and
- * "MustBeFresh=true" parameters, which will not be included in subsequent
- * Interests.
+ * This Interest may include certain fields, such as ForwardingHint, that
+ * will propagate to all subsequent Interests sent by this SegmentFetcher.
+ * As a special case, the initial Interest will be forced to include the
+ * CanBePrefix field, which will not be included in subsequent Interests.
+ * If Options::probeLatestVersion is true, the initial Interest will also
+ * be forced to include the MustBeFresh field, while all subsequent Interests
+ * will not include it.
* @param validator Reference to the Validator the fetcher will use to validate data.
* The caller must ensure the validator remains valid until either #onComplete
* or #onError has been signaled.
diff --git a/tests/unit/util/segment-fetcher.t.cpp b/tests/unit/util/segment-fetcher.t.cpp
index 567918a..126dfe5 100644
--- a/tests/unit/util/segment-fetcher.t.cpp
+++ b/tests/unit/util/segment-fetcher.t.cpp
@@ -39,9 +39,8 @@
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->setContent("Hello, world!\0"sv);
data->setFreshnessPeriod(1_s);
if (isFinal) {
data->setFinalBlock(data->getName()[-1]);
@@ -176,6 +175,32 @@
std::invalid_argument);
}
+BOOST_AUTO_TEST_CASE(BasicSingleSegment)
+{
+ DummyValidator acceptValidator;
+ shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+ acceptValidator);
+ connectSignals(fetcher);
+
+ advanceClocks(10_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);
+
+ 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(ExceedMaxTimeout)
{
DummyValidator acceptValidator;
@@ -209,27 +234,6 @@
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;
@@ -245,31 +249,30 @@
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));
+ 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));
+ 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));
+ face.receive(*makeDataSegment("/hello/world/version0", 3, false));
advanceClocks(10_ms);
BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
@@ -284,8 +287,8 @@
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));
+ face.receive(*makeDataSegment("/hello/world/version0", 4, true));
advanceClocks(10_ms);
BOOST_CHECK_EQUAL(nErrors, 0);
@@ -303,12 +306,20 @@
nSegments = 401;
sendNackInsteadOfDropping = false;
- shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
- acceptValidator);
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1));
connectSignals(fetcher);
face.processEvents(1_s);
+ BOOST_REQUIRE_GE(face.sentInterests.size(), 2);
+
+ BOOST_CHECK_EQUAL(face.sentInterests[0].getName(), "/hello/world");
+ BOOST_CHECK_EQUAL(face.sentInterests[0].getMustBeFresh(), true);
+ BOOST_CHECK_EQUAL(face.sentInterests[0].getCanBePrefix(), true);
+ BOOST_CHECK_EQUAL(face.sentInterests[1].getName().size(), 4);
+ BOOST_CHECK(face.sentInterests[1].getName().at(-1).isSegment());
+ BOOST_CHECK_EQUAL(face.sentInterests[1].getMustBeFresh(), false);
+ BOOST_CHECK_EQUAL(face.sentInterests[1].getCanBePrefix(), false);
BOOST_CHECK_EQUAL(nErrors, 0);
BOOST_CHECK_EQUAL(nCompletions, 1);
@@ -344,6 +355,58 @@
BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
}
+BOOST_AUTO_TEST_CASE(VersionedPrefix)
+{
+ DummyValidator acceptValidator;
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world/v=42"), acceptValidator);
+ connectSignals(fetcher);
+
+ advanceClocks(1_ms);
+ BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+ const Interest& interest = face.sentInterests[0];
+ BOOST_CHECK_EQUAL(interest.getName(), "/hello/world/v=42");
+ BOOST_CHECK_EQUAL(interest.getCanBePrefix(), true);
+ BOOST_CHECK_EQUAL(interest.getMustBeFresh(), false);
+
+ face.receive(*makeDataSegment("/hello/world/v=42", 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(ProbeLatestVersionDisabled)
+{
+ DummyValidator acceptValidator;
+ SegmentFetcher::Options options;
+ options.probeLatestVersion = false;
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options);
+ connectSignals(fetcher);
+
+ 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.getCanBePrefix(), true);
+ BOOST_CHECK_EQUAL(interest.getMustBeFresh(), false);
+
+ face.receive(*makeDataSegment("/hello/world/v=0", 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(FirstSegmentNotZero)
{
DummyValidator acceptValidator;
@@ -620,8 +683,7 @@
sendNackInsteadOfDropping = true;
nackReason = lp::NackReason::DUPLICATE;
- shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
- acceptValidator);
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1));
connectSignals(fetcher);
@@ -645,8 +707,7 @@
sendNackInsteadOfDropping = true;
nackReason = lp::NackReason::CONGESTION;
- shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
- acceptValidator);
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
face.onSendInterest.connect(std::bind(&SegmentFetcherFixture::onInterest, this, _1));
connectSignals(fetcher);
@@ -669,8 +730,7 @@
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);
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
connectSignals(fetcher);
face.processEvents(1_s);
@@ -690,8 +750,7 @@
validator.getPolicy().setResultCallback([] (const Name& name) {
return name.at(-1).toSegment() % 2 == 0;
});
- shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
- validator);
+ auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), validator);
connectSignals(fetcher);
auto data1 = makeDataSegment("/hello/world", 0, false);
@@ -787,7 +846,6 @@
BOOST_CHECK_EQUAL(weakFetcher.expired(), false);
face.receive(*makeDataSegment("/hello/world/version0", 0, true));
-
advanceClocks(10_ms);
BOOST_CHECK_EQUAL(nErrors, 0);