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