From e2baa0a6467ebcc6171315a3354e82c3d023c145 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sun, 4 Jan 2004 23:51:54 +0000 Subject: [PATCH] *** empty log message *** --- docs/index.html | 137 ++-- docs/index.rst | 50 +- examples/client_test.cpp | 2 +- include/libtorrent/peer_connection.hpp | 31 +- include/libtorrent/peer_info.hpp | 6 + include/libtorrent/session.hpp | 13 + include/libtorrent/storage.hpp | 2 +- src/peer_connection.cpp | 917 +++++++++++++------------ src/policy.cpp | 30 +- src/session.cpp | 5 + src/torrent.cpp | 2 +- src/torrent_handle.cpp | 2 + 12 files changed, 682 insertions(+), 515 deletions(-) diff --git a/docs/index.html b/docs/index.html index c923bf491..1c4c81743 100755 --- a/docs/index.html +++ b/docs/index.html @@ -24,52 +24,52 @@

Contents

-

introduction

+

introduction

libtorrent is a C++ library that aims to be a good alternative to all the other bittorrent implementations around. It is a library and not a full featured client, although it comes with a working @@ -105,7 +105,6 @@ of a resumed torrent. Saves the storage state in a separate fast-resume file.Functions that are yet to be implemented:

    -
  • choke/unchoke policy for seed-mode
  • number of connections limit
  • better handling of peers that send bad data
  • ip-filters
  • @@ -113,7 +112,7 @@ of a resumed torrent. Saves the storage state in a separate fast-resume file.

libtorrent is portable at least among windows, macosx, and UNIX-systems. It uses boost.thread, -boost.filesystem boost.date_time and various other boost libraries and zlib.

+boost.filesystem, boost.date_time and various other boost libraries as well as zlib.

libtorrent has been successfully compiled and tested on:

    @@ -130,7 +129,7 @@ boost.filesystem boost.date_time and various other boost libraries and zlib.

-

building

+

building

To build libtorrent you need boost and bjam installed. Then you can use bjam to build libtorrent.

To make bjam work, you need to set the environment variable BOOST_ROOT to the @@ -162,11 +161,11 @@ loop scope" and "treat wchar_t as built-in type" to Yes.

TODO: more detailed build instructions.

-

using

+

using

The interface of libtorrent consists of a few classes. The main class is the session, it contains the main loop that serves all torrents.

-

session

+

session

The session class has the following synopsis:

 class session: public boost::noncopyable
@@ -310,7 +309,7 @@ struct hash_failed_alert: alert
 
-

parsing torrent files

+

parsing torrent files

The torrent files are bencoded. There are two functions in libtorrent that can encode and decode bencoded data. They are:

@@ -349,7 +348,7 @@ entry e = bdecode(buf, buf + data_size);
 it will throw invalid_encoding.

-

entry

+

entry

The entry class represents one node in a bencoded hierarchy. It works as a variant type, it can be either a list, a dictionary (std::map), an integer or a string. This is its synopsis:

@@ -419,7 +418,7 @@ if (i != dict.end()) exists.

-

torrent_info

+

torrent_info

The torrent_info has the following synopsis:

 class torrent_info
@@ -506,7 +505,7 @@ object, representing the time when this torrent file was created. If there's no
 in the torrent file, this will return a date of january 1:st 1970.

-

torrent_handle

+

torrent_handle

You will usually have to store your torrent handles somewhere, since it's the object through which you retrieve infromation about the torrent and aborts the torrent. Its declaration looks like this:

@@ -545,7 +544,7 @@ torrent. If you set this to -1, there will be no limit.

write_resume_data() takes a non-const reference to a char-vector, that vector will be filled with the fast-resume data. For more information about how fast-resume works, see fast resume.

-

status()

+

status()

status() will return a structure with information about the status of this torrent. If the torrent_handle is invalid, it will throw invalid_handle exception. It contains the following fields:

@@ -627,7 +626,7 @@ all peers. The rates are given as the number of bytes per second.

total_done is the total number of bytes of the file(s) that we have.

-

get_download_queue()

+

get_download_queue()

get_download_queue() takes a non-const reference to a vector which it will fill information about pieces that are partially downloaded or not downloaded at all but partially requested. The entry in the vector (partial_piece_info) looks like this:

@@ -659,7 +658,7 @@ When a piece fails a hash verification, single blocks may be redownloaded to see may pass then.

-

get_peer_info()

+

get_peer_info()

get_peer_info() takes a reference to a vector that will be cleared and filled with one entry for each peer connected to this torrent, given the handle is valid. If the torrent_handle is invalid, it will throw invalid_handle exception. Each entry in @@ -673,7 +672,8 @@ struct peer_info interesting = 0x1, choked = 0x2, remote_interested = 0x4, - remote_choked = 0x8 + remote_choked = 0x8, + supports_extensions = 0x10 }; unsigned int flags; address ip; @@ -699,7 +699,8 @@ any combination of the four enums above. Where choked means that we have choked this peer. remote_interested and remote_choked means the same thing but that the peer is interested in pieces from us and the peer has choked -us.

+us. support_extensions means that this peer supports the extension protocol +as described by nolar.

The ip field is the IP-address to this peer. Its type is a wrapper around the actual address and the port number. See address class.

up_speed and down_speed is the current upload and download speed @@ -734,19 +735,19 @@ of bytes of this block we have received from the peer, and < the total number of bytes in this block.

-

get_torrent_info()

+

get_torrent_info()

Returns a const reference to the torrent_info object associated with this torrent. This reference is valid as long as the torrent_handle is valid, no longer. If the torrent_handle is invalid, invalid_handle exception will be thrown.

-

is_valid()

+

is_valid()

Returns true if this handle refers to a valid torrent and false if it hasn't been initialized or if the torrent it refers to has been aborted.

-

address

+

address

The address class represents a name of a network endpoint (usually referred to as IP-address) and a port number. This is the same thing as a sockaddr_in would contain. Its declaration looks like this:

@@ -779,7 +780,7 @@ while it does the DNS lookup, it returns a string that points to the address rep

ip() will return the 32-bit ip-address as an integer. port() returns the port number.

-

http_settings

+

http_settings

You have some control over tracker requests through the http_settings object. You create it and fill it with your settings and the use session::set_http_settings() to apply them. You have control over proxy and authorization settings and also the user-agent @@ -819,7 +820,7 @@ uncompressed (given your limit is lower than 2 megs). Default limit is 1 megabyte.

-

big_number

+

big_number

Both the peer_id and sha1_hash types are typedefs of the class big_number. It represents 20 bytes of data. Its synopsis follows:

@@ -840,7 +841,7 @@ public:
 

The iterators gives you access to individual bytes.

-

hasher

+

hasher

This class creates sha1-hashes. Its declaration looks like this:

 class hasher
@@ -864,7 +865,7 @@ call reset() to reinitialize i
 For more info, see src/sha1.c.

-

fingerprint

+

fingerprint

The fingerprint class represents information about a client and its version. It is used to encode this information into the client's peer id.

This is the class declaration:

@@ -913,7 +914,7 @@ version of your client. All these numbers must be within the range [0, 9].

to_string() will generate the actual string put in the peer-id, and return it.

-

alert

+

alert

The alert class is used to pass messages of events from the libtorrent code to the user. It is a base class that specific messages are derived from. This is its synopsis:

@@ -936,11 +937,11 @@ public:
-

exceptions

+

exceptions

There are a number of exceptions that can be thrown from different places in libtorrent, here's a complete list with description.

-

invalid_handle

+

invalid_handle

This exception is thrown when querying information from a torrent_handle that hasn't been initialized or that has become invalid.

@@ -951,7 +952,7 @@ struct invalid_handle: std::exception
 
-

duplicate_torrent

+

duplicate_torrent

This is thrown by session::add_torrent() if the torrent already has been added to the session.

@@ -962,7 +963,7 @@ struct duplicate_torrent: std::exception
 
-

invalid_encoding

+

invalid_encoding

This is thrown by bdecode() if the input data is not a valid bencoding.

 struct invalid_encoding: std::exception
@@ -972,7 +973,7 @@ struct invalid_encoding: std::exception
 
-

type_error

+

type_error

This is thrown from the accessors of entry if the data type of the entry doesn't match the type you want to extract from it.

@@ -983,7 +984,7 @@ struct type_error: std::runtime_error
 
-

invalid_torrent_file

+

invalid_torrent_file

This exception is thrown from the constructor of torrent_info if the given bencoded information doesn't meet the requirements on what information has to be present in a torrent file.

@@ -995,9 +996,9 @@ struct invalid_torrent_file: std::exception
 
-

example usage

+

example usage

-

dump_torrent

+

dump_torrent

This is an example of a program that will take a torrent-file as a parameter and print information about it to std out:

@@ -1061,7 +1062,7 @@ int main(int argc, char* argv[])
 
-

simple client

+

simple client

This is a simple client. It doesn't have much output to keep it simple:

 #include <iostream>
@@ -1113,7 +1114,7 @@ int main(int argc, char* argv[])
 
-

fast resume

+

fast resume

The fast resume mechanism is a way to remember which pieces are downloaded and where they are put between sessions. You can generate fast resume data by calling torrent_handle::write_resume_data() on torrent_handle. You can then save this data @@ -1126,7 +1127,7 @@ will skip the time consuming checks. It may have to do the checking anyway, if t fast-resume data is corrupt or doesn't fit the storage for that torrent, then it will not trust the fast-resume data and just do the checking.

-

file format

+

file format

The format of the fast-resume data is as follows, given that all 4-byte integers are stored as big-endian:

@@ -1149,12 +1150,12 @@ for each unfinished piece
 
-

Feedback

+

Feedback

There's a mailing list.

You can usually find me as hydri in #btports @ irc.freenode.net.

-

Aknowledgements

+

Aknowledgements

Written by Arvid Norberg and Daniel Wallin. Copyright (c) 2003

Contributions by Magnus Jonsson

Thanks to Reimond Retz for bugfixes, suggestions and testing

diff --git a/docs/index.rst b/docs/index.rst index 75c83ef4f..be471e82f 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -51,14 +51,13 @@ __ http://home.elp.rr.com/tur/multitracker-spec.txt Functions that are yet to be implemented: - * choke/unchoke policy for seed-mode * number of connections limit * better handling of peers that send bad data * ip-filters * file-level piece priority libtorrent is portable at least among windows, macosx, and UNIX-systems. It uses boost.thread, -boost.filesystem boost.date_time and various other boost libraries and zlib. +boost.filesystem, boost.date_time and various other boost libraries as well as zlib. libtorrent has been successfully compiled and tested on: @@ -245,13 +244,17 @@ dispatcher mechanism that's available in libtorrent. TODO: describe the type dispatching mechanism -The currently available alert types are: +You can do a ``dynamic_cast`` to a specific alert type to get more message-specific information. +These are the different alert types. - * tracker_alert - * hash_failed_alert +tracker_alert +~~~~~~~~~~~~~ -You can try a ``dynamic_cast`` to these types to get more message-pecific information. Here -are their definitions:: +This alert is generated on tracker time outs, premature disconnects, invalid response or +a HTTP response other than "200 OK". From the alert you can get the handle to the torrent +the tracker belongs to. This alert is generated as severity level ``warning``. + +:: struct tracker_alert: alert { @@ -261,6 +264,15 @@ are their definitions:: torrent_handle handle; }; +hash_failed_alert +~~~~~~~~~~~~~~~~~ + +This alert is generated when a finished piece fails its hash check. You can get the handle +to the torrent which got the failed piece and the index of the piece itself from the alert. +This alert is generated as severity level ``info``. + +:: + struct hash_failed_alert: alert { hash_failed_alert( @@ -274,6 +286,22 @@ are their definitions:: int piece_index; }; +peer_error_alert +~~~~~~~~~~~~~~~~ + +This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer +will be disconnected, but you get its peer-id from the alert. This alert is generated +as severity level ``debug``. + +:: + + struct peer_error_alert: alert + { + peer_error_alert(const peer_id& pid, const std::string& msg); + virtual std::auto_ptr clone() const; + + peer_id id; + }; parsing torrent files @@ -690,7 +718,8 @@ fields:: interesting = 0x1, choked = 0x2, remote_interested = 0x4, - remote_choked = 0x8 + remote_choked = 0x8, + supports_extensions = 0x10 }; unsigned int flags; address ip; @@ -716,7 +745,10 @@ any combination of the four enums above. Where ``interesting`` means that we are interested in pieces from this peer. ``choked`` means that **we** have choked this peer. ``remote_interested`` and ``remote_choked`` means the same thing but that the peer is interested in pieces from us and the peer has choked -**us**. +**us**. ``support_extensions`` means that this peer supports the `extension protocol +as described by nolar`__. + +__ http://nolar.com/azureus/extended.htm The ``ip`` field is the IP-address to this peer. Its type is a wrapper around the actual address and the port number. See address_ class. diff --git a/examples/client_test.cpp b/examples/client_test.cpp index ede34a098..e271d38c0 100755 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -314,7 +314,7 @@ int main(int argc, char* argv[]) << "u: " << add_suffix(i->up_speed) << "/s " << "(" << add_suffix(i->total_upload) << ") " // << "df: " << add_suffix((int)i->total_download - (int)i->total_upload) << " " - << "b: " << add_suffix(i->load_balancing) << " " + << "q: " << i->download_queue_length << " " << "f: " << static_cast((i->flags & peer_info::interesting)?"I":"_") << static_cast((i->flags & peer_info::choked)?"C":"_") diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index f3664b1e0..69553f356 100755 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -305,6 +305,28 @@ namespace libtorrent boost::shared_ptr m_logger; #endif + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + void on_extension_list(int received); + void on_extended(int received); + + typedef void (peer_connection::*message_handler)(int received); + private: bool dispatch_message(int received); @@ -345,11 +367,14 @@ namespace libtorrent msg_piece, msg_cancel, // extension protocol message - msg_extensions = 20, - // extended messages - msg_gzip_piece + msg_extension_list = 20, + msg_extended, + + num_supported_messages }; + const static message_handler m_message_handler[num_supported_messages]; + std::size_t m_packet_size; std::size_t m_recv_pos; std::vector m_recv_buffer; diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp index d85b4ad1b..e4c8ef7ee 100755 --- a/include/libtorrent/peer_info.hpp +++ b/include/libtorrent/peer_info.hpp @@ -62,6 +62,12 @@ namespace libtorrent int load_balancing; + // this is the number of requests + // we have sent to this peer + // that we haven't got a response + // for yet + int download_queue_length; + // the currently downloading piece // if piece index is -1 all associated // members are just set to 0 diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp index ec7259cae..10ab4aab1 100755 --- a/include/libtorrent/session.hpp +++ b/include/libtorrent/session.hpp @@ -69,6 +69,19 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + struct peer_error_alert: alert + { + peer_error_alert(const peer_id& pid, const std::string& msg) + : alert(alert::debug, msg) + , id(pid) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new peer_error_alert(*this)); } + + peer_id id; + }; + namespace detail { // workaround for microsofts diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index b193e7e40..cec37b6cc 100755 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -47,7 +47,7 @@ namespace libtorrent { namespace detail { - class piece_checker_data; + struct piece_checker_data; } class session; diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 7c3a58537..fae6476ce 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -54,6 +54,23 @@ namespace libtorrent const char* peer_connection::extension_names[] = { "gzip" }; + const peer_connection::message_handler peer_connection::m_message_handler[] = + { + &peer_connection::on_choke, + &peer_connection::on_unchoke, + &peer_connection::on_interested, + &peer_connection::on_not_interested, + &peer_connection::on_have, + &peer_connection::on_bitfield, + &peer_connection::on_request, + &peer_connection::on_piece, + &peer_connection::on_cancel, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &peer_connection::on_extension_list, + &peer_connection::on_extended + }; + + peer_connection::peer_connection( detail::session_impl& ses , selector& sel @@ -262,441 +279,479 @@ namespace libtorrent return boost::optional(p); } + + // message handlers + + // ----------------------------- + // ----------- CHOKE ----------- + // ----------------------------- + + void peer_connection::on_choke(int received) + { + if (m_packet_size != 1) + throw protocol_error("'choke' message size != 1"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== CHOKE\n"; +#endif + m_peer_choked = true; + m_torrent->get_policy().choked(*this); + + // remove all pieces from this peers download queue and + // remove the 'downloading' flag from piece_picker. + for (std::deque::iterator i = m_download_queue.begin(); + i != m_download_queue.end(); + ++i) + { + m_torrent->picker().abort_download(*i); + } + m_download_queue.clear(); +#ifndef NDEBUG +// m_torrent->picker().integrity_check(m_torrent); +#endif + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void peer_connection::on_unchoke(int received) + { + if (m_packet_size != 1) + throw protocol_error("'unchoke' message size != 1"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== UNCHOKE\n"; +#endif + m_peer_choked = false; + m_torrent->get_policy().unchoked(*this); + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void peer_connection::on_interested(int received) + { + if (m_packet_size != 1) + throw protocol_error("'interested' message size != 1"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== INTERESTED\n"; +#endif + m_peer_interested = true; + m_torrent->get_policy().interested(*this); + } + + // ----------------------------- + // ------ NOT INTERESTED ------- + // ----------------------------- + + void peer_connection::on_not_interested(int received) + { + if (m_packet_size != 1) + throw protocol_error("'not interested' message size != 1"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + + // clear the request queue if the client isn't interested + m_requests.clear(); + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== NOT_INTERESTED\n"; +#endif + m_peer_interested = false; + m_torrent->get_policy().not_interested(*this); + } + + // ----------------------------- + // ----------- HAVE ------------ + // ----------------------------- + + void peer_connection::on_have(int received) + { + if (m_packet_size != 5) + throw protocol_error("'have' message size != 5"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + + const char* ptr = &m_recv_buffer[1]; + int index = detail::read_int(ptr); + // if we got an invalid message, abort + if (index >= m_have_piece.size() || index < 0) + throw protocol_error("have message with higher index than the number of pieces"); + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== HAVE [ piece: " << index << "]\n"; +#endif + + if (m_have_piece[index]) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " oops.. we already knew that: " << index << "\n"; +#endif + } + else + { + m_have_piece[index] = true; + + m_torrent->peer_has(index); + if (!m_torrent->have_piece(index) && !is_interesting()) + m_torrent->get_policy().peer_is_interesting(*this); + } + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void peer_connection::on_bitfield(int received) + { + if (m_packet_size - 1 != (m_have_piece.size() + 7) / 8) + throw protocol_error("bitfield with invalid size"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== BITFIELD\n"; +#endif + // build a vector of all pieces + std::vector piece_list; + for (std::size_t i = 0; i < m_have_piece.size(); ++i) + { + bool have = m_recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7))); + if (have && !m_have_piece[i]) + { + m_have_piece[i] = true; + piece_list.push_back(i); + } + else if (!have && m_have_piece[i]) + { + m_have_piece[i] = false; + m_torrent->peer_lost(i); + } + } + + // shuffle the piece list + std::random_shuffle(piece_list.begin(), piece_list.end()); + + // let the torrent know which pieces the + // peer has, in a shuffled order + bool interesting = false; + for (std::vector::iterator i = piece_list.begin(); + i != piece_list.end(); + ++i) + { + int index = *i; + m_torrent->peer_has(index); + if (!m_torrent->have_piece(index)) + interesting = true; + } + + if (piece_list.empty()) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " *** THIS IS A SEED ***\n"; +#endif + } + + if (interesting) m_torrent->get_policy().peer_is_interesting(*this); + } + + // ----------------------------- + // ---------- REQUEST ---------- + // ----------------------------- + + void peer_connection::on_request(int received) + { + if (m_packet_size != 13) + throw protocol_error("'request' message size != 13"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + + peer_request r; + const char* ptr = &m_recv_buffer[1]; + r.piece = detail::read_int(ptr); + r.start = detail::read_int(ptr); + r.length = detail::read_int(ptr); + + // make sure this request + // is legal and taht the peer + // is not choked + if (r.piece >= 0 + && r.piece < m_torrent->torrent_file().num_pieces() + && r.start >= 0 + && r.start < m_torrent->torrent_file().piece_size(r.piece) + && r.length > 0 + && r.length + r.start < m_torrent->torrent_file().piece_size(r.piece) + && m_peer_interested) + { + // if we have choked the client + // ignore the request + if (m_choked) return; + + m_requests.push_back(r); + send_buffer_updated(); +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== REQUEST [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + } + else + { + // TODO: log this illegal request + } + } + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void peer_connection::on_piece(int received) + { + if (m_recv_pos <= 9) + // only received protocol data + m_statistics.received_bytes(0, received); + else if (m_recv_pos - received >= 9) + // only received payload data + m_statistics.received_bytes(received, 0); + else + { + // received a bit of both + assert(m_recv_pos - received < 9); + assert(m_recv_pos > 9); + assert(9 - (m_recv_pos - received) <= 9); + m_statistics.received_bytes( + m_recv_pos - 9 + , 9 - (m_recv_pos - received)); + } + + if (m_recv_pos < m_packet_size) return; + + const char* ptr = &m_recv_buffer[1]; + int index = detail::read_int(ptr); + if (index < 0 || index >= m_torrent->torrent_file().num_pieces()) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " piece index invalid\n"; +#endif + throw protocol_error("invalid piece index in piece message"); + } + int offset = detail::read_int(ptr); + int len = m_packet_size - 9; + + if (offset < 0) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " offset < 0\n"; +#endif + throw protocol_error("offset < 0 in piece message"); + } + + if (offset + len > m_torrent->torrent_file().piece_size(index)) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " piece packet contains more data than the piece size\n"; +#endif + throw protocol_error("piece message contains more data than the piece size"); + } + // TODO: make sure that len is == block_size or less only + // if its's the last block. + + if (offset % m_torrent->block_size() != 0) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " piece packet contains unaligned offset\n"; +#endif + throw protocol_error("piece message contains unaligned offset"); + } +/* + piece_block req = m_download_queue.front(); + if (req.piece_index != index) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " piece packet contains unrequested index\n"; +#endif + return false; + } + + if (req.block_index != offset / m_torrent->block_size()) + { +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " piece packet contains unrequested offset\n"; +#endif + return false; + } +*/ +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== PIECE [ piece: " << index << " | s: " << offset << " | l: " << len << " ]\n"; +#endif + + piece_picker& picker = m_torrent->picker(); + piece_block block_finished(index, offset / m_torrent->block_size()); + + std::deque::iterator b + = std::find( + m_download_queue.begin() + , m_download_queue.end() + , block_finished); + + if (b != m_download_queue.end()) + { + // pop the request that just finished + // from the download queue + m_download_queue.erase(b); + } + else + { + // TODO: cancel the block from the + // peer that has taken over it. + } + + // if the block we got is already finished, then ignore it + if (picker.is_finished(block_finished)) return; + + m_torrent->filesystem().write(&m_recv_buffer[9], index, offset, len); + + picker.mark_as_finished(block_finished, m_peer_id); + + m_torrent->get_policy().block_finished(*this, block_finished); + + // did we just finish the piece? + if (picker.is_piece_finished(index)) + { + bool verified = m_torrent->verify_piece(index); + if (verified) + { + m_torrent->announce_piece(index); + } + else + { + m_torrent->piece_failed(index); + } + m_torrent->get_policy().piece_finished(index, verified); + } + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void peer_connection::on_cancel(int received) + { + if (m_packet_size != 13) + throw protocol_error("'cancel' message size != 13"); + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + + peer_request r; + const char* ptr = &m_recv_buffer[1]; + r.piece = detail::read_int(ptr); + r.start = detail::read_int(ptr); + r.length = detail::read_int(ptr); + + std::deque::iterator i + = std::find(m_requests.begin(), m_requests.end(), r); + if (i != m_requests.end()) + { + m_requests.erase(i); + } + + if (!has_data() && m_added_to_selector) + { + m_added_to_selector = false; + m_selector.remove_writable(m_socket); + } + +#ifndef NDEBUG + (*m_logger) << m_socket->sender().as_string() << " <== CANCEL [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; +#endif + } + + // ----------------------------- + // ------ EXTENSION LIST ------- + // ----------------------------- + + void peer_connection::on_extension_list(int received) + { + if (m_packet_size > 100 * 1024) + { + // too big extension message, abort + throw protocol_error("'extensions' message size > 100kB"); + } + m_statistics.received_bytes(0, received); + if (m_recv_pos < m_packet_size) return; + + try + { + entry e = bdecode(m_recv_buffer.begin()+1, m_recv_buffer.end()); + entry::dictionary_type& extensions = e.dict(); + + for (int i = 0; i < num_supported_extensions; ++i) + { + entry::dictionary_type::iterator f = + extensions.find(extension_names[i]); + if (f != extensions.end()) + { + m_extension_messages[i] = f->second.integer(); + } + } + } + catch(invalid_encoding& e) + { + throw protocol_error("'extensions' packet contains invalid bencoding"); + } + catch(type_error& e) + { + throw protocol_error("'extensions' packet contains incorrect types"); + } + } + + // ----------------------------- + // --------- EXTENDED ---------- + // ----------------------------- + + void peer_connection::on_extended(int received) + { + + } + + + + + + + + + + + + bool peer_connection::dispatch_message(int received) { assert(m_recv_pos >= received); assert(m_recv_pos > 0); int packet_type = m_recv_buffer[0]; - - if (packet_type == 20) + if (packet_type < 0 + || packet_type >= num_supported_messages + || m_message_handler[packet_type] == 0) { - int i = 0; - } - - switch (packet_type) - { - - // *************** CHOKE *************** - case msg_choke: - if (m_packet_size != 1) - throw protocol_error("'choke' message size != 1"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== CHOKE\n"; - #endif - m_peer_choked = true; - m_torrent->get_policy().choked(*this); - - // remove all pieces from this peers download queue and - // remove the 'downloading' flag from piece_picker. - for (std::deque::iterator i = m_download_queue.begin(); - i != m_download_queue.end(); - ++i) - { - m_torrent->picker().abort_download(*i); - } - m_download_queue.clear(); - #ifndef NDEBUG - // m_torrent->picker().integrity_check(m_torrent); - #endif - break; - - - - // *************** UNCHOKE *************** - case msg_unchoke: - if (m_packet_size != 1) - throw protocol_error("'unchoke' message size != 1"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== UNCHOKE\n"; - #endif - m_peer_choked = false; - m_torrent->get_policy().unchoked(*this); - break; - - - // *************** INTERESTED *************** - case msg_interested: - if (m_packet_size != 1) - throw protocol_error("'interested' message size != 1"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== INTERESTED\n"; - #endif - m_peer_interested = true; - m_torrent->get_policy().interested(*this); - break; - - - // *************** NOT INTERESTED *************** - case msg_not_interested: - if (m_packet_size != 1) - throw protocol_error("'not interested' message size != 1"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - // clear the request queue if the client isn't interested - m_requests.clear(); - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== NOT_INTERESTED\n"; - #endif - m_peer_interested = false; - m_torrent->get_policy().not_interested(*this); - break; - - - - // *************** HAVE *************** - case msg_have: - { - if (m_packet_size != 5) - throw protocol_error("'have' message size != 5"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - const char* ptr = &m_recv_buffer[1]; - int index = detail::read_int(ptr); - // if we got an invalid message, abort - if (index >= m_have_piece.size() || index < 0) - throw protocol_error("have message with higher index than the number of pieces"); - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== HAVE [ piece: " << index << "]\n"; - #endif - - if (m_have_piece[index]) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " oops.. we already knew that: " << index << "\n"; - #endif - } - else - { - m_have_piece[index] = true; - - m_torrent->peer_has(index); - if (!m_torrent->have_piece(index) && !is_interesting()) - m_torrent->get_policy().peer_is_interesting(*this); - } - break; - } - - - - - // *************** BITFIELD *************** - case msg_bitfield: - { - if (m_packet_size - 1 != (m_have_piece.size() + 7) / 8) - throw protocol_error("bitfield with invalid size"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== BITFIELD\n"; - #endif - // build a vector of all pieces - std::vector piece_list; - for (std::size_t i = 0; i < m_have_piece.size(); ++i) - { - bool have = m_recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7))); - if (have && !m_have_piece[i]) - { - m_have_piece[i] = true; - piece_list.push_back(i); - } - else if (!have && m_have_piece[i]) - { - m_have_piece[i] = false; - m_torrent->peer_lost(i); - } - } - - // shuffle the piece list - std::random_shuffle(piece_list.begin(), piece_list.end()); - - // let the torrent know which pieces the - // peer has, in a shuffled order - bool interesting = false; - for (std::vector::iterator i = piece_list.begin(); - i != piece_list.end(); - ++i) - { - int index = *i; - m_torrent->peer_has(index); - if (!m_torrent->have_piece(index)) - interesting = true; - } - - if (piece_list.empty()) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " *** THIS IS A SEED ***\n"; - #endif - } - - if (interesting) m_torrent->get_policy().peer_is_interesting(*this); - - break; - } - - - // *************** EXTENSIONS *************** - case msg_extensions: - { - if (m_packet_size > 100 * 1024) - { - // too big extension message, abort - throw protocol_error("'extensions' message size > 100kB"); - } - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - try - { - entry e = bdecode(m_recv_buffer.begin()+1, m_recv_buffer.end()); - entry::dictionary_type& extensions = e.dict(); - - for (int i = 0; i < num_supported_extensions; ++i) - { - entry::dictionary_type::iterator f = - extensions.find(extension_names[i]); - if (f != extensions.end()) - { - m_extension_messages[i] = f->second.integer(); - } - } - } - catch(invalid_encoding& e) - { - throw protocol_error("'extensions' packet contains invalid bencoding"); - } - catch(type_error& e) - { - throw protocol_error("'extensions' packet contains incorrect types"); - } - - break; - } - - - // *************** REQUEST *************** - case msg_request: - { - if (m_packet_size != 13) - throw protocol_error("'request' message size != 13"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - peer_request r; - const char* ptr = &m_recv_buffer[1]; - r.piece = detail::read_int(ptr); - r.start = detail::read_int(ptr); - r.length = detail::read_int(ptr); - - // make sure this request - // is legal and taht the peer - // is not choked - if (r.piece >= 0 - && r.piece < m_torrent->torrent_file().num_pieces() - && r.start >= 0 - && r.start < m_torrent->torrent_file().piece_size(r.piece) - && r.length > 0 - && r.length + r.start < m_torrent->torrent_file().piece_size(r.piece) - && !m_choked - && m_peer_interested) - { - m_requests.push_back(r); - send_buffer_updated(); - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== REQUEST [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; - #endif - } - else - { - // TODO: log this illegal request - // if the only error is that the - // peer is choked, it may not be a - // mistake - } - - break; - } - - - - // *************** PIECE *************** - case msg_piece: - { - if (m_recv_pos <= 9) - // only received protocol data - m_statistics.received_bytes(0, received); - else if (m_recv_pos - received >= 9) - // only received payload data - m_statistics.received_bytes(received, 0); - else - { - // received a bit of both - assert(m_recv_pos - received < 9); - assert(m_recv_pos > 9); - assert(9 - (m_recv_pos - received) <= 9); - m_statistics.received_bytes( - m_recv_pos - 9 - , 9 - (m_recv_pos - received)); - } - - if (m_recv_pos < m_packet_size) return false; - - const char* ptr = &m_recv_buffer[1]; - int index = detail::read_int(ptr); - if (index < 0 || index >= m_torrent->torrent_file().num_pieces()) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " piece index invalid\n"; - #endif - throw protocol_error("invalid piece index in piece message"); - } - int offset = detail::read_int(ptr); - int len = m_packet_size - 9; - - if (offset < 0) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " offset < 0\n"; - #endif - throw protocol_error("offset < 0 in piece message"); - } - - if (offset + len > m_torrent->torrent_file().piece_size(index)) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " piece packet contains more data than the piece size\n"; - #endif - throw protocol_error("piece message contains more data than the piece size"); - } - // TODO: make sure that len is == block_size or less only - // if its's the last block. - - if (offset % m_torrent->block_size() != 0) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " piece packet contains unaligned offset\n"; - #endif - throw protocol_error("piece message contains unaligned offset"); - } - /* - piece_block req = m_download_queue.front(); - if (req.piece_index != index) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " piece packet contains unrequested index\n"; - #endif - return false; - } - - if (req.block_index != offset / m_torrent->block_size()) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " piece packet contains unrequested offset\n"; - #endif - return false; - } - */ - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== PIECE [ piece: " << index << " | s: " << offset << " | l: " << len << " ]\n"; - #endif - - piece_picker& picker = m_torrent->picker(); - piece_block block_finished(index, offset / m_torrent->block_size()); - - std::deque::iterator b - = std::find( - m_download_queue.begin() - , m_download_queue.end() - , block_finished); - - if (b != m_download_queue.end()) - { - // pop the request that just finished - // from the download queue - m_download_queue.erase(b); - } - else - { - // TODO: cancel the block from the - // peer that has taken over it. - } - - if (picker.is_finished(block_finished)) break; - - m_torrent->filesystem().write(&m_recv_buffer[9], index, offset, len); - - picker.mark_as_finished(block_finished, m_peer_id); - - m_torrent->get_policy().block_finished(*this, block_finished); - - // did we just finish the piece? - if (picker.is_piece_finished(index)) - { - bool verified = m_torrent->verify_piece(index); - if (verified) - { - m_torrent->announce_piece(index); - } - else - { - m_torrent->piece_failed(index); - } - m_torrent->get_policy().piece_finished(index, verified); - } - break; - } - - - // *************** CANCEL *************** - case msg_cancel: - { - if (m_packet_size != 13) - throw protocol_error("'cancel' message size != 13"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return false; - - peer_request r; - const char* ptr = &m_recv_buffer[1]; - r.piece = detail::read_int(ptr); - r.start = detail::read_int(ptr); - r.length = detail::read_int(ptr); - - std::deque::iterator i - = std::find(m_requests.begin(), m_requests.end(), r); - if (i != m_requests.end()) - { - m_requests.erase(i); - } - - if (!has_data() && m_added_to_selector) - { - m_added_to_selector = false; - m_selector.remove_writable(m_socket); - } - - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " <== CANCEL [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; - #endif - break; - } - default: throw protocol_error("unknown message id"); } + + assert(m_message_handler[packet_type] != 0); + + // call the correct handler for this packet type + (this->*m_message_handler[packet_type])(received); + + if (m_recv_pos < m_packet_size) return false; + assert(m_recv_pos == m_packet_size); return true; } @@ -820,7 +875,7 @@ namespace libtorrent for (int i = 0; i < num_supported_extensions; ++i) { entry msg_index(entry::int_t); - msg_index.integer() = msg_extensions + 1 + i; + msg_index.integer() = i; extension_list.dict()[extension_names[i]] = msg_index; } @@ -832,7 +887,7 @@ namespace libtorrent const int msg_size_pos = m_send_buffer.size(); m_send_buffer.resize(msg_size_pos + 4); - m_send_buffer.push_back(msg_extensions); + m_send_buffer.push_back(msg_extension_list); bencode(std::back_inserter(m_send_buffer), extension_list); @@ -919,11 +974,11 @@ namespace libtorrent int diff = share_diff(); - if (diff > 2*m_torrent->block_size()) + if (diff > 2*m_torrent->block_size() || m_torrent->is_seed()) { // if we have downloaded more than one piece more - // than we have uploaded, have an unlimited - // upload rate + // than we have uploaded OR if we are a seed + // have an unlimited upload rate m_send_quota_limit = -1; } else diff --git a/src/policy.cpp b/src/policy.cpp index 5c18aceae..b6715c40e 100755 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -368,7 +368,35 @@ namespace libtorrent , m_torrent->end() , m_available_free_upload); - if (m_max_uploads != -1) + + if (m_torrent->is_seed()) + { + // make sure we have enough + // unchoked peers + while (m_num_unchoked < m_max_uploads) + { + peer* p = 0; + for (std::vector::iterator i = m_peers.begin(); + i != m_peers.end(); + ++i) + { + peer_connection* c = i->connection; + if (c == 0) continue; + if (!c->is_choked()) continue; + if (!c->is_peer_interested()) continue; +// TODO: add some more criterion here. Maybe the peers +// that have less should be promoted? (to allow them to trade) + p = &(*i); + } + + if (p == 0) break; + + p->connection->unchoke(); + p->last_optimistically_unchoked = boost::posix_time::second_clock::local_time(); + ++m_num_unchoked; + } + } + else if (m_max_uploads != -1) { // make sure we don't have too many // unchoked peers diff --git a/src/session.cpp b/src/session.cpp index 6f5dfbdb8..b45287147 100755 --- a/src/session.cpp +++ b/src/session.cpp @@ -497,6 +497,11 @@ namespace libtorrent } catch(std::exception& e) { + if (m_alerts.should_post(alert::debug)) + { + m_alerts.post_alert( + peer_error_alert(p->second->get_peer_id(), e.what())); + } // the connection wants to disconnect for some reason, remove it // from the connection-list m_selector.remove(*i); diff --git a/src/torrent.cpp b/src/torrent.cpp index 81eb28fbd..1e8c12946 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -700,7 +700,7 @@ namespace libtorrent << m_torrent_file.trackers()[m_currently_trying_tracker].url << "\" timed out"; torrent_handle self(&m_ses, 0, m_torrent_file.info_hash()); - m_ses.m_alerts.post_alert(tracker_alert( self, s.str())); + m_ses.m_alerts.post_alert(tracker_alert(self, s.str())); } // TODO: increase the retry_delay for // each failed attempt on the same tracker! diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 5c28bc060..f74090c07 100755 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -319,6 +319,8 @@ namespace libtorrent p.load_balancing = peer->total_free_upload(); + p.download_queue_length = peer->download_queue().size(); + boost::optional ret = peer->downloading_piece(); if (ret) {