summaryrefslogtreecommitdiffstats
path: root/libtorrent-extended/magnet_uri.patch
diff options
context:
space:
mode:
Diffstat (limited to 'libtorrent-extended/magnet_uri.patch')
-rw-r--r--libtorrent-extended/magnet_uri.patch1652
1 files changed, 1652 insertions, 0 deletions
diff --git a/libtorrent-extended/magnet_uri.patch b/libtorrent-extended/magnet_uri.patch
new file mode 100644
index 0000000..3d00540
--- /dev/null
+++ b/libtorrent-extended/magnet_uri.patch
@@ -0,0 +1,1652 @@
+diff --git a/src/download/download_constructor.cc b/src/download/download_constructor.cc
+index f37f848..86e5351 100644
+--- a/src/download/download_constructor.cc
++++ b/src/download/download_constructor.cc
+@@ -81,7 +81,10 @@ struct download_constructor_encoding_match :
+ };
+
+ void
+-DownloadConstructor::initialize(const Object& b) {
++DownloadConstructor::initialize(Object& b) {
++ if (!b.has_key_map("info") && b.has_key_string("magnet-uri"))
++ parse_magnet_uri(b, b.get_key_string("magnet-uri"));
++
+ if (b.has_key_string("encoding"))
+ m_defaultEncoding = b.get_key_string("encoding");
+
+@@ -136,10 +139,24 @@ DownloadConstructor::parse_info(const Object& b) {
+ if (b.flags() & Object::flag_unordered)
+ throw input_error("Download has unordered info dictionary.");
+
+- uint32_t chunkSize = b.get_key_value("piece length");
++ uint32_t chunkSize;
++
++ if (b.has_key_value("meta_download") && b.get_key_value("meta_download"))
++ m_download->info()->set_meta_download(true);
++
++ if (m_download->info()->is_meta_download()) {
++ if (b.get_key_string("pieces").length() != HashString::size_data)
++ throw input_error("Meta-download has invalid piece data.");
++
++ chunkSize = 1;
++ parse_single_file(b, chunkSize);
++
++ } else {
++ chunkSize = b.get_key_value("piece length");
+
+- if (chunkSize <= (1 << 10) || chunkSize > (128 << 20))
+- throw input_error("Torrent has an invalid \"piece length\".");
++ if (chunkSize <= (1 << 10) || chunkSize > (128 << 20))
++ throw input_error("Torrent has an invalid \"piece length\".");
++ }
+
+ if (b.has_key("length")) {
+ parse_single_file(b, chunkSize);
+@@ -148,11 +165,11 @@ DownloadConstructor::parse_info(const Object& b) {
+ parse_multi_files(b.get_key("files"), chunkSize);
+ fileList->set_root_dir("./" + m_download->info()->name());
+
+- } else {
++ } else if (!m_download->info()->is_meta_download()) {
+ throw input_error("Torrent must have either length or files entry.");
+ }
+
+- if (fileList->size_bytes() == 0)
++ if (fileList->size_bytes() == 0 && !m_download->info()->is_meta_download())
+ throw input_error("Torrent has zero length.");
+
+ // Set chunksize before adding files to make sure the index range is
+@@ -239,7 +256,7 @@ DownloadConstructor::parse_single_file(const Object& b, uint32_t chunkSize) {
+ throw input_error("Bad torrent file, \"name\" is an invalid path name.");
+
+ FileList* fileList = m_download->main()->file_list();
+- fileList->initialize(b.get_key_value("length"), chunkSize);
++ fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), chunkSize);
+ fileList->set_multi_file(false);
+
+ std::list<Path> pathList;
+@@ -343,4 +360,132 @@ DownloadConstructor::choose_path(std::list<Path>* pathList) {
+ return pathList->front();
+ }
+
++static const char*
++parse_base32_sha1(const char* pos, HashString& hash) {
++ HashString::iterator hashItr = hash.begin();
++
++ static const int base_shift = 8+8-5;
++ int shift = base_shift;
++ uint16_t decoded = 0;
++
++ while (*pos) {
++ char c = *pos++;
++ uint16_t value;
++
++ if (c >= 'A' && c <= 'Z')
++ value = c - 'A';
++ else if (c >= 'a' && c <= 'z')
++ value = c - 'a';
++ else if (c >= '2' && c <= '7')
++ value = 26 + c - '2';
++ else if (c == '&')
++ break;
++ else
++ return NULL;
++
++ decoded |= (value << shift);
++ if (shift <= 8) {
++ // Too many characters for a base32 SHA1.
++ if (hashItr == hash.end())
++ return NULL;
++
++ *hashItr++ = (decoded >> 8);
++ decoded <<= 8;
++ shift += 3;
++ } else {
++ shift -= 5;
++ }
++ }
++
++ return hashItr != hash.end() || shift != base_shift ? NULL : pos;
++}
++
++void
++DownloadConstructor::parse_magnet_uri(Object& b, const std::string& uri) {
++ if (std::strncmp(uri.c_str(), "magnet:?", 8))
++ throw input_error("Invalid magnet URI.");
++
++ const char* pos = uri.c_str() + 8;
++
++ Object trackers(Object::create_list());
++ HashString hash;
++ bool hashValid = false;
++
++ while (*pos) {
++ const char* tagStart = pos;
++ while (*pos != '=')
++ if (!*pos++)
++ break;
++
++ SimpleString tag(tagStart, pos - tagStart);
++ pos++;
++
++ // hash may be base32 encoded (optional in BEP 0009 and common practice)
++ if (tag == "xt") {
++ if (strncmp(pos, "urn:btih:", 9))
++ throw input_error("Invalid magnet URI.");
++
++ pos += 9;
++
++ const char* nextPos = parse_base32_sha1(pos, hash);
++ if (nextPos != NULL) {
++ pos = nextPos;
++ hashValid = true;
++ continue;
++ }
++ }
++
++ // everything else, including sometimes the hash, is url encoded.
++ std::string decoded;
++ while (*pos) {
++ char c = *pos++;
++ if (c == '%') {
++ if (sscanf(pos, "%02hhx", &c) != 1)
++ throw input_error("Invalid magnet URI.");
++
++ pos += 2;
++
++ } else if (c == '&') {
++ break;
++ }
++
++ decoded.push_back(c);
++ }
++
++ if (tag == "xt") {
++ // url-encoded hash as per magnet URN specs
++ if (decoded.length() == hash.size_data) {
++ hash = *HashString::cast_from(decoded);
++ hashValid = true;
++
++ // hex-encoded hash as per BEP 0009
++ } else if (decoded.length() == hash.size_data * 2) {
++ std::string::iterator hexItr = decoded.begin();
++ for (HashString::iterator itr = hash.begin(), last = hash.end(); itr != last; itr++, hexItr += 2)
++ *itr = (rak::hexchar_to_value(*hexItr) << 4) + rak::hexchar_to_value(*(hexItr + 1));
++ hashValid = true;
++
++ } else {
++ throw input_error("Invalid magnet URI.");
++ }
++ } else if (tag == "tr") {
++ trackers.insert_back(Object::create_list()).insert_back(decoded);
++ }
++ // could also handle "dn" = display name (torrent name), but we can't really use that
++ }
++
++ if (!hashValid)
++ throw input_error("Invalid magnet URI.");
++
++ Object& info = b.insert_key("info", Object::create_map());
++ info.insert_key("pieces", hash.str());
++ info.insert_key("name", rak::transform_hex(hash.str()) + ".meta");
++ info.insert_key("meta_download", (int64_t)1);
++
++ if (!trackers.as_list().empty()) {
++ b.insert_preserve_copy("announce", trackers.as_list().begin()->as_list().begin()->as_string());
++ b.insert_preserve_type("announce-list", trackers);
++ }
++}
++
+ }
+diff --git a/src/download/download_constructor.h b/src/download/download_constructor.h
+index 7192f90..8af520f 100644
+--- a/src/download/download_constructor.h
++++ b/src/download/download_constructor.h
+@@ -55,7 +55,7 @@ class DownloadConstructor {
+ public:
+ DownloadConstructor() : m_download(NULL), m_encodingList(NULL) {}
+
+- void initialize(const Object& b);
++ void initialize(Object& b);
+
+ void set_download(DownloadWrapper* d) { m_download = d; }
+ void set_encoding_list(const EncodingList* e) { m_encodingList = e; }
+@@ -64,6 +64,7 @@ private:
+ void parse_name(const Object& b);
+ void parse_tracker(const Object& b);
+ void parse_info(const Object& b);
++ void parse_magnet_uri(Object& b, const std::string& uri);
+
+ void add_tracker_group(const Object& b);
+ void add_tracker_single(const Object& b, int group);
+diff --git a/src/download/download_info.h b/src/download/download_info.h
+index 0a3c0e8..68fb178 100644
+--- a/src/download/download_info.h
++++ b/src/download/download_info.h
+@@ -76,6 +76,7 @@ public:
+ m_isCompact(true),
+ m_isAcceptingNewPeers(true),
+ m_isPrivate(false),
++ m_isMetaDownload(false),
+ m_pexEnabled(true),
+ m_pexActive(true),
+
+@@ -86,7 +87,8 @@ public:
+ m_uploadedBaseline(0),
+ m_completedBaseline(0),
+ m_sizePex(0),
+- m_maxSizePex(8) {
++ m_maxSizePex(8),
++ m_metadataSize(0) {
+ }
+
+ const std::string& name() const { return m_name; }
+@@ -116,6 +118,9 @@ public:
+ bool is_private() const { return m_isPrivate; }
+ void set_private(bool p) { m_isPrivate = p; if (p) m_pexEnabled = false; }
+
++ bool is_meta_download() const { return m_isMetaDownload; }
++ void set_meta_download(bool m) { m_isMetaDownload = m; }
++
+ bool is_pex_enabled() const { return m_pexEnabled; }
+ void set_pex_enabled(bool enabled) { m_pexEnabled = enabled && !m_isPrivate; }
+
+@@ -134,6 +139,9 @@ public:
+ uint64_t completed_adjusted() const { return std::max<int64_t>(m_slotStatCompleted() - completed_baseline(), 0); }
+ void set_completed_baseline(uint64_t b) { m_completedBaseline = b; }
+
++ size_t metadata_size() const { return m_metadataSize; }
++ void set_metadata_size(size_t size) { m_metadataSize = size; }
++
+ uint32_t size_pex() const { return m_sizePex; }
+ void set_size_pex(uint32_t b) { m_sizePex = b; }
+
+@@ -165,6 +173,7 @@ private:
+ bool m_isCompact;
+ bool m_isAcceptingNewPeers;
+ bool m_isPrivate;
++ bool m_isMetaDownload;
+ bool m_pexEnabled;
+ bool m_pexActive;
+
+@@ -176,6 +185,7 @@ private:
+ uint64_t m_completedBaseline;
+ uint32_t m_sizePex;
+ uint32_t m_maxSizePex;
++ size_t m_metadataSize;
+
+ slot_stat_type m_slotStatCompleted;
+ slot_stat_type m_slotStatLeft;
+diff --git a/src/download/download_main.cc b/src/download/download_main.cc
+index 1dd5f98..5691021 100644
+--- a/src/download/download_main.cc
++++ b/src/download/download_main.cc
+@@ -455,4 +455,19 @@ DownloadMain::do_peer_exchange() {
+ }
+ }
+
++void
++DownloadMain::set_metadata_size(size_t size) {
++ if (m_info->is_meta_download()) {
++ if (m_fileList.size_bytes() < 2)
++ file_list()->reset_filesize(size);
++ else if (size != m_fileList.size_bytes())
++ throw communication_error("Peer-supplied metadata size mismatch.");
++
++ } else if (m_info->metadata_size() && m_info->metadata_size() != size) {
++ throw communication_error("Peer-supplied metadata size mismatch.");
++ }
++
++ m_info->set_metadata_size(size);
++}
++
+ }
+diff --git a/src/download/download_main.h b/src/download/download_main.h
+index 5d0090b..700f41e 100644
+--- a/src/download/download_main.h
++++ b/src/download/download_main.h
+@@ -116,6 +116,8 @@ public:
+
+ bool want_pex_msg() { return m_info->is_pex_active() && m_peerList.available_list()->want_more(); };
+
++ void set_metadata_size(size_t s);
++
+ // Carefull with these.
+ void setup_delegator();
+ void setup_tracker();
+diff --git a/src/net/data_buffer.h b/src/net/data_buffer.h
+index a26ca36..e3d9e38 100644
+--- a/src/net/data_buffer.h
++++ b/src/net/data_buffer.h
+@@ -48,6 +48,7 @@ struct DataBuffer {
+ DataBuffer(char* data, char* end) : m_data(data), m_end(end), m_owned(true) {}
+
+ DataBuffer clone() const { DataBuffer d = *this; d.m_owned = false; return d; }
++ DataBuffer release() { DataBuffer d = *this; set(NULL, NULL, false); return d; }
+
+ char* data() const { return m_data; }
+ char* end() const { return m_end; }
+@@ -70,7 +71,7 @@ private:
+
+ inline void
+ DataBuffer::clear() {
+- if (!empty())
++ if (!empty() && m_owned)
+ delete[] m_data;
+
+ m_data = m_end = NULL;
+diff --git a/src/net/socket_base.cc b/src/net/socket_base.cc
+index 90457dc..13a9c8b 100644
+--- a/src/net/socket_base.cc
++++ b/src/net/socket_base.cc
+@@ -47,7 +47,7 @@
+
+ namespace torrent {
+
+-char* SocketBase::m_nullBuffer = new char[1 << 17];
++char* SocketBase::m_nullBuffer = new char[SocketBase::null_buffer_size];
+
+ SocketBase::~SocketBase() {
+ if (get_fd().is_valid())
+diff --git a/src/net/socket_base.h b/src/net/socket_base.h
+index 9340a23..0f0f424 100644
+--- a/src/net/socket_base.h
++++ b/src/net/socket_base.h
+@@ -68,6 +68,8 @@ protected:
+ SocketBase(const SocketBase&);
+ void operator = (const SocketBase&);
+
++ static const size_t null_buffer_size = 1 << 17;
++
+ static char* m_nullBuffer;
+ };
+
+diff --git a/src/protocol/Makefile.am b/src/protocol/Makefile.am
+index 6171d06..18f671d 100644
+--- a/src/protocol/Makefile.am
++++ b/src/protocol/Makefile.am
+@@ -17,6 +17,8 @@ libsub_protocol_la_SOURCES = \
+ peer_connection_base.h \
+ peer_connection_leech.cc \
+ peer_connection_leech.h \
++ peer_connection_metadata.cc \
++ peer_connection_metadata.h \
+ peer_factory.cc \
+ peer_factory.h \
+ protocol_base.h \
+diff --git a/src/protocol/extensions.cc b/src/protocol/extensions.cc
+index 7cbf6e3..3e0cf60 100644
+--- a/src/protocol/extensions.cc
++++ b/src/protocol/extensions.cc
+@@ -43,6 +43,8 @@
+
+ #include "download/available_list.h"
+ #include "download/download_main.h"
++#include "download/download_manager.h"
++#include "download/download_wrapper.h"
+ #include "protocol/peer_connection_base.h"
+ #include "torrent/connection_manager.h"
+ #include "torrent/object.h"
+@@ -58,7 +60,9 @@ namespace torrent {
+
+ enum ext_handshake_keys {
+ key_e,
++ key_m_utMetadata,
+ key_m_utPex,
++ key_metadataSize,
+ key_p,
+ key_reqq,
+ key_v,
+@@ -70,6 +74,13 @@ enum ext_pex_keys {
+ key_pex_LAST
+ };
+
++enum ext_metadata_keys {
++ key_msgType,
++ key_piece,
++ key_totalSize,
++ key_metadata_LAST
++};
++
+ class ExtHandshakeMessage : public StaticMap<ext_handshake_keys, key_handshake_LAST> {
+ public:
+ typedef StaticMap<ext_handshake_keys, key_handshake_LAST> base_type;
+@@ -82,9 +93,17 @@ public:
+ typedef StaticMapKeys::mapping_type mapping_type;
+ };
+
++class ExtMetadataMessage : public StaticMap<ext_metadata_keys, key_metadata_LAST> {
++public:
++ typedef StaticMap<ext_metadata_keys, key_metadata_LAST> base_type;
++ typedef StaticMapKeys::mapping_type mapping_type;
++};
++
+ ExtHandshakeMessage::mapping_type ext_handshake_key_names[ExtHandshakeMessage::length] = {
+ { key_e, "e" },
++ { key_m_utMetadata, "m::ut_metadata" },
+ { key_m_utPex, "m::ut_pex" },
++ { key_metadataSize, "metadata_size" },
+ { key_p, "p" },
+ { key_reqq, "reqq" },
+ { key_v, "v" },
+@@ -94,9 +113,16 @@ ExtPEXMessage::mapping_type ext_pex_key_names[ExtPEXMessage::length] = {
+ { key_pex_added, "added" },
+ };
+
++ExtMetadataMessage::mapping_type ext_metadata_key_names[ExtMetadataMessage::length] = {
++ { key_msgType, "msg_type" },
++ { key_piece, "piece" },
++ { key_totalSize, "total_size" },
++};
++
+ ext_handshake_keys message_keys[ProtocolExtension::FIRST_INVALID] = {
+ key_handshake_LAST, // Handshake, not actually used.
+ key_m_utPex,
++ key_m_utMetadata,
+ };
+
+ template<>
+@@ -105,6 +131,9 @@ const ExtHandshakeMessage::key_map_init ExtHandshakeMessage::base_type::keyMap(e
+ template<>
+ const ExtPEXMessage::key_map_init ExtPEXMessage::base_type::keyMap(ext_pex_key_names);
+
++template<>
++const ExtMetadataMessage::key_map_init ExtMetadataMessage::base_type::keyMap(ext_metadata_key_names);
++
+ void
+ ProtocolExtension::cleanup() {
+ // if (is_default())
+@@ -160,7 +189,11 @@ ProtocolExtension::generate_handshake_message() {
+ message[key_v] = SimpleString("libTorrent " VERSION);
+ message[key_reqq] = 2048; // maximum request queue size
+
++ if (!m_download->info()->is_meta_download())
++ message[key_metadataSize] = m_download->info()->metadata_size();
++
+ message[key_m_utPex] = is_local_enabled(UT_PEX) ? UT_PEX : 0;
++ message[key_m_utMetadata] = UT_METADATA;
+
+ char buffer[1024];
+ object_buffer_t result = staticMap_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), message);
+@@ -224,7 +257,7 @@ ProtocolExtension::generate_ut_pex_message(const PEXList& added, const PEXList&
+
+ void
+ ProtocolExtension::read_start(int type, uint32_t length, bool skip) {
+- if (is_default() || (type >= FIRST_INVALID) || length > (1 << 14))
++ if (is_default() || (type >= FIRST_INVALID) || length > (1 << 15))
+ throw communication_error("Received invalid extension message.");
+
+ if (m_read != NULL || length < 0)
+@@ -244,19 +277,25 @@ ProtocolExtension::read_start(int type, uint32_t length, bool skip) {
+ m_readPos = m_read = new char[length];
+ }
+
+-void
++bool
+ ProtocolExtension::read_done() {
++ bool blocked = false;
++
+ try {
+ switch(m_readType) {
+ case SKIP_EXTENSION:
+ break;
+
+ case HANDSHAKE:
+- parse_handshake();
++ blocked = parse_handshake();
+ break;
+
+ case UT_PEX:
+- parse_ut_pex();
++ blocked = parse_ut_pex();
++ break;
++
++ case UT_METADATA:
++ blocked = parse_ut_metadata();
+ break;
+
+ default:
+@@ -272,6 +311,8 @@ ProtocolExtension::read_done() {
+
+ m_readType = FIRST_INVALID;
+ m_flags |= flag_received_ext;
++
++ return !blocked;
+ }
+
+ // Called whenever peer enables or disables an extension.
+@@ -285,7 +326,7 @@ ProtocolExtension::peer_toggle_remote(int type, bool active) {
+ }
+ }
+
+-void
++bool
+ ProtocolExtension::parse_handshake() {
+ ExtHandshakeMessage message;
+ staticMap_read_bencode(m_read, m_readPos, message);
+@@ -323,10 +364,15 @@ ProtocolExtension::parse_handshake() {
+ if (message[key_reqq].is_value())
+ m_maxQueueLength = message[key_reqq].as_value();
+
++ if (message[key_metadataSize].is_value())
++ m_download->set_metadata_size(message[key_metadataSize].as_value());
++
+ m_flags &= ~flag_initial_handshake;
++
++ return false;
+ }
+
+-void
++bool
+ ProtocolExtension::parse_ut_pex() {
+ // Ignore message if we're still in the handshake (no connection
+ // yet), or no peers are present.
+@@ -336,11 +382,11 @@ ProtocolExtension::parse_ut_pex() {
+
+ // TODO: Check if pex is enabled?
+ if (!message[key_pex_added].is_sstring())
+- return;
++ return false;
+
+ SimpleString peers = message[key_pex_added].as_sstring();
+ if (peers.empty())
+- return;
++ return false;
+
+ AddressList l;
+ l.parse_address_compact(peers);
+@@ -348,6 +394,82 @@ ProtocolExtension::parse_ut_pex() {
+ l.erase(std::unique(l.begin(), l.end()), l.end());
+
+ m_download->peer_list()->insert_available(&l);
++
++ return false;
++}
++
++bool
++ProtocolExtension::parse_ut_metadata() {
++ ExtMetadataMessage message;
++
++ // Piece data comes after bencoded extension message.
++ const char* dataStart = staticMap_read_bencode(m_read, m_readPos, message);
++
++ switch(message[key_msgType].as_value()) {
++ case 0:
++ // Can't process new request while still having data to send.
++ if (has_pending_message())
++ return true;
++
++ send_metadata_piece(message[key_piece].as_value());
++ break;
++
++ case 1:
++ if (m_connection == NULL)
++ break;
++
++ m_connection->receive_metadata_piece(message[key_piece].as_value(), dataStart, m_readPos - dataStart);
++ break;
++
++ case 2:
++ if (m_connection != NULL)
++ m_connection->receive_metadata_piece(message[key_piece].as_value(), NULL, 0);
++ break;
++ };
++
++ return false;
++}
++
++void
++ProtocolExtension::send_metadata_piece(size_t piece) {
++ // Reject out-of-range piece, or if we don't have the complete metadata yet.
++ size_t metadataSize = m_download->info()->metadata_size();
++ size_t pieceEnd = (metadataSize + metadata_piece_size - 1) >> metadata_piece_shift;
++
++ if (m_download->info()->is_meta_download() || piece >= pieceEnd) {
++ // reject: { "msg_type" => 2, "piece" => ... }
++ m_pendingType = UT_METADATA;
++ m_pending = build_bencode(40, "d8:msg_typei2e5:piecei%zuee", piece);
++ return;
++ }
++
++ // These messages will be rare, so we'll just build the
++ // metadata here instead of caching it uselessly.
++ char* buffer = new char[metadataSize];
++ object_buffer_t result = object_write_bencode_c(object_write_to_buffer, NULL, object_buffer_t(buffer, buffer + metadataSize),
++ &(*manager->download_manager()->find(m_download->info()))->bencode()->get_key("info"));
++
++ // data: { "msg_type" => 1, "piece" => ..., "total_size" => ... } followed by piece data (outside of dictionary)
++ size_t length = piece == pieceEnd - 1 ? m_download->info()->metadata_size() % metadata_piece_size : metadata_piece_size;
++ m_pendingType = UT_METADATA;
++ m_pending = build_bencode(length + 128, "d8:msg_typei1e5:piecei%zue10:total_sizei%zuee", piece, metadataSize);
++
++ memcpy(m_pending.end(), buffer + (piece << metadata_piece_shift), length);
++ m_pending.set(m_pending.data(), m_pending.end() + length, m_pending.owned());
++ delete [] buffer;
++}
++
++bool
++ProtocolExtension::request_metadata_piece(const Piece* p) {
++ if (p->offset() % metadata_piece_size)
++ throw internal_error("ProtocolExtension::request_metadata_piece got misaligned piece offset.");
++
++ if (has_pending_message())
++ return false;
++
++ m_pendingType = UT_METADATA;
++ m_pending = build_bencode(40, "d8:msg_typei0e5:piecei%uee", (unsigned)(p->offset() >> metadata_piece_shift));
++ return true;
+ }
+
+ }
+diff --git a/src/protocol/extensions.h b/src/protocol/extensions.h
+index 96ed652..485e7d7 100644
+--- a/src/protocol/extensions.h
++++ b/src/protocol/extensions.h
+@@ -60,6 +60,7 @@ public:
+ typedef enum {
+ HANDSHAKE = 0,
+ UT_PEX,
++ UT_METADATA,
+
+ FIRST_INVALID, // first invalid message ID
+
+@@ -81,6 +82,10 @@ public:
+ // Number of extensions we support, not counting handshake.
+ static const int extension_count = FIRST_INVALID - HANDSHAKE - 1;
+
++ // Fixed size of a metadata piece (16 KB).
++ static const size_t metadata_piece_shift = 14;
++ static const size_t metadata_piece_size = 1 << metadata_piece_shift;
++
+ ProtocolExtension();
+ ~ProtocolExtension() { delete [] m_read; }
+
+@@ -91,6 +96,7 @@ public:
+ static ProtocolExtension make_default();
+
+ void set_info(PeerInfo* peerInfo, DownloadMain* download) { m_peerInfo = peerInfo; m_download = download; }
++ void set_connection(PeerConnectionBase* c) { m_connection = c; }
+
+ DataBuffer generate_handshake_message();
+ static DataBuffer generate_toggle_message(MessageType t, bool on);
+@@ -112,7 +118,7 @@ public:
+
+ // Handle reading extension data from peer.
+ void read_start(int type, uint32_t length, bool skip);
+- void read_done();
++ bool read_done();
+
+ char* read_position() { return m_readPos; }
+ bool read_move(uint32_t v) { m_readPos += v; return (m_readLeft -= v) == 0; }
+@@ -132,13 +138,23 @@ public:
+ void clear_initial_pex() { m_flags &= ~flag_initial_pex; }
+ void reset() { std::memset(&m_idMap, 0, sizeof(m_idMap)); }
+
++ bool request_metadata_piece(const Piece* p);
++
++ // To handle cases where the extension protocol needs to send a reply.
++ bool has_pending_message() const { return m_pendingType != HANDSHAKE; }
++ MessageType pending_message_type() const { return m_pendingType; }
++ DataBuffer pending_message_data() { return m_pending.release(); }
++ void clear_pending_message() { if (m_pending.empty()) m_pendingType = HANDSHAKE; }
++
+ private:
+- void parse_handshake();
+- void parse_ut_pex();
++ bool parse_handshake();
++ bool parse_ut_pex();
++ bool parse_ut_metadata();
+
+ static DataBuffer build_bencode(size_t maxLength, const char* format, ...) ATTRIBUTE_PRINTF(2);
+
+ void peer_toggle_remote(int type, bool active);
++ void send_metadata_piece(size_t piece);
+
+ // Map of IDs peer uses for each extension message type, excluding
+ // HANDSHAKE.
+@@ -149,11 +165,15 @@ private:
+ int m_flags;
+ PeerInfo* m_peerInfo;
+ DownloadMain* m_download;
++ PeerConnectionBase* m_connection;
+
+ uint8_t m_readType;
+ uint32_t m_readLeft;
+ char* m_read;
+ char* m_readPos;
++
++ MessageType m_pendingType;
++ DataBuffer m_pending;
+ };
+
+ inline
+@@ -163,10 +183,13 @@ ProtocolExtension::ProtocolExtension() :
+ m_flags(flag_local_enabled_base | flag_remote_supported_base | flag_initial_handshake),
+ m_peerInfo(NULL),
+ m_download(NULL),
++ m_connection(NULL),
+ m_readType(FIRST_INVALID),
+- m_read(NULL) {
++ m_read(NULL),
++ m_pendingType(HANDSHAKE) {
+
+ reset();
++ set_local_enabled(UT_METADATA);
+ }
+
+ inline ProtocolExtension
+diff --git a/src/protocol/handshake.cc b/src/protocol/handshake.cc
+index d863f7b..7fb389b 100644
+--- a/src/protocol/handshake.cc
++++ b/src/protocol/handshake.cc
+@@ -723,6 +723,17 @@ restart:
+
+ case READ_MESSAGE:
+ case POST_HANDSHAKE:
++ // For meta-downloads, we aren't interested in the bitfield or
++ // extension messages here, PCMetadata handles all that. The
++ // bitfield only refers to the single-chunk meta-data, so fake that.
++ if (m_download->info()->is_meta_download()) {
++ m_bitfield.set_size_bits(1);
++ m_bitfield.allocate();
++ m_bitfield.set(0);
++ read_done();
++ break;
++ }
++
+ fill_read_buffer(5);
+
+ // Received a keep-alive message which means we won't be
+@@ -1022,6 +1033,10 @@ Handshake::prepare_peer_info() {
+ std::memcpy(m_peerInfo->set_options(), m_options, 8);
+ m_peerInfo->mutable_id().assign((const char*)m_readBuffer.position());
+ m_readBuffer.consume(20);
++
++ // For meta downloads, we require support of the extension protocol.
++ if (m_download->info()->is_meta_download() && !m_peerInfo->supports_extensions())
++ throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_unwanted_connection);
+ }
+
+ void
+diff --git a/src/protocol/peer_connection_base.cc b/src/protocol/peer_connection_base.cc
+index ab043a6..815ea93 100644
+--- a/src/protocol/peer_connection_base.cc
++++ b/src/protocol/peer_connection_base.cc
+@@ -93,8 +93,7 @@ PeerConnectionBase::~PeerConnectionBase() {
+ if (m_extensions != NULL && !m_extensions->is_default())
+ delete m_extensions;
+
+- if (m_extensionMessage.owned())
+- m_extensionMessage.clear();
++ m_extensionMessage.clear();
+ }
+
+ void
+@@ -116,6 +115,8 @@ PeerConnectionBase::initialize(DownloadMain* download, PeerInfo* peerInfo, Socke
+ m_encryption = *encryptionInfo;
+ m_extensions = extensions;
+
++ m_extensions->set_connection(this);
++
+ m_peerChunks.set_peer_info(m_peerInfo);
+ m_peerChunks.bitfield()->swap(*bitfield);
+
+@@ -581,8 +582,12 @@ PeerConnectionBase::down_extension() {
+ m_extensions->read_move(bytes);
+ }
+
+- if (m_extensions->is_complete())
+- m_extensions->read_done();
++ // If extension can't be processed yet (due to a pending write),
++ // disable reads until the pending message is completely sent.
++ if (m_extensions->is_complete() && !m_extensions->is_invalid() && !m_extensions->read_done()) {
++ manager->poll()->remove_read(this);
++ return false;
++ }
+
+ return m_extensions->is_complete();
+ }
+@@ -693,12 +698,15 @@ PeerConnectionBase::up_extension() {
+ if (m_extensionOffset < m_extensionMessage.length())
+ return false;
+
+- // clear() deletes the buffer, only do that if we made a copy,
+- // otherwise the buffer is shared among all connections.
+- if (m_extensionMessage.owned())
+- m_extensionMessage.clear();
+- else
+- m_extensionMessage.set(NULL, NULL, false);
++ m_extensionMessage.clear();
++
++ // If we have an unprocessed message, process it now and enable reads again.
++ if (m_extensions->is_complete() && !m_extensions->is_invalid()) {
++ if (!m_extensions->read_done())
++ throw internal_error("PeerConnectionBase::up_extension could not process complete extension message.");
++
++ manager->poll()->insert_read(this);
++ }
+
+ return true;
+ }
+@@ -857,4 +865,16 @@ PeerConnectionBase::send_pex_message() {
+ return true;
+ }
+
++// Extension protocol needs to send a reply.
++bool
++PeerConnectionBase::send_ext_message() {
++ write_prepare_extension(m_extensions->pending_message_type(), m_extensions->pending_message_data());
++ m_extensions->clear_pending_message();
++ return true;
++}
++
++void
++PeerConnectionBase::receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) {
++}
++
+ }
+diff --git a/src/protocol/peer_connection_base.h b/src/protocol/peer_connection_base.h
+index 2994963..d131341 100644
+--- a/src/protocol/peer_connection_base.h
++++ b/src/protocol/peer_connection_base.h
+@@ -140,6 +140,9 @@ public:
+ void read_insert_poll_safe();
+ void write_insert_poll_safe();
+
++ // Communication with the protocol extensions
++ virtual void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length);
++
+ protected:
+ static const uint32_t extension_must_encrypt = ~uint32_t();
+
+@@ -179,6 +182,7 @@ protected:
+ bool try_request_pieces();
+
+ bool send_pex_message();
++ bool send_ext_message();
+
+ DownloadMain* m_download;
+
+diff --git a/src/protocol/peer_connection_leech.cc b/src/protocol/peer_connection_leech.cc
+index a75d333..36c6d7a 100644
+--- a/src/protocol/peer_connection_leech.cc
++++ b/src/protocol/peer_connection_leech.cc
+@@ -333,9 +333,13 @@ PeerConnection<type>::read_message() {
+ m_down->set_state(ProtocolRead::READ_EXTENSION);
+ }
+
+- if (down_extension())
+- m_down->set_state(ProtocolRead::IDLE);
++ if (!down_extension())
++ return false;
+
++ if (m_extensions->has_pending_message())
++ write_insert_poll_safe();
++
++ m_down->set_state(ProtocolRead::IDLE);
+ return true;
+
+ default:
+@@ -433,6 +437,9 @@ PeerConnection<type>::event_read() {
+ if (!down_extension())
+ return;
+
++ if (m_extensions->has_pending_message())
++ write_insert_poll_safe();
++
+ m_down->set_state(ProtocolRead::IDLE);
+ break;
+
+@@ -546,6 +553,10 @@ PeerConnection<type>::fill_write_buffer() {
+ send_pex_message()) {
+ // Don't do anything else if send_pex_message() succeeded.
+
++ } else if (m_extensions->has_pending_message() && m_up->can_write_extension() &&
++ send_ext_message()) {
++ // Same.
++
+ } else if (!m_upChoke.choked() &&
+ !m_peerChunks.upload_queue()->empty() &&
+ m_up->can_write_piece() &&
+diff --git a/src/protocol/peer_connection_metadata.cc b/src/protocol/peer_connection_metadata.cc
+new file mode 100644
+index 0000000..24f13ca
+--- /dev/null
++++ b/src/protocol/peer_connection_metadata.cc
+@@ -0,0 +1,461 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// This program 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 2 of the License, or
++// (at your option) any later version.
++//
++// This program 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 this program; if not, write to the Free Software
++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++//
++// In addition, as a special exception, the copyright holders give
++// permission to link the code of portions of this program with the
++// OpenSSL library under certain conditions as described in each
++// individual source file, and distribute linked combinations
++// including the two.
++//
++// You must obey the GNU General Public License in all respects for
++// all of the code used other than OpenSSL. If you modify file(s)
++// with this exception, you may extend this exception to your version
++// of the file(s), but you are not obligated to do so. If you do not
++// wish to do so, delete this exception statement from your version.
++// If you delete this exception statement from all source files in the
++// program, then also delete it here.
++//
++// Contact: Jari Sundell <jaris@ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <cstring>
++#include <sstream>
++
++#include "data/chunk_list_node.h"
++#include "download/choke_manager.h"
++#include "download/chunk_selector.h"
++#include "download/chunk_statistics.h"
++#include "download/download_info.h"
++#include "download/download_main.h"
++#include "torrent/dht_manager.h"
++#include "torrent/peer/connection_list.h"
++#include "torrent/peer/peer_info.h"
++
++#include "extensions.h"
++#include "peer_connection_metadata.h"
++
++namespace torrent {
++
++PeerConnectionMetadata::~PeerConnectionMetadata() {
++}
++
++void
++PeerConnectionMetadata::initialize_custom() {
++}
++
++void
++PeerConnectionMetadata::update_interested() {
++}
++
++bool
++PeerConnectionMetadata::receive_keepalive() {
++ if (cachedTime - m_timeLastRead > rak::timer::from_seconds(240))
++ return false;
++
++ m_tryRequest = true;
++
++ // There's no point in adding ourselves to the write poll if the
++ // buffer is full, as that will already have been taken care of.
++ if (m_up->get_state() == ProtocolWrite::IDLE &&
++ m_up->can_write_keepalive()) {
++
++ write_insert_poll_safe();
++
++ ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end();
++ m_up->write_keepalive();
++
++ if (is_encrypted())
++ m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end);
++ }
++
++ return true;
++}
++
++// We keep the message in the buffer if it is incomplete instead of
++// keeping the state and remembering the read information. This
++// shouldn't happen very often compared to full reads.
++inline bool
++PeerConnectionMetadata::read_message() {
++ ProtocolBuffer<512>* buf = m_down->buffer();
++
++ if (buf->remaining() < 4)
++ return false;
++
++ // Remember the start of the message so we may reset it if we don't
++ // have the whole message.
++ ProtocolBuffer<512>::iterator beginning = buf->position();
++
++ uint32_t length = buf->read_32();
++
++ if (length == 0) {
++ // Keepalive message.
++ m_down->set_last_command(ProtocolBase::KEEP_ALIVE);
++
++ return true;
++
++ } else if (buf->remaining() < 1) {
++ buf->set_position_itr(beginning);
++ return false;
++
++ } else if (length > (1 << 20)) {
++ throw communication_error("PeerConnection::read_message() got an invalid message length.");
++ }
++
++ m_down->set_last_command((ProtocolBase::Protocol)buf->peek_8());
++
++ // Ignore most messages, they aren't relevant for a metadata download.
++ switch (buf->read_8()) {
++ case ProtocolBase::CHOKE:
++ case ProtocolBase::UNCHOKE:
++ case ProtocolBase::INTERESTED:
++ case ProtocolBase::NOT_INTERESTED:
++ return true;
++
++ case ProtocolBase::HAVE:
++ if (!m_down->can_read_have_body())
++ break;
++
++ buf->read_32();
++ return true;
++
++ case ProtocolBase::REQUEST:
++ if (!m_down->can_read_request_body())
++ break;
++
++ m_down->read_request();
++ return true;
++
++ case ProtocolBase::PIECE:
++ throw communication_error("Received a piece but the connection is strictly for meta data.");
++
++ case ProtocolBase::CANCEL:
++ if (!m_down->can_read_cancel_body())
++ break;
++
++ m_down->read_request();
++ return true;
++
++ case ProtocolBase::PORT:
++ if (!m_down->can_read_port_body())
++ break;
++
++ manager->dht_manager()->add_node(m_peerInfo->socket_address(), m_down->buffer()->read_16());
++ return true;
++
++ case ProtocolBase::EXTENSION_PROTOCOL:
++ if (!m_down->can_read_extension_body())
++ break;
++
++ if (m_extensions->is_default()) {
++ m_extensions = new ProtocolExtension();
++ m_extensions->set_info(m_peerInfo, m_download);
++ }
++
++ {
++ int extension = m_down->buffer()->read_8();
++ m_extensions->read_start(extension, length - 2, (extension == ProtocolExtension::UT_PEX) && !m_download->want_pex_msg());
++ m_down->set_state(ProtocolRead::READ_EXTENSION);
++ }
++
++ if (!down_extension())
++ return false;
++
++ // Drop peer if it disabled the metadata extension.
++ if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA))
++ throw close_connection();
++
++ m_down->set_state(ProtocolRead::IDLE);
++ m_tryRequest = true;
++ write_insert_poll_safe();
++
++ return true;
++
++ case ProtocolBase::BITFIELD:
++ // Discard the bitfield sent by the peer.
++ m_skipLength = length - 1;
++ m_down->set_state(ProtocolRead::READ_SKIP_PIECE);
++ return false;
++
++ default:
++ throw communication_error("Received unsupported message type.");
++ }
++
++ // We were unsuccessfull in reading the message, need more data.
++ buf->set_position_itr(beginning);
++ return false;
++}
++
++void
++PeerConnectionMetadata::event_read() {
++ m_timeLastRead = cachedTime;
++
++ // Need to make sure ProtocolBuffer::end() is pointing to the end of
++ // the unread data, and that the unread data starts from the
++ // beginning of the buffer. Or do we use position? Propably best,
++ // therefor ProtocolBuffer::position() points to the beginning of
++ // the unused data.
++
++ try {
++
++ // Normal read.
++ //
++ // We rarely will read zero bytes as the read of 64 bytes will
++ // almost always either not fill up or it will require additional
++ // reads.
++ //
++ // Only loop when end hits 64.
++
++ do {
++ switch (m_down->get_state()) {
++ case ProtocolRead::IDLE:
++ if (m_down->buffer()->size_end() < read_size) {
++ unsigned int length = read_stream_throws(m_down->buffer()->end(), read_size - m_down->buffer()->size_end());
++ m_down->throttle()->node_used_unthrottled(length);
++
++ if (is_encrypted())
++ m_encryption.decrypt(m_down->buffer()->end(), length);
++
++ m_down->buffer()->move_end(length);
++ }
++
++ while (read_message());
++
++ if (m_down->buffer()->size_end() == read_size) {
++ m_down->buffer()->move_unused();
++ break;
++ } else {
++ m_down->buffer()->move_unused();
++ return;
++ }
++
++ case ProtocolRead::READ_EXTENSION:
++ if (!down_extension())
++ return;
++
++ // Drop peer if it disabled the metadata extension.
++ if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA))
++ throw close_connection();
++
++ m_down->set_state(ProtocolRead::IDLE);
++ m_tryRequest = true;
++ write_insert_poll_safe();
++ break;
++
++ // Actually skipping the bitfield.
++ // We never receive normal piece messages anyway.
++ case ProtocolRead::READ_SKIP_PIECE:
++ if (!read_skip_bitfield())
++ return;
++
++ m_down->set_state(ProtocolRead::IDLE);
++ break;
++
++ default:
++ throw internal_error("PeerConnection::event_read() wrong state.");
++ }
++
++ // Figure out how to get rid of the shouldLoop boolean.
++ } while (true);
++
++ // Exception handlers:
++
++ } catch (close_connection& e) {
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (blocked_connection& e) {
++ m_download->info()->signal_network_log().emit("Momentarily blocked read connection.");
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (network_error& e) {
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (storage_error& e) {
++ m_download->info()->signal_storage_error().emit(e.what());
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (base_error& e) {
++ std::stringstream s;
++ s << "Connection read fd(" << get_fd().get_fd() << ',' << m_down->get_state() << ',' << m_down->last_command() << ") \"" << e.what() << '"';
++
++ throw internal_error(s.str());
++ }
++}
++
++inline void
++PeerConnectionMetadata::fill_write_buffer() {
++ ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end();
++
++ if (m_tryRequest)
++ m_tryRequest = try_request_metadata_pieces();
++
++ if (m_sendPEXMask && m_up->can_write_extension() &&
++ send_pex_message()) {
++ // Don't do anything else if send_pex_message() succeeded.
++
++ } else if (m_extensions->has_pending_message() && m_up->can_write_extension() &&
++ send_ext_message()) {
++ // Same.
++ }
++
++ if (is_encrypted())
++ m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end);
++}
++
++void
++PeerConnectionMetadata::event_write() {
++ try {
++
++ do {
++
++ switch (m_up->get_state()) {
++ case ProtocolWrite::IDLE:
++
++ fill_write_buffer();
++
++ if (m_up->buffer()->remaining() == 0) {
++ manager->poll()->remove_write(this);
++ return;
++ }
++
++ m_up->set_state(ProtocolWrite::MSG);
++
++ case ProtocolWrite::MSG:
++ if (!m_up->buffer()->consume(m_up->throttle()->node_used_unthrottled(write_stream_throws(m_up->buffer()->position(), m_up->buffer()->remaining()))))
++ return;
++
++ m_up->buffer()->reset();
++
++ if (m_up->last_command() != ProtocolBase::EXTENSION_PROTOCOL) {
++ m_up->set_state(ProtocolWrite::IDLE);
++ break;
++ }
++
++ m_up->set_state(ProtocolWrite::WRITE_EXTENSION);
++
++ case ProtocolWrite::WRITE_EXTENSION:
++ if (!up_extension())
++ return;
++
++ m_up->set_state(ProtocolWrite::IDLE);
++ break;
++
++ default:
++ throw internal_error("PeerConnection::event_write() wrong state.");
++ }
++
++ } while (true);
++
++ } catch (close_connection& e) {
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (blocked_connection& e) {
++ m_download->info()->signal_network_log().emit("Momentarily blocked write connection.");
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (network_error& e) {
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (storage_error& e) {
++ m_download->info()->signal_storage_error().emit(e.what());
++ m_download->connection_list()->erase(this, 0);
++
++ } catch (base_error& e) {
++ std::stringstream s;
++ s << "Connection write fd(" << get_fd().get_fd() << ',' << m_up->get_state() << ',' << m_up->last_command() << ") \"" << e.what() << '"';
++
++ throw internal_error(s.str());
++ }
++}
++
++bool
++PeerConnectionMetadata::read_skip_bitfield() {
++ if (m_down->buffer()->remaining()) {
++ uint32_t length = std::min(m_skipLength, (uint32_t)m_down->buffer()->remaining());
++ m_down->buffer()->consume(length);
++ m_skipLength -= length;
++ }
++
++ if (m_skipLength) {
++ uint32_t length = std::min(m_skipLength, (uint32_t)null_buffer_size);
++ length = read_stream_throws(m_nullBuffer, length);
++ if (!length)
++ return false;
++ m_skipLength -= length;
++ }
++
++ return !m_skipLength;
++}
++
++// Same as the PCB code, but only one at a time and with the extension protocol.
++bool
++PeerConnectionMetadata::try_request_metadata_pieces() {
++ if (m_download->file_list()->chunk_size() == 1 || !m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA))
++ return false;
++
++ if (download_queue()->queued_empty())
++ m_downStall = 0;
++
++ uint32_t pipeSize = download_queue()->calculate_pipe_size(m_peerChunks.download_throttle()->rate()->rate());
++
++ // Don't start requesting if we can't do it in large enough chunks.
++ if (download_queue()->queued_size() >= (pipeSize + 10) / 2)
++ return false;
++
++ if (!download_queue()->queued_size() < pipeSize || !m_up->can_write_extension() ||
++ m_extensions->has_pending_message())
++ return false;
++
++ const Piece* p = download_queue()->delegate();
++
++ if (p == NULL)
++ return false;
++
++ if (!m_download->file_list()->is_valid_piece(*p) || !m_peerChunks.bitfield()->get(p->index()))
++ throw internal_error("PeerConnectionMetadata::try_request_metadata_pieces() tried to use an invalid piece.");
++
++ return m_extensions->request_metadata_piece(p);
++}
++
++void
++PeerConnectionMetadata::receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) {
++ if (data == NULL) {
++ // Length is not set in a reject message.
++ length = ProtocolExtension::metadata_piece_size;
++ if ((piece << ProtocolExtension::metadata_piece_shift) + ProtocolExtension::metadata_piece_size >= m_download->file_list()->size_bytes())
++ length = m_download->file_list()->chunk_size() % ProtocolExtension::metadata_piece_size;
++ m_tryRequest = false;
++ read_cancel_piece(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length));
++ return;
++ }
++
++ if (!down_chunk_start(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length)))
++ down_chunk_skip_process(data, length);
++ else
++ down_chunk_process(data, length);
++
++ if (!m_downloadQueue.transfer()->is_finished())
++ throw internal_error("PeerConnectionMetadata::receive_metadata_piece did not have complete piece.");
++
++ m_tryRequest = true;
++ down_chunk_finished();
++}
++
++}
+diff --git a/src/protocol/peer_connection_metadata.h b/src/protocol/peer_connection_metadata.h
+new file mode 100644
+index 0000000..127700a
+--- /dev/null
++++ b/src/protocol/peer_connection_metadata.h
+@@ -0,0 +1,73 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// This program 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 2 of the License, or
++// (at your option) any later version.
++//
++// This program 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 this program; if not, write to the Free Software
++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++//
++// In addition, as a special exception, the copyright holders give
++// permission to link the code of portions of this program with the
++// OpenSSL library under certain conditions as described in each
++// individual source file, and distribute linked combinations
++// including the two.
++//
++// You must obey the GNU General Public License in all respects for
++// all of the code used other than OpenSSL. If you modify file(s)
++// with this exception, you may extend this exception to your version
++// of the file(s), but you are not obligated to do so. If you do not
++// wish to do so, delete this exception statement from your version.
++// If you delete this exception statement from all source files in the
++// program, then also delete it here.
++//
++// Contact: Jari Sundell <jaris@ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H
++#define LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H
++
++#include "peer_connection_base.h"
++
++#include "torrent/download.h"
++
++namespace torrent {
++
++class PeerConnectionMetadata : public PeerConnectionBase {
++public:
++ ~PeerConnectionMetadata();
++
++ virtual void initialize_custom();
++ virtual void update_interested();
++ virtual bool receive_keepalive();
++
++ virtual void event_read();
++ virtual void event_write();
++
++ virtual void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length);
++
++private:
++ inline bool read_message();
++
++ bool read_skip_bitfield();
++
++ bool try_request_metadata_pieces();
++
++ inline void fill_write_buffer();
++
++ uint32_t m_skipLength;
++};
++
++}
++
++#endif
+diff --git a/src/protocol/peer_factory.cc b/src/protocol/peer_factory.cc
+index 7ab9fe8..cfe6a1e 100644
+--- a/src/protocol/peer_factory.cc
++++ b/src/protocol/peer_factory.cc
+@@ -38,6 +38,7 @@
+
+ #include "peer_factory.h"
+ #include "peer_connection_leech.h"
++#include "peer_connection_metadata.h"
+
+ namespace torrent {
+
+@@ -62,4 +63,11 @@ createPeerConnectionInitialSeed(bool encrypted) {
+ return pc;
+ }
+
++PeerConnectionBase*
++createPeerConnectionMetadata(bool encrypted) {
++ PeerConnectionBase* pc = new PeerConnectionMetadata;
++
++ return pc;
++}
++
+ }
+diff --git a/src/protocol/peer_factory.h b/src/protocol/peer_factory.h
+index 363a5c3..f22d76f 100644
+--- a/src/protocol/peer_factory.h
++++ b/src/protocol/peer_factory.h
+@@ -44,6 +44,7 @@ class PeerConnectionBase;
+ PeerConnectionBase* createPeerConnectionDefault(bool encrypted);
+ PeerConnectionBase* createPeerConnectionSeed(bool encrypted);
+ PeerConnectionBase* createPeerConnectionInitialSeed(bool encrypted);
++PeerConnectionBase* createPeerConnectionMetadata(bool encrypted);
+
+ }
+
+diff --git a/src/torrent/data/file_list.cc b/src/torrent/data/file_list.cc
+index 2f5d8d2..7208612 100644
+--- a/src/torrent/data/file_list.cc
++++ b/src/torrent/data/file_list.cc
+@@ -466,6 +466,18 @@ FileList::open(int flags) {
+
+ m_isOpen = true;
+ m_frozenRootDir = m_rootDir;
++
++ // For meta-downloads, if the file exists, we have to assume that
++ // it is either 0 or 1 length or the correct size. If the size
++ // turns out wrong later, a communication_error will be thrown elsewhere
++ // to alert the user in this (unlikely) case.
++ if (size_bytes() < 2) {
++ rak::file_stat stat;
++
++ // This probably recurses into open() once, but that is harmless.
++ if (stat.update((*begin())->frozen_path()) && stat.size() > 1)
++ return reset_filesize(stat.size());
++ }
+ }
+
+ void
+@@ -661,4 +673,14 @@ FileList::update_completed() {
+ }
+ }
+
++void
++FileList::reset_filesize(int64_t size) {
++ close();
++ m_chunkSize = size;
++ m_torrentSize = size;
++ (*begin())->set_size_bytes(size);
++ (*begin())->set_range(m_chunkSize);
++ open(open_no_create);
++}
++
+ }
+diff --git a/src/torrent/data/file_list.h b/src/torrent/data/file_list.h
+index bcc8939..60d418a 100644
+--- a/src/torrent/data/file_list.h
++++ b/src/torrent/data/file_list.h
+@@ -167,6 +167,10 @@ protected:
+ iterator inc_completed(iterator firstItr, uint32_t index) LIBTORRENT_NO_EXPORT;
+ void update_completed() LIBTORRENT_NO_EXPORT;
+
++ // Used for meta downloads; we only know the
++ // size after the first extension handshake.
++ void reset_filesize(int64_t) LIBTORRENT_NO_EXPORT;
++
+ private:
+ bool open_file(File* node, const Path& lastPath, int flags) LIBTORRENT_NO_EXPORT;
+ void make_directory(Path::const_iterator pathBegin, Path::const_iterator pathEnd, Path::const_iterator startItr) LIBTORRENT_NO_EXPORT;
+diff --git a/src/torrent/download.cc b/src/torrent/download.cc
+index d6cc199..49daad9 100644
+--- a/src/torrent/download.cc
++++ b/src/torrent/download.cc
+@@ -225,6 +225,11 @@ Download::set_pex_enabled(bool enabled) {
+ m_ptr->info()->set_pex_enabled(enabled);
+ }
+
++bool
++Download::is_meta_download() const {
++ return m_ptr->info()->is_meta_download();
++}
++
+ const std::string&
+ Download::name() const {
+ if (m_ptr == NULL)
+@@ -504,6 +509,11 @@ Download::connection_type() const {
+
+ void
+ Download::set_connection_type(ConnectionType t) {
++ if (m_ptr->info()->is_meta_download()) {
++ m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionMetadata);
++ return;
++ }
++
+ switch (t) {
+ case CONNECTION_LEECH:
+ m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionDefault);
+diff --git a/src/torrent/download.h b/src/torrent/download.h
+index 5e9700e..5d16d4e 100644
+--- a/src/torrent/download.h
++++ b/src/torrent/download.h
+@@ -100,6 +100,8 @@ public:
+ bool is_pex_enabled() const;
+ void set_pex_enabled(bool enabled);
+
++ bool is_meta_download() const;
++
+ // Returns "" if the object is not valid.
+ const std::string& name() const;
+
+@@ -184,6 +186,7 @@ public:
+ CONNECTION_LEECH,
+ CONNECTION_SEED,
+ CONNECTION_INITIAL_SEED,
++ CONNECTION_METADATA,
+ } ConnectionType;
+
+ ConnectionType connection_type() const;
+diff --git a/src/torrent/object_stream.cc b/src/torrent/object_stream.cc
+index 73c816b..9d9a962 100644
+--- a/src/torrent/object_stream.cc
++++ b/src/torrent/object_stream.cc
+@@ -600,4 +600,11 @@ object_write_to_stream(void* data, object_buffer_t buffer) {
+ return buffer;
+ }
+
++object_buffer_t
++object_write_to_size(void* data, object_buffer_t buffer) {
++ *reinterpret_cast<uint64_t*>(data) += std::distance(buffer.first, buffer.second);
++
++ return buffer;
++}
++
+ }
+diff --git a/src/torrent/object_stream.h b/src/torrent/object_stream.h
+index b399bf7..3de5d82 100644
+--- a/src/torrent/object_stream.h
++++ b/src/torrent/object_stream.h
+@@ -91,6 +91,9 @@ object_buffer_t staticMap_write_bencode_c_wrap(object_write_t writeFunc, void* d
+ object_buffer_t object_write_to_buffer(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT;
+ object_buffer_t object_write_to_sha1(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT;
+ object_buffer_t object_write_to_stream(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT;
++
++// Measures bencode size, 'data' is uint64_t*.
++object_buffer_t object_write_to_size(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT;
+ }
+
+ #endif
+diff --git a/src/torrent/torrent.cc b/src/torrent/torrent.cc
+index e8ffbac..47027cc 100644
+--- a/src/torrent/torrent.cc
++++ b/src/torrent/torrent.cc
+@@ -350,11 +350,22 @@ download_add(Object* object) {
+
+ ctor.initialize(*object);
+
+- std::string infoHash = object_sha1(&object->get_key("info"));
++ std::string infoHash;
++ if (download->info()->is_meta_download())
++ infoHash = object->get_key("info").get_key("pieces").as_string();
++ else
++ infoHash = object_sha1(&object->get_key("info"));
+
+ if (manager->download_manager()->find(infoHash) != manager->download_manager()->end())
+ throw input_error("Info hash already used by another torrent.");
+
++ if (!download->info()->is_meta_download()) {
++ char buffer[1024];
++ uint64_t metadata_size = 0;
++ object_write_bencode_c(&object_write_to_size, &metadata_size, object_buffer_t(buffer, buffer + sizeof(buffer)), &object->get_key("info"));
++ download->main()->set_metadata_size(metadata_size);
++ }
++
+ download->set_hash_queue(manager->hash_queue());
+ download->initialize(infoHash, PEER_NAME + rak::generate_random<std::string>(20 - std::string(PEER_NAME).size()));
+