tools: implement ndngetfile (no pipelining)
Change-Id: I314401e8ce4df3493c8ac00f7955a62b25f03e96
Refs: #1514
diff --git a/tools/ndngetfile.cpp b/tools/ndngetfile.cpp
new file mode 100644
index 0000000..2a26962
--- /dev/null
+++ b/tools/ndngetfile.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (c) 2014, Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng 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.
+ *
+ * repo-ng 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
+ * repo-ng, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ndngetfile.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace repo {
+
+using namespace ndn;
+
+void
+Consumer::fetchData(const Name& name)
+{
+ Interest interest(name);
+ interest.setInterestLifetime(m_interestLifetime);
+
+ if (m_hasVersion)
+ {
+ interest.setMustBeFresh(m_mustBeFresh);
+ }
+ else
+ {
+ interest.setMustBeFresh(true);
+ interest.setChildSelector(1);
+ }
+
+ m_face.expressInterest(interest,
+ bind(&Consumer::onData, this, _1, _2),
+ bind(&Consumer::onTimeout, this, _1));
+}
+
+void
+Consumer::run()
+{
+ // Send the first Interest
+ Name name(m_dataName);
+ if (m_hasVersion)
+ // If '-u' is specified, we already have version number in the name
+ name.appendSegment(m_nextSegment++);
+
+ fetchData(name);
+
+ // processEvents will block until the requested data received or timeout occurs
+ m_face.processEvents(m_timeout);
+}
+
+void
+Consumer::onData(const Interest& interest, Data& data)
+{
+ const Name& name = data.getName();
+ if (name.size() != m_dataName.size() + (m_hasVersion ? 1 : 2))
+ throw std::invalid_argument("unexpected data name size.");
+
+ uint64_t segment = name[-1].toSegment();
+
+ if (!m_hasVersion)
+ {
+ // Assume the second to last component is the version number
+ // and the last component is the segment number
+ m_dataName.append(name[-2]);
+ m_hasVersion = true;
+ if (m_verbose)
+ {
+ std::cerr << "LOG: version number is " << name[-2].toVersion() << std::endl;
+ }
+
+ if (segment != 0)
+ {
+ // Discard this segment and fetch the first segment
+ fetchData(Name(m_dataName).appendSegment(m_nextSegment++));
+ return;
+ }
+
+ m_nextSegment++;
+ }
+
+ BOOST_ASSERT(segment == (m_nextSegment - 1));
+
+ // Output the current segment
+ const Block& content = data.getContent();
+ m_os.write(reinterpret_cast<const char*>(content.value()), content.value_size());
+ m_totalSize += content.value_size();
+ if (m_verbose)
+ {
+ std::cerr << "LOG: received segment #" << segment << std::endl;
+ }
+
+ // Check final block id
+ const name::Component& finalBlockId = data.getMetaInfo().getFinalBlockId();
+ if (finalBlockId == name[-1])
+ {
+ // Reach EOF
+ std::cerr << "INFO: End of file is reached." << std::endl;
+ std::cerr << "INFO: Total # of segments received: " << (m_nextSegment - 1) << std::endl;
+ std::cerr << "INFO: Total # bytes of content received: " << m_totalSize << std::endl;
+ }
+ else
+ {
+ // Reset retry counter
+ m_retryCount = 0;
+
+ // Fetch next segment
+ fetchData(Name(m_dataName).appendSegment(m_nextSegment++));
+ }
+}
+
+const int Consumer::MAX_RETRY = 3;
+
+void
+Consumer::onTimeout(const Interest& interest)
+{
+ if (m_retryCount++ < Consumer::MAX_RETRY)
+ {
+ // Retransmit the interest
+ fetchData(interest.getName());
+ if (m_verbose)
+ {
+ std::cerr << "TIMEOUT: retransmit interest for " << interest.getName() << std::endl;
+ }
+ }
+ else
+ {
+ std::cerr << "TIMEOUT: last interest sent for segment #" << (m_nextSegment - 1) << std::endl;
+ std::cerr << "TIMEOUT: abort fetching after " << Consumer::MAX_RETRY
+ << " times of retry" << std::endl;
+ }
+}
+
+
+int
+usage(const std::string& filename)
+{
+ std::cerr << "Usage: \n "
+ << filename << " [-v] [-u] [-l lifetime] [-w timeout] [-o filename] ndn-name\n\n"
+ << "-v: be verbose\n"
+ << "-u: unversioned: do not try to find the latest version; "
+ "ndn-name contains version component\n"
+ << "ndn-name: NDN Name prefix for Data to be read\n"
+ << "-l: InterestLifetime in milliseconds\n"
+ << "-w: timeout in milliseconds for whole process (default unlimited)\n"
+ << "-o: write to local file name instead of stdout\n";
+ return 1;
+}
+
+
+int
+main(int argc, char** argv)
+{
+ std::string name;
+ const char* outputFile = 0;
+ bool verbose = false, unversioned = false;
+ int interestLifetime = 4000; // in milliseconds
+ int timeout = 0; // in milliseconds
+
+ int opt;
+ while ((opt = getopt(argc, argv, "vul:w:o:")) != -1)
+ {
+ switch (opt) {
+ case 'v':
+ verbose = true;
+ break;
+ case 'u':
+ unversioned = true;
+ break;
+ case 'l':
+ try
+ {
+ interestLifetime = boost::lexical_cast<int>(optarg);
+ }
+ catch (boost::bad_lexical_cast&)
+ {
+ std::cerr << "ERROR: -l option should be an integer." << std::endl;
+ return 1;
+ }
+ interestLifetime = std::max(interestLifetime, 0);
+ break;
+ case 'w':
+ try
+ {
+ timeout = boost::lexical_cast<int>(optarg);
+ }
+ catch (boost::bad_lexical_cast&)
+ {
+ std::cerr << "ERROR: -w option should be an integer." << std::endl;
+ return 1;
+ }
+ timeout = std::max(timeout, 0);
+ break;
+ case 'o':
+ outputFile = optarg;
+ break;
+ default:
+ return usage(argv[0]);
+ }
+ }
+
+ if (optind < argc)
+ {
+ name = argv[optind];
+ }
+
+ if (name.empty())
+ {
+ return usage(argv[0]);
+ }
+
+ std::streambuf* buf;
+ std::ofstream of;
+
+ if (outputFile != 0)
+ {
+ of.open(outputFile);
+ if (!of)
+ {
+ std::cerr << "ERROR: output file is invalid" << std::endl;
+ return 1;
+ }
+ buf = of.rdbuf();
+ }
+ else
+ {
+ buf = std::cout.rdbuf();
+ }
+
+ std::ostream os(buf);
+
+ Consumer consumer(name, os, verbose, unversioned,
+ interestLifetime, timeout);
+
+ try
+ {
+ consumer.run();
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ }
+
+ return 0;
+}
+
+} // namespace repo
+
+int
+main(int argc, char** argv)
+{
+ return repo::main(argc, argv);
+}
diff --git a/tools/ndngetfile.hpp b/tools/ndngetfile.hpp
new file mode 100644
index 0000000..26a4956
--- /dev/null
+++ b/tools/ndngetfile.hpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (c) 2014, Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng 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.
+ *
+ * repo-ng 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
+ * repo-ng, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REPO_NG_TOOLS_NDNGETFILE_HPP
+#define REPO_NG_TOOLS_NDNGETFILE_HPP
+
+#include <ndn-cxx/face.hpp>
+
+namespace repo {
+
+class Consumer : boost::noncopyable
+{
+public:
+ Consumer(const std::string& dataName, std::ostream& os,
+ bool verbose, bool unversioned,
+ int interestLifetime, int timeout,
+ bool mustBeFresh = false)
+ : m_dataName(dataName)
+ , m_os(os)
+ , m_verbose(verbose)
+ , m_hasVersion(unversioned)
+ , m_interestLifetime(interestLifetime)
+ , m_timeout(timeout)
+ , m_nextSegment(0)
+ , m_totalSize(0)
+ , m_retryCount(0)
+ , m_mustBeFresh(mustBeFresh)
+ {
+ }
+
+ void
+ run();
+
+private:
+ void
+ fetchData(const ndn::Name& name);
+
+ void
+ onData(const ndn::Interest& interest, ndn::Data& data);
+
+ void
+ onTimeout(const ndn::Interest& interest);
+
+private:
+ static const int MAX_RETRY;
+
+ ndn::Face m_face;
+ ndn::Name m_dataName;
+ std::ostream& m_os;
+ bool m_verbose;
+ bool m_hasVersion;
+ ndn::time::milliseconds m_interestLifetime;
+ ndn::time::milliseconds m_timeout;
+ uint64_t m_nextSegment;
+ int m_totalSize;
+ int m_retryCount;
+ bool m_mustBeFresh;
+};
+
+} // namespace repo
+
+#endif // REPO_NG_TOOLS_NDNGETFILE_HPP
diff --git a/tools/wscript b/tools/wscript
new file mode 100644
index 0000000..41f1ea8
--- /dev/null
+++ b/tools/wscript
@@ -0,0 +1,11 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+top = '..'
+
+def build(bld):
+ for app in bld.path.ant_glob('*.cpp'):
+ bld(features=['cxx', 'cxxprogram'],
+ target='%s' % (str(app.change_ext('', '.cpp'))),
+ source=app,
+ use='NDN_CXX',
+ )