diff --git a/Jamfile b/Jamfile index e3dd91603..19e52c082 100755 --- a/Jamfile +++ b/Jamfile @@ -236,6 +236,7 @@ SOURCES = metadata_transfer upnp ut_pex + ut_metadata logger file_pool lsd diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp index dc5237a7d..d02e005b3 100755 --- a/include/libtorrent/bt_peer_connection.hpp +++ b/include/libtorrent/bt_peer_connection.hpp @@ -267,6 +267,8 @@ namespace libtorrent // initializes m_RC4_handler void init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key); +public: + // these functions encrypt the send buffer if m_rc4_encrypted // is true, otherwise it passes the call to the // peer_connection functions of the same names @@ -283,6 +285,8 @@ namespace libtorrent } void setup_send(); +private: + // Returns offset at which bytestream (src, src + src_size) // matches bytestream(target, target + target_size). // If no sync found, return -1 diff --git a/include/libtorrent/extensions/ut_metadata.hpp b/include/libtorrent/extensions/ut_metadata.hpp new file mode 100644 index 000000000..91437b17c --- /dev/null +++ b/include/libtorrent/extensions/ut_metadata.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_METADATA_HPP_INCLUDED +#define TORRENT_UT_METADATA_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include "libtorrent/config.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace libtorrent +{ + struct torrent_plugin; + class torrent; + TORRENT_EXPORT boost::shared_ptr create_ut_metadata_plugin(torrent*, void*); +} + +#endif // TORRENT_UT_METADATA_HPP_INCLUDED + diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index 805b38d9d..cf3569318 100755 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -399,6 +399,11 @@ namespace libtorrent int send_buffer_capacity() const { return m_send_buffer.capacity(); } + int packet_size() const { return m_packet_size; } + + bool packet_finished() const + { return m_packet_size <= m_recv_pos; } + protected: virtual void get_specific_peer_info(peer_info& p) const = 0; @@ -443,12 +448,6 @@ namespace libtorrent void cut_receive_buffer(int size, int packet_size); void reset_recv_buffer(int packet_size); - int packet_size() const { return m_packet_size; } - - bool packet_finished() const - { - return m_packet_size <= m_recv_pos; - } void setup_receive(); diff --git a/src/ut_metadata.cpp b/src/ut_metadata.cpp new file mode 100644 index 000000000..d3bb8b1ed --- /dev/null +++ b/src/ut_metadata.cpp @@ -0,0 +1,448 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/alert_types.hpp" + +namespace libtorrent { namespace +{ + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + void nop(char*) {} + + struct ut_metadata_plugin : torrent_plugin + { + ut_metadata_plugin(torrent& t) + : m_torrent(t) + , m_metadata_progress(0) + , m_metadata_size(0) + { + } + + virtual void on_files_checked() + { + // if the torrent is a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + virtual boost::shared_ptr new_connection( + peer_connection* pc); + + std::vector const& metadata() const + { + TORRENT_ASSERT(m_torrent.valid_metadata()); + + if (m_metadata.empty()) + { + bencode(std::back_inserter(m_metadata) + , m_torrent.torrent_file().create_info_metadata()); + + TORRENT_ASSERT(hasher(&m_metadata[0], m_metadata.size()).final() + == m_torrent.torrent_file().info_hash()); + } + TORRENT_ASSERT(!m_metadata.empty()); + return m_metadata; + } + + bool received_metadata(char const* buf, int size, int piece, int total_size) + { + if (m_torrent.valid_metadata()) return false; + + if (m_metadata.empty()) + { + // verify the total_size + if (total_size <= 0 || total_size > 500 * 1024) return false; + + m_metadata.resize(total_size); + m_requested_metadata.resize(div_round_up(total_size, 16 * 1024), 0); + } + + if (piece < 0 || piece >= int(m_requested_metadata.size())) + return false; + + TORRENT_ASSERT(piece * 16 * 1024 + size <= int(m_metadata.size())); + std::memcpy(&m_metadata[piece * 16 * 1024], buf, size); + // mark this piece has 'have' + m_requested_metadata[piece] = (std::numeric_limits::max)(); + + bool have_all = std::count(m_requested_metadata.begin() + , m_requested_metadata.end(), (std::numeric_limits::max)()) + == int(m_requested_metadata.size()); + + if (!have_all) return false; + + hasher h; + h.update(&m_metadata[0], (int)m_metadata.size()); + sha1_hash info_hash = h.final(); + + if (info_hash != m_torrent.torrent_file().info_hash()) + { + std::fill(m_requested_metadata.begin(), m_requested_metadata.end(), 0); + + if (m_torrent.alerts().should_post(alert::info)) + { + m_torrent.alerts().post_alert(metadata_failed_alert( + m_torrent.get_handle(), "invalid metadata received from swarm")); + } + + return false; + } + + entry metadata = bdecode(m_metadata.begin(), m_metadata.end()); + m_torrent.set_metadata(metadata); + + // clear the storage for the bitfield + std::vector().swap(m_requested_metadata); + + return true; + } + + // returns a piece of the metadata that + // we should request. + int metadata_request(); + + // this is called from the peer_connection for + // each piece of metadata it receives + void metadata_progress(int total_size, int received) + { + m_metadata_progress += received; + m_metadata_size = total_size; + } + + void piece_pass(int) + { + // if we became a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + void metadata_size(int size) + { + if (m_metadata_size > 0 || size <= 0 || size > 500 * 1024) return; + m_metadata_size = size; + m_metadata.resize(size); + m_requested_metadata.resize(div_round_up(size, 16 * 1024), 0); + } + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable std::vector m_metadata; + + int m_metadata_progress; + int m_metadata_size; + + // this vector keeps track of how many times each meatdata + // block has been requested + // std::numeric_limits::max() means we have the piece + std::vector m_requested_metadata; + }; + + + struct ut_metadata_peer_plugin : peer_plugin + { + ut_metadata_peer_plugin(torrent& t, bt_peer_connection& pc + , ut_metadata_plugin& tp) + : m_message_index(0) + , m_no_metadata(min_time()) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + {} + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) + { + entry& messages = h["m"]; + messages["ut_metadata"] = 15; + if (m_torrent.valid_metadata()) + h["metadata_size"] = m_tp.metadata().size(); + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(entry const& h) + { + entry const* metadata_size = h.find_key("metadata_size"); + if (metadata_size && metadata_size->type() == entry::int_t) + m_tp.metadata_size(metadata_size->integer()); + + entry const& messages = h["m"]; + if (entry const* index = messages.find_key("ut_metadata")) + { + m_message_index = index->integer(); + return true; + } + else + { + m_message_index = 0; + return false; + } + } + + void write_metadata_packet(int type, int piece) + { + TORRENT_ASSERT(type >= 0 && type <= 2); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_pc.m_logger) << time_now_string() << " ==> UT_METADATA [ " + "type: " << type << " | piece: " << piece << " ]\n"; +#endif + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + int total_size = 4; // msg_extended, m_message_index, 'd', ... , 'e' + char pkt_header[7]; + + char prefix[200]; + int prefix_len = 0; + char suffix[200]; + int suffix_len = std::sprintf(suffix, "8:msg_typei%de5:piecei%de", type, piece); + + char const* metadata = 0; + int metadata_piece_size = 0; + + if (type == 1) + { + TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); + int offset = piece * 16 * 1024; + metadata = &m_tp.metadata()[0] + offset; + metadata_piece_size = (std::min)(int(m_tp.metadata().size() - offset), 16 * 1024); + TORRENT_ASSERT(metadata_piece_size > 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.metadata().size())); + + prefix_len = std::sprintf(prefix, "8:metadata%d:", metadata_piece_size); + total_size += prefix_len + metadata_piece_size; + + suffix_len += std::sprintf(suffix + suffix_len, "10:total_sizei%de", int(m_tp.metadata().size())); + } + + total_size += suffix_len; + + suffix[suffix_len++] = 'e'; + suffix[suffix_len] = 0; + + char* p = pkt_header; + namespace io = detail; + io::write_uint32(total_size, p); + io::write_uint8(bt_peer_connection::msg_extended, p); + io::write_uint8(m_message_index, p); + io::write_uint8('d', p); + + m_pc.send_buffer(pkt_header, 7); + if (prefix_len) m_pc.send_buffer(prefix, prefix_len); + if (metadata_piece_size) m_pc.append_send_buffer((char*)metadata, metadata_piece_size, &nop); + m_pc.send_buffer(suffix, suffix_len); + } + + virtual bool on_extended(int length + , int extended_msg, buffer::const_interval body) + { + if (extended_msg != 15) return false; + if (m_message_index == 0) return false; + + if (length > 17 * 1024) + throw protocol_error("ut_metadata message larger than 17 kB"); + + if (!m_pc.packet_finished()) return true; + + entry msg = bdecode(body.begin, body.end); + + int type = msg["msg_type"].integer(); + int piece = msg["piece"].integer(); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ " + "type: " << type << " | piece: " << piece << " ]\n"; +#endif + + switch (type) + { + case 0: // request + { + if (!m_torrent.valid_metadata()) + { + write_metadata_packet(2, piece); + return true; + } + // TODO: put the request on the queue in some cases + write_metadata_packet(1, piece); + } + break; + case 1: // data + { + std::vector::iterator i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + + // unwanted piece? + if (i == m_sent_requests.end()) return true; + + m_sent_requests.erase(i); + std::string const& d = msg["metadata"].string(); + entry const* total_size = msg.find_key("total_size"); + m_tp.received_metadata(d.c_str(), d.size(), piece + , (total_size && total_size->type() == entry::int_t) ? total_size->integer() : 0); + } + break; + case 2: // have no data + { + m_no_metadata = time_now(); + std::vector::iterator i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + // unwanted piece? + if (i == m_sent_requests.end()) return true; + m_sent_requests.erase(i); + } + break; + default: + throw protocol_error("unknown metadata extension message: " + + boost::lexical_cast(type)); + } + return true; + } + + virtual void tick() + { + // if we don't have any metadata, and this peer + // supports the request metadata extension + // and we aren't currently waiting for a request + // reply. Then, send a request for some metadata. + if (!m_torrent.valid_metadata() + && m_message_index != 0 + && m_sent_requests.size() < 2 + && has_metadata()) + { + int piece = m_tp.metadata_request(); + m_sent_requests.push_back(piece); + write_metadata_packet(0, piece); + } + } + + bool has_metadata() const + { + return time_now() - m_no_metadata > minutes(1); + } + + private: + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // this is set to the current time each time we get a + // "I don't have metadata" message. + ptime m_no_metadata; + + // request queues + std::vector m_sent_requests; + std::vector m_incoming_requests; + + torrent& m_torrent; + bt_peer_connection& m_pc; + ut_metadata_plugin& m_tp; + }; + + boost::shared_ptr ut_metadata_plugin::new_connection( + peer_connection* pc) + { + bt_peer_connection* c = dynamic_cast(pc); + if (!c) return boost::shared_ptr(); + return boost::shared_ptr(new ut_metadata_peer_plugin(m_torrent, *c, *this)); + } + + int ut_metadata_plugin::metadata_request() + { + std::vector::iterator i = std::min_element( + m_requested_metadata.begin(), m_requested_metadata.end()); + + if (m_requested_metadata.empty()) + { + // if we don't know how many pieces there are + // just ask for piece 0 + m_requested_metadata.resize(1, 1); + return 0; + } + + int piece = i - m_requested_metadata.begin(); + m_requested_metadata[piece] = piece; + return piece; + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_ut_metadata_plugin(torrent* t, void*) + { + return boost::shared_ptr(new ut_metadata_plugin(*t)); + } + +} + + diff --git a/test/test_metadata_extension.cpp b/test/test_metadata_extension.cpp index 543684e3f..e2712e910 100644 --- a/test/test_metadata_extension.cpp +++ b/test/test_metadata_extension.cpp @@ -7,18 +7,20 @@ #include "test.hpp" #include "setup_transfer.hpp" #include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" using boost::filesystem::remove_all; using boost::tuples::ignore; -void test_transfer(bool clear_files = true, bool disconnect = false) +void test_transfer(bool clear_files, bool disconnect + , boost::shared_ptr (*constructor)(libtorrent::torrent*, void*)) { using namespace libtorrent; session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48000, 49000)); session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49000, 50000)); - ses1.add_extension(&create_metadata_plugin); - ses2.add_extension(&create_metadata_plugin); + ses1.add_extension(constructor); + ses2.add_extension(constructor); torrent_handle tor1; torrent_handle tor2; #ifndef TORRENT_DISABLE_ENCRYPTION @@ -72,13 +74,18 @@ int test_main() using namespace boost::filesystem; // test to disconnect one client prematurely - test_transfer(true, true); - + test_transfer(true, true, &create_metadata_plugin); // test where one has data and one doesn't - test_transfer(true); - + test_transfer(true, false, &create_metadata_plugin); // test where both have data (to trigger the file check) - test_transfer(false); + test_transfer(false, false, &create_metadata_plugin); + + // test to disconnect one client prematurely + test_transfer(true, true, &create_ut_metadata_plugin); + // test where one has data and one doesn't + test_transfer(true, false, &create_ut_metadata_plugin); + // test where both have data (to trigger the file check) + test_transfer(false, false, &create_ut_metadata_plugin); remove_all("./tmp1"); remove_all("./tmp2");