Network-related operations of the TorrentManager class

This commit adds the downloadTorrentFile, download_file_manifest, download_data_packet methods

Change-Id: Ie221bc07907eb58117e83e317eca578174142d6d
Refs: #3505
diff --git a/src/torrent-manager.cpp b/src/torrent-manager.cpp
index afda819..c06db40 100644
--- a/src/torrent-manager.cpp
+++ b/src/torrent-manager.cpp
@@ -434,5 +434,195 @@
   // TODO(msweatt) IMPLEMENT ME
 }
 
+void
+TorrentManager::downloadTorrentFile(const std::string& path,
+                                    DataReceivedCallback onSuccess,
+                                    FailedCallback onFailed)
+{
+  auto manifestNames = make_shared<std::vector<Name>>();
+  this->downloadTorrentFileSegment(m_torrentFileName, path, manifestNames,
+                                   true, onSuccess, onFailed);
+}
+
+std::vector<Name>
+TorrentManager::downloadTorrentFile(const std::string& path)
+{
+  auto manifestNames = make_shared<std::vector<Name>>();
+  this->downloadTorrentFileSegment(m_torrentFileName, path, manifestNames,
+                                  false, {}, {});
+  return *manifestNames;
+}
+
+void
+TorrentManager::downloadTorrentFileSegment(const ndn::Name& name,
+                                           const std::string& path,
+                                           std::shared_ptr<std::vector<Name>> manifestNames,
+                                           bool async,
+                                           DataReceivedCallback onSuccess,
+                                           FailedCallback onFailed)
+{
+  shared_ptr<Interest> interest = createInterest(name);
+
+  auto dataReceived = [manifestNames, path, async, onSuccess, onFailed, this]
+                                            (const Interest& interest, const Data& data) {
+      // Stats Table update here...
+      m_stats_table_iter->incrementReceivedData();
+      m_retries = 0;
+
+      TorrentFile file(data.wireEncode());
+
+      // Write the torrent file segment to disk...
+      writeTorrentSegment(file, path);
+
+      const std::vector<Name>& manifestCatalog = file.getCatalog();
+      manifestNames->insert(manifestNames->end(), manifestCatalog.begin(), manifestCatalog.end());
+
+      shared_ptr<Name> nextSegmentPtr = file.getTorrentFilePtr();
+
+      if (async) {
+        onSuccess(file.getName());
+      }
+      if (nextSegmentPtr != nullptr) {
+        this->downloadTorrentFileSegment(*nextSegmentPtr, path, manifestNames,
+                                         async, onSuccess, onFailed);
+      }
+  };
+
+  auto dataFailed = [manifestNames, path, name, async, onSuccess, onFailed, this]
+                                                (const Interest& interest) {
+    ++m_retries;
+    if (m_retries >= MAX_NUM_OF_RETRIES) {
+      ++m_stats_table_iter;
+      if (m_stats_table_iter == m_statsTable.end()) {
+        m_stats_table_iter = m_statsTable.begin();
+      }
+    }
+    if (async) {
+      onFailed(interest.getName(), "Unknown error");
+    }
+    this->downloadTorrentFileSegment(name, path, manifestNames, async, onSuccess, onFailed);
+  };
+
+  m_face.expressInterest(*interest, dataReceived, dataFailed);
+
+  if (!async) {
+    m_face.processEvents();
+  }
+}
+
+
+void
+TorrentManager::download_file_manifest(const Name&              manifestName,
+                                       const std::string&       path,
+                                       TorrentManager::ManifestReceivedCallback onSuccess,
+                                       TorrentManager::FailedCallback           onFailed)
+{
+  auto packetNames = make_shared<std::vector<Name>>();
+  this->downloadFileManifestSegment(manifestName, path, packetNames, onSuccess, onFailed);
+}
+
+void
+TorrentManager::downloadFileManifestSegment(const Name& manifestName,
+                                            const std::string& path,
+                                            std::shared_ptr<std::vector<Name>> packetNames,
+                                            TorrentManager::ManifestReceivedCallback onSuccess,
+                                            TorrentManager::FailedCallback onFailed)
+{
+  shared_ptr<Interest> interest = this->createInterest(manifestName);
+
+  auto dataReceived = [packetNames, path, onSuccess, onFailed, this]
+                                          (const Interest& interest, const Data& data) {
+    // Stats Table update here...
+    m_stats_table_iter->incrementReceivedData();
+    m_retries = 0;
+
+    FileManifest file(data.wireEncode());
+
+    // Write the file manifest segment to disk...
+    writeFileManifest(file, path);
+
+    const std::vector<Name>& packetsCatalog = file.catalog();
+    packetNames->insert(packetNames->end(), packetsCatalog.begin(), packetsCatalog.end());
+    shared_ptr<Name> nextSegmentPtr = file.submanifest_ptr();
+    if (nextSegmentPtr != nullptr) {
+      this->downloadFileManifestSegment(*nextSegmentPtr, path, packetNames, onSuccess, onFailed);
+    }
+    else
+      onSuccess(*packetNames);
+  };
+
+  auto dataFailed = [packetNames, path, manifestName, onFailed, this]
+                                                (const Interest& interest) {
+    m_retries++;
+    if (m_retries >= MAX_NUM_OF_RETRIES) {
+      m_stats_table_iter++;
+      if (m_stats_table_iter == m_statsTable.end())
+        m_stats_table_iter = m_statsTable.begin();
+    }
+    onFailed(interest.getName(), "Unknown failure");
+  };
+
+  m_face.expressInterest(*interest, dataReceived, dataFailed);
+}
+
+void
+TorrentManager::download_data_packet(const Name& packetName,
+                                     DataReceivedCallback onSuccess,
+                                     FailedCallback onFailed)
+{
+  shared_ptr<Interest> interest = this->createInterest(packetName);
+
+  auto dataReceived = [onSuccess, onFailed, this]
+                                          (const Interest& interest, const Data& data) {
+    // Write data to disk...
+    writeData(data);
+
+    // Stats Table update here...
+    m_stats_table_iter->incrementReceivedData();
+    m_retries = 0;
+    onSuccess(data.getName());
+  };
+  auto dataFailed = [onFailed, this]
+                             (const Interest& interest) {
+    m_retries++;
+    if (m_retries >= MAX_NUM_OF_RETRIES) {
+      m_stats_table_iter++;
+      if (m_stats_table_iter == m_statsTable.end())
+        m_stats_table_iter = m_statsTable.begin();
+    }
+    onFailed(interest.getName(), "Unknown failure");
+  };
+
+  m_face.expressInterest(*interest, dataReceived, dataFailed);
+}
+
+shared_ptr<Interest>
+TorrentManager::createInterest(Name name)
+{
+  shared_ptr<Interest> interest = make_shared<Interest>(name);
+  interest->setInterestLifetime(time::milliseconds(2000));
+  interest->setMustBeFresh(true);
+
+  // Select routable prefix
+  Link link(name, { {1, m_stats_table_iter->getRecordName()} });
+  m_keyChain->sign(link, signingWithSha256());
+  Block linkWire = link.wireEncode();
+
+  // Stats Table update here...
+  m_stats_table_iter->incrementSentInterests();
+
+  m_sortingCounter++;
+  if (m_sortingCounter >= SORTING_INTERVAL) {
+    m_sortingCounter = 0;
+    m_statsTable.sort();
+    m_stats_table_iter = m_statsTable.begin();
+    m_retries = 0;
+  }
+
+  interest->setLink(linkWire);
+
+  return interest;
+}
+
 }  // end ntorrent
-}  // end ndn
\ No newline at end of file
+}  // end ndn
diff --git a/src/torrent-manager.hpp b/src/torrent-manager.hpp
index fc790a8..a96f21b 100644
--- a/src/torrent-manager.hpp
+++ b/src/torrent-manager.hpp
@@ -21,11 +21,16 @@
 #ifndef INCLUDED_TORRENT_FILE_MANAGER_H
 #define INCLUDED_TORRENT_FILE_MANAGER_H
 
-#include "torrent-file.hpp"
 #include "file-manifest.hpp"
+#include "stats-table.hpp"
+#include "torrent-file.hpp"
 
-#include <ndn-cxx/name.hpp>
 #include <ndn-cxx/data.hpp>
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/link.hpp>
+#include <ndn-cxx/name.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
 
 #include <boost/filesystem/fstream.hpp>
 
@@ -47,60 +52,118 @@
    * \brief A class to manage the interaction with the system in seeding/leaching a torrent
    */
  public:
-  typedef std::function<void(const ndn::Name&)>                     DataReceivedCallback;
-  typedef std::function<void(const std::vector<ndn::Name>&)>        ManifestReceivedCallback;
-  typedef std::function<void(const ndn::Name&, const std::string&)> FailedCallback;
+   typedef std::function<void(const ndn::Name&)>                     DataReceivedCallback;
+   typedef std::function<void(const std::vector<ndn::Name>&)>        ManifestReceivedCallback;
+   typedef std::function<void(const ndn::Name&, const std::string&)> FailedCallback;
 
-  TorrentManager(const ndn::Name&   torrentFileName,
-                 const std::string& dataPath);
+   /*
+    * \brief Create a new Torrent manager with the specified parameters.
+    * @param torrentFileName The full name of the initial segment of the torrent file
+    * @param dataPath The path to the location on disk to use for the torrent data
+    * @param face Optional face object to be used for data retrieval
+    *
+    * The behavior is undefined unless Initialize() is called before calling any other method on a
+    * TorrentManger object.
+    */
+   TorrentManager(const ndn::Name&   torrentFileName,
+                  const std::string& dataPath,
+                  ndn::Face& face);
+
+   /*
+    * \brief Create a new Torrent manager with the specified parameters.
+    * @param torrentFileName The full name of the initial segment of the torrent file
+    * @param dataPath The path to the location on disk to use for the torrent data
+    *
+    * The behavior is undefined unless Initialize() is called before calling any other method on a
+    * TorrentManger object.
+    */
+   TorrentManager(const ndn::Name&   torrentFileName,
+                  const std::string& dataPath);
+
   /*
-   * \brief Create a new Torrent manager with the specified parameters.
-   * @param torrentFileName The full name of the initial segment of the torrent file
-   * @param filePath The path to the location on disk to use for the torrent data
+   * @brief Initialize the state of this object.
    *
-   * The behavior is undefined unless Initialize() is called before calling any other method on a
-   * TorrentManger object.
-   */
-
-  void Initialize();
-  /*
-   * \brief Initialize the state of this object.
    * Read and validate from disk all torrent file segments, file manifests, and data packets for
    * the torrent file managed by this object initializing all state in this manager respectively.
    * Also seeds all validated data.
+  */
+  void
+  Initialize();
+
+  /*
+   * @brief Download the torrent file
+   * @param path The path to write the downloaded segments
+   * @return A vector of the file manifest names contained in the torrent file
+   *
    */
+  std::vector<Name>
+  downloadTorrentFile(const std::string& path);
 
-  std::vector<Name> downloadTorrentFile(const std::string& path = "");
-  // Request from the network the all segments of the 'torrentFileName' and write onto local disk at
-  // the specified 'path'.
+  /*
+   * @brief Download the torrent file
+   * @param path The path to write the downloaded segments
+   * @param onSuccess Callback to be called if we successfully download all the
+   *                  segments of the torrent file. It passes the name of the file manifest
+   *                  to be downloaded to the callback
+   * @param onFailed Callaback to be called if we fail to download a segment of
+   *                 the torrent file. It passes the name of the torrent file segment that
+   *                 failed to download and a failure reason to the callback
+   *
+   * This method provides non-blocking downloading of all the torrent file segments
+   *
+   */
+  void
+  downloadTorrentFile(const std::string& path,
+                      DataReceivedCallback onSuccess,
+                      FailedCallback onFailed);
 
+  /*
+   * @brief Download a file manifest
+   * @param manifestName The name of the manifest file to be downloaded
+   * @param path The path to write the downloaded segments
+   * @param onSuccess Callback to be called if we successfully download all the
+   *                  segments of the file manifest. It passes the names of the data packets
+   *                  to be downloaded to the callback
+   * @param onFailed Callaback to be called if we fail to download a segment of
+   *                 the file manifest. It passes the name of the data packet that failed
+   *                 to download and a failure reason
+   *
+   * This method provides non-blocking downloading of all the file manifest segments
+   *
+   */
   void download_file_manifest(const Name&              manifestName,
                               const std::string&       path,
                               ManifestReceivedCallback onSuccess,
-                              FailedCallback           onFailed) const;
-  // Request from the network the all segments of the 'manifestName' and write onto local disk at
-  // the specified 'path'.
+                              FailedCallback           onFailed);
 
+  /*
+   * @brief Download a data packet
+   * @param packetName The name of the data packet to be downloaded
+   * @param onSuccess Callback to be called if we successfully download the data packet.
+   *                  It passes the name of the data packet to the callback
+   * @param onFailed Callaback to be called if we fail to download the requested data packet
+   *                 It passes the name of the data packet to the callback and a failure reason
+   *
+   * This method writes the downloaded data packet to m_dataPath on disk
+   *
+   */
   void download_data_packet(const Name&          packetName,
                             DataReceivedCallback onSuccess,
-                            FailedCallback       onFailed) const;
-  // Request from the network the Data with the specified 'packetName' and write onto local disk at
-  // 'm_dataPath'.
+                            FailedCallback       onFailed);
 
-  void seed(const Data& data) const;
   // Seed the specified 'data' to the network.
+  void seed(const Data& data) const;
 
  protected:
-  bool writeData(const Data& packet);
   /*
    * \brief Write @p packet composed of torrent date to disk.
    * @param packet The data packet to be written to the disk
    * Write the Data packet to disk, return 'true' if data successfully written to disk 'false'
    * otherwise. Behavior is undefined unless the corresponding file manifest has already been
    * downloaded.
-  */
+   */
+  bool writeData(const Data& packet);
 
-  bool writeTorrentSegment(const TorrentFile& segment, const std::string& path);
   /*
    * \brief Write the @p segment torrent segment to disk at the specified path.
    * @param segment The torrent file segment to be written to disk
@@ -109,8 +172,8 @@
    * otherwise. Behavior is undefined unless @segment is a correct segment for the torrent file of
    * this manager and @p path is the directory used for all segments of this torrent file.
    */
+  bool writeTorrentSegment(const TorrentFile& segment, const std::string& path);
 
-  bool writeFileManifest(const FileManifest& manifest, const std::string& path);
   /*
    * \brief Write the @p manifest file manifest to disk at the specified @p path.
    * @param manifest The file manifest  to be written to disk
@@ -120,10 +183,53 @@
    * torrent file of this manager and @p path is the directory used for all file manifests of this
    * torrent file.
    */
+  bool writeFileManifest(const FileManifest& manifest, const std::string& path);
 
-  void onDataReceived(const Data& data);
+  /*
+   * \brief Download the segments of the torrent file
+   * @param name The name of the torrent file to be downloaded
+   * @param path The path to write the torrent file on disk
+   * @param manifestNames A vector containing the name of the manifests in the torrent file.
+   *                      This parameter will be updated every time we receive a torrent
+   *                      file segment
+   * @param async Blocking (sync) or non-blocking (async) operation
+   * @param onSuccess Optional callback to be called when all the segments of the torrent file
+   *                  have been downloaded. The default value is an empty callack. A callback
+   *                  should be specified when async is false
+   * @param onFailed Optional callback to be called when we fail to download a segment of the
+   *                 torrent file. The default value is an empty callack. A callback should be
+   *                 specified when async is false
+   *
+   */
+  void downloadTorrentFileSegment(const ndn::Name& name,
+                                  const std::string& path,
+                                  std::shared_ptr<std::vector<Name>> manifestNames,
+                                  bool async,
+                                  DataReceivedCallback onSuccess,
+                                  FailedCallback onFailed);
 
-  void onInterestReceived(const Name& name);
+  /*
+   * \brief Download the segments of a file manifest
+   * @param manifestName The name of the file manifest to be downloaded
+   * @param path The path to write the file manifest on disk
+   * @param packetNames A vector containing the name of the data packets in the file manifest
+   * @param onSuccess Callback to be called when all the segments of the file manifest have been
+   *                  downloaded
+   * @param onFailed Callback to be called when we fail to download a file manifest segment
+   *
+   */
+  void downloadFileManifestSegment(const Name& manifestName,
+                                   const std::string& path,
+                                   std::shared_ptr<std::vector<Name>> packetNames,
+                                   TorrentManager::ManifestReceivedCallback onSuccess,
+                                   TorrentManager::FailedCallback onFailed);
+
+  enum {
+    // Number of times to retry if a routable prefix fails to retrieve data
+    MAX_NUM_OF_RETRIES = 5,
+    // Number of Interests to be sent before sorting the stats table
+    SORTING_INTERVAL = 100
+  };
 
   // A map from each fileManifest to corresponding file stream on disk and a bitmap of which Data
   // packets this manager currently has
@@ -138,20 +244,67 @@
   Name                                                                m_torrentFileName;
   // The path to the location on disk of the Data packet for this manager
   std::string                                                         m_dataPath;
+
+private:
+  shared_ptr<Interest>
+  createInterest(Name name);
+
+  // Stats table where routable prefixes are stored
+  StatsTable                                                          m_statsTable;
+  // Face used for network communication
+  Face&                                                               m_face;
+  // Iterator to the routable prefix that we currently use
+  StatsTable::iterator                                                m_stats_table_iter;
+  // Number of retries per routable prefix
+  uint64_t                                                            m_retries;
+  // Number of Interests sent since last sorting
+  uint64_t                                                            m_sortingCounter;
+  // Keychain instance
+  unique_ptr<KeyChain>                                                m_keyChain;
 };
 
 inline
-TorrentManager::TorrentManager(const ndn::Name&   torrentFileName,
-                               const std::string& dataPath)
+TorrentManager::TorrentManager(const ndn::Name&      torrentFileName,
+                               const std::string&    dataPath,
+                               ndn::Face& face)
 : m_fileStates()
 , m_torrentSegments()
 , m_fileManifests()
 , m_torrentFileName(torrentFileName)
 , m_dataPath(dataPath)
+, m_face(face)
+, m_retries(0)
+, m_sortingCounter(0)
+, m_keyChain(new KeyChain())
 {
+  // Hardcoded prefixes for now
+  // TODO(Spyros): Think of something more clever to bootstrap...
+  m_statsTable.insert("/ucla");
+  m_statsTable.insert("/arizona");
+  m_stats_table_iter = m_statsTable.begin();
+}
+
+inline
+TorrentManager::TorrentManager(const ndn::Name&      torrentFileName,
+                               const std::string&    dataPath)
+: m_fileStates()
+, m_torrentSegments()
+, m_fileManifests()
+, m_torrentFileName(torrentFileName)
+, m_dataPath(dataPath)
+, m_face(*(new ndn::Face()))
+, m_retries(0)
+, m_sortingCounter(0)
+, m_keyChain(new KeyChain())
+{
+  // Hardcoded prefixes for now
+  // TODO(Spyros): Think of something more clever to bootstrap...
+  m_statsTable.insert("/ucla");
+  m_statsTable.insert("/arizona");
+  m_stats_table_iter = m_statsTable.begin();
 }
 
 }  // end ntorrent
 }  // end ndn
 
-#endif // INCLUDED_TORRENT_FILE_MANAGER_H
\ No newline at end of file
+#endif // INCLUDED_TORRENT_FILE_MANAGER_H
diff --git a/tests/unit-tests/file-manifest.t.cpp b/tests/unit-tests/file-manifest.t.cpp
index 80a344e..72316f8 100644
--- a/tests/unit-tests/file-manifest.t.cpp
+++ b/tests/unit-tests/file-manifest.t.cpp
@@ -345,11 +345,11 @@
     // Negative tests
     //   non-existent file
     {128          , 128          , "tests/testdata/foo/fake.txt", "/NTORRENT/foo/", false, true },
-    //   // prefix mismatch
+    // prefix mismatch
     {128          , 128          , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/",  false, true },
-    //  scaling test
+    // scaling test
     // {10240         , 5120 ,         "tests/testdata/foo/huge_file", "/NTORRENT/foo/", false, false },
-      // assertion failures (tests not supported on platforms)
+    // assertion failures (tests not supported on platforms)
     // {0            , 128          , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/", true },
     // {128          , 0            , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/", true },
   };
diff --git a/tests/unit-tests/torrent-manager.t.cpp b/tests/unit-tests/torrent-manager.t.cpp
index 722173b..bb16344 100644
--- a/tests/unit-tests/torrent-manager.t.cpp
+++ b/tests/unit-tests/torrent-manager.t.cpp
@@ -23,11 +23,13 @@
 
 #include "torrent-manager.hpp"
 #include "torrent-file.hpp"
+#include "unit-test-time-fixture.hpp"
 
 #include <set>
 
 #include <boost/filesystem.hpp>
 
+#include <ndn-cxx/util/dummy-client-face.hpp>
 #include <ndn-cxx/util/io.hpp>
 
 namespace ndn {
@@ -36,6 +38,7 @@
 
 using std::vector;
 using ndn::Name;
+using ndn::util::DummyClientFace;
 
 namespace fs = boost::filesystem;
 
@@ -44,7 +47,15 @@
   TestTorrentManager(const ndn::Name&   torrentFileName,
                      const std::string& filePath)
   : TorrentManager(torrentFileName, filePath)
-  {}
+  {
+  }
+
+  TestTorrentManager(const ndn::Name&   torrentFileName,
+                     const std::string& filePath,
+                     DummyClientFace& face)
+  : TorrentManager(torrentFileName, filePath, face)
+  {
+  }
 
   std::vector<TorrentFile> torrentSegments() const {
     return m_torrentSegments;
@@ -74,6 +85,28 @@
   }
 };
 
+class FaceFixture : public UnitTestTimeFixture
+{
+public:
+  explicit
+  FaceFixture(bool enableRegistrationReply = true)
+    : face(io, { true, enableRegistrationReply })
+  {
+  }
+
+public:
+  DummyClientFace face;
+};
+
+class FacesNoRegistrationReplyFixture : public FaceFixture
+{
+public:
+  FacesNoRegistrationReplyFixture()
+    : FaceFixture(false)
+  {
+  }
+};
+
 BOOST_AUTO_TEST_SUITE(TestTorrentManagerInitialize)
 
 BOOST_AUTO_TEST_CASE(CheckInitializeComplete)
@@ -233,6 +266,163 @@
   fs::remove_all(dirPath);
 }
 
+BOOST_FIXTURE_TEST_SUITE(TestTorrentManagerNetworkingStuff, FaceFixture)
+
+BOOST_AUTO_TEST_CASE(TestDownloadingTorrentFile)
+{
+  vector<FileManifest> manifests;
+  vector<TorrentFile> torrentSegments;
+  std::string filePath = ".appdata/foo/";
+  // get torrent files and manifests
+  {
+    auto temp = TorrentFile::generate("tests/testdata/foo", 1, 10, 10, false);
+
+    torrentSegments = temp.first;
+    auto temp1      = temp.second;
+    temp1.pop_back(); // remove the manifests for the last file
+    for (const auto& ms : temp1) {
+      for (const auto& m : ms.first) {
+        manifests.push_back(m);
+      }
+    }
+  }
+
+  TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
+                             filePath, face);
+  manager.Initialize();
+
+  // Test download torrent file segments
+  uint32_t counter = 0;
+  manager.downloadTorrentFile(filePath + "torrent_files", [&counter, &torrentSegments] (const ndn::Name& name) {
+                                          BOOST_CHECK_EQUAL(torrentSegments[counter].getName(), name);
+                                          counter++;
+                                        },
+                                        bind([] {
+                                          BOOST_FAIL("Unexpected failure");
+                                        }));
+
+  for (auto i = torrentSegments.begin(); i != torrentSegments.end(); i++) {
+    advanceClocks(time::milliseconds(1), 40);
+    face.receive(dynamic_cast<Data&>(*i));
+  }
+
+  fs::remove_all(filePath);
+}
+
+BOOST_AUTO_TEST_CASE(TestDownloadingFileManifests)
+{
+  vector<FileManifest> manifests;
+  vector<TorrentFile> torrentSegments;
+  std::string filePath = ".appdata/foo/";
+  // get torrent files and manifests
+  {
+    auto temp = TorrentFile::generate("tests/testdata/foo", 1, 10, 10, false);
+
+    torrentSegments = temp.first;
+    auto temp1      = temp.second;
+    temp1.pop_back(); // remove the manifests for the last file
+    for (const auto& ms : temp1) {
+      for (const auto& m : ms.first) {
+        manifests.push_back(m);
+      }
+    }
+  }
+
+  TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
+                             filePath, face);
+  manager.Initialize();
+
+  // Test download manifest segments -- 2 files (the first one segment, the second multiple)
+  int counter = 0;
+  manager.download_file_manifest(manifests[0].getFullName(), filePath + "manifests",
+                                [&counter, &manifests]
+                                (const std::vector<ndn::Name>& vec) {
+                                  uint32_t packetIndex = 0;
+                                  for (auto j = vec.begin(); j != vec.end(); j++) {
+                                    BOOST_CHECK_EQUAL(manifests[counter].catalog()[packetIndex],
+                                                      *j);
+                                    packetIndex++;
+                                  }
+                                  counter++;
+                                },
+                                [](const ndn::Name& name, const std::string& reason) {
+                                  BOOST_FAIL("Unexpected failure");
+                                });
+
+  advanceClocks(time::milliseconds(1), 40);
+  face.receive(dynamic_cast<Data&>(manifests[0]));
+
+  manager.download_file_manifest(manifests[1].getFullName(), filePath + "manifests",
+                                [&counter, &manifests]
+                                (const std::vector<ndn::Name>& vec) {
+                                  uint32_t packetIndex = 0;
+                                  for (auto j = vec.begin(); j != vec.end(); j++) {
+                                    BOOST_CHECK_EQUAL(manifests[counter].catalog()[packetIndex],
+                                                      *j);
+                                    // if we have read all the packet names from a
+                                    // segment, move to the next one
+                                    if (packetIndex == manifests[counter].catalog().size() - 1) {
+                                      packetIndex = 0;
+                                      counter++;
+                                    }
+                                    else {
+                                      packetIndex++;
+                                    }
+                                  }
+                                },
+                                [](const ndn::Name& name, const std::string& reason) {
+                                  BOOST_FAIL("Unexpected failure");
+                                });
+
+  for (auto i = manifests.begin() + 1; i != manifests.end(); i++) {
+    advanceClocks(time::milliseconds(1), 40);
+    face.receive(dynamic_cast<Data&>(*i));
+  }
+
+  fs::remove_all(filePath);
+}
+
+BOOST_AUTO_TEST_CASE(TestDownloadingDataPackets)
+{
+  std::string filePath = ".appdata/foo/";
+  TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
+                             filePath, face);
+  manager.Initialize();
+
+  Name dataName("/test/ucla");
+
+  // Download data successfully
+  manager.download_data_packet(dataName,
+                              [&dataName] (const ndn::Name& name) {
+                                BOOST_CHECK_EQUAL(name, dataName);
+                              },
+                              [](const ndn::Name& name, const std::string& reason) {
+                                BOOST_FAIL("Unexpected failure");
+                              });
+
+  auto data = make_shared<Data>(dataName);
+  SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(encoding::makeEmptyBlock(tlv::SignatureValue));
+  data->setSignature(fakeSignature);
+  data->wireEncode();
+
+  advanceClocks(time::milliseconds(1), 40);
+  face.receive(*data);
+
+  // Fail to download data
+  manager.download_data_packet(dataName,
+                              [] (const ndn::Name& name) {
+                                BOOST_FAIL("Unexpected failure");
+                              },
+                              [&dataName](const ndn::Name& name, const std::string& reason) {
+                                BOOST_CHECK_EQUAL(name, dataName);
+                              });
+
+  advanceClocks(time::milliseconds(1), 2100);
+
+  fs::remove_all(filePath);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 BOOST_AUTO_TEST_SUITE(CheckTorrentManagerUtilities)
@@ -472,9 +662,10 @@
   }
 }
 
+BOOST_AUTO_TEST_SUITE_END()
 
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
 } // namespace nTorrent
-} // namespace ndn
\ No newline at end of file
+} // namespace ndn
diff --git a/tests/unit-tests/unit-test-time-fixture.hpp b/tests/unit-tests/unit-test-time-fixture.hpp
new file mode 100644
index 0000000..d50f22f
--- /dev/null
+++ b/tests/unit-tests/unit-test-time-fixture.hpp
@@ -0,0 +1,71 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+* Copyright (c) 2016 Regents of the University of California.
+*
+* This file is part of the nTorrent codebase.
+*
+* nTorrent 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.
+*
+* nTorrent 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 nTorrent, e.g., in COPYING.md file. If not, see
+* <http://www.gnu.org/licenses/>.
+*
+* See AUTHORS for complete list of nTorrent authors and contributors.
+*/
+
+#ifndef NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+#define NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace ntorrent {
+namespace tests {
+
+class UnitTestTimeFixture
+{
+public:
+  UnitTestTimeFixture()
+    : steadyClock(make_shared<time::UnitTestSteadyClock>())
+    , systemClock(make_shared<time::UnitTestSystemClock>())
+  {
+    time::setCustomClocks(steadyClock, systemClock);
+  }
+
+  ~UnitTestTimeFixture()
+  {
+    time::setCustomClocks(nullptr, nullptr);
+  }
+
+  void
+  advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1)
+  {
+    for (size_t i = 0; i < nTicks; ++i) {
+      steadyClock->advance(tick);
+      systemClock->advance(tick);
+
+      if (io.stopped())
+        io.reset();
+      io.poll();
+    }
+  }
+
+public:
+  shared_ptr<time::UnitTestSteadyClock> steadyClock;
+  shared_ptr<time::UnitTestSystemClock> systemClock;
+  boost::asio::io_service io;
+};
+
+} // namespace tests
+} // namespace ntorrent
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP