Add Update Handler class

Change-Id: I465297bfa3b8c4c8e6e7f7cd028b2d4afeb4e768
Refs: #3598
diff --git a/src/stats-table.hpp b/src/stats-table.hpp
index 0dba20d..a8a0c11 100644
--- a/src/stats-table.hpp
+++ b/src/stats-table.hpp
@@ -29,7 +29,7 @@
 /**
  * @brief Represents a stats table
  */
-class StatsTable : boost::noncopyable {
+class StatsTable {
 public:
   /**
    * @brief Create an empty stats table
diff --git a/src/torrent-manager.cpp b/src/torrent-manager.cpp
index c6780e2..75e09b5 100644
--- a/src/torrent-manager.cpp
+++ b/src/torrent-manager.cpp
@@ -163,6 +163,20 @@
 
 void TorrentManager::Initialize()
 {
+  // initialize the update handler
+
+  // figure out the name of the torrent
+  Name torrentName;
+  if (m_torrentFileName.get(m_torrentFileName.size() - 2).isSequenceNumber()) {
+    torrentName = m_torrentFileName.getSubName(1, m_torrentFileName.size() - 4);
+  }
+  else {
+    torrentName = m_torrentFileName.getSubName(1, m_torrentFileName.size() - 3);
+  }
+
+  m_updateHandler = make_shared<UpdateHandler>(torrentName, m_keyChain,
+                                               make_shared<StatsTable>(m_statsTable), m_face);
+
   // .../<torrent_name>/torrent-file/<implicit_digest>
   string dataPath = ".appdata/" + m_torrentFileName.get(-3).toUri();
   string manifestPath = dataPath +"/manifests";
@@ -231,6 +245,10 @@
 std::vector<Name>
 TorrentManager::downloadTorrentFile(const std::string& path)
 {
+  // check whether we should send out an "ALIVE" Interest
+  if (m_updateHandler->needsUpdate()) {
+    m_updateHandler->sendAliveInterest(m_stats_table_iter);
+  }
   shared_ptr<Name> searchRes = this->findTorrentFileSegmentToDownload();
   auto manifestNames = make_shared<std::vector<Name>>();
   if (searchRes == nullptr) {
@@ -743,6 +761,12 @@
 
   m_sortingCounter++;
   if (m_sortingCounter >= SORTING_INTERVAL) {
+    // Use the sorting interval to send out "ALIVE" Interests as well
+    // check whether we should send out an "ALIVE" Interest
+    if (m_updateHandler->needsUpdate()) {
+      m_updateHandler->sendAliveInterest(m_stats_table_iter);
+    }
+    // Do the actual sorting related stuff
     m_sortingCounter = 0;
     m_statsTable.sort();
     m_stats_table_iter = m_statsTable.begin();
diff --git a/src/torrent-manager.hpp b/src/torrent-manager.hpp
index 997f322..17e59db 100644
--- a/src/torrent-manager.hpp
+++ b/src/torrent-manager.hpp
@@ -22,8 +22,8 @@
 #define INCLUDED_TORRENT_FILE_MANAGER_H
 
 #include "file-manifest.hpp"
-#include "stats-table.hpp"
 #include "torrent-file.hpp"
+#include "update-handler.hpp"
 
 #include <ndn-cxx/data.hpp>
 #include <ndn-cxx/face.hpp>
@@ -329,7 +329,9 @@
   // Number of Interests sent since last sorting
   uint64_t                                                            m_sortingCounter;
   // Keychain instance
-  unique_ptr<KeyChain>                                                m_keyChain;
+  shared_ptr<KeyChain>                                                m_keyChain;
+  // Update Handler instance
+  shared_ptr<UpdateHandler>                                           m_updateHandler;
 };
 
 inline
@@ -347,8 +349,9 @@
 , m_keyChain(new KeyChain())
 {
   if(face == nullptr) {
-    face = make_shared<Face>();
+    m_face = make_shared<Face>();
   }
+
   // Hardcoded prefixes for now
   // TODO(Spyros): Think of something more clever to bootstrap...
   m_statsTable.insert("/ucla");
diff --git a/src/update-handler.cpp b/src/update-handler.cpp
new file mode 100644
index 0000000..3d13b21
--- /dev/null
+++ b/src/update-handler.cpp
@@ -0,0 +1,225 @@
+/* -*- 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.
+*/
+
+#include "update-handler.hpp"
+
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+namespace ndn {
+namespace ntorrent {
+
+void
+UpdateHandler::sendAliveInterest(StatsTable::iterator iter)
+{
+  Name interestName = Name("/NTORRENT" + m_torrentName.toUri() +
+                           "/ALIVE" + m_ownRoutablePrefix.toUri());
+
+  shared_ptr<Interest> i = make_shared<Interest>(interestName);
+
+  // Create and set the LINK object
+  Link link(interestName, { {1, iter->getRecordName()} });
+  m_keyChain->sign(link, signingWithSha256());
+  Block linkWire = link.wireEncode();
+
+  i->setLink(linkWire);
+
+  m_face->expressInterest(*i, bind(&UpdateHandler::decodeDataPacketContent, this, _1, _2),
+                          bind(&UpdateHandler::tryNextRoutablePrefix, this, _1));
+  m_face->processEvents(time::milliseconds(-1));
+}
+
+shared_ptr<Data>
+UpdateHandler::createDataPacket(const Name& name)
+{
+  // Parse the sender's routable prefix contained in the name
+  Name sendersRoutablePrefix = name.getSubName(2 + m_torrentName.size());
+
+  if (m_statsTable->find(sendersRoutablePrefix) == m_statsTable->end()) {
+    m_statsTable->insert(sendersRoutablePrefix);
+  }
+
+  shared_ptr<Data> data = make_shared<Data>(name);
+
+  EncodingEstimator estimator;
+  size_t estimatedSize = encodeContent(estimator);
+
+  EncodingBuffer buffer(estimatedSize, 0);
+  encodeContent(buffer);
+
+  data->setContentType(tlv::ContentType_Blob);
+  data->setContent(buffer.block());
+
+  return data;
+}
+
+template<encoding::Tag TAG>
+size_t
+UpdateHandler::encodeContent(EncodingImpl<TAG>& encoder) const
+{
+  // Content ::= CONTENT-TYPE TLV-LENGTH
+  //             RoutableName+
+
+  // RoutableName ::= NAME-TYPE TLV-LENGTH
+  //                  Name
+
+  size_t totalLength = 0;
+  // Encode the names of the first five entries of the stats table
+  uint32_t namesEncoded = 0;
+  for (const auto& entry : *m_statsTable) {
+    if (namesEncoded >= MAX_NUM_OF_ENCODED_NAMES) {
+      break;
+    }
+    size_t nameLength = 0;
+    nameLength += entry.getRecordName().wireEncode(encoder);
+    totalLength += nameLength;
+    ++namesEncoded;
+  }
+  totalLength += encoder.prependVarNumber(totalLength);
+  totalLength += encoder.prependVarNumber(tlv::Content);
+  return totalLength;
+}
+
+void
+UpdateHandler::decodeDataPacketContent(const Interest& interest, const Data& data)
+{
+  // Content ::= CONTENT-TYPE TLV-LENGTH
+  //             RoutableName+
+
+  // RoutableName ::= NAME-TYPE TLV-LENGTH
+  //                  Name
+
+  std::cout << "ALIVE data packet received: " << data.getName() << std::endl;
+
+  if (data.getContentType() != tlv::ContentType_Blob) {
+      BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
+  }
+
+  const Block& content = data.getContent();
+  content.parse();
+
+  // Decode the names (maintain their ordering)
+  for (auto element = content.elements_end() - 1; element != content.elements_begin() - 1; element--) {
+    element->parse();
+    Name name(*element);
+    if (name.empty()) {
+      BOOST_THROW_EXCEPTION(Error("Empty routable name was received"));
+    }
+    if (m_statsTable->find(name) == m_statsTable->end()) {
+      m_statsTable->insert(name);
+    }
+  }
+}
+
+bool
+UpdateHandler::needsUpdate()
+{
+  if (m_statsTable->size() < MIN_NUM_OF_ROUTABLE_NAMES) {
+    return true;
+  }
+  for (auto i = m_statsTable->begin(); i != m_statsTable->end(); i++) {
+    if (i->getRecordSuccessRate() >= 0.5) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void
+UpdateHandler::learnOwnRoutablePrefix()
+{
+  Interest i(Name("/localhop/nfd/rib/routable-prefixes"));
+  i.setInterestLifetime(time::milliseconds(100));
+
+  // parse the first contained routable prefix and set it as the ownRoutablePrefix
+  auto prefixReceived = [this] (const Interest& interest, const Data& data) {
+    const Block& content = data.getContent();
+    content.parse();
+
+    auto element = content.elements_begin();
+    element->parse();
+    Name ownRoutablePrefix(*element);
+    m_ownRoutablePrefix = ownRoutablePrefix;
+  };
+
+  auto prefixRetrievalFailed = [this] (const Interest&) {
+    std::cerr << "Own Routable Prefix Retrieval Failed. Trying again." << std::endl;
+    // TODO(Spyros): This could lead to an infinite loop. Figure out something better...
+    this->learnOwnRoutablePrefix();
+  };
+
+  m_face->expressInterest(i, prefixReceived, prefixRetrievalFailed);
+  m_face->processEvents(time::milliseconds(-1));
+}
+
+void
+UpdateHandler::onInterestReceived(const InterestFilter& filter, const Interest& interest)
+{
+  std::cout << "Interest Received: " << interest.getName().toUri() << std::endl;
+  shared_ptr<Data> data = this->createDataPacket(interest.getName());
+  m_keyChain->sign(*data, signingWithSha256());
+  m_face->put(*data);
+}
+
+void
+UpdateHandler::onRegisterFailed(const Name& prefix, const std::string& reason)
+{
+  std::cerr << "ERROR: Failed to register prefix \""
+            << prefix << "\" in local hub's daemon (" << reason << ")"
+            << std::endl;
+  m_face->shutdown();
+}
+
+void
+UpdateHandler::tryNextRoutablePrefix(const Interest& interest)
+{
+  Link link(interest.getLink());
+  const Name& name = link.getDelegations().begin()->second;
+  auto iter = m_statsTable->find(name);
+
+  if (iter != m_statsTable->end()) {
+    if (iter + 1 == m_statsTable->end()) {
+      iter = m_statsTable->begin();
+    }
+    else {
+      ++iter;
+    }
+  }
+  else {
+    iter = m_statsTable->begin();
+  }
+
+  shared_ptr<Interest> newInterest = make_shared<Interest>(interest);
+
+  link.removeDelegation(name);
+  link.addDelegation(1, iter->getRecordName());
+
+  m_keyChain->sign(link, signingWithSha256());
+  Block block = link.wireEncode();
+
+  newInterest->setLink(block);
+
+  m_face->expressInterest(*newInterest, bind(&UpdateHandler::decodeDataPacketContent, this, _1, _2),
+                          bind(&UpdateHandler::tryNextRoutablePrefix, this, _1));
+  m_face->processEvents(time::milliseconds(-1));
+}
+
+} // namespace ntorrent
+} // namespace ndn
diff --git a/src/update-handler.hpp b/src/update-handler.hpp
new file mode 100644
index 0000000..c1177df
--- /dev/null
+++ b/src/update-handler.hpp
@@ -0,0 +1,158 @@
+/* -*- 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 UPDATE_HANDLER_H
+#define UPDATE_HANDLER_H
+
+#include "stats-table.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+namespace ndn {
+namespace ntorrent {
+
+class UpdateHandler {
+public:
+  class Error : public tlv::Error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : tlv::Error(what)
+    {
+    }
+  };
+
+  UpdateHandler(Name torrentName, shared_ptr<KeyChain> keyChain,
+                shared_ptr<StatsTable> statsTable, shared_ptr<Face> face);
+
+  ~UpdateHandler();
+
+  /**
+   * @brief Send an ALIVE Interest
+   * @param routablePrefix The routable prefix to be included in the LINK object attached
+   *        to this Interest
+   */
+  void
+  sendAliveInterest(StatsTable::iterator iter);
+
+  /**
+   * @brief Check whether we need to send out an "ALIVE" interest
+   * @return True if an "ALIVE" interest should be sent out, otherwise false
+   *
+   * Returns true if we have less than MIN_NUM_OF_ROUTABLE_NAMES prefixes in the stats table
+   * or all the routable prefixes has success rate less than 0.5. Otherwise, it returns false
+   */
+  bool
+  needsUpdate();
+
+  enum {
+    // Maximum number of names to be encoded as a response to an "ALIVE" Interest
+    MAX_NUM_OF_ENCODED_NAMES = 5,
+    // Minimum number of routable prefixes that the peer would like to have
+    MIN_NUM_OF_ROUTABLE_NAMES = 5,
+  };
+
+protected:
+  // Used for testing purposes
+  const Name&
+  getOwnRoutablePrefix();
+
+private:
+  template<encoding::Tag TAG>
+  size_t
+  encodeContent(EncodingImpl<TAG>& encoder) const;
+
+  void
+  onInterestReceived(const InterestFilter& filter, const Interest& interest);
+
+  void
+  onRegisterFailed(const Name& prefix, const std::string& reason);
+
+  /**
+   * @brief Encode the first MAX_NUM_OF_ENCODED_NAMES prefixes of the table into a data packet
+   * @param name The name of the data packet
+   * @return A shared pointer to the created data packet
+   *
+   */
+  shared_ptr<Data>
+  createDataPacket(const Name& name);
+
+  /**
+   * @brief Given a received data packet, decode the contained routable name prefixes
+   *        and insert them to the table (if not already there)
+   * @param interest The interest that retrieved the data packet
+   * @param data A shared pointer to the received data packet
+   *
+   */
+  void
+  decodeDataPacketContent(const Interest& interest, const Data& data);
+
+  /**
+   * @brief Send an Interest to the local NFD to get the routable prefixes under which the
+   * published data is available
+   */
+  void
+  learnOwnRoutablePrefix();
+
+  void
+  tryNextRoutablePrefix(const Interest& interest);
+
+private:
+  Name m_torrentName;
+  shared_ptr<KeyChain> m_keyChain;
+  shared_ptr<StatsTable> m_statsTable;
+  shared_ptr<Face> m_face;
+  Name m_ownRoutablePrefix;
+};
+
+inline
+UpdateHandler::UpdateHandler(Name torrentName, shared_ptr<KeyChain> keyChain,
+                             shared_ptr<StatsTable> statsTable, shared_ptr<Face> face)
+: m_torrentName(torrentName)
+, m_keyChain(keyChain)
+, m_statsTable(statsTable)
+, m_face(face)
+{
+  this->learnOwnRoutablePrefix();
+  m_face->setInterestFilter(Name("/NTORRENT" + m_torrentName.toUri() + "/ALIVE"),
+                            bind(&UpdateHandler::onInterestReceived, this, _1, _2),
+                            RegisterPrefixSuccessCallback(),
+                            bind(&UpdateHandler::onRegisterFailed, this, _1, _2));
+}
+
+inline
+UpdateHandler::~UpdateHandler()
+{
+}
+
+inline const Name&
+UpdateHandler::getOwnRoutablePrefix()
+{
+  return m_ownRoutablePrefix;
+}
+
+} // namespace ntorrent
+} // namespace ndn
+
+#endif // UPDATE_HANDLER_H
diff --git a/tests/unit-tests/dummy-parser-fixture.hpp b/tests/unit-tests/dummy-parser-fixture.hpp
new file mode 100644
index 0000000..434cfde
--- /dev/null
+++ b/tests/unit-tests/dummy-parser-fixture.hpp
@@ -0,0 +1,102 @@
+/* -*- 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_DUMMY_PARSER_HPP
+#define NDN_TESTS_UNIT_TESTS_DUMMY_PARSER_HPP
+
+#include <ndn-cxx/data.hpp>
+
+#include <vector>
+
+namespace ndn {
+namespace ntorrent {
+namespace tests {
+
+using std::vector;
+
+class DummyParser {
+public:
+  DummyParser()
+  {
+  }
+
+  ~DummyParser()
+  {
+  }
+
+  static shared_ptr<Data>
+  createDataPacket(const Name& packetName, const std::vector<Name>& vec)
+  {
+    shared_ptr<Data> data = make_shared<Data>(packetName);
+
+    EncodingEstimator estimator;
+    size_t estimatedSize = encodeContent(estimator, vec);
+
+    EncodingBuffer buffer(estimatedSize, 0);
+    encodeContent(buffer, vec);
+
+    data->setContentType(tlv::ContentType_Blob);
+    data->setContent(buffer.block());
+
+    return data;
+  }
+
+  static shared_ptr<vector<Name>>
+  decodeContent(const Block& content)
+  {
+    shared_ptr<vector<Name>> nameVec = make_shared<vector<Name>>();
+    content.parse();
+    // Decode the names (do not worry about the order)
+    for (auto element = content.elements_begin(); element != content.elements_end(); element++) {
+      element->parse();
+      Name name(*element);
+      nameVec->push_back(name);
+    }
+    return nameVec;
+  }
+private:
+  template<encoding::Tag TAG>
+  static size_t
+  encodeContent(EncodingImpl<TAG>& encoder, const std::vector<Name>& vec)
+  {
+    // Content ::= CONTENT-TYPE TLV-LENGTH
+    //             RoutableName+
+
+    // RoutableName ::= NAME-TYPE TLV-LENGTH
+    //                  Name
+
+    size_t totalLength = 0;
+    for (const auto& element : vec) {
+      size_t nameLength = 0;
+      nameLength += element.wireEncode(encoder);
+      totalLength += nameLength;
+    }
+    totalLength += encoder.prependVarNumber(totalLength);
+    totalLength += encoder.prependVarNumber(tlv::Content);
+    return totalLength;
+  }
+};
+
+} // namespace tests
+} // namespace ntorrent
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_DUMMY_PARSER_HPP
diff --git a/tests/unit-tests/torrent-manager.t.cpp b/tests/unit-tests/torrent-manager.t.cpp
index eba4ae4..cf009f1 100644
--- a/tests/unit-tests/torrent-manager.t.cpp
+++ b/tests/unit-tests/torrent-manager.t.cpp
@@ -21,6 +21,7 @@
 
 #include "boost-test.hpp"
 
+#include "dummy-parser-fixture.hpp"
 #include "torrent-manager.hpp"
 #include "torrent-file.hpp"
 #include "unit-test-time-fixture.hpp"
@@ -43,18 +44,14 @@
 namespace fs = boost::filesystem;
 
 class TestTorrentManager : public TorrentManager {
- public:
-  TestTorrentManager(const ndn::Name&   torrentFileName,
-                     const std::string& filePath)
-  : TorrentManager(torrentFileName, filePath)
-  {
-  }
-
+public:
   TestTorrentManager(const ndn::Name&                 torrentFileName,
                      const std::string&               filePath,
                      std::shared_ptr<DummyClientFace> face)
   : TorrentManager(torrentFileName, filePath, face)
+  , m_face(face)
   {
+    m_keyChain = make_shared<KeyChain>();
   }
 
   std::vector<TorrentFile> torrentSegments() const {
@@ -111,6 +108,18 @@
   bool writeFileManifest(const FileManifest& manifest, const std::string& path) {
     return TorrentManager::writeFileManifest(manifest, path);
   }
+
+  void sendRoutablePrefixResponse() {
+    // Create a data packet containing one name as content
+    shared_ptr<Data> d = DummyParser::createDataPacket(Name("/localhop/nfd/rib/routable-prefixes"),
+                                                       { Name("ucla") });
+    m_keyChain->sign(*d);
+    m_face->receive(*d);
+  }
+
+private:
+  shared_ptr<KeyChain> m_keyChain;
+  shared_ptr<DummyClientFace> m_face;
 };
 
 class FaceFixture : public UnitTestTimeFixture
@@ -138,7 +147,6 @@
     : FaceFixture(false)
   {
   }
-
 };
 
 BOOST_FIXTURE_TEST_SUITE(TestTorrentManagerInitialize, FaceFixture)
@@ -202,8 +210,12 @@
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
 
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     // Check that the torrent segments and file manifests match (content and order)
     BOOST_CHECK(manager.torrentSegments() == torrentSegments);
     BOOST_CHECK(manager.fileManifests()   == manifests);
@@ -222,8 +234,13 @@
 BOOST_AUTO_TEST_CASE(CheckInitializeEmpty)
 {
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=02c737fd4c6e7de4b4825b089f39700c2dfa8fd2bb2b91f09201e357c4463253",
-                             "tests/testdata/");
+                             "tests/testdata/", face);
+
   manager.Initialize();
+
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   BOOST_CHECK(manager.torrentSegments() == vector<TorrentFile>());
   BOOST_CHECK(manager.fileManifests()   == vector<FileManifest>());
 }
@@ -276,8 +293,12 @@
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
 
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     // Check that the torrent segments and file manifests match (content and order)
     BOOST_CHECK(manager.torrentSegments() == torrentSegments);
     BOOST_CHECK(manager.fileManifests()   == vector<FileManifest>());
@@ -342,12 +363,17 @@
       boost::filesystem::create_directory(filename.parent_path());
       io::save(m, filename.string());
     }
+
     // Initialize and verify
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
 
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     // Check that the torrent segments and file manifests match (content and order)
     BOOST_CHECK(manager.torrentSegments() == torrentSegments);
     BOOST_CHECK(manager.fileManifests()   == manifests);
@@ -388,8 +414,12 @@
 
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
                              filePath, face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // Test download torrent file segments
   uint32_t counter = 0;
   manager.downloadTorrentFile(filePath + "torrent_files", [&counter, &torrentSegments]
@@ -436,8 +466,12 @@
 
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
                              filePath, face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // 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",
@@ -494,8 +528,12 @@
   std::string filePath = ".appdata/foo/";
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=946b92641d2b87bf4f5913930be20e3789ff5fb5d72739614f93f677d90fbd9d",
                              filePath, face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   Name dataName("/test/ucla");
 
   // Download data successfully
@@ -537,8 +575,12 @@
   std::string filePath = ".appdata/foo/";
   TestTorrentManager manager("NTORRENT/test/torrent-file/sha256digest",
                              filePath, face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   TorrentFile t1(Name("NTORRENT/test/torrent-file/sha256digest"),
                  Name("NTORRENT/test/torrent-file/1/sha256digest"), Name("/test"),
                  { Name("/manifest1") });
@@ -576,11 +618,16 @@
 BOOST_AUTO_TEST_CASE(TestFindTorrentFileSegmentToDownload2)
 {
   std::string filePath = ".appdata/foo/";
-  TestTorrentManager manager("/test/0/sha256digest",
+  TestTorrentManager manager("NTORRENT/test/torrent-file/0/sha256digest",
                              filePath, face);
+
   manager.Initialize();
 
-  BOOST_CHECK_EQUAL(manager.findTorrentFileSegmentToDownload()->toUri(), "/test/0/sha256digest");
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
+  BOOST_CHECK_EQUAL(manager.findTorrentFileSegmentToDownload()->toUri(),
+                    "/NTORRENT/test/torrent-file/0/sha256digest");
 
   fs::remove_all(filePath);
   fs::remove_all(".appdata");
@@ -631,8 +678,12 @@
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=a8a2e98cd943d895b8c4b12a208343bcf9344ce85a6376dc6f5754fe8f4a573e",
                              filePath,
                              face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // Set the file state
   std::vector<bool> v1 = {true};
   manager.setFileState(manifests[0].getFullName(), make_shared<fs::fstream>(), v1);
@@ -675,10 +726,14 @@
 BOOST_AUTO_TEST_CASE(TestFindManifestSegmentToDownload1)
 {
   std::string filePath = ".appdata/foo/";
-  TestTorrentManager manager("NTORRENT/test/sha256digest",
+  TestTorrentManager manager("NTORRENT/test/torrent-file/sha256digest",
                              filePath, face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   Name n1(Name("NTORRENT/test/file0"));
   n1.appendSequenceNumber(0);
 
@@ -786,8 +841,12 @@
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=a8a2e98cd943d895b8c4b12a208343bcf9344ce85a6376dc6f5754fe8f4a573e",
                              filePath,
                              face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // Set the file state
   std::vector<bool> v1 = {true};
   manager.setFileState(manifests[0].getFullName(), make_shared<fs::fstream>(), v1);
@@ -885,8 +944,12 @@
   TestTorrentManager manager("/NTORRENT/foo/torrent-file/sha256digest=a8a2e98cd943d895b8c4b12a208343bcf9344ce85a6376dc6f5754fe8f4a573e",
                              filePath,
                              face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // Set the file state
   std::vector<bool> v1 = {true};
   manager.setFileState(manifests[0].getFullName(), make_shared<fs::fstream>(), v1);
@@ -973,7 +1036,12 @@
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     size_t nData = 0;
     BOOST_CHECK_EQUAL(0, face->sentData.size());
     // request all the torrent segments
@@ -1093,8 +1161,12 @@
   TestTorrentManager manager(initialSegmentName,
                              filePath,
                              face);
+
   manager.Initialize();
 
+  advanceClocks(time::milliseconds(1), 10);
+  manager.sendRoutablePrefixResponse();
+
   // insert the other entities
   data.insert(data.end(), torrentSegments.begin(), torrentSegments.end());
   data.insert(data.end(), manifests.begin(), manifests.end());
@@ -1189,7 +1261,12 @@
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     // check that initially there is no data on disk
     for (auto m : manager.fileManifests()) {
       auto fileState = manager.fileState(m.getFullName());
@@ -1273,7 +1350,12 @@
     TestTorrentManager manager(initialSegmentName,
                                filePath,
                                face);
+
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     std::string dirPath = ".appdata/foo/";
     std::string torrentPath = dirPath + "torrent_files/";
     BOOST_CHECK(manager.torrentSegments().empty());
@@ -1285,13 +1367,22 @@
     TestTorrentManager manager2(initialSegmentName,
                                 filePath,
                                 face);
+
     manager2.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager2.sendRoutablePrefixResponse();
+
     BOOST_CHECK(manager2.torrentSegments() == torrentSegments);
 
     // start anew
     fs::remove_all(torrentPath);
     fs::create_directories(torrentPath);
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     BOOST_CHECK(manager.torrentSegments().empty());
 
     // check that there is no dependence on the order of torrent segments
@@ -1350,7 +1441,12 @@
     TestTorrentManager manager(initialSegmentName,
                               filePath,
                               face);
+
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     for (const auto& t : torrentSegments) {
       manager.writeTorrentSegment(t, torrentPath);
     }
@@ -1366,12 +1462,20 @@
                                 face);
 
     manager2.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager2.sendRoutablePrefixResponse();
+
     BOOST_CHECK(manager2.fileManifests() == manifests);
 
     // start anew
     fs::remove_all(manifestPath);
     fs::create_directories(manifestPath);
     manager.Initialize();
+
+    advanceClocks(time::milliseconds(1), 10);
+    manager.sendRoutablePrefixResponse();
+
     BOOST_CHECK(manager.fileManifests().empty());
 
     // check that there is no dependence on the order of torrent segments
diff --git a/tests/unit-tests/update-handler.t.cpp b/tests/unit-tests/update-handler.t.cpp
new file mode 100644
index 0000000..22624b6
--- /dev/null
+++ b/tests/unit-tests/update-handler.t.cpp
@@ -0,0 +1,277 @@
+/* -*- 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.
+*/
+
+#include "boost-test.hpp"
+#include "update-handler.hpp"
+#include "unit-test-time-fixture.hpp"
+#include "dummy-parser-fixture.hpp"
+
+#include <ndn-cxx/name.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/util/io.hpp>
+
+namespace ndn {
+namespace ntorrent {
+namespace tests {
+
+using util::DummyClientFace;
+using std::vector;
+
+class TestUpdateHandler : public UpdateHandler {
+public:
+  TestUpdateHandler(Name torrentName, shared_ptr<KeyChain> keyChain,
+                    shared_ptr<StatsTable> statsTable, shared_ptr<Face> face)
+  : UpdateHandler(torrentName, keyChain, statsTable, face)
+  {
+  }
+
+  ~TestUpdateHandler()
+  {
+  }
+
+  Name
+  getOwnRoutablePrefix()
+  {
+    return UpdateHandler::getOwnRoutablePrefix();
+  }
+};
+
+class FaceFixture : public UnitTestTimeFixture
+{
+public:
+  explicit
+  FaceFixture()
+  : face1(util::makeDummyClientFace(io, { true, true }))
+  , face2(util::makeDummyClientFace(io, { true, true }))
+  {
+  }
+
+public:
+  shared_ptr<DummyClientFace> face1;
+  shared_ptr<DummyClientFace> face2;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestUpdateHandlerClass, FaceFixture)
+
+BOOST_AUTO_TEST_CASE(TestInitialSetup)
+{
+  shared_ptr<StatsTable> table1 = make_shared<StatsTable>(Name("linux15.01"));
+
+  shared_ptr<KeyChain> keyChain = make_shared<KeyChain>();
+
+  TestUpdateHandler handler1(Name("linux15.01"), keyChain, table1, face1);
+  advanceClocks(time::milliseconds(1), 10);
+  // Create a data packet containing one name as content
+  shared_ptr<Data> d = DummyParser::createDataPacket(Name("/localhop/nfd/rib/routable-prefixes"),
+                                                      { Name("ucla") });
+  keyChain->sign(*d);
+  face1->receive(*d);
+
+  BOOST_CHECK_EQUAL(handler1.getOwnRoutablePrefix().toUri(), "/ucla");
+
+  shared_ptr<StatsTable> table2 = make_shared<StatsTable>(Name("linux15.01"));
+  TestUpdateHandler handler2(Name("linux15.01"), keyChain, table2, face2);
+  advanceClocks(time::milliseconds(1), 10);
+  // Create a data packet containing one name as content
+  d = DummyParser::createDataPacket(Name("/localhop/nfd/rib/routable-prefixes"),
+                                     { Name("arizona") });
+  keyChain->sign(*d);
+  face2->receive(*d);
+
+  BOOST_CHECK_EQUAL(handler2.getOwnRoutablePrefix().toUri(), "/arizona");
+}
+
+BOOST_AUTO_TEST_CASE(TestAliveInterestExchange)
+{
+  shared_ptr<StatsTable> table1 = make_shared<StatsTable>(Name("linux15.01"));
+  table1->insert(Name("isp1"));
+  table1->insert(Name("isp2"));
+  table1->insert(Name("isp3"));
+
+  shared_ptr<KeyChain> keyChain = make_shared<KeyChain>();
+
+  TestUpdateHandler handler1(Name("linux15.01"), keyChain, table1, face1);
+  advanceClocks(time::milliseconds(1), 10);
+  // Create a data packet containing one name as content
+  shared_ptr<Data> d = DummyParser::createDataPacket(Name("/localhop/nfd/rib/routable-prefixes"),
+                                                      { Name("ucla") });
+  keyChain->sign(*d);
+  face1->receive(*d);
+
+  shared_ptr<StatsTable> table2 = make_shared<StatsTable>(Name("linux15.01"));
+  table2->insert(Name("ucla"));
+  TestUpdateHandler handler2(Name("linux15.01"), keyChain, table2, face2);
+  advanceClocks(time::milliseconds(1), 10);
+  // Create a data packet containing one name as content
+  d = DummyParser::createDataPacket(Name("/localhop/nfd/rib/routable-prefixes"),
+                                     { Name("arizona") });
+  keyChain->sign(*d);
+  face2->receive(*d);
+
+  handler2.sendAliveInterest(table2->begin());
+
+  advanceClocks(time::milliseconds(1), 40);
+  Interest interest(Name("/NTORRENT/linux15.01/ALIVE/test"));
+  face1->receive(interest);
+
+  advanceClocks(time::milliseconds(1), 10);
+  std::vector<Data> dataVec = face1->sentData;
+
+  BOOST_CHECK_EQUAL(dataVec.size(), 1);
+  BOOST_CHECK_EQUAL(dataVec[0].getName().toUri(), "/NTORRENT/linux15.01/ALIVE/test");
+
+  auto block = dataVec[0].getContent();
+  shared_ptr<vector<Name>> nameVec = DummyParser::decodeContent(block);
+  auto it = nameVec->begin();
+
+  BOOST_CHECK_EQUAL(it->toUri(), "/test");
+  ++it;
+  BOOST_CHECK_EQUAL(it->toUri(), "/isp3");
+  ++it;
+  BOOST_CHECK_EQUAL(it->toUri(), "/isp2");
+  ++it;
+  BOOST_CHECK_EQUAL(it->toUri(), "/isp1");
+
+  BOOST_CHECK_EQUAL((table1->end() - 1)->getRecordName().toUri(), "/test");
+
+  d = DummyParser::createDataPacket(Name("/NTORRENT/linux15.01/ALIVE/arizona"),
+                                     { Name("isp1"), Name("isp2"), Name("isp3") });
+  keyChain->sign(*d);
+
+  advanceClocks(time::milliseconds(1), 40);
+  face2->receive(*d);
+
+  auto i = table2->begin();
+  ++i;
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp1");
+  ++i;
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp2");
+  ++i;
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp3");
+
+  table1->erase(Name("isp1"));
+  table1->erase(Name("isp2"));
+
+  table1->insert(Name("isp4"));
+  table1->insert(Name("isp5"));
+  table1->insert(Name("isp6"));
+  table1->insert(Name("isp7"));
+  table1->insert(Name("isp8"));
+  table1->insert(Name("isp9"));
+
+  handler2.sendAliveInterest(table2->begin());
+
+  advanceClocks(time::milliseconds(1), 40);
+  Interest interest2(Name("/NTORRENT/linux15.01/ALIVE/arizona"));
+  face1->receive(interest2);
+
+  advanceClocks(time::milliseconds(1), 10);
+
+  dataVec = face1->sentData;
+  BOOST_CHECK_EQUAL(dataVec.size(), 2);
+
+  auto iter = dataVec.begin() + 1;
+  advanceClocks(time::milliseconds(1), 30);
+  face2->receive(*iter);
+
+  i = table2->begin();
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/ucla");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp1");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp2");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp3");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/test");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp4");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp5");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK_EQUAL(i->getRecordName().toUri(), "/isp6");
+  BOOST_CHECK_EQUAL(i->getRecordSuccessRate(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordSentInterests(), 0);
+  BOOST_CHECK_EQUAL(i->getRecordReceivedData(), 0);
+  ++i;
+
+  BOOST_CHECK(i == table2->end());
+}
+
+BOOST_AUTO_TEST_CASE(TestNeedsUpdate)
+{
+  shared_ptr<StatsTable> table1 = make_shared<StatsTable>(Name("linux15.01"));
+  table1->insert(Name("isp1"));
+  table1->insert(Name("isp2"));
+  table1->insert(Name("isp3"));
+
+  shared_ptr<KeyChain> keyChain = make_shared<KeyChain>();
+
+  UpdateHandler handler1(Name("linux15.01"), keyChain, table1, face1);
+
+  BOOST_CHECK(handler1.needsUpdate());
+
+  auto i = table1->begin() + 1;
+  i->incrementSentInterests();
+  i->incrementSentInterests();
+  i->incrementReceivedData();
+
+  BOOST_CHECK(handler1.needsUpdate());
+
+  table1->insert(Name("isp4"));
+  table1->insert(Name("isp5"));
+
+  BOOST_CHECK(!handler1.needsUpdate());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace ntorrent
+} // namespace ndn