premiere-libtorrent/src/ut_metadata.cpp

546 lines
16 KiB
C++
Raw Normal View History

2007-12-02 05:16:51 +01:00
/*
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"
2011-01-29 13:13:49 +01:00
#ifndef TORRENT_DISABLE_EXTENSIONS
2007-12-02 05:16:51 +01:00
#ifdef _MSC_VER
#pragma warning(push, 1)
#endif
#include <boost/shared_ptr.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <vector>
#include <utility>
#include <numeric>
#include <cstdio>
#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/ut_metadata.hpp"
2007-12-02 05:16:51 +01:00
#include "libtorrent/alert_types.hpp"
#include "libtorrent/random.hpp"
#ifdef TORRENT_STATS
#include "libtorrent/aux_/session_impl.hpp"
#endif
2007-12-02 05:16:51 +01:00
namespace libtorrent { namespace
{
int div_round_up(int numerator, int denominator)
{
return (numerator + denominator - 1) / denominator;
}
struct ut_metadata_peer_plugin;
2007-12-02 05:16:51 +01:00
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<peer_plugin> new_connection(
peer_connection* pc);
buffer::const_interval metadata() const
2007-12-02 05:16:51 +01:00
{
TORRENT_ASSERT(m_torrent.valid_metadata());
if (!m_metadata)
2007-12-02 05:16:51 +01:00
{
m_metadata = m_torrent.torrent_file().metadata();
m_metadata_size = m_torrent.torrent_file().metadata_size();
TORRENT_ASSERT(hasher(m_metadata.get(), m_metadata_size).final()
2007-12-02 05:16:51 +01:00
== m_torrent.torrent_file().info_hash());
}
return buffer::const_interval(m_metadata.get(), m_metadata.get()
+ m_metadata_size);
2007-12-02 05:16:51 +01:00
}
bool received_metadata(boost::weak_ptr<ut_metadata_peer_plugin> const& source
, char const* buf, int size, int piece, int total_size);
2007-12-02 05:16:51 +01:00
// returns a piece of the metadata that
// we should request.
// returns -1 if we should hold off the request
2007-12-02 05:16:51 +01:00
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;
m_torrent.set_progress_ppm(boost::int64_t(m_metadata_progress) * 1000000 / m_metadata_size);
2007-12-02 05:16:51 +01:00
}
2007-12-16 23:43:24 +01:00
void on_piece_pass(int)
2007-12-02 05:16:51 +01:00
{
// 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.reset(new char[size]);
m_requested_metadata.resize(div_round_up(size, 16 * 1024));
2007-12-02 05:16:51 +01:00
}
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 boost::shared_array<char> m_metadata;
2007-12-02 05:16:51 +01:00
int m_metadata_progress;
mutable int m_metadata_size;
2007-12-02 05:16:51 +01:00
struct metadata_piece
{
metadata_piece(): num_requests(0), last_request(0) {}
int num_requests;
time_t last_request;
boost::weak_ptr<ut_metadata_peer_plugin> source;
bool operator<(metadata_piece const& rhs) const
{ return num_requests < rhs.num_requests; }
};
2007-12-02 05:16:51 +01:00
// this vector keeps track of how many times each meatdata
// block has been requested and who we ended up getting it from
2007-12-02 05:16:51 +01:00
// std::numeric_limits<int>::max() means we have the piece
std::vector<metadata_piece> m_requested_metadata;
2007-12-02 05:16:51 +01:00
};
struct ut_metadata_peer_plugin : peer_plugin, boost::enable_shared_from_this<ut_metadata_peer_plugin>
2007-12-02 05:16:51 +01:00
{
ut_metadata_peer_plugin(torrent& t, bt_peer_connection& pc
, ut_metadata_plugin& tp)
: m_message_index(0)
, m_request_limit(min_time())
2007-12-02 05:16:51 +01:00
, m_torrent(t)
, m_pc(pc)
, m_tp(tp)
{}
2010-11-29 02:33:05 +01:00
virtual char const* type() const { return "ut_metadata"; }
2007-12-02 05:16:51 +01:00
// 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().left();
2007-12-02 05:16:51 +01:00
}
// called when the extension handshake from the other end is received
virtual bool on_extension_handshake(lazy_entry const& h)
2007-12-02 05:16:51 +01:00
{
m_message_index = 0;
if (h.type() != lazy_entry::dict_t) return false;
lazy_entry const* messages = h.dict_find("m");
if (!messages || messages->type() != lazy_entry::dict_t) return false;
int index = messages->dict_find_int_value("ut_metadata", -1);
if (index == -1) return false;
m_message_index = index;
int metadata_size = h.dict_find_int_value("metadata_size");
if (metadata_size > 0)
m_tp.metadata_size(metadata_size);
2010-12-17 04:12:45 +01:00
maybe_send_request();
return true;
2007-12-02 05:16:51 +01:00
}
void write_metadata_packet(int type, int piece)
{
TORRENT_ASSERT(type >= 0 && type <= 2);
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
2007-12-02 05:16:51 +01:00
// abort if the peer doesn't support the metadata extension
if (m_message_index == 0) return;
entry e;
e["msg_type"] = type;
e["piece"] = piece;
2007-12-02 05:16:51 +01:00
char const* metadata = 0;
int metadata_piece_size = 0;
if (type == 1)
{
if (piece < 0 || piece >= int(m_tp.metadata().left() + 16 * 1024 - 1)/(16*1024))
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ invalid piece "
<< piece << " metadata size: " << m_tp.metadata().left() << " ]\n";
#endif
m_pc.disconnect(errors::invalid_metadata_message, 2);
return;
}
2007-12-02 05:16:51 +01:00
TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata());
e["total_size"] = m_tp.metadata().left();
2007-12-02 05:16:51 +01:00
int offset = piece * 16 * 1024;
metadata = m_tp.metadata().begin + offset;
metadata_piece_size = (std::min)(
int(m_tp.metadata().left() - offset), 16 * 1024);
2007-12-02 05:16:51 +01:00
TORRENT_ASSERT(metadata_piece_size > 0);
TORRENT_ASSERT(offset >= 0);
TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.metadata().left()));
2007-12-02 05:16:51 +01:00
}
char msg[200];
char* header = msg;
char* p = &msg[6];
int len = bencode(p, e);
int total_size = 2 + len + metadata_piece_size;
2007-12-02 05:16:51 +01:00
namespace io = detail;
io::write_uint32(total_size, header);
io::write_uint8(bt_peer_connection::msg_extended, header);
io::write_uint8(m_message_index, header);
m_pc.send_buffer(msg, len + 6);
if (metadata_piece_size) m_pc.append_const_send_buffer(
metadata, metadata_piece_size);
2007-12-02 05:16:51 +01:00
}
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)
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ packet too big "
<< length << " ]\n";
#endif
m_pc.disconnect(errors::invalid_metadata_message, 2);
return true;
}
2007-12-02 05:16:51 +01:00
if (!m_pc.packet_finished()) return true;
int len;
entry msg = bdecode(body.begin, body.end, len);
2008-06-24 00:00:27 +02:00
if (msg.type() == entry::undefined_t)
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ not a dictionary ]\n";
#endif
m_pc.disconnect(errors::invalid_metadata_message, 2);
2008-06-24 00:00:27 +02:00
return true;
}
2007-12-02 05:16:51 +01:00
entry const* type_ent = msg.find_key("msg_type");
entry const* piece_ent = msg.find_key("piece");
if (type_ent == 0 || type_ent->type() != entry::int_t
|| piece_ent == 0 || piece_ent->type() != entry::int_t)
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ missing or invalid keys ]\n";
#endif
m_pc.disconnect(errors::invalid_metadata_message, 2);
return true;
}
int type = type_ent->integer();
2011-01-28 04:49:20 +01:00
int piece = piece_ent->integer();
2007-12-02 05:16:51 +01:00
#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<int>::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);
entry const* total_size = msg.find_key("total_size");
m_tp.received_metadata(shared_from_this(), body.begin + len, body.left() - len, piece
2007-12-02 05:16:51 +01:00
, (total_size && total_size->type() == entry::int_t) ? total_size->integer() : 0);
2010-12-17 04:12:45 +01:00
maybe_send_request();
2007-12-02 05:16:51 +01:00
}
break;
case 2: // have no data
{
m_request_limit = (std::min)(time_now() + minutes(1), m_request_limit);
2007-12-02 05:16:51 +01:00
std::vector<int>::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:
2009-11-24 20:40:54 +01:00
// unknown message, ignore
2009-11-25 03:18:17 +01:00
break;
2007-12-02 05:16:51 +01:00
}
return true;
}
virtual void tick()
2010-12-17 04:12:45 +01:00
{
maybe_send_request();
}
void maybe_send_request()
2007-12-02 05:16:51 +01:00
{
// 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();
if (piece == -1) return;
2007-12-02 05:16:51 +01:00
m_sent_requests.push_back(piece);
write_metadata_packet(0, piece);
}
}
bool has_metadata() const
{
return time_now() > m_request_limit;
}
void failed_hash_check(ptime const& now)
{
m_request_limit = now + seconds(20 + (boost::int64_t(random()) * 50) / UINT_MAX);
2007-12-02 05:16:51 +01:00
}
private:
// this is the message index the remote peer uses
// for metadata extension messages.
int m_message_index;
// this is set to the next time we can request pieces
// again. It is updated every time we get a
// "I don't have metadata" message, but also when
// we receive metadata that fails the infohash check
ptime m_request_limit;
2007-12-02 05:16:51 +01:00
// request queues
std::vector<int> m_sent_requests;
std::vector<int> m_incoming_requests;
torrent& m_torrent;
bt_peer_connection& m_pc;
ut_metadata_plugin& m_tp;
};
boost::shared_ptr<peer_plugin> ut_metadata_plugin::new_connection(
peer_connection* pc)
{
2009-11-02 21:43:38 +01:00
if (pc->type() != peer_connection::bittorrent_connection)
return boost::shared_ptr<peer_plugin>();
bt_peer_connection* c = static_cast<bt_peer_connection*>(pc);
2007-12-02 05:16:51 +01:00
return boost::shared_ptr<peer_plugin>(new ut_metadata_peer_plugin(m_torrent, *c, *this));
}
int ut_metadata_plugin::metadata_request()
{
std::vector<metadata_piece>::iterator i = std::min_element(
2007-12-02 05:16:51 +01:00
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);
i = m_requested_metadata.begin();
2007-12-02 05:16:51 +01:00
}
int piece = i - m_requested_metadata.begin();
// don't request the same block more than once every 3 seconds
time_t now = time(0);
if (now - m_requested_metadata[piece].last_request < 3) return -1;
++m_requested_metadata[piece].num_requests;
m_requested_metadata[piece].last_request = now;
2007-12-02 05:16:51 +01:00
return piece;
}
inline bool ut_metadata_plugin::received_metadata(
boost::weak_ptr<ut_metadata_peer_plugin> const& source
, char const* buf, int size, int piece, int total_size)
{
if (m_torrent.valid_metadata())
{
m_torrent.add_redundant_bytes(size);
return false;
}
if (!m_metadata)
{
// verify the total_size
if (total_size <= 0 || total_size > m_torrent.session().settings().max_metadata_size)
return false;
m_metadata.reset(new char[total_size]);
m_requested_metadata.resize(div_round_up(total_size, 16 * 1024));
m_metadata_size = total_size;
}
if (piece < 0 || piece >= int(m_requested_metadata.size()))
return false;
if (total_size != m_metadata_size)
{
// they disagree about the size!
return false;
}
if (piece * 16 * 1024 + size > m_metadata_size)
{
// this piece is invalid
return false;
}
std::memcpy(&m_metadata[piece * 16 * 1024], buf, size);
// mark this piece has 'have'
m_requested_metadata[piece].num_requests = (std::numeric_limits<int>::max)();
m_requested_metadata[piece].source = source;
bool have_all = std::count_if(m_requested_metadata.begin()
, m_requested_metadata.end(), boost::bind(&metadata_piece::num_requests, _1)
== (std::numeric_limits<int>::max)()) == int(m_requested_metadata.size());
if (!have_all) return false;
if (!m_torrent.set_metadata(&m_metadata[0], m_metadata_size))
{
if (!m_torrent.valid_metadata())
{
ptime now = time_now();
// any peer that we downloaded metadata from gets a random time
// penalty, from 5 to 30 seconds or so. During this time we don't
// make any metadata requests from those peers (to mix it up a bit
// of which peers we use)
// if we only have one block, and thus requested it from a single
// peer, we bump up the retry time a lot more to try other peers
bool single_peer = m_requested_metadata.size() == 1;
for (int i = 0; i < int(m_requested_metadata.size()); ++i)
{
m_requested_metadata[i].num_requests = 0;
boost::shared_ptr<ut_metadata_peer_plugin> peer
= m_requested_metadata[i].source.lock();
if (!peer) continue;
peer->failed_hash_check(single_peer ? now + minutes(5) : now);
}
}
return false;
}
// clear the storage for the bitfield
std::vector<metadata_piece>().swap(m_requested_metadata);
return true;
}
2007-12-02 05:16:51 +01:00
} }
namespace libtorrent
{
boost::shared_ptr<torrent_plugin> create_ut_metadata_plugin(torrent* t, void*)
{
// don't add this extension if the torrent is private
if (t->valid_metadata() && t->torrent_file().priv()) return boost::shared_ptr<torrent_plugin>();
2007-12-02 05:16:51 +01:00
return boost::shared_ptr<torrent_plugin>(new ut_metadata_plugin(*t));
}
}
2011-01-29 13:13:49 +01:00
#endif
2007-12-02 05:16:51 +01:00