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, 0 insertions, 1652 deletions
diff --git a/libtorrent-extended/magnet_uri.patch b/libtorrent-extended/magnet_uri.patch
deleted file mode 100644
index 3d00540..0000000
--- a/libtorrent-extended/magnet_uri.patch
+++ /dev/null
@@ -1,1652 +0,0 @@
-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()));
-