premiere-libtorrent/src/bt_peer_connection.cpp

1551 lines
40 KiB
C++
Raw Normal View History

/*
Copyright (c) 2003, 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 <vector>
#include <iostream>
#include <iomanip>
#include <limits>
#include <boost/bind.hpp>
#include "libtorrent/bt_peer_connection.hpp"
#include "libtorrent/session.hpp"
#include "libtorrent/identify_client.hpp"
#include "libtorrent/entry.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/alert_types.hpp"
#include "libtorrent/invariant_check.hpp"
#include "libtorrent/io.hpp"
#include "libtorrent/version.hpp"
using namespace boost::posix_time;
using boost::bind;
using boost::shared_ptr;
using libtorrent::detail::session_impl;
namespace libtorrent
{
// the names of the extensions to look for in
// the extensions-message
const char* bt_peer_connection::extension_names[] =
{ "", "LT_chat", "LT_metadata", "LT_peer_exchange" };
const bt_peer_connection::message_handler
bt_peer_connection::m_message_handler[] =
{
&bt_peer_connection::on_choke,
&bt_peer_connection::on_unchoke,
&bt_peer_connection::on_interested,
&bt_peer_connection::on_not_interested,
&bt_peer_connection::on_have,
&bt_peer_connection::on_bitfield,
&bt_peer_connection::on_request,
&bt_peer_connection::on_piece,
&bt_peer_connection::on_cancel,
&bt_peer_connection::on_dht_port,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
&bt_peer_connection::on_extended
};
bt_peer_connection::bt_peer_connection(
detail::session_impl& ses
, boost::weak_ptr<torrent> tor
, shared_ptr<stream_socket> s
, tcp::endpoint const& remote)
: peer_connection(ses, tor, s, remote)
, m_state(read_protocol_length)
, m_supports_extensions(false)
, m_supports_dht_port(false)
, m_no_metadata(
boost::gregorian::date(1970, boost::date_time::Jan, 1)
, boost::posix_time::seconds(0))
, m_metadata_request(
boost::gregorian::date(1970, boost::date_time::Jan, 1)
, boost::posix_time::seconds(0))
, m_waiting_metadata_request(false)
, m_metadata_progress(0)
#ifndef NDEBUG
, m_in_constructor(true)
#endif
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "*** bt_peer_connection\n";
#endif
// initialize the extension list to zero, since
// we don't know which extensions the other
// end supports yet
std::fill(m_extension_messages, m_extension_messages + num_supported_extensions, 0);
write_handshake();
// start in the state where we are trying to read the
// handshake from the other side
reset_recv_buffer(1);
// assume the other end has no pieces
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
if (t->ready_for_connections())
write_bitfield(t->pieces());
setup_send();
setup_receive();
#ifndef NDEBUG
m_in_constructor = false;
#endif
}
bt_peer_connection::bt_peer_connection(
detail::session_impl& ses
, boost::shared_ptr<stream_socket> s)
: peer_connection(ses, s)
, m_state(read_protocol_length)
, m_supports_extensions(false)
, m_supports_dht_port(false)
, m_no_metadata(
boost::gregorian::date(1970, boost::date_time::Jan, 1)
, boost::posix_time::seconds(0))
, m_metadata_request(
boost::gregorian::date(1970, boost::date_time::Jan, 1)
, boost::posix_time::seconds(0))
, m_waiting_metadata_request(false)
, m_metadata_progress(0)
#ifndef NDEBUG
, m_in_constructor(true)
#endif
{
// initialize the extension list to zero, since
// we don't know which extensions the other
// end supports yet
std::fill(m_extension_messages, m_extension_messages + num_supported_extensions, 0);
// we are not attached to any torrent yet.
// we have to wait for the handshake to see
// which torrent the connector want's to connect to
// start in the state where we are trying to read the
// handshake from the other side
reset_recv_buffer(1);
setup_receive();
#ifndef NDEBUG
m_in_constructor = false;
#endif
}
bt_peer_connection::~bt_peer_connection()
{
}
void bt_peer_connection::write_dht_port(int listen_port)
{
INVARIANT_CHECK;
buffer::interval packet = allocate_send_buffer(7);
detail::write_uint32(3, packet.begin);
detail::write_uint8(msg_dht_port, packet.begin);
detail::write_uint16(listen_port, packet.begin);
assert(packet.begin == packet.end);
setup_send();
}
void bt_peer_connection::get_peer_info(peer_info& p) const
{
assert(!associated_torrent().expired());
p.down_speed = statistics().download_rate();
p.up_speed = statistics().upload_rate();
p.payload_down_speed = statistics().download_payload_rate();
p.payload_up_speed = statistics().upload_payload_rate();
p.pid = pid();
p.ip = remote();
p.total_download = statistics().total_payload_download();
p.total_upload = statistics().total_payload_upload();
if (m_ul_bandwidth_quota.given == std::numeric_limits<int>::max())
p.upload_limit = -1;
else
p.upload_limit = m_ul_bandwidth_quota.given;
if (m_dl_bandwidth_quota.given == std::numeric_limits<int>::max())
p.download_limit = -1;
else
p.download_limit = m_dl_bandwidth_quota.given;
p.load_balancing = total_free_upload();
p.download_queue_length = (int)download_queue().size();
p.upload_queue_length = (int)upload_queue().size();
if (boost::optional<piece_block_progress> ret = downloading_piece_progress())
{
p.downloading_piece_index = ret->piece_index;
p.downloading_block_index = ret->block_index;
p.downloading_progress = ret->bytes_downloaded;
p.downloading_total = ret->full_block_bytes;
}
else
{
p.downloading_piece_index = -1;
p.downloading_block_index = -1;
p.downloading_progress = 0;
p.downloading_total = 0;
}
p.flags = 0;
if (is_interesting()) p.flags |= peer_info::interesting;
if (is_choked()) p.flags |= peer_info::choked;
if (is_peer_interested()) p.flags |= peer_info::remote_interested;
if (has_peer_choked()) p.flags |= peer_info::remote_choked;
if (support_extensions()) p.flags |= peer_info::supports_extensions;
if (is_local()) p.flags |= peer_info::local_connection;
if (!is_connecting() && m_state < read_packet_size)
p.flags |= peer_info::handshake;
if (is_connecting() && !is_queued()) p.flags |= peer_info::connecting;
if (is_queued()) p.flags |= peer_info::queued;
p.pieces = get_bitfield();
p.seed = is_seed();
p.client = m_client_version;
p.connection_type = peer_info::standard_bittorrent;
}
void bt_peer_connection::write_handshake()
{
INVARIANT_CHECK;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
// add handshake to the send buffer
const char version_string[] = "BitTorrent protocol";
const int string_len = sizeof(version_string)-1;
buffer::interval i = allocate_send_buffer(1 + string_len + 8 + 20 + 20);
// length of version string
*i.begin = string_len;
++i.begin;
// version string itself
std::copy(
version_string
, version_string + string_len
, i.begin);
i.begin += string_len;
// 8 zeroes
std::fill(
i.begin
, i.begin + 8
, 0);
// indicate that we support the DHT messages
*(i.begin + 7) = 0x01;
// we support extensions
*(i.begin + 5) = 0x10;
i.begin += 8;
// info hash
sha1_hash const& ih = t->torrent_file().info_hash();
std::copy(ih.begin(), ih.end(), i.begin);
i.begin += 20;
// peer id
std::copy(
m_ses.get_peer_id().begin()
, m_ses.get_peer_id().end()
, i.begin);
i.begin += 20;
assert(i.begin == i.end);
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " ==> HANDSHAKE\n";
#endif
setup_send();
}
boost::optional<piece_block_progress> bt_peer_connection::downloading_piece_progress() const
{
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
buffer::const_interval recv_buffer = receive_buffer();
// are we currently receiving a 'piece' message?
if (m_state != read_packet
|| (recv_buffer.end - recv_buffer.begin) < 9
|| recv_buffer[0] != msg_piece)
return boost::optional<piece_block_progress>();
const char* ptr = recv_buffer.begin + 1;
peer_request r;
r.piece = detail::read_int32(ptr);
r.start = detail::read_int32(ptr);
r.length = packet_size() - 9;
// is any of the piece message header data invalid?
if (!verify_piece(r))
return boost::optional<piece_block_progress>();
piece_block_progress p;
p.piece_index = r.piece;
p.block_index = r.start / t->block_size();
p.bytes_downloaded = recv_buffer.end - recv_buffer.begin - 9;
p.full_block_bytes = r.length;
return boost::optional<piece_block_progress>(p);
}
// message handlers
// -----------------------------
// --------- KEEPALIVE ---------
// -----------------------------
void bt_peer_connection::on_keepalive()
{
INVARIANT_CHECK;
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " <== KEEPALIVE\n";
#endif
incoming_keepalive();
}
// -----------------------------
// ----------- CHOKE -----------
// -----------------------------
void bt_peer_connection::on_choke(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 1)
throw protocol_error("'choke' message size != 1");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
incoming_choke();
}
// -----------------------------
// ---------- UNCHOKE ----------
// -----------------------------
void bt_peer_connection::on_unchoke(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 1)
throw protocol_error("'unchoke' message size != 1");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
incoming_unchoke();
}
// -----------------------------
// -------- INTERESTED ---------
// -----------------------------
void bt_peer_connection::on_interested(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 1)
throw protocol_error("'interested' message size != 1");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
incoming_interested();
}
// -----------------------------
// ------ NOT INTERESTED -------
// -----------------------------
void bt_peer_connection::on_not_interested(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 1)
throw protocol_error("'not interested' message size != 1");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
incoming_not_interested();
}
// -----------------------------
// ----------- HAVE ------------
// -----------------------------
void bt_peer_connection::on_have(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 5)
throw protocol_error("'have' message size != 5");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
const char* ptr = recv_buffer.begin + 1;
int index = detail::read_int32(ptr);
incoming_have(index);
}
// -----------------------------
// --------- BITFIELD ----------
// -----------------------------
void bt_peer_connection::on_bitfield(int received)
{
INVARIANT_CHECK;
assert(received > 0);
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
// if we don't have the metedata, we cannot
// verify the bitfield size
if (t->valid_metadata()
&& packet_size() - 1 != ((int)get_bitfield().size() + 7) / 8)
throw protocol_error("bitfield with invalid size");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
std::vector<bool> bitfield;
if (!t->valid_metadata())
bitfield.resize((packet_size() - 1) * 8);
else
bitfield.resize(get_bitfield().size());
// if we don't have metadata yet
// just remember the bitmask
// don't update the piecepicker
// (since it doesn't exist yet)
for (int i = 0; i < (int)bitfield.size(); ++i)
bitfield[i] = (recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7)))) != 0;
incoming_bitfield(bitfield);
}
// -----------------------------
// ---------- REQUEST ----------
// -----------------------------
void bt_peer_connection::on_request(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 13)
throw protocol_error("'request' message size != 13");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
peer_request r;
const char* ptr = recv_buffer.begin + 1;
r.piece = detail::read_int32(ptr);
r.start = detail::read_int32(ptr);
r.length = detail::read_int32(ptr);
incoming_request(r);
}
// -----------------------------
// ----------- PIECE -----------
// -----------------------------
void bt_peer_connection::on_piece(int received)
{
INVARIANT_CHECK;
assert(received > 0);
buffer::const_interval recv_buffer = receive_buffer();
int recv_pos = recv_buffer.end - recv_buffer.begin;
// classify the received data as protocol chatter
// or data payload for the statistics
if (recv_pos <= 9)
// only received protocol data
m_statistics.received_bytes(0, received);
else if (recv_pos - received >= 9)
// only received payload data
m_statistics.received_bytes(received, 0);
else
{
// received a bit of both
assert(recv_pos - received < 9);
assert(recv_pos > 9);
assert(9 - (recv_pos - received) <= 9);
m_statistics.received_bytes(
recv_pos - 9
, 9 - (recv_pos - received));
}
incoming_piece_fragment();
if (!packet_finished()) return;
const char* ptr = recv_buffer.begin + 1;
peer_request p;
p.piece = detail::read_int32(ptr);
p.start = detail::read_int32(ptr);
p.length = packet_size() - 9;
incoming_piece(p, recv_buffer.begin + 9);
}
// -----------------------------
// ---------- CANCEL -----------
// -----------------------------
void bt_peer_connection::on_cancel(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 13)
throw protocol_error("'cancel' message size != 13");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
peer_request r;
const char* ptr = recv_buffer.begin + 1;
r.piece = detail::read_int32(ptr);
r.start = detail::read_int32(ptr);
r.length = detail::read_int32(ptr);
incoming_cancel(r);
}
// -----------------------------
// --------- DHT PORT ----------
// -----------------------------
void bt_peer_connection::on_dht_port(int received)
{
INVARIANT_CHECK;
assert(received > 0);
if (packet_size() != 3)
throw protocol_error("'dht_port' message size != 3");
m_statistics.received_bytes(0, received);
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
const char* ptr = recv_buffer.begin + 1;
int listen_port = detail::read_uint16(ptr);
incoming_dht_port(listen_port);
}
// -----------------------------
// --------- EXTENDED ----------
// -----------------------------
void bt_peer_connection::on_extended(int received)
{
INVARIANT_CHECK;
assert(received > 0);
m_statistics.received_bytes(0, received);
if (packet_size() < 2)
throw protocol_error("'extended' message smaller than 2 bytes");
if (associated_torrent().expired())
throw protocol_error("'extended' message sent before proper handshake");
buffer::const_interval recv_buffer = receive_buffer();
if (recv_buffer.end - recv_buffer.begin < 2) return;
assert(*recv_buffer.begin == msg_extended);
++recv_buffer.begin;
int extended_id = detail::read_uint8(recv_buffer.begin);
if (extended_id > 0 && extended_id < num_supported_extensions
&& !m_ses.m_extension_enabled[extended_id])
throw protocol_error("'extended' message using disabled extension");
switch (extended_id)
{
case extended_handshake:
on_extended_handshake(); break;
case extended_chat_message:
on_chat(); break;
case extended_metadata_message:
on_metadata(); break;
case extended_peer_exchange_message:
on_peer_exchange(); break;
default:
throw protocol_error("unknown extended message id: "
+ boost::lexical_cast<std::string>(extended_id));
};
}
void bt_peer_connection::write_chat_message(const std::string& msg)
{
INVARIANT_CHECK;
assert(msg.length() <= 1 * 1024);
if (!supports_extension(extended_chat_message)) return;
entry e(entry::dictionary_t);
e["msg"] = msg;
std::vector<char> message;
bencode(std::back_inserter(message), e);
buffer::interval i = allocate_send_buffer(message.size() + 6);
detail::write_uint32(1 + 1 + (int)message.size(), i.begin);
detail::write_uint8(msg_extended, i.begin);
detail::write_uint8(m_extension_messages[extended_chat_message], i.begin);
std::copy(message.begin(), message.end(), i.begin);
i.begin += message.size();
assert(i.begin == i.end);
setup_send();
}
void bt_peer_connection::on_extended_handshake() try
{
if (!packet_finished()) return;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
buffer::const_interval recv_buffer = receive_buffer();
entry root = bdecode(recv_buffer.begin + 2, recv_buffer.end);
#ifdef TORRENT_VERBOSE_LOGGING
std::stringstream ext;
root.print(ext);
(*m_logger) << "<== EXTENDED HANDSHAKE: \n" << ext.str();
#endif
if (entry* msgs = root.find_key("m"))
{
if (msgs->type() == entry::dictionary_t)
{
// this must be the initial handshake message
// lets see if any of our extensions are supported
// if not, we will signal no extensions support to the upper layer
for (int i = 1; i < num_supported_extensions; ++i)
{
if (entry* f = msgs->find_key(extension_names[i]))
{
m_extension_messages[i] = (int)f->integer();
}
else
{
m_extension_messages[i] = 0;
}
}
}
}
// there is supposed to be a remote listen port
if (entry* listen_port = root.find_key("p"))
{
if (listen_port->type() == entry::int_t)
{
tcp::endpoint adr((unsigned short)listen_port->integer()
, remote().address());
t->get_policy().peer_from_tracker(adr, pid());
}
}
// there should be a version too
// but where do we put that info?
if (entry* client_info = root.find_key("v"))
{
if (client_info->type() == entry::string_t)
m_client_version = client_info->string();
}
}
catch (std::exception& exc)
{
#ifdef TORRENT_VERBOSE_LOGGIGN
(*m_logger) << "invalid extended handshake: " << exc.what() << "\n";
#endif
}
// -----------------------------
// ----------- CHAT ------------
// -----------------------------
void bt_peer_connection::on_chat()
{
if (packet_size() > 2 * 1024)
throw protocol_error("CHAT message larger than 2 kB");
if (!packet_finished()) return;
try
{
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
buffer::const_interval recv_buffer = receive_buffer();
entry d = bdecode(recv_buffer.begin + 2, recv_buffer.end);
const std::string& str = d["msg"].string();
if (t->alerts().should_post(alert::critical))
{
t->alerts().post_alert(
chat_message_alert(
t->get_handle()
, remote(), str));
}
}
catch (invalid_encoding&)
{
// TODO: post an alert about the invalid chat message
return;
// throw protocol_error("invalid bencoding in CHAT message");
}
catch (type_error&)
{
// TODO: post an alert about the invalid chat message
return;
// throw protocol_error("invalid types in bencoded CHAT message");
}
return;
}
// -----------------------------
// --------- METADATA ----------
// -----------------------------
void bt_peer_connection::on_metadata()
{
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
if (packet_size() > 500 * 1024)
throw protocol_error("metadata message larger than 500 kB");
if (!packet_finished()) return;
buffer::const_interval recv_buffer = receive_buffer();
recv_buffer.begin += 2;
int type = detail::read_uint8(recv_buffer.begin);
switch (type)
{
case 0: // request
{
int start = detail::read_uint8(recv_buffer.begin);
int size = detail::read_uint8(recv_buffer.begin) + 1;
if (packet_size() != 5)
{
// invalid metadata request
throw protocol_error("invalid metadata request");
}
write_metadata(std::make_pair(start, size));
}
break;
case 1: // data
{
if (recv_buffer.end - recv_buffer.begin < 8) return;
int total_size = detail::read_int32(recv_buffer.begin);
int offset = detail::read_int32(recv_buffer.begin);
int data_size = packet_size() - 2 - 9;
if (total_size > 500 * 1024)
throw protocol_error("metadata size larger than 500 kB");
if (total_size <= 0)
throw protocol_error("invalid metadata size");
if (offset > total_size || offset < 0)
throw protocol_error("invalid metadata offset");
if (offset + data_size > total_size)
throw protocol_error("invalid metadata message");
t->metadata_progress(total_size
, recv_buffer.left() - m_metadata_progress);
m_metadata_progress = recv_buffer.left();
if (!packet_finished()) return;
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " <== METADATA [ tot: " << total_size << " offset: "
<< offset << " size: " << data_size << " ]\n";
#endif
m_waiting_metadata_request = false;
t->received_metadata(recv_buffer.begin, data_size
, offset, total_size);
m_metadata_progress = 0;
}
break;
case 2: // have no data
if (!packet_finished()) return;
m_no_metadata = second_clock::universal_time();
if (m_waiting_metadata_request)
t->cancel_metadata_request(m_last_metadata_request);
m_waiting_metadata_request = false;
break;
default:
throw protocol_error("unknown metadata extension message: "
+ boost::lexical_cast<std::string>(type));
}
}
// -----------------------------
// ------ PEER EXCHANGE --------
// -----------------------------
void bt_peer_connection::on_peer_exchange()
{
}
bool bt_peer_connection::has_metadata() const
{
using namespace boost::posix_time;
return second_clock::universal_time() - m_no_metadata > minutes(5);
}
bool bt_peer_connection::dispatch_message(int received)
{
INVARIANT_CHECK;
assert(received > 0);
// this means the connection has been closed already
if (associated_torrent().expired()) return false;
buffer::const_interval recv_buffer = receive_buffer();
int packet_type = recv_buffer[0];
if (packet_type < 0
|| packet_type >= num_supported_messages
|| m_message_handler[packet_type] == 0)
{
throw protocol_error("unknown message id: "
+ boost::lexical_cast<std::string>(packet_type)
+ " size: " + boost::lexical_cast<std::string>(packet_size()));
}
assert(m_message_handler[packet_type] != 0);
// call the correct handler for this packet type
(this->*m_message_handler[packet_type])(received);
if (!packet_finished()) return false;
return true;
}
void bt_peer_connection::write_keepalive()
{
INVARIANT_CHECK;
char buf[] = {0,0,0,0};
send_buffer(buf, buf + sizeof(buf));
}
void bt_peer_connection::write_cancel(peer_request const& r)
{
INVARIANT_CHECK;
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,13, msg_cancel};
buffer::interval i = allocate_send_buffer(17);
std::copy(buf, buf + 5, i.begin);
i.begin += 5;
// index
detail::write_int32(r.piece, i.begin);
// begin
detail::write_int32(r.start, i.begin);
// length
detail::write_int32(r.length, i.begin);
assert(i.begin == i.end);
setup_send();
}
void bt_peer_connection::write_request(peer_request const& r)
{
INVARIANT_CHECK;
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,13, msg_request};
buffer::interval i = allocate_send_buffer(17);
std::copy(buf, buf + 5, i.begin);
i.begin += 5;
// index
detail::write_int32(r.piece, i.begin);
// begin
detail::write_int32(r.start, i.begin);
// length
detail::write_int32(r.length, i.begin);
assert(i.begin == i.end);
setup_send();
}
void bt_peer_connection::write_metadata(std::pair<int, int> req)
{
assert(req.first >= 0);
assert(req.second > 0);
assert(req.second <= 256);
assert(req.first + req.second <= 256);
assert(!associated_torrent().expired());
INVARIANT_CHECK;
// abort if the peer doesn't support the metadata extension
if (!supports_extension(extended_metadata_message)) return;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
if (t->valid_metadata())
{
std::pair<int, int> offset
= req_to_offset(req, (int)t->metadata().size());
buffer::interval i = allocate_send_buffer(15 + offset.second);
// yes, we have metadata, send it
detail::write_uint32(11 + offset.second, i.begin);
detail::write_uint8(msg_extended, i.begin);
detail::write_uint8(m_extension_messages[extended_metadata_message]
, i.begin);
// means 'data packet'
detail::write_uint8(1, i.begin);
detail::write_uint32((int)t->metadata().size(), i.begin);
detail::write_uint32(offset.first, i.begin);
std::vector<char> const& metadata = t->metadata();
std::copy(metadata.begin() + offset.first
, metadata.begin() + offset.first + offset.second, i.begin);
i.begin += offset.second;
assert(i.begin == i.end);
}
else
{
buffer::interval i = allocate_send_buffer(4 + 3);
// we don't have the metadata, reply with
// don't have-message
detail::write_uint32(1 + 2, i.begin);
detail::write_uint8(msg_extended, i.begin);
detail::write_uint8(m_extension_messages[extended_metadata_message]
, i.begin);
// means 'have no data'
detail::write_uint8(2, i.begin);
assert(i.begin == i.end);
}
setup_send();
}
void bt_peer_connection::write_metadata_request(std::pair<int, int> req)
{
assert(req.first >= 0);
assert(req.second > 0);
assert(req.first + req.second <= 256);
assert(!associated_torrent().expired());
assert(!associated_torrent().lock()->valid_metadata());
INVARIANT_CHECK;
int start = req.first;
int size = req.second;
// abort if the peer doesn't support the metadata extension
if (!supports_extension(extended_metadata_message)) return;
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " ==> METADATA_REQUEST [ start: " << req.first
<< " size: " << req.second << " ]\n";
#endif
buffer::interval i = allocate_send_buffer(9);
detail::write_uint32(1 + 1 + 3, i.begin);
detail::write_uint8(msg_extended, i.begin);
detail::write_uint8(m_extension_messages[extended_metadata_message]
, i.begin);
// means 'request data'
detail::write_uint8(0, i.begin);
detail::write_uint8(start, i.begin);
detail::write_uint8(size - 1, i.begin);
assert(i.begin == i.end);
setup_send();
}
void bt_peer_connection::write_bitfield(std::vector<bool> const& bitfield)
{
INVARIANT_CHECK;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
if (t->num_pieces() == 0) return;
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " ==> BITFIELD ";
for (int i = 0; i < (int)get_bitfield().size(); ++i)
{
if (bitfield[i]) (*m_logger) << "1";
else (*m_logger) << "0";
}
(*m_logger) << "\n";
#endif
const int packet_size = ((int)bitfield.size() + 7) / 8 + 5;
buffer::interval i = allocate_send_buffer(packet_size);
detail::write_int32(packet_size - 4, i.begin);
detail::write_uint8(msg_bitfield, i.begin);
std::fill(i.begin, i.end, 0);
for (int c = 0; c < (int)bitfield.size(); ++c)
{
if (bitfield[c])
i.begin[c >> 3] |= 1 << (7 - (c & 7));
}
assert(i.end - i.begin == ((int)bitfield.size() + 7) / 8);
setup_send();
}
void bt_peer_connection::write_extensions()
{
INVARIANT_CHECK;
#ifdef TORRENT_VERBOSE_LOGGING
using namespace boost::posix_time;
(*m_logger) << to_simple_string(second_clock::universal_time())
<< " ==> EXTENSIONS\n";
#endif
assert(m_supports_extensions);
entry handshake(entry::dictionary_t);
entry extension_list(entry::dictionary_t);
for (int i = 1; i < num_supported_extensions; ++i)
{
// if this specific extension is disabled
// just don't add it to the supported set
if (!m_ses.m_extension_enabled[i]) continue;
extension_list[extension_names[i]] = i;
}
handshake["m"] = extension_list;
handshake["p"] = m_ses.m_listen_interface.port();
handshake["v"] = m_ses.m_http_settings.user_agent;
std::vector<char> msg;
bencode(std::back_inserter(msg), handshake);
// make room for message
buffer::interval i = allocate_send_buffer(6 + msg.size());
// write the length of the message
detail::write_int32((int)msg.size() + 2, i.begin);
detail::write_uint8(msg_extended, i.begin);
// signal handshake message
detail::write_uint8(extended_handshake, i.begin);
std::copy(msg.begin(), msg.end(), i.begin);
i.begin += msg.size();
assert(i.begin == i.end);
#ifdef TORRENT_VERBOSE_LOGGING
std::stringstream ext;
handshake.print(ext);
(*m_logger) << "==> EXTENDED HANDSHAKE: \n" << ext.str();
#endif
setup_send();
}
void bt_peer_connection::write_choke()
{
INVARIANT_CHECK;
if (is_choked()) return;
char msg[] = {0,0,0,1,msg_choke};
send_buffer(msg, msg + sizeof(msg));
}
void bt_peer_connection::write_unchoke()
{
INVARIANT_CHECK;
char msg[] = {0,0,0,1,msg_unchoke};
send_buffer(msg, msg + sizeof(msg));
}
void bt_peer_connection::write_interested()
{
INVARIANT_CHECK;
char msg[] = {0,0,0,1,msg_interested};
send_buffer(msg, msg + sizeof(msg));
}
void bt_peer_connection::write_not_interested()
{
INVARIANT_CHECK;
char msg[] = {0,0,0,1,msg_not_interested};
send_buffer(msg, msg + sizeof(msg));
}
void bt_peer_connection::write_have(int index)
{
assert(associated_torrent().lock()->valid_metadata());
assert(index >= 0);
assert(index < associated_torrent().lock()->torrent_file().num_pieces());
INVARIANT_CHECK;
const int packet_size = 9;
char msg[packet_size] = {0,0,0,5,msg_have};
char* ptr = msg + 5;
detail::write_int32(index, ptr);
send_buffer(msg, msg + packet_size);
}
void bt_peer_connection::write_piece(peer_request const& r)
{
INVARIANT_CHECK;
const int packet_size = 4 + 5 + 4 + r.length;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
buffer::interval i = allocate_send_buffer(packet_size);
detail::write_int32(packet_size-4, i.begin);
detail::write_uint8(msg_piece, i.begin);
detail::write_int32(r.piece, i.begin);
detail::write_int32(r.start, i.begin);
t->filesystem().read(
i.begin, r.piece, r.start, r.length);
assert(i.begin + r.length == i.end);
m_payloads.push_back(range(send_buffer_size() - r.length, r.length));
setup_send();
}
// --------------------------
// RECEIVE DATA
// --------------------------
// throws exception when the client should be disconnected
void bt_peer_connection::on_receive(const asio::error& error
, std::size_t bytes_transferred)
{
INVARIANT_CHECK;
if (error) return;
buffer::const_interval recv_buffer = receive_buffer();
boost::shared_ptr<torrent> t = associated_torrent().lock();
switch(m_state)
{
case read_protocol_length:
{
m_statistics.received_bytes(0, bytes_transferred);
if (!packet_finished()) break;
int packet_size = recv_buffer[0];
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << " protocol length: " << packet_size << "\n";
#endif
if (packet_size > 100 || packet_size <= 0)
{
std::stringstream s;
s << "incorrect protocol length ("
<< packet_size
<< ") should be 19.";
throw std::runtime_error(s.str());
}
m_state = read_protocol_string;
reset_recv_buffer(packet_size);
}
break;
case read_protocol_string:
{
m_statistics.received_bytes(0, bytes_transferred);
if (!packet_finished()) break;
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << " protocol: '" << std::string(recv_buffer.begin
, recv_buffer.end) << "'\n";
#endif
const char protocol_string[] = "BitTorrent protocol";
if (!std::equal(recv_buffer.begin, recv_buffer.end
, protocol_string))
{
const char cmd[] = "version";
if (recv_buffer.end - recv_buffer.begin == 7 && std::equal(
recv_buffer.begin, recv_buffer.end, cmd))
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "sending libtorrent version\n";
#endif
asio::write(*get_socket(), asio::buffer("libtorrent version " LIBTORRENT_VERSION "\n", 27));
throw std::runtime_error("closing");
}
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "incorrect protocol name\n";
#endif
std::stringstream s;
s << "got invalid protocol name: '"
<< std::string(recv_buffer.begin, recv_buffer.end)
<< "'";
throw std::runtime_error(s.str());
}
m_state = read_info_hash;
reset_recv_buffer(28);
}
break;
case read_info_hash:
{
m_statistics.received_bytes(0, bytes_transferred);
if (!packet_finished()) break;
// the use of this bit collides with Mainline
// the new way of identifying support for the extensions
// is in the peer_id
// MassaRoddel
#ifdef TORRENT_VERBOSE_LOGGING
for (int i=0; i < 8; ++i)
{
for (int j=0; j < 8; ++j)
{
if (recv_buffer[i] & (0x80 >> j)) (*m_logger) << "1";
else (*m_logger) << "0";
}
}
(*m_logger) << "\n";
if (recv_buffer[7] & 0x01)
(*m_logger) << "supports DHT port message\n";
if (recv_buffer[7] & 0x02)
(*m_logger) << "supports XBT peer exchange message\n";
if (recv_buffer[5] & 0x10)
(*m_logger) << "supports LT/uT extensions\n";
#endif
if ((recv_buffer[5] & 0x10) && m_ses.extensions_enabled())
m_supports_extensions = true;
if (recv_buffer[7] & 0x01)
m_supports_dht_port = true;
// ok, now we have got enough of the handshake. Is this connection
// attached to a torrent?
if (!t)
{
// now, we have to see if there's a torrent with the
// info_hash we got from the peer
sha1_hash info_hash;
std::copy(recv_buffer.begin + 8, recv_buffer.begin + 28
, (char*)info_hash.begin());
attach_to_torrent(info_hash);
t = associated_torrent().lock();
assert(t);
assert(t->get_policy().has_connection(this));
// yes, we found the torrent
// reply with our handshake
write_handshake();
write_bitfield(t->pieces());
// if (m_supports_dht_port)
// write_dht_port(m_ses.dht_port());
}
else
{
// verify info hash
if (!std::equal(recv_buffer.begin + 8, recv_buffer.begin + 28
, (const char*)t->torrent_file().info_hash().begin()))
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << " received invalid info_hash\n";
#endif
throw std::runtime_error("invalid info-hash in handshake");
}
}
m_state = read_peer_id;
reset_recv_buffer(20);
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << " info_hash received\n";
#endif
}
break;
case read_peer_id:
{
if (!t) return;
m_statistics.received_bytes(0, bytes_transferred);
if (!packet_finished()) break;
assert(packet_size() == 20);
#ifdef TORRENT_VERBOSE_LOGGING
{
peer_id tmp;
std::copy(recv_buffer.begin, recv_buffer.begin + 20, (char*)tmp.begin());
std::stringstream s;
s << "received peer_id: " << tmp << " client: " << identify_client(tmp) << "\n";
s << "as ascii: ";
for (peer_id::iterator i = tmp.begin(); i != tmp.end(); ++i)
{
if (std::isprint(*i)) s << *i;
else s << ".";
}
s << "\n";
(*m_logger) << s.str();
}
#endif
peer_id pid;
std::copy(recv_buffer.begin, recv_buffer.begin + 20, (char*)pid.begin());
set_pid(pid);
m_client_version = identify_client(pid);
// disconnect if the peer has the same peer-id as ourself
// since it most likely is ourself then
if (pid == m_ses.get_peer_id())
throw std::runtime_error("closing connection to ourself");
if (m_supports_extensions) write_extensions();
/*
if (!m_active)
{
m_attached_to_torrent = true;
assert(m_torrent->get_policy().has_connection(this));
}
*/
m_state = read_packet_size;
reset_recv_buffer(4);
}
break;
case read_packet_size:
{
if (!t) return;
m_statistics.received_bytes(0, bytes_transferred);
if (!packet_finished()) break;
const char* ptr = recv_buffer.begin;
int packet_size = detail::read_int32(ptr);
// don't accept packets larger than 1 MB
if (packet_size > 1024*1024 || packet_size < 0)
{
// packet too large
throw std::runtime_error("packet > 1 MB ("
+ boost::lexical_cast<std::string>(
(unsigned int)packet_size) + " bytes)");
}
if (packet_size == 0)
{
incoming_keepalive();
// keepalive message
m_state = read_packet_size;
reset_recv_buffer(4);
}
else
{
m_state = read_packet;
reset_recv_buffer(packet_size);
}
}
break;
case read_packet:
{
if (!t) return;
if (dispatch_message(bytes_transferred))
{
m_state = read_packet_size;
reset_recv_buffer(4);
}
}
break;
}
}
// --------------------------
// SEND DATA
// --------------------------
// throws exception when the client should be disconnected
void bt_peer_connection::on_sent(asio::error const& error
, std::size_t bytes_transferred)
{
INVARIANT_CHECK;
if (error) return;
// manage the payload markers
int amount_payload = 0;
if (!m_payloads.empty())
{
for (std::deque<range>::iterator i = m_payloads.begin();
i != m_payloads.end(); ++i)
{
i->start -= bytes_transferred;
if (i->start < 0)
{
if (i->start + i->length <= 0)
{
amount_payload += i->length;
}
else
{
amount_payload += -i->start;
i->length -= -i->start;
i->start = 0;
}
}
}
}
// TODO: move the erasing into the loop above
// remove all payload ranges that has been sent
m_payloads.erase(
std::remove_if(m_payloads.begin(), m_payloads.end(), range_below_zero)
, m_payloads.end());
assert(amount_payload <= (int)bytes_transferred);
m_statistics.sent_bytes(amount_payload, bytes_transferred - amount_payload);
}
void bt_peer_connection::on_tick()
{
boost::shared_ptr<torrent> t = associated_torrent().lock();
if (!t) return;
// 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 (!t->valid_metadata()
&& supports_extension(extended_metadata_message)
&& !m_waiting_metadata_request
&& has_metadata())
{
m_last_metadata_request = t->metadata_request();
write_metadata_request(m_last_metadata_request);
m_waiting_metadata_request = true;
m_metadata_request = second_clock::universal_time();
}
}
#ifndef NDEBUG
void bt_peer_connection::check_invariant() const
{
if (!m_in_constructor)
peer_connection::check_invariant();
if (!m_payloads.empty())
{
for (std::deque<range>::const_iterator i = m_payloads.begin();
i != m_payloads.end() - 1; ++i)
{
assert(i->start + i->length <= (i+1)->start);
}
}
}
#endif
}