From 41810b1166795224a75f67c2a59f5dcd8aac5c0d Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Tue, 25 Apr 2006 21:04:48 +0000 Subject: [PATCH] merged back the asio development branch --- AUTHORS | 4 +- ChangeLog | 13 + Jamfile | 16 +- Makefile.am | 153 +- configure.in | 2 +- docs/extension_protocol.html | 247 ++ docs/extension_protocol.rst | 179 ++ docs/manual.html | 1143 +++++--- docs/manual.rst | 307 +- examples/Makefile.am | 2 +- examples/client_test.cpp | 29 +- examples/make_torrent.cpp | 5 +- include/Makefile.am | 4 +- include/libtorrent/alert.hpp | 14 +- include/libtorrent/alert_types.hpp | 41 +- include/libtorrent/allocate_resources.hpp | 5 +- include/libtorrent/bt_peer_connection.hpp | 295 ++ include/libtorrent/buffer.hpp | 43 +- include/libtorrent/debug.hpp | 2 +- include/libtorrent/escape_string.hpp | 1 + include/libtorrent/file.hpp | 2 +- include/libtorrent/fingerprint.hpp | 8 +- .../libtorrent/http_tracker_connection.hpp | 79 +- include/libtorrent/ip_filter.hpp | 12 +- include/libtorrent/peer.hpp | 6 +- include/libtorrent/peer_connection.hpp | 400 ++- include/libtorrent/peer_info.hpp | 22 +- include/libtorrent/piece_picker.hpp | 43 +- include/libtorrent/policy.hpp | 7 +- include/libtorrent/resource_request.hpp | 5 +- include/libtorrent/session.hpp | 77 +- include/libtorrent/session_settings.hpp | 68 + include/libtorrent/socket.hpp | 290 +- include/libtorrent/stat.hpp | 10 +- include/libtorrent/storage.hpp | 5 +- include/libtorrent/torrent.hpp | 85 +- include/libtorrent/torrent_handle.hpp | 28 +- include/libtorrent/torrent_info.hpp | 23 +- include/libtorrent/tracker_manager.hpp | 99 +- include/libtorrent/udp_tracker_connection.hpp | 47 +- include/libtorrent/version.hpp | 2 +- include/libtorrent/web_peer_connection.hpp | 170 ++ src/Makefile.am | 11 +- src/allocate_resources.cpp | 33 +- src/bt_peer_connection.cpp | 1537 ++++++++++ src/entry.cpp | 2 +- src/escape_string.cpp | 30 + src/file.cpp | 12 +- src/file_win.cpp | 6 +- src/http_tracker_connection.cpp | 562 ++-- src/identify_client.cpp | 23 +- src/ip_filter.cpp | 20 +- src/peer_connection.cpp | 2569 ++++++----------- src/piece_picker.cpp | 320 +- src/policy.cpp | 132 +- src/session.cpp | 1077 ++++--- src/stat.cpp | 12 +- src/storage.cpp | 135 +- src/torrent.cpp | 576 ++-- src/torrent_handle.cpp | 197 +- src/torrent_info.cpp | 117 +- src/tracker_manager.cpp | 348 ++- src/udp_tracker_connection.cpp | 584 ++-- src/web_peer_connection.cpp | 417 +++ test/Jamfile | 1 + test/Makefile.am | 10 +- test/test_bencoding.cpp | 2 +- test/test_ip_filter.cpp | 60 +- test/test_metadata_extension.cpp | 106 + test/test_piece_picker.cpp | 31 +- test/test_storage.cpp | 9 +- 71 files changed, 8271 insertions(+), 4661 deletions(-) create mode 100644 docs/extension_protocol.html create mode 100644 docs/extension_protocol.rst create mode 100755 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/session_settings.hpp create mode 100755 include/libtorrent/web_peer_connection.hpp create mode 100755 src/bt_peer_connection.cpp create mode 100755 src/web_peer_connection.cpp create mode 100644 test/test_metadata_extension.cpp diff --git a/AUTHORS b/AUTHORS index 894a439fb..46f096584 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,8 +1,10 @@ -Written by Arvid Norberg. Copyright (c) 2003-2005 +Written by Arvid Norberg. Copyright (c) 2003-2006 Contributions by Magnus Jonsson, Daniel Wallin and Cory Nelson +Lots of testing, suggestions and contributions by Massaroddel. + Big thanks to Michael Wojciechowski and Peter Koeleman for making the autotools scripts. diff --git a/ChangeLog b/ChangeLog index ca960daed..26ff2db78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. (currently only for single file torrents). + * converted the network code to use asio * made libtorrent build in vc7 (patches from Allen Zhao) * fixed bug caused when binding outgoing connections to a non-local interface. * add_torrent() will now throw if called while the session object is diff --git a/Jamfile b/Jamfile index b203ba0e8..c0b29690e 100755 --- a/Jamfile +++ b/Jamfile @@ -23,6 +23,7 @@ project torrent : requirements ./include + ./asio/include ./zlib $(BOOST_ROOT) release:NDEBUG @@ -33,8 +34,19 @@ project torrent /boost/filesystem//boost_filesystem/static /boost/date_time//boost_date_time/static multi + msvc:_WIN32_WINNT=0x0500 +# WIN32 makes sure the win32 socket api is used +# instead of win16 + msvc:WIN32 +# without WIN32_LEAN_AND_MEAN there will be conflicts between +# winsock.h and winsock2.h + msvc:WIN32_LEAN_AND_MEAN +# these compiler settings just makes the compiler standard conforming msvc:/Zc:wchar_t msvc:/Zc:forScope +# this should be defined when libtorrent is built as +# a dll. It will make sure the functions and classes +# are exported (GCC 4 and msvc) shared:TORRENT_BUILDING_SHARED : usage-requirements @@ -50,17 +62,17 @@ project torrent SOURCES = allocate_resources.cpp alert.cpp - async_gethostbyname.cpp entry.cpp escape_string.cpp file.cpp identify_client.cpp ip_filter.cpp peer_connection.cpp + bt_peer_connection.cpp + web_peer_connection.cpp piece_picker.cpp policy.cpp session.cpp - socket.cpp stat.cpp storage.cpp torrent.cpp diff --git a/Makefile.am b/Makefile.am index 0359e3adb..e047faf89 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,13 +4,162 @@ docs/extension_protocol.html docs/udp_tracker_protocol.rst \ docs/udp_tracker_protocol.html docs/client_test.rst docs/client_test.html \ docs/unicode_support.png docs/client_test.png docs/style.css Jamfile project-root.jam \ m4/ac_cxx_namespaces.m4 m4/acx_pthread.m4 m4/ax_boost_date-time.m4 \ -m4/ax_boost_filesystem.m4 m4/ax_boost_thread.m4 src/file_win.cpp libtorrent.pc +m4/ax_boost_filesystem.m4 m4/ax_boost_thread.m4 src/file_win.cpp libtorrent.pc \ +asio/COPYING \ +asio/INSTALL \ +asio/LICENSE_1_0.txt \ +asio/Makefile.am \ +asio/README \ +asio/THANKS \ +asio/TODO \ +asio/autogen.sh \ +asio/boostify.pl \ +asio/configure.ac \ +asio/include/Makefile.am \ +asio/include/asio.hpp \ +asio/include/asio/basic_datagram_socket.hpp \ +asio/include/asio/basic_deadline_timer.hpp \ +asio/include/asio/basic_io_object.hpp \ +asio/include/asio/basic_io_service.hpp \ +asio/include/asio/basic_locking_dispatcher.hpp \ +asio/include/asio/basic_socket.hpp \ +asio/include/asio/basic_socket_acceptor.hpp \ +asio/include/asio/basic_stream_socket.hpp \ +asio/include/asio/buffer.hpp \ +asio/include/asio/buffered_read_stream.hpp \ +asio/include/asio/buffered_read_stream_fwd.hpp \ +asio/include/asio/buffered_stream.hpp \ +asio/include/asio/buffered_stream_fwd.hpp \ +asio/include/asio/buffered_write_stream.hpp \ +asio/include/asio/buffered_write_stream_fwd.hpp \ +asio/include/asio/completion_condition.hpp \ +asio/include/asio/datagram_socket_service.hpp \ +asio/include/asio/deadline_timer.hpp \ +asio/include/asio/deadline_timer_service.hpp \ +asio/include/asio/error.hpp \ +asio/include/asio/error_handler.hpp \ +asio/include/asio/handler_alloc_hook.hpp \ +asio/include/asio/io_service.hpp \ +asio/include/asio/is_read_buffered.hpp \ +asio/include/asio/is_write_buffered.hpp \ +asio/include/asio/locking_dispatcher.hpp \ +asio/include/asio/placeholders.hpp \ +asio/include/asio/read.hpp \ +asio/include/asio/service_factory.hpp \ +asio/include/asio/socket_acceptor_service.hpp \ +asio/include/asio/socket_base.hpp \ +asio/include/asio/ssl.hpp \ +asio/include/asio/stream_socket_service.hpp \ +asio/include/asio/system_exception.hpp \ +asio/include/asio/thread.hpp \ +asio/include/asio/time_traits.hpp \ +asio/include/asio/write.hpp \ +asio/include/asio/detail/bind_handler.hpp \ +asio/include/asio/detail/buffer_resize_guard.hpp \ +asio/include/asio/detail/buffered_stream_storage.hpp \ +asio/include/asio/detail/call_stack.hpp \ +asio/include/asio/detail/consuming_buffers.hpp \ +asio/include/asio/detail/epoll_reactor.hpp \ +asio/include/asio/detail/event.hpp \ +asio/include/asio/detail/fd_set_adapter.hpp \ +asio/include/asio/detail/handler_alloc_helpers.hpp \ +asio/include/asio/detail/hash_map.hpp \ +asio/include/asio/detail/io_control.hpp \ +asio/include/asio/detail/kqueue_reactor.hpp \ +asio/include/asio/detail/locking_dispatcher.hpp \ +asio/include/asio/detail/mutex.hpp \ +asio/include/asio/detail/noncopyable.hpp \ +asio/include/asio/detail/null_event.hpp \ +asio/include/asio/detail/null_mutex.hpp \ +asio/include/asio/detail/null_signal_blocker.hpp \ +asio/include/asio/detail/null_thread.hpp \ +asio/include/asio/detail/null_tss_ptr.hpp \ +asio/include/asio/detail/pipe_select_interrupter.hpp \ +asio/include/asio/detail/pop_options.hpp \ +asio/include/asio/detail/posix_event.hpp \ +asio/include/asio/detail/posix_mutex.hpp \ +asio/include/asio/detail/posix_signal_blocker.hpp \ +asio/include/asio/detail/posix_thread.hpp \ +asio/include/asio/detail/posix_tss_ptr.hpp \ +asio/include/asio/detail/push_options.hpp \ +asio/include/asio/detail/reactive_deadline_timer_service.hpp \ +asio/include/asio/detail/reactive_socket_service.hpp \ +asio/include/asio/detail/reactor_op_queue.hpp \ +asio/include/asio/detail/reactor_timer_queue.hpp \ +asio/include/asio/detail/scoped_lock.hpp \ +asio/include/asio/detail/select_interrupter.hpp \ +asio/include/asio/detail/select_reactor.hpp \ +asio/include/asio/detail/service_registry.hpp \ +asio/include/asio/detail/signal_blocker.hpp \ +asio/include/asio/detail/signal_init.hpp \ +asio/include/asio/detail/socket_holder.hpp \ +asio/include/asio/detail/socket_ops.hpp \ +asio/include/asio/detail/socket_option.hpp \ +asio/include/asio/detail/socket_select_interrupter.hpp \ +asio/include/asio/detail/socket_types.hpp \ +asio/include/asio/detail/task_io_service.hpp \ +asio/include/asio/detail/thread.hpp \ +asio/include/asio/detail/tss_ptr.hpp \ +asio/include/asio/detail/win_event.hpp \ +asio/include/asio/detail/win_iocp_io_service.hpp \ +asio/include/asio/detail/win_iocp_operation.hpp \ +asio/include/asio/detail/win_iocp_socket_service.hpp \ +asio/include/asio/detail/win_local_free_on_block_exit.hpp \ +asio/include/asio/detail/win_mutex.hpp \ +asio/include/asio/detail/win_signal_blocker.hpp \ +asio/include/asio/detail/win_thread.hpp \ +asio/include/asio/detail/win_tss_ptr.hpp \ +asio/include/asio/detail/winsock_init.hpp \ +asio/include/asio/detail/wrapped_handler.hpp \ +asio/include/asio/impl/read.ipp \ +asio/include/asio/impl/write.ipp \ +asio/include/asio/ip/address.hpp \ +asio/include/asio/ip/basic_endpoint.hpp \ +asio/include/asio/ip/tcp.hpp \ +asio/include/asio/ip/udp.hpp \ +asio/include/asio/ipv4/address.hpp \ +asio/include/asio/ipv4/basic_endpoint.hpp \ +asio/include/asio/ipv4/basic_host_resolver.hpp \ +asio/include/asio/ipv4/host.hpp \ +asio/include/asio/ipv4/host_resolver.hpp \ +asio/include/asio/ipv4/host_resolver_service.hpp \ +asio/include/asio/ipv4/multicast.hpp \ +asio/include/asio/ipv4/tcp.hpp \ +asio/include/asio/ipv4/udp.hpp \ +asio/include/asio/ipv4/detail/host_resolver_service.hpp \ +asio/include/asio/ipv4/detail/socket_option.hpp \ +asio/include/asio/ipv6/address.hpp \ +asio/include/asio/ipv6/basic_endpoint.hpp \ +asio/include/asio/ipv6/multicast.hpp \ +asio/include/asio/ipv6/tcp.hpp \ +asio/include/asio/ipv6/udp.hpp \ +asio/include/asio/ipv6/detail/socket_option.hpp \ +asio/include/asio/ssl/basic_context.hpp \ +asio/include/asio/ssl/context.hpp \ +asio/include/asio/ssl/context_base.hpp \ +asio/include/asio/ssl/context_service.hpp \ +asio/include/asio/ssl/stream.hpp \ +asio/include/asio/ssl/stream_base.hpp \ +asio/include/asio/ssl/stream_service.hpp \ +asio/include/asio/ssl/detail/openssl_context_service.hpp \ +asio/include/asio/ssl/detail/openssl_init.hpp \ +asio/include/asio/ssl/detail/openssl_operation.hpp \ +asio/include/asio/ssl/detail/openssl_stream_service.hpp \ +asio/include/asio/ssl/detail/openssl_types.hpp + pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libtorrent.pc check: test - test/test_hasher && test/test_bencoding && test/test_ip_filter && echo "tests done, all OK" + test/test_hasher \ + && test/test_bencoding \ + && test/test_ip_filter \ + && test/test_piece_picker \ + && test/test_storage \ + && test/test_metadata_extension \ + && test/test_buffer \ + && echo && echo && echo " **** all tests passed ****" && echo && echo deb: dpkg-buildpackage -rfakeroot -us -uc diff --git a/configure.in b/configure.in index 4ce1301e8..553575ffe 100644 --- a/configure.in +++ b/configure.in @@ -1,7 +1,7 @@ AC_PREREQ(2.59) AC_INIT(src/torrent.cpp) -AM_INIT_AUTOMAKE(libtorrent, 0.9.1) +AM_INIT_AUTOMAKE(libtorrent, 0.9.2) AM_CONFIG_HEADER(config.h) diff --git a/docs/extension_protocol.html b/docs/extension_protocol.html new file mode 100644 index 000000000..015bbcee5 --- /dev/null +++ b/docs/extension_protocol.html @@ -0,0 +1,247 @@ + + + + + + + + + + + +
+ +++ + + + +
Author:Arvid Norberg, arvid@rasterbar.com +Ludvig Strigeus, ludde@utorrent.com
+
+

extension protocol for bittorrent

+

The intention of this protocol is to provide a simple and thin transport +for extensions to the bittorrent protocol. Supporting this protocol makes +it easy to add new extensions without interfering with the standard +bittorrent protocol or clients that don't support this extension or the +one you want to add.

+

To advertise to other clients that you support, one bit from the reserved +bytes is used.

+
+
Right now, two bits have known usages.
+
    +
  • [7] & 1 is used by Mainline for DHT support
  • +
  • [7] & 2 is used by XBT client for peer-exchange support
  • +
+
+
+

The bit selected for the extension protocol is bit 20 from the right (counting +starts at 0). So (reserved_byte[5] & 0x10) is the expression to use for checking +if the client supports extended messaging.

+

Once support for the protocol is established, the client is supposed to +support 1 new message:

+ ++++ + + + + + + + + + + +
nameid
extended20
+

This message is sent as any other bittorrent message, with a 4 byte length +prefix and a single byte identifying the message (the single byte being 20 +in this case). At the start of the payload of the message, is a single byte +message identifier. This identifier can refer to different extension messages +and only one ID is specified, 0. If the ID is 0, the message is a handshake +message which is described below. The layout of a general extended message +follows (including the message headers used by the bittorrent protocol):

+ ++++ + + + + + + + + + + + + + + + + +
sizedescription
uint32_tlength prefix. Specifies the number of bytes for the +entire message. (Big endian)
uint8_tbittorrent message ID, = 20
uint8_textended message ID. 0 = handshake, >0 = extended +message as specified by the handshake.
+
+

handshake message

+

The payload of the handshake message is a bencoded dictionary. All items +in the dictionary are optional. Any unknown names should be ignored +by the client. All parts of the dictionary are case sensitive. +This is the defined item in the dictionary:

+ ++++ + + + + + + + + + + +
namedescription
mDictionary of supported extension messages which maps +names of extensions to identification numbers of each +extension. The only requirement on the identification +numbers is that no extensions share the same. Setting +an extension number to zero means that the extension is +not supported/disabled. The client should ignore any +extension names it doesn't recognize.
+

Here are two other items that an implementation may choose to support:

+ ++++ + + + + + + + + + + + + + +
namedescription
pLocal TCP listen port. Allows each side to learn about +the TCP port number of the other side. Note that there is +no need for the receiving side of the connection to send +this extension message, since its port number is already +known.
vClient name and version (as an utf-8 string). +This is a much more reliable way of identifying the +client than relying on the peer id encoding.
+

The handshake dictionary could also include extended handshake +information, such as support for encrypted headers or anything +imaginable.

+

An example of what the payload of a handshake message could look like:

+ ++++ + + + + + + + + + + + + + + + +
Dictionary
m ++++ + + + + + + + + + + + + +
Dictionary
LT_metadata1
µT_PEX2
+
p6881
v"µTorrent 1.2"
+

and in the encoded form:

+

d1:md11:LT_metadatai1e6:µT_PEXi2ee1:pi6881e1:v13:\xc2\xb5Torrent 1.2e

+

To make sure the extension names do not collide by mistake, they should be +prefixed with the two (or one) character code that is used to identify the +client that introduced the extension. This applies for both the names of +extension messages, and for any additional information put inside the +top-level dictionary. All one and two byte identifiers are invalid to use +unless defined by this specification.

+

This message should be sent immediately after the standard bittorrent handshake +to any peer that supports this extension protocol. It is valid to send the +handshake message more than once during the lifetime of a connection, +the sending client should not be disconnected. An implementation may choose +to ignore the subsequent handshake messages (or parts of them).

+

Subsequent handshake messages can be used to enable/disable extensions +without restarting the connection. If a peer supports changing extensions +at run time, it should note that the m dictionary is additive. +It's enough that it contains the actual CHANGES to the extension list. +To disable the support for LT_metadata at run-time, without affecting +any other extensions, this message should be sent: +d11:LT_metadatai0ee. +As specified above, the value 0 is used to turn off an extension.

+

The extension IDs must be stored for every peer, becuase every peer may have +different IDs for the same extension.

+

This specification, deliberately, does not specify any extensions such as +peer-exchange or metadata exchange. This protocol is merely a transport +for the actual extensions to the bittorrent protocol and the extensions +named in the example above (such as p) are just examples of possible +extensions.

+
+
+

rationale

+

The reason why the extension messages' IDs would be defined in the handshake +is to avoid having a global registry somewhere, where ID's are assigned +global identifiers. Now the extensions have unique names.

+

If the client supporting the extensions can decide which numbers the messages +it receives will have, it means they are constants within that client. i.e. +they can be used in switch statements. It's easy for the other end to +store an array with the ID's we expect for each message and use that for +lookups each time it sends an extension message.

+

The reason for having a dictionary instead of having an array (using +implicitly assigned index numbers to the extensions) is that if a client +want to disable some extensions, the ID numbers would change, and it wouldn't +be able to use constants (and hence, not use them in a switch). If the +messages IDs would map directly to bittorrent message IDs, It would also make +it possible to map extensions in the handshake to existing extensions with +fixed message IDs.

+

The reasoning behind having a single byte as extended message identifier is +to follow the the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously.

+

The reason for using single byte identifiers for the standardized handshake +identifiers is 1) The mainline DHT uses single byte identifiers. 2) Saves +bandwidth. The only advantage of longer messages is that it makes the +protocol more readable for a human, but the BT protocol wasn't designed to +be a human readable protocol, so why bother.

+
+
+
+ + diff --git a/docs/extension_protocol.rst b/docs/extension_protocol.rst new file mode 100644 index 000000000..22d024b8f --- /dev/null +++ b/docs/extension_protocol.rst @@ -0,0 +1,179 @@ +:Author: Arvid Norberg, arvid@rasterbar.com + Ludvig Strigeus, ludde@utorrent.com + +extension protocol for bittorrent +================================= + +The intention of this protocol is to provide a simple and thin transport +for extensions to the bittorrent protocol. Supporting this protocol makes +it easy to add new extensions without interfering with the standard +bittorrent protocol or clients that don't support this extension or the +one you want to add. + +To advertise to other clients that you support, one bit from the reserved +bytes is used. + +Right now, two bits have known usages. + * [7] & 1 is used by Mainline for DHT support + * [7] & 2 is used by XBT client for peer-exchange support + +The bit selected for the extension protocol is bit 20 from the right (counting +starts at 0). So (reserved_byte[5] & 0x10) is the expression to use for checking +if the client supports extended messaging. + +Once support for the protocol is established, the client is supposed to +support 1 new message: + ++------------------------+----+ +|name | id | ++========================+====+ +|``extended`` | 20 | ++------------------------+----+ + +This message is sent as any other bittorrent message, with a 4 byte length +prefix and a single byte identifying the message (the single byte being 20 +in this case). At the start of the payload of the message, is a single byte +message identifier. This identifier can refer to different extension messages +and only one ID is specified, 0. If the ID is 0, the message is a handshake +message which is described below. The layout of a general ``extended`` message +follows (including the message headers used by the bittorrent protocol): + ++----------+---------------------------------------------------------+ +| size | description | ++==========+=========================================================+ +| uint32_t | length prefix. Specifies the number of bytes for the | +| | entire message. (Big endian) | ++----------+---------------------------------------------------------+ +| uint8_t | bittorrent message ID, = 20 | ++----------+---------------------------------------------------------+ +| uint8_t | extended message ID. 0 = handshake, >0 = extended | +| | message as specified by the handshake. | ++----------+---------------------------------------------------------+ + + +handshake message +----------------- + +The payload of the handshake message is a bencoded dictionary. All items +in the dictionary are optional. Any unknown names should be ignored +by the client. All parts of the dictionary are case sensitive. +This is the defined item in the dictionary: + ++-------+-----------------------------------------------------------+ +| name | description | ++=======+===========================================================+ +| m | Dictionary of supported extension messages which maps | +| | names of extensions to identification numbers of each | +| | extension. The only requirement on the identification | +| | numbers is that no extensions share the same. Setting | +| | an extension number to zero means that the extension is | +| | not supported/disabled. The client should ignore any | +| | extension names it doesn't recognize. | ++-------+-----------------------------------------------------------+ + + +Here are two other items that an implementation may choose to support: + ++-------+-----------------------------------------------------------+ +| name | description | ++=======+===========================================================+ +| p | Local TCP listen port. Allows each side to learn about | +| | the TCP port number of the other side. Note that there is | +| | no need for the receiving side of the connection to send | +| | this extension message, since its port number is already | +| | known. | ++-------+-----------------------------------------------------------+ +| v | Client name and version (as an utf-8 string). | +| | This is a much more reliable way of identifying the | +| | client than relying on the peer id encoding. | ++-------+-----------------------------------------------------------+ + +The handshake dictionary could also include extended handshake +information, such as support for encrypted headers or anything +imaginable. + +An example of what the payload of a handshake message could look like: + ++------------------------------------------------------+ +| Dictionary | ++===================+==================================+ +| ``m`` | +--------------------------+ | +| | | Dictionary | | +| | +======================+===+ | +| | | ``LT_metadata`` | 1 | | +| | +----------------------+---+ | +| | | ``µT_PEX`` | 2 | | +| | +----------------------+---+ | +| | | ++-------------------+----------------------------------+ +| ``p`` | 6881 | ++-------------------+----------------------------------+ +| ``v`` | "µTorrent 1.2" | ++-------------------+----------------------------------+ + +and in the encoded form: + +``d1:md11:LT_metadatai1e6:µT_PEXi2ee1:pi6881e1:v13:\xc2\xb5Torrent 1.2e`` + +To make sure the extension names do not collide by mistake, they should be +prefixed with the two (or one) character code that is used to identify the +client that introduced the extension. This applies for both the names of +extension messages, and for any additional information put inside the +top-level dictionary. All one and two byte identifiers are invalid to use +unless defined by this specification. + +This message should be sent immediately after the standard bittorrent handshake +to any peer that supports this extension protocol. It is valid to send the +handshake message more than once during the lifetime of a connection, +the sending client should not be disconnected. An implementation may choose +to ignore the subsequent handshake messages (or parts of them). + +Subsequent handshake messages can be used to enable/disable extensions +without restarting the connection. If a peer supports changing extensions +at run time, it should note that the ``m`` dictionary is additive. +It's enough that it contains the actual *CHANGES* to the extension list. +To disable the support for ``LT_metadata`` at run-time, without affecting +any other extensions, this message should be sent: +``d11:LT_metadatai0ee``. +As specified above, the value 0 is used to turn off an extension. + +The extension IDs must be stored for every peer, becuase every peer may have +different IDs for the same extension. + +This specification, deliberately, does not specify any extensions such as +peer-exchange or metadata exchange. This protocol is merely a transport +for the actual extensions to the bittorrent protocol and the extensions +named in the example above (such as ``p``) are just examples of possible +extensions. + +rationale +--------- + +The reason why the extension messages' IDs would be defined in the handshake +is to avoid having a global registry somewhere, where ID's are assigned +global identifiers. Now the extensions have unique names. + +If the client supporting the extensions can decide which numbers the messages +it receives will have, it means they are constants within that client. i.e. +they can be used in ``switch`` statements. It's easy for the other end to +store an array with the ID's we expect for each message and use that for +lookups each time it sends an extension message. + +The reason for having a dictionary instead of having an array (using +implicitly assigned index numbers to the extensions) is that if a client +want to disable some extensions, the ID numbers would change, and it wouldn't +be able to use constants (and hence, not use them in a ``switch``). If the +messages IDs would map directly to bittorrent message IDs, It would also make +it possible to map extensions in the handshake to existing extensions with +fixed message IDs. + +The reasoning behind having a single byte as extended message identifier is +to follow the the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously. + +The reason for using single byte identifiers for the standardized handshake +identifiers is 1) The mainline DHT uses single byte identifiers. 2) Saves +bandwidth. The only advantage of longer messages is that it makes the +protocol more readable for a human, but the BT protocol wasn't designed to +be a human readable protocol, so why bother. diff --git a/docs/manual.html b/docs/manual.html index 54cc2ab24..690f1210f 100755 --- a/docs/manual.html +++ b/docs/manual.html @@ -3,10 +3,130 @@ - + libtorrent manual - +
@@ -19,148 +139,154 @@ Arvid Norberg, arvid@rasterbar.com -
-

Table of contents

+
+

Table of 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 @@ -182,6 +308,7 @@ following features:

  • serves multiple torrents on a single port and a single thread
  • supports http proxies and proxy authentication
  • gzipped tracker-responses
  • +
  • HTTP seeding, as specified by Michael Burford of GetRight.
  • piece picking on block-level like in Azureus (as opposed to piece-level). This means it can download parts of the same piece from different peers. It will also prefer to download whole pieces from single peers if the @@ -216,8 +343,10 @@ want to download.
  • ip filter
  • -

    libtorrent is portable at least among Windows, MacOS X and other UNIX-systems. It uses Boost.Thread, -Boost.Filesystem, Boost.Date_time and various other boost libraries as well as zlib.

    +

    libtorrent is portable at least among Windows, MacOS X and other UNIX-systems. +It uses Boost.Thread, Boost.Filesystem, Boost.Date_time and various other +boost libraries as well as zlib (shipped) and asio (shipped). At least version +1.33.1 of boost is required.

    libtorrent has been successfully compiled and tested on:

      @@ -237,16 +366,16 @@ Boost.Filesystem, Boost.Date_time and various other boost libraries as well as z

    libtorrent is released under the BSD-license.

    -
    -

    downloading and building

    +
    +

    downloading and building

    To acquire the latest version of libtorrent, you'll have to grab it from CVS. You'll find instructions on how to do this here (see Anonymous CVS access).

    The build systems supported "out of the box" in libtorrent are boost-build v2 (BBv2) and autotools (for unix-like systems). If you still can't build after following these instructions, you can usually get help in the #libtorrent IRC channel on irc.freenode.net.

    -
    -

    building with BBv2

    +
    +

    building with BBv2

    The primary reason to use boost-build is that it will automatically build the dependent boost libraries with the correct compiler settings, in order to ensure that the build targets are link compatible (see boost guidelines @@ -255,8 +384,8 @@ for some details on this issue).

    source package. Having boost installed via some package system is usually not enough (and even if it is enough, the necessary environment variables are usually not set by the package installer).

    -
    -

    Step 1: Download boost

    +
    +

    Step 1: Download boost

    You'll find boost here.

    Extract the archive to some directory where you want it. For the sake of this guide, let's assume you extract the package to c:\boost_1_33_0 (I'm using @@ -266,8 +395,8 @@ in order to build libtorrent.

    If you use 1.32, you need to download BBv2 separately, so for now, let's assume you will use version 1.33.

    -
    -

    Step 2: Setup BBv2

    +
    +

    Step 2: Setup BBv2

    First you need to build bjam. You do this by opening a terminal (In windows, run cmd). Change directory to c:\boost_1_33_0\tools\build\jam_src. Then run the script called @@ -313,8 +442,8 @@ using darwin : 4.0 : g++-4.0 ;

    Note that the spaces around the semi-colons and colons are important!

    -
    -

    Step 3: Building libtorrent

    +
    +

    Step 3: Building libtorrent

    When building libtorrent, the Jamfile expects the environment variable BOOST_ROOT to be set to the boost installation directory. It uses this to find the boost libraries it depends on, so they can be built and their headers @@ -373,14 +502,17 @@ For more build configuration flags see debug_vlog - debug version with verbose logging +

    When building the example client on windows, you need to build with +link=static otherwise you may get unresolved external symbols for some +boost.program-options symbols.

    -
    -

    building with autotools

    +
    +

    building with autotools

    First of all, you need to install automake and autoconf. Many unix/linux systems comes with these preinstalled.

    -
    -

    Step 1: Running configure

    +
    +

    Step 1: Running configure

    In your shell, change directory to the libtorrent directory and run ./configure. This will look for libraries and C++ features that libtorrent is dependent on. If something is missing or can't be found it will print an @@ -420,8 +552,8 @@ checking for main in -lboost_thread... yes directory contains spaces. Make sure you either rename the directories with spaces in their names to remove the spaces or move the libtorrent directory.

    -
    -

    Creating a debug build

    +
    +

    Creating a debug build

    To tell configure to build a debug version (with debug info, asserts and invariant checks enabled), you have to run the configure script with the following option:

    @@ -429,8 +561,8 @@ with the following option:

    ./configure --enable-debug=yes
    -
    -

    Creating a release build

    +
    +

    Creating a release build

    To tell the configure to build a release version (without debug info, asserts and invariant checks), you have to run the configure script with the following option:

    @@ -439,8 +571,8 @@ with the following option:

    The above option make use of -DNDEBUG, which is used throughout libtorrent.

    -
    -

    Step 2: Building libtorrent

    +
    +

    Step 2: Building libtorrent

    Once the configure script is run successfully, you just type make and libtorrent, the examples and the tests will be built.

    When libtorrent is built it may be a good idea to run the tests, you do this @@ -453,8 +585,8 @@ make clean make

    -
    -

    generating the build system

    +
    +

    generating the build system

    No build system is present if libtorrent is checked out from CVS - it needs to be generated first.

    Execute the following commands to generate the build system:

    @@ -469,8 +601,8 @@ needs to be generated first.

    libtorrent. This was described earlier.

    -
    -

    building with other build systems

    +
    +

    building with other build systems

    If you're making your own project file, note that there are two versions of the file abstraction. There's one file_win.cpp which relies on windows file API that supports files larger than 2 Gigabytes. This does not work in @@ -484,8 +616,8 @@ filenames, so if your target is Windows 2000 and up, you may want to use options "force conformance in for loop scope", "treat wchar_t as built-in type" and "Enable Run-Time Type Info" to Yes.

    -
    -

    build configurations

    +
    +

    build configurations

    By default libtorrent is built In debug mode, and will have pretty expensive invariant checks and asserts built into it. If you want to disable such checks (you want to do that in a release build) you can see the table below for which @@ -496,8 +628,8 @@ defines you can use to control the build.

    -macro -description +macro +description @@ -564,13 +696,13 @@ definately help to define NDEBUG< within the library.

    -
    -

    overview

    +
    +

    overview

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

    The basic usage is as follows:

      -
    • conststruct a session

      +
    • construct a session

    • parse .torrent-files and add them to the session (see bdecode() bencode())

    • @@ -591,8 +723,8 @@ the session, it conta

    Each class and function is described in this manual.

    -
    -

    session

    +
    +

    session

    The session class has the following synopsis:

     class session: public boost::noncopyable
    @@ -651,8 +783,8 @@ class session: public boost::noncopyable
     

    Once it's created, the session object will spawn the main thread that will do all the work. The main thread will be idle as long it doesn't have any torrents to participate in.

    -
    -

    session()

    +
    +

    session()

     session(fingerprint const& print = libtorrent::fingerprint("LT", 0, 1, 0, 0));
    @@ -670,16 +802,16 @@ The other constructor, that takes a port range and an interface as well as the f
     will automatically try to listen on a port on the given interface. For more information about
     the parameters, see listen_on() function.

    -
    -

    ~session()

    +
    +

    ~session()

    The destructor of session will notify all trackers that our torrents have been shut down. If some trackers are down, they will time out. All this before the destructor of session returns. So, it's adviced that any kind of interface (such as windows) are closed before destructing the sessoin object. Because it can take a few second for it to finish. The timeout can be set with set_http_settings().

    -
    -

    add_torrent()

    +
    +

    add_torrent()

     torrent_handle add_torrent(
    @@ -725,8 +857,8 @@ about the torrent's progress, its peers etc. It is also used to abort a torrent.
     the metadata extension. For the overload to be available, libtorrent must be built
     with extensions enabled (TORRENT_ENABLE_EXTENSIONS defined).

    -
    -

    remove_torrent()

    +
    +

    remove_torrent()

     void remove_torrent(torrent_handle const& h);
    @@ -735,8 +867,8 @@ void remove_torrent(torrent_handle const& h);
     

    remove_torrent() will close all peer connections associated with the torrent and tell the tracker that we've stopped participating in the swarm.

    -
    -

    disable_extensions() enable_extension()

    +
    +

    disable_extensions() enable_extension()

     void disable_extensions();
    @@ -760,8 +892,8 @@ enum extension_index
     

    By default, all extensions are enabled. For more information about the extensions, see the extensions section.

    -
    -

    set_upload_rate_limit() set_download_rate_limit()

    +
    +

    set_upload_rate_limit() set_download_rate_limit()

     void set_upload_rate_limit(int bytes_per_second);
    @@ -774,8 +906,8 @@ you don't want to limit upload rate, you can set this to -1 (the default).
     set_download_rate_limit() works the same way but for download rate instead
     of upload rate.

    -
    -

    set_max_uploads() set_max_connections()

    +
    +

    set_max_uploads() set_max_connections()

     void set_max_uploads(int limit);
    @@ -788,8 +920,8 @@ minimum of at least two connections per torrent, so if you set a too low
     connections limit, and open too many torrents, the limit will not be met. The
     number of uploads is at least one per torrent.

    -
    -

    set_max_half_open_connections()

    +
    +

    set_max_half_open_connections()

     void set_max_half_open_connections(int limit);
    @@ -805,8 +937,8 @@ and passing -1 as the limit, means to have no limit. When limiting the number
     of simultaneous connection attempts, peers will be put in a queue waiting for
     their turn to get connected.

    -
    -

    set_ip_filter()

    +
    +

    set_ip_filter()

     void set_ip_filter(ip_filter const& filter);
    @@ -817,8 +949,8 @@ connections based on their originating ip address. The default filter will allow
     connections to any ip address. To build a set of rules for which addresses are
     accepted and not, see ip_filter.

    -
    -

    status()

    +
    +

    status()

     session_status status() const;
    @@ -857,8 +989,8 @@ uploaded to and from all torrents. num_peers is the total number of peer connections this session have.

    -
    -

    is_listening() listen_port() listen_on()

    + -
    -

    pop_alert() set_severity_level()

    +
    +

    pop_alert() set_severity_level()

     std::auto_ptr<alert> pop_alert();
    @@ -899,8 +1031,8 @@ void set_severity_level(alert::severity_t s);
     receive it through pop_alert(). For information, see alerts.

    -
    -

    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:

    @@ -951,20 +1083,20 @@ public: dictionary_type const& dict() const; // these functions requires that the entry - // is a dictionary, otherwise they will throw + // is a dictionary, otherwise they will throw entry& operator[](char const* key); entry& operator[](std::string const& key); entry const& operator[](char const* key) const; entry const& operator[](std::string const& key) const; entry* find_key(char const* key); entry const* find_key(char const* key) const; - + void print(std::ostream& os, int indent = 0) const; };

    TODO: finish documentation of entry.

    -
    -

    integer() string() list() dict() type()

    +
    +

    integer() string() list() dict() type()

     integer_type& integer();
    @@ -1016,8 +1148,8 @@ if (entry* i = torrent_file.find_key("announce"))
     

    To make it easier to extract information from a torrent file, the class torrent_info exists.

    -
    -

    operator[]

    +
    +

    operator[]

     entry& operator[](char const* key);
    @@ -1035,8 +1167,8 @@ given key, a reference to a newly inserted element at that key.

    existing element at the given key. If the key is not found, it will throw libtorrent::type_error.

    -
    -

    find_key()

    +
    +

    find_key()

     entry* find_key(char const* key);
    @@ -1050,8 +1182,8 @@ element cannot be found, they will return 0. If an element with the given
     key is found, the return a pointer to it.

    -
    -

    torrent_info

    +
    +

    torrent_info

    The torrent_info has the following synopsis:

     class torrent_info
    @@ -1069,6 +1201,7 @@ public:
             void set_hash(int index, sha1_hash const& h);
             void add_tracker(std::string const& url, int tier = 0);
             void add_file(boost::filesystem::path file, size_type size);
    +        void add_url_seed(std::string const& url);
     
             typedef std::vector<file_entry>::const_iterator file_iterator;
             typedef std::vector<file_entry>::const_reverse_iterator
    @@ -1082,8 +1215,15 @@ public:
             int num_files() const;
             file_entry const& file_at(int index) const;
     
    +        std::vector<file_slice> map_block(int piece, size_type offset
    +                , int size) const;
    +        peer_request map_file(int file_index, size_type file_offset
    +                , int size) const;
    +
             std::vector<announce_entry> const& trackers() const;
     
    +        std::vector<std::string> const& url_seeds() const;
    +
             size_type total_size() const;
             size_type piece_length() const;
             int num_pieces() const;
    @@ -1102,8 +1242,8 @@ public:
             sha1_hash const& hash_for_piece(unsigned int index) const;
     };
     
    -
    -

    torrent_info()

    +
    +

    torrent_info()

     torrent_info();
    @@ -1126,8 +1266,8 @@ object from the information found in the given torrent_file. The entry, use bdecode(),
     see bdecode() bencode().

    -
    -

    set_comment() set_piece_size() set_creator() set_hash() add_tracker() add_file()

    +
    +

    set_comment() set_piece_size() set_creator() set_hash() add_tracker() add_file()

     void set_comment(char const* str);
    @@ -1159,8 +1299,8 @@ of the torrent. The sizecreate_torrent().

    For a complete example of how to create a torrent from a file structure, see make_torrent.

    -
    -

    create_torrent()

    +
    +

    create_torrent()

     entry create_torrent();
    @@ -1172,8 +1312,8 @@ complete example, see make_torrent
     

    This function is not const because it will also set the info-hash of the torrent_info object.

    -
    -

    begin_files() end_files() rbegin_files() rend_files()

    +
    +

    begin_files() end_files() rbegin_files() rend_files()

     file_iterator begin_files() const;
    @@ -1197,8 +1337,8 @@ struct file_entry
     };
     
    -
    -

    num_files() file_at()

    +
    +

    num_files() file_at()

     int num_files() const;
    @@ -1208,8 +1348,76 @@ file_entry const& file_at(int index) const;
     

    If you need index-access to files you can use the num_files() and file_at() to access files using indices.

    -
    -

    print()

    +
    +

    map_block()

    +
    +
    +std::vector<file_slice> map_block(int piece, size_type offset
    +        , int size) const;
    +
    +
    +

    This function will map a piece index, a byte offset within that piece and +a size (in bytes) into the corresponding files with offsets where that data +for that piece is supposed to be stored.

    +

    The file slice struct looks like this:

    +
    +struct file_slice
    +{
    +        int file_index;
    +        size_type offset;
    +        size_type size;
    +};
    +
    +

    The file_index refers to the index of the file (in the torrent_info). +To get the path and filename, use file_at() and give the file_index +as argument. The offset is the byte offset in the file where the range +starts, and size is the number of bytes this range is. The size + offset +will never be greater than the file size.

    +
    +
    +

    map_file()

    +
    +
    +peer_request map_file(int file_index, size_type file_offset
    +        , int size) const;
    +
    +
    +

    This function will map a range in a specific file into a range in the torrent. +The file_offset parameter is the offset in the file, given in bytes, where +0 is the start of the file. +The peer_request structure looks like this:

    +
    +struct peer_request
    +{
    +        int piece;
    +        int start;
    +        int length;
    +        bool operator==(peer_request const& r) const;
    +};
    +
    +

    piece is the index of the piece in which the range starts. +start is the offset within that piece where the range starts. +length is the size of the range, in bytes.

    +

    The input range is assumed to be valid within the torrent. file_offset ++ size is not allowed to be greater than the file size. file_index +must refer to a valid file, i.e. it cannot be >= num_files().

    +
    +
    +

    url_seeds()

    +
    +
    +std::vector<std::string> const& url_seeds() const;
    +void add_url_seed(std::string const& url);
    +
    +
    +

    If there are any url-seeds in this torrent, url_seeds() will return a +vector of those urls. If you're creating a torrent file, add_url_seed() +adds one url to the list of url-seeds. Currently, the only transport protocol +supported for the url is http.

    +

    See HTTP seeding for more information.

    +
    +
    +

    print()

     void print(std::ostream& os) const;
    @@ -1218,8 +1426,8 @@ void print(std::ostream& os) const;
     

    The print() function is there for debug purposes only. It will print the info from the torrent file to the given outstream.

    -
    -

    trackers()

    +
    +

    trackers()

     std::vector<announce_entry> const& trackers() const;
    @@ -1238,8 +1446,8 @@ struct announce_entry
     };
     
    -
    -

    total_size() piece_length() piece_size() num_pieces()

    +
    +

    total_size() piece_length() piece_size() num_pieces()

     size_type total_size() const;
    @@ -1256,8 +1464,8 @@ the piece index as argument and gives you the exact size of that piece. It will
     be the same as piece_length() except in the case of the last piece, which may
     be smaller.

    -
    -

    hash_for_piece() info_hash()

    +
    +

    hash_for_piece() info_hash()

     size_type piece_size(unsigned int index) const;
    @@ -1266,10 +1474,12 @@ sha1_hash const& hash_for_piece(unsigned int index) const;
     

    hash_for_piece() takes a piece-index and returns the 20-bytes sha1-hash for that piece and info_hash() returns the 20-bytes sha1-hash for the info-section of the -torrent file. For more information on the sha1_hash, see the big_number class.

    +torrent file. For more information on the sha1_hash, see the big_number class. +info_hash() will only return a valid hash if the torrent_info was read from a +.torrent file or if an entry was created from it (through create_torrent).

    -
    -

    name() comment() creation_date() creator()

    +
    +

    name() comment() creation_date() creator()

     std::string const& name() const;
    @@ -1287,8 +1497,8 @@ in the torrent file, this will return a date of january 1:st 1970.

    it will return an empty string.

    -
    -

    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:

    @@ -1306,7 +1516,7 @@ struct torrent_handle entry write_resume_data() const; std::vector<char> const& metadata() const; void force_reannounce() const; - void connect_peer(address const& adr) const; + void connect_peer(asio::ipv4::tcp::endpoint const& adr) const; void set_tracker_login(std::string const& username , std::string const& password) const; @@ -1314,11 +1524,17 @@ struct torrent_handle std::vector<announce_entry> const& trackers() const; void replace_trackers(std::vector<announce_entry> const&); + void add_url_seed(std::string const& url); + void set_ratio(float ratio) const; void set_max_uploads(int max_uploads) const; void set_max_connections(int max_connections) const; void set_upload_limit(int limit) const; void set_download_limit(int limit) const; + + void set_peer_upload_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + void set_peer_download_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + void use_interface(char const* net_interface) const; void pause() const; @@ -1349,8 +1565,8 @@ struct torrent_handle perform any operation on it, unless you first assign it a valid handle. If you try to perform any operation on an uninitialized handle, it will throw invalid_handle.

    TODO: document filter_piece(), filter_pieces(), is_piece_filtered(), filtered_pieces() and filter_files()

    -
    -

    save_path()

    +
    +

    save_path()

     boost::filsystem::path save_path() const;
    @@ -1359,8 +1575,8 @@ boost::filsystem::path save_path() const;
     

    save_path() returns the path that was given to add_torrent() when this torrent was started.

    -
    -

    move_storage()

    +
    +

    move_storage()

     bool move_storage(boost::filsystem::path const& save_path) const;
    @@ -1372,8 +1588,8 @@ the same drive as the original save path. If the move operation fails, this func
     returns false, otherwise true. Post condition for successful operation is:
     save_path() == save_path.

    -
    -

    force_reannounce()

    +
    +

    force_reannounce()

     void force_reannounce() const;
    @@ -1383,11 +1599,11 @@ void force_reannounce() const;
     peers. If the torrent is invalid, queued or in checking mode, this functions will throw
     invalid_handle.

    -
    -

    connect_peer()

    +
    +

    connect_peer()

    -void connect_peer(address const& adr) const;
    +void connect_peer(asio::ipv4::tcp::endpoint const& adr) const;
     

    connect_peer() is a way to manually connect to peers that one believe is a part of the @@ -1396,8 +1612,8 @@ be disconnected. No harm can be done by using this other than an unnecessary con attempt is made. If the torrent is uninitialized or in queued or checking mode, this will throw invalid_handle.

    -
    -

    set_ratio()

    +
    +

    set_ratio()

     void set_ratio(float ratio) const;
    @@ -1411,8 +1627,8 @@ attempt to upload in return for each download. e.g. if set to 2, the client will
     2 bytes for every byte received. The default setting for this is 0, which will make it work
     as a standard client.

    -
    -

    set_upload_limit() set_download_limit()

    +
    +

    set_upload_limit() set_download_limit()

     void set_upload_limit(int limit) const;
    @@ -1426,8 +1642,19 @@ Note that setting a higher limit on a torrent then the global limit (
    -

    pause() resume() is_paused()

    +
    +

    set_peer_upload_limit() set_peer_download_limit()

    +
    +
    +void set_peer_upload_limit(asio::ipv4::tcp::endpoint ip, int limit) const;
    +void set_peer_download_limit(asio::ipv4::tcp::endpoint ip, int limit) const;
    +
    +
    +

    Works like set_upload_limit and set_download_limit respectively, but controls individual +peer instead of the whole torrent.

    +
    +
    +

    pause() resume() is_paused()

     void pause() const;
    @@ -1441,8 +1668,8 @@ all potential (not connected) peers. You can use file_error_alert.

    -
    -

    is_seed()

    +
    +

    is_seed()

     bool is_seed() const;
    @@ -1450,8 +1677,8 @@ bool is_seed() const;
     

    Returns true if the torrent is in seed mode (i.e. if it has finished downloading).

    -
    -

    has_metadata()

    +
    +

    has_metadata()

     bool has_metadata() const;
    @@ -1462,8 +1689,8 @@ metadata has been downloaded). The only scenario where this can return false is
     was started torrent-less (i.e. with just an info-hash and tracker ip). Note that if the torrent
     doesn't have metadata, the member get_torrent_info() will throw.

    -
    -

    set_tracker_login()

    +
    +

    set_tracker_login()

     void set_tracker_login(std::string const& username
    @@ -1473,8 +1700,8 @@ void set_tracker_login(std::string const& username
     

    set_tracker_login() sets a username and password that will be sent along in the HTTP-request of the tracker announce. Set this if the tracker requires authorization.

    -
    -

    trackers() replace_trackers()

    +
    +

    trackers() replace_trackers()

     std::vector<announce_entry> const& trackers() const;
    @@ -1490,8 +1717,21 @@ a list of the same form as the one returned from force_reannounce().

    -
    -

    use_interface()

    +
    +

    add_url_seed()

    +
    +
    +void add_url_seed(std::string const& url);
    +
    +
    +

    add_url_seed() adds another url to the torrent's list of url seeds. If the +given url already exists in that list, the call has no effect. The torrent +will connect to the server and try to download pieces from it, unless it's +paused, queued, checking or seeding.

    +

    See HTTP seeding for more information.

    +
    +
    +

    use_interface()

     void use_interface(char const* net_interface) const;
    @@ -1501,8 +1741,8 @@ void use_interface(char const* net_interface) const;
     connections. By default, it uses the same interface as the session uses to listen on. The
     parameter can be a string containing an ip-address or a hostname.

    -
    -

    info_hash()

    +
    +

    info_hash()

     sha1_hash info_hash() const;
    @@ -1510,8 +1750,8 @@ sha1_hash info_hash() const;
     

    info_hash() returns the info-hash for the torrent.

    -
    -

    set_max_uploads() set_max_connections()

    +
    +

    set_max_uploads() set_max_connections()

     void set_max_uploads(int max_uploads) const;
    @@ -1525,8 +1765,8 @@ connections are used up, incoming connections may be refused or poor connections
     This must be at least 2. The default is unlimited number of connections. If -1 is given to the
     function, it means unlimited.

    -
    -

    write_resume_data()

    +
    +

    write_resume_data()

     entry write_resume_data() const;
    @@ -1548,8 +1788,8 @@ not be ready to write resume data.
     is still downloading! The recommended practice is to first pause the torrent, then generate the
     fast resume data, and then close it down.

    -
    -

    metadata()

    +
    +

    metadata()

     std::vector<char> const& metadata() const;
    @@ -1559,8 +1799,8 @@ std::vector<char> const& metadata() const;
     .torrent file. This buffer will be valid as long as the torrent is still running. When hashed,
     it will produce the same hash as the info-hash.

    -
    -

    status()

    +
    +

    status()

     torrent_status status() const;
    @@ -1570,8 +1810,8 @@ torrent_status status() const;
     torrent. If the torrent_handle is invalid, it will throw invalid_handle exception.
     See torrent_status.

    -
    -

    get_download_queue()

    +
    +

    get_download_queue()

     void get_download_queue(std::vector<partial_piece_info>& queue) const;
    @@ -1607,8 +1847,8 @@ or not. And the num_downloads
     
    -
    -

    get_peer_info()

    +
    +

    get_peer_info()

     void get_peer_info(std::vector<peer_info>&) const;
    @@ -1619,8 +1859,8 @@ with one entry for each peer connected to this torrent, given the handle is vali
     torrent_handle is invalid, it will throw invalid_handle exception. Each entry in
     the vector contains information about that particular peer. See peer_info.

    -
    -

    get_torrent_info()

    +
    +

    get_torrent_info()

     torrent_info const& get_torrent_info() const;
    @@ -1633,8 +1873,8 @@ exception will be thrown. The torrent may be in a state without metadata only if
     it was started without a .torrent file, i.e. by using the libtorrent extension of
     just supplying a tracker and info-hash.

    -
    -

    is_valid()

    +
    +

    is_valid()

     bool is_valid() const;
    @@ -1649,8 +1889,8 @@ that refers to that torrent will become invalid.

    TODO: document storage

    -
    -

    torrent_status

    +
    +

    torrent_status

    It contains the following fields:

     struct torrent_status
    @@ -1681,6 +1921,7 @@ struct torrent_status
             size_type total_payload_upload;
     
             size_type total_failed_bytes;
    +        size_type total_redundant_bytes;
     
             float download_rate;
             float upload_rate;
    @@ -1694,6 +1935,8 @@ struct torrent_status
             int num_incomplete;
     
             const std::vector<bool>* pieces;
    +        int num_pieces;
    +
             size_type total_done;
             size_type total_wanted_done;
             size_type total_wanted;
    @@ -1763,9 +2006,21 @@ data), these counters ignore any protocol overhead.

    total_failed_bytes is the number of bytes that has been downloaded and that has failed the piece hash test. In other words, this is just how much crap that has been downloaded.

    +

    total_redundant_bytes is the number of bytes that has been downloaded even +though that data already was downloaded. The reason for this is that in some +situations the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a certain +timeout, libtorrent will re-request that block. Another situation when +libtorrent will re-request blocks is when the requests it sends out are not +replyed in FIFO-order (it will re-request blocks that are skipped by an out of +order block). This is supposed to be as low as possible.

    pieces is the bitmask that represents which pieces we have (set to true) and the pieces we don't have. It's a pointer and may be set to 0 if the torrent isn't downloading or seeding.

    +

    num_pieces is the number of pieces that has been downloaded. It is equivalent +to: std::accumulate(pieces->begin(), pieces->end()). So you don't have to +count yourself. This can be used to see if anything has updated since last time +if you want to keep a graph of the pieces up to date.

    download_rate and upload_rate are the total rates for all peers for this torrent. These will usually have better precision than summing the rates from all peers. The rates are given as the number of bytes per second. The @@ -1805,8 +2060,8 @@ bytes that each bit in the partia (see get_download_queue()). This is typically 16 kB, but it may be larger if the pieces are larger.

    -
    -

    peer_info

    +
    +

    peer_info

    It contains the following fields:

     struct peer_info
    @@ -1819,22 +2074,23 @@ struct peer_info
                     remote_choked = 0x8,
                     supports_extensions = 0x10,
                     local_connection = 0x20,
    -                connecting = 0x40,
    -                queued = 0x80
    +                handshake = 0x40,
    +                connecting = 0x80,
    +                queued = 0x100
             };
             unsigned int flags;
    -        address ip;
    +        asio::ipv4::tcp::endpoint ip;
             float up_speed;
             float down_speed;
             float payload_up_speed;
             float payload_down_speed;
             size_type total_download;
             size_type total_upload;
    -        peer_id id;
    +        peer_id pid;
             std::vector<bool> pieces;
             bool seed;
             int upload_limit;
    -        int upload_ceiling;
    +        int download_limit;
     
             size_type load_balancing;
     
    @@ -1845,6 +2101,15 @@ struct peer_info
             int downloading_block_index;
             int downloading_progress;
             int downloading_total;
    +
    +        std::string client;
    +
    +        enum
    +        {
    +                standard_bittorrent = 0,
    +                web_seed = 1
    +        };
    +        int connection_type;
     };
     

    The flags attribute tells you in which state the peer is. It is set to @@ -1874,10 +2139,15 @@ any combination of the enums above. The following table describes each flag:

    local_connection The connection was initiated by us, the peer has a listen port open, and that port is the same as in the -address of this peer. If this flag is not set, this +address of this peer. If this flag is not set, this peer connection was opened by this peer connecting to us. +handshake +The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified. + connecting The connection is in a half-open state (i.e. it is being connected). @@ -1889,8 +2159,8 @@ the number of half-open TCP connections. -

    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.

    +

    The ip field is the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio documentation.

    up_speed and down_speed contains the current upload and download speed we have to and from this peer (including any protocol messages). The transfer rates of payload data only are found in payload_up_speed and payload_down_speed. @@ -1898,7 +2168,7 @@ These figures are updated aproximately once every second.

    total_download and total_upload are the total number of bytes downloaded from and uploaded to this peer. These numbers do not include the protocol chatter, but only the payload data.

    -

    id is the peer's id as used in the bit torrent protocol. This id can be used to +

    pid is the peer's id as used in the bit torrent protocol. This id can be used to extract 'fingerprints' from the peer. Sometimes it can tell you which client the peer is using. See identify_client()_

    pieces is a vector of booleans that has as many entries as there are pieces @@ -1908,9 +2178,8 @@ or if the peer miss that piece (set to false).

    upload_limit is the number of bytes per second we are allowed to send to this peer every second. It may be -1 if there's no limit. The upload limits of all peers should sum up to the upload limit set by session::set_upload_limit.

    -

    upload_ceiling is the current maximum allowed upload rate given the cownload -rate and share ratio. If the global upload rate is inlimited, the upload_limit -for every peer will be the same as their upload_ceiling.

    +

    download_limit is the number of bytes per second this peer is allowed to +receive. -1 means it's unlimited.

    load_balancing is a measurment of the balancing of free download (that we get) and free upload that we give. Every peer gets a certain amount of free upload, but this member says how much extra free upload this peer has got. If it is a negative @@ -1928,42 +2197,16 @@ This may be set to -1 if there's currently no piece downloading from this peer. block (or sub-piece) that is being downloaded. downloading_progress is the number of bytes of this block we have received from the peer, and downloading_total is the total number of bytes in this block.

    +

    client is a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain a string +that may give away something about which software is running in the other end. +In the case of a web seed, the server type and version will be a part of this +string.

    +

    connection_type can currently be one of standard_bittorrent or +web_seed. These are currently the only implemented protocols.

    -
    -

    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:

    -
    -class address
    -{
    -public:
    -        address();
    -        address(unsigned char a
    -                , unsigned char b
    -                , unsigned char c
    -                , unsigned char d
    -                , unsigned short  port);
    -        address(unsigned int addr, unsigned short port);
    -        address(const std::string& addr, unsigned short port);
    -        address(const address& a);
    -        ~address();
    -
    -        std::string as_string() const;
    -        unsigned int ip() const;
    -        unsigned short port() const;
    -
    -        bool operator<(const address& a) const;
    -        bool operator!=(const address& a) const;
    -        bool operator==(const address& a) const;
    -};
    -
    -

    It is less-than comparable to make it possible to use it as a key in a map. as_string() may block -while it does the DNS lookup, it returns a string that points to the address represented by the object.

    -

    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 then use session::set_http_settings() to apply them. You have control over proxy and authorization settings and also the user-agent @@ -1977,7 +2220,8 @@ struct http_settings std::string proxy_login; std::string proxy_password; std::string user_agent; - int tracker_timeout; + int tracker_completion_timeout; + int tracker_receive_timeout; int tracker_maximum_response_length; };

    @@ -1990,10 +2234,17 @@ empty, the http proxy will be tried to be used without authentication.

    proxy_password the password string for the http proxy.

    user_agent this is the client identification to the tracker. It will be followed by the string "(libtorrent)" to identify that this library -is being used. This should be set to your client's name and version number.

    -

    tracker_timeout is the number of seconds the tracker connection will -wait until it considers the tracker to have timed-out. Default value is 10 -seconds.

    +is being used. This should be set to your client's name and version number. +This name will not only be used when making HTTP requests, but also when +sending extended headers to peers that support that extension.

    +

    tracker_completion_timeout is the number of seconds the tracker +connection will wait from when it sent the request until it considers the +tracker to have timed-out. Default value is 60 seconds.

    +

    tracker_receive_timeout is the number of seconds to wait to receive +any data from the tracker. If no data is received for this number of +seconds, the tracker will be considered as having timed out. If a tracker +is down, this is the kind of timeout that will occur. The default value +is 20 seconds.

    tracker_maximum_response_length is the maximum number of bytes in a tracker response. If a response size passes this number it will be rejected and the connection will be closed. On gzipped responses this size is measured @@ -2002,11 +2253,13 @@ expand to 2 megs, it will be interrupted before the entire response has been uncompressed (given your limit is lower than 2 megs). Default limit is 1 megabyte.

    -
    -

    ip_filter

    +
    +

    ip_filter

    The ip_filter class is a set of rules that uniquely categorizes all ip addresses as allowed or disallowed. The default constructor creates -a single rule that allowes all addresses (0.0.0.0 - 255.255.255.255).

    +a single rule that allowes all addresses (0.0.0.0 - 255.255.255.255). +The address type here is asio::ipv4::address. It can also be +accessed as libtorrent::address.

     class ip_filter
    @@ -2029,8 +2282,8 @@ public:
     };
     
    -
    -

    ip_filter()

    +
    +

    ip_filter()

     ip_filter()
    @@ -2040,8 +2293,8 @@ ip_filter()
     

    postcondition: access(x) == 0 for every x

    -
    -

    add_rule()

    +
    +

    add_rule()

     void add_rule(address first, address last, int flags);
    @@ -2056,8 +2309,8 @@ means disallowed.

    This means that in a case of overlapping ranges, the last one applied takes precedence.

    -
    -

    access()

    +
    +

    access()

     int access(address const& addr) const;
    @@ -2068,8 +2321,8 @@ can currently be 0 or ip_filter::
     is O(log n), where n is the minimum number of non-overlapping ranges to describe
     the current filter.

    -
    -

    export_filter()

    +
    +

    export_filter()

     std::vector<ip_range> export_filter() const;
    @@ -2081,8 +2334,8 @@ entry in the returned vector is a range with the access control specified in its
     flags field.

    -
    -

    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:

    @@ -2102,16 +2355,17 @@ public:
     

    The iterators gives you access to individual bytes.

    -
    -

    hasher

    +
    +

    hasher

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

     class hasher
     {
     public:
             hasher();
    +        hasher(char const* data, unsigned int len);
     
    -        void update(const char* data, unsigned int len);
    +        void update(char const* data, unsigned int len);
             sha1_hash final();
             void reset();
     };
    @@ -2121,13 +2375,15 @@ with data. i.e. you don't have to keep the entire buffer of which you want to
     create the hash in memory. You can feed the hasher parts of it at a time. When
     You have fed the hasher with all the data, you call final() and it
     will return the sha1-hash of the data.

    +

    The constructor that takes a char const* and an integer will construct the +sha1 context and feed it the data passed in.

    If you want to reuse the hasher object once you have created a hash, you have to call reset() to reinitialize it.

    The sha1-algorithm used was implemented by Steve Reid and released as public domain. For more info, see src/sha1.cpp.

    -
    -

    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:

    @@ -2139,7 +2395,7 @@ struct fingerprint std::string to_string() const; - char id[2]; + char name[2]; char major_version; char minor_version; char revision_version; @@ -2156,8 +2412,8 @@ sure not to clash with anybody else. Here are some taken id's:

    -id chars -client +id chars +client @@ -2189,10 +2445,10 @@ sure not to clash with anybody else. Here are some taken id's:

    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.

    -
    -

    free functions

    -
    -

    identify_client()

    +
    +

    free functions

    +
    +

    identify_client()

     std::string identify_client(peer_id const& id);
    @@ -2202,8 +2458,8 @@ std::string identify_client(peer_id const& id);
     to extract a string describing a client version from its peer-id. It will recognize most clients
     that have this kind of identification in the peer-id.

    -
    -

    bdecode() bencode()

    +
    +

    bdecode() bencode()

     template<class InIt> entry bdecode(InIt start, InIt end);
    @@ -2243,8 +2499,8 @@ entry e = bdecode(buf, buf + data_size);
     it will throw invalid_encoding.

    -
    -

    alerts

    +
    +

    alerts

    The pop_alert() function on session is the interface for retrieving alerts, warnings, messages and errors from libtorrent. If there hasn't occured any errors (matching your severity level) pop_alert() will @@ -2325,8 +2581,8 @@ public: have a severity level that can be used to sort them or present them to the user in different ways.

    The specific alerts, that all derives from alert, are:

    -
    -

    listen_failed_alert

    +
    +

    listen_failed_alert

    This alert is generated when none of the ports, given in the port range, to session can be opened for listening. This alert is generated as severity level fatal.

    @@ -2338,8 +2594,8 @@ struct listen_failed_alert: alert };
    -
    -

    file_error_alert

    +
    +

    file_error_alert

    If the storage fails to read or write files that it needs access to, this alert is generated and the torrent is paused. It is generated as severity level fatal.

    @@ -2348,14 +2604,14 @@ struct file_error_alert: alert
             file_error_alert(
                     const torrent_handle& h
                     , const std::string& msg);
    -                
    +
             virtual std::auto_ptr<alert> clone() const;
             torrent_handle handle;
     };
     
    -
    -

    tracker_announce_alert

    +
    +

    tracker_announce_alert

    This alert is generated each time a tracker announce is sent (or attempted to be sent). It is generated at severity level info.

    @@ -2364,14 +2620,14 @@ struct tracker_announce_alert: alert
             tracker_announce_alert(
                     const torrent_handle& h
                     , const std::string& msg);
    -                
    +
             virtual std::auto_ptr<alert> clone() const;
             torrent_handle handle;
     };
     
    -
    -

    tracker_alert

    +
    +

    tracker_alert

    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.

    @@ -2392,8 +2648,8 @@ struct tracker_alert: alert };
    -
    -

    tracker_reply_alert

    +
    +

    tracker_reply_alert

    This alert is only for informational purpose. It is generated when a tracker announce succeeds. It is generated with severity level info.

    @@ -2407,8 +2663,8 @@ struct tracker_reply_alert: alert
     };
     
    -
    -

    tracker_warning_alert

    +
    +

    tracker_warning_alert

    This alert is triggered if the tracker reply contains a warning field. Usually this means that the tracker announce was successful, but the tracker has a message to the client. The message string in the alert will contain the warning message from @@ -2424,8 +2680,23 @@ struct tracker_warning_alert: alert };

    -
    -

    hash_failed_alert

    +
    +

    url_seed_alert

    +

    This alert is generated when a HTTP seed name lookup fails. This alert is +generated as severity level warning.

    +

    It contains url to the HTTP seed that failed along with an error message.

    +
    +struct url_seed_alert: alert
    +{
    +        url_seed_alert(std::string const& h, const std::string& msg);
    +        virtual std::auto_ptr<alert> clone() const;
    +
    +        std::string url;
    +};
    +
    +
    +
    +

    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.

    @@ -2443,8 +2714,8 @@ struct hash_failed_alert: alert };
    -
    -

    peer_ban_alert

    +
    +

    peer_ban_alert

    This alert is generated when a peer is banned because it has sent too many corrupt pieces to us. It is generated at severity level info. The handle member is a torrent_handle to the torrent that this peer was a member of.

    @@ -2452,18 +2723,18 @@ to the torrent that this peer was a member of.

    struct peer_ban_alert: alert { peer_ban_alert( - address const& pip + asio::ipv4::tcp::endpoint const& pip , torrent_handle h , const std::string& msg); virtual std::auto_ptr<alert> clone() const; - address ip; + asio::ipv4::tcp::endpoint ip; torrent_handle handle; };
    -
    -

    peer_error_alert

    +
    +

    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 ip address from the alert, to identify it. This alert is generated as severity level debug.

    @@ -2471,18 +2742,18 @@ is generated as severity level de struct peer_error_alert: alert { peer_error_alert( - address const& pip + asio::ipv4::tcp::endpoint const& pip , peer_id const& pid , const std::string& msg); virtual std::auto_ptr<alert> clone() const; - address ip; + asio::ipv4::tcp::endpoint ip; peer_id id; };
    -
    -

    invalid_request_alert

    +
    +

    invalid_request_alert

    This is a debug alert that is generated by an incoming invalid piece request. The handle is a handle to the torrent the peer is a member of. ìp is the address of the peer and the request is the actual incoming request from the peer. The alert is generated as severity level @@ -2493,13 +2764,13 @@ struct invalid_request_alert: alert invalid_request_alert( peer_request const& r , torrent_handle const& h - , address const& send + , asio::ipv4::tcp::endpoint const& send , peer_id const& pid , std::string const& msg); virtual std::auto_ptr<alert> clone() const; torrent_handle handle; - address ip; + asio::ipv4::tcp::endpoint ip; peer_request request; peer_id id; }; @@ -2517,8 +2788,8 @@ struct peer_request the index of the piece it want data from, start is the offset within the piece where the data should be read, and length is the amount of data it wants.

    -
    -

    torrent_finished_alert

    +
    +

    torrent_finished_alert

    This alert is generated when a torrent switches from being a downloader to a seed. It will only be generated once per torrent. It contains a torrent_handle to the torrent in question. This alert is generated as severity level info.

    @@ -2534,8 +2805,8 @@ struct torrent_finished_alert: alert };
    -
    -

    metadata_failed_alert

    +
    +

    metadata_failed_alert

    This alert is generated when the metadata has been completely received and the info-hash failed to match it. i.e. the metadata that was received was corrupt. libtorrent will automatically retry to fetch it in this case. This is only relevant when running a @@ -2547,14 +2818,14 @@ struct metadata_received_alert: alert metadata_received_alert( const torrent_handle& h , const std::string& msg); - + virtual std::auto_ptr<alert> clone() const; torrent_handle handle; };

    -
    -

    metadata_received_alert

    +
    +

    metadata_received_alert

    This alert is generated when the metadata has been completely received and the torrent can start downloading. It is not generated on torrents that are started with metadata, but only those that needs to download it from peers (when utilizing the libtorrent extension). @@ -2565,14 +2836,14 @@ struct metadata_received_alert: alert metadata_received_alert( const torrent_handle& h , const std::string& msg); - + virtual std::auto_ptr<alert> clone() const; torrent_handle handle; };

    -
    -

    fastresume_rejected_alert

    +
    +

    fastresume_rejected_alert

    This alert is generated when a fastresume file has been passed to add_torrent but the files on disk did not match the fastresume file. The string explaints the reason why the resume file was rejected. It is generated at severity level warning.

    @@ -2610,17 +2881,17 @@ of the sending peer. address ip; }; -->
    -
    -

    dispatcher

    +
    +

    dispatcher

    TODO: describe the dispatcher mechanism

    -
    -

    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.

    @@ -2630,8 +2901,8 @@ struct invalid_handle: std::exception
     };
     
    -
    -

    duplicate_torrent

    +
    +

    duplicate_torrent

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

    @@ -2641,8 +2912,8 @@ 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
    @@ -2651,8 +2922,8 @@ 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.

    @@ -2662,8 +2933,8 @@ 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.

    @@ -2674,13 +2945,13 @@ struct invalid_torrent_file: std::exception
     
    -
    -

    examples

    +
    +

    examples

    Except for the example programs in this manual, there's also a bigger example of a (little bit) more complete client, client_test. There are separate instructions for how to use it here if you'd like to try it.

    -
    -

    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:

    @@ -2736,7 +3007,7 @@ int main(int argc, char* argv[])
                             std::cout << "  " << std::setw(11) << i->size
                                     << " " << i->path.string() << "\n";
                     }
    -                
    +
             }
             catch (std::exception& e)
             {
    @@ -2747,8 +3018,8 @@ 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>
    @@ -2785,7 +3056,7 @@ int main(int argc, char* argv[])
                     entry e = bdecode(std::istream_iterator<char>(in)
                             , std::istream_iterator<char>());
                     s.add_torrent(torrent_info(e), "");
    -                        
    +
                     // wait for the user to end
                     char a;
                     std::cin.unsetf(std::ios_base::skipws);
    @@ -2799,8 +3070,8 @@ int main(int argc, char* argv[])
     }
     
    -
    -

    make_torrent

    +
    +

    make_torrent

    Shows how to create a torrent from a directory tree:

     #include <iostream>
    @@ -2896,8 +3167,8 @@ 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 @@ -2910,8 +3181,8 @@ start from scratch on the partially downloaded pieces.

    will skip the time consuming checks. It may have to do the checking anyway, if the 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 file format is a bencoded dictionary containing the following fields:

    @@ -3006,8 +3277,8 @@ re-check is issued.
    -
    -

    threads

    +
    +

    threads

    libtorrent starts 3 threads.

      @@ -3027,8 +3298,8 @@ in its own thread to avoid stalling the main thread.
    -
    -

    storage allocation

    +
    +

    storage allocation

    There are two modes in which storage (files on disk) are allocated in libtorrent.

      @@ -3041,8 +3312,8 @@ pieces that have been downloaded. This is the default allocation mode in libtorr

      The allocation mode is selected when a torrent is started. It is passed as a boolean argument to session::add_torrent() (see add_torrent()). These two modes have different drawbacks and benefits.

      -
      -

      full allocation

      +
      +

      full allocation

      When a torrent is started in full allocation mode, the checker thread (see threads) will make sure that the entire storage is allocated, and fill any gaps with zeroes. It will of course still check for existing pieces and fast resume data. The main @@ -3066,8 +3337,8 @@ filesystems' file allocation, and reduce fragmentation.

    -
    -

    compact allocation

    +
    +

    compact allocation

    The compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. This means that pieces will be moved around to be placed at their final position in the files while downloading (to make sure the completed @@ -3113,8 +3384,8 @@ contain any piece), return that slot index.

    -
    -

    extensions

    +
    +

    extensions

    These extensions all operates within the extension protocol. The name of the extension is the name used in the extension-list packets, and the payload is the data in the extended message (not counting the @@ -3123,8 +3394,8 @@ length-prefix, message-id nor extension-id).

    handshake, it may be incompatible with future versions of the mainline bittorrent client.

    These are the extensions that are currently implemented.

    -
    -

    chat messages

    +
    +

    chat messages

    Extension name: "chat"

    The payload in the packet is a bencoded dictionary with any combination of the following entries:

    @@ -3151,8 +3422,8 @@ Any unrecognized strings should be ignored.
    -
    -

    metadata from peers

    +
    +

    metadata from peers

    Extension name: "metadata"

    The point with this extension is that you don't have to distribute the metadata (.torrent-file) separately. The metadata can be distributed @@ -3178,9 +3449,9 @@ are put as payload to the extension message. The three packets are:

    -size -name -description +size +name +description @@ -3219,9 +3490,9 @@ metadata. -size -name -description +size +name +description @@ -3259,9 +3530,9 @@ protocol packet. -size -name -description +size +name +description @@ -3276,9 +3547,19 @@ doesn't have any metadata.
    +
    +

    HTTP seeding

    +

    The HTTP seed extension implements this specification.

    +

    The libtorrent implementation assumes that, if the URL ends with a slash +('/'), the filename should be appended to it in order to request pieces from +that file. The way this works is that if the torrent is a single-file torrent, +only that filename is appended. If the torrent is a multi-file torrent, the +torrent's name '/' the file name is appended. This is the same directory +structure that libtorrent will download torrents into.

    - +
    +

    filename checks

    Boost.Filesystem will by default check all its paths to make sure they conform to filename requirements on many platforms. If you don't want this check, you can set it to either only check for native filesystem requirements or turn it off @@ -3288,8 +3569,8 @@ boost::filesystem::path::default_name_check(boost::filesystem::native);

    for example. For more information, see the Boost.Filesystem docs.

    -
    -

    acknowledgements

    +
    +

    acknowledgements

    Written by Arvid Norberg. Copyright © 2003-2005

    Contributions by Magnus Jonsson, Daniel Wallin and Cory Nelson

    Big thanks to Michael Wojciechowski and Peter Koeleman for making the autotools diff --git a/docs/manual.rst b/docs/manual.rst index 9ba358364..6a0c9be95 100755 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -30,6 +30,7 @@ following features: * serves multiple torrents on a single port and a single thread * supports http proxies and proxy authentication * gzipped tracker-responses + * `HTTP seeding`_, as `specified by Michael Burford of GetRight`__. * piece picking on block-level like in Azureus_ (as opposed to piece-level). This means it can download parts of the same piece from different peers. It will also prefer to download whole pieces from single peers if the @@ -64,13 +65,19 @@ following features: * ip filter __ http://home.elp.rr.com/tur/multitracker-spec.txt +__ http://www.getright.com/seedtorrent.html .. _Azureus: http://azureus.sourceforge.net __ extension_protocol.html __ udp_tracker_protocol.html -libtorrent is portable at least among Windows, MacOS X and other UNIX-systems. It uses Boost.Thread, -Boost.Filesystem, Boost.Date_time and various other boost libraries as well as zlib. +libtorrent is portable at least among Windows, MacOS X and other UNIX-systems. +It uses Boost.Thread, Boost.Filesystem, Boost.Date_time and various other +boost libraries as well as zlib_ (shipped) and asio_ (shipped). At least version +1.33.1 of boost is required. + +.. _zlib: http://www.zlib.org +.. _asio: http://asio.sf.net libtorrent has been successfully compiled and tested on: @@ -255,6 +262,10 @@ The ``Jamfile`` has the following build variants: * ``debug_log`` - debug version with standard logging * ``debug_vlog`` - debug version with verbose logging +When building the example client on windows, you need to build with +``link=static`` otherwise you may get unresolved external symbols for some +boost.program-options symbols. + building with autotools ----------------------- @@ -983,6 +994,7 @@ The ``torrent_info`` has the following synopsis:: void set_hash(int index, sha1_hash const& h); void add_tracker(std::string const& url, int tier = 0); void add_file(boost::filesystem::path file, size_type size); + void add_url_seed(std::string const& url); typedef std::vector::const_iterator file_iterator; typedef std::vector::const_reverse_iterator @@ -996,8 +1008,15 @@ The ``torrent_info`` has the following synopsis:: int num_files() const; file_entry const& file_at(int index) const; + std::vector map_block(int piece, size_type offset + , int size) const; + peer_request map_file(int file_index, size_type file_offset + , int size) const; + std::vector const& trackers() const; + std::vector const& url_seeds() const; + size_type total_size() const; size_type piece_length() const; int num_pieces() const; @@ -1138,6 +1157,81 @@ If you need index-access to files you can use the ``num_files()`` and ``file_at( to access files using indices. +map_block() +----------- + + :: + + std::vector map_block(int piece, size_type offset + , int size) const; + +This function will map a piece index, a byte offset within that piece and +a size (in bytes) into the corresponding files with offsets where that data +for that piece is supposed to be stored. + +The file slice struct looks like this:: + + struct file_slice + { + int file_index; + size_type offset; + size_type size; + }; + + +The ``file_index`` refers to the index of the file (in the torrent_info). +To get the path and filename, use ``file_at()`` and give the ``file_index`` +as argument. The ``offset`` is the byte offset in the file where the range +starts, and ``size`` is the number of bytes this range is. The size + offset +will never be greater than the file size. + + +map_file() +---------- + + :: + + peer_request map_file(int file_index, size_type file_offset + , int size) const; + +This function will map a range in a specific file into a range in the torrent. +The ``file_offset`` parameter is the offset in the file, given in bytes, where +0 is the start of the file. +The ``peer_request`` structure looks like this:: + + struct peer_request + { + int piece; + int start; + int length; + bool operator==(peer_request const& r) const; + }; + +``piece`` is the index of the piece in which the range starts. +``start`` is the offset within that piece where the range starts. +``length`` is the size of the range, in bytes. + +The input range is assumed to be valid within the torrent. ``file_offset`` ++ ``size`` is not allowed to be greater than the file size. ``file_index`` +must refer to a valid file, i.e. it cannot be >= ``num_files()``. + + +url_seeds() +----------- + + :: + + std::vector const& url_seeds() const; + void add_url_seed(std::string const& url); + +If there are any url-seeds in this torrent, ``url_seeds()`` will return a +vector of those urls. If you're creating a torrent file, ``add_url_seed()`` +adds one url to the list of url-seeds. Currently, the only transport protocol +supported for the url is http. + +See `HTTP seeding`_ for more information. + + print() ------- @@ -1202,6 +1296,8 @@ hash_for_piece() info_hash() ``hash_for_piece()`` takes a piece-index and returns the 20-bytes sha1-hash for that piece and ``info_hash()`` returns the 20-bytes sha1-hash for the info-section of the torrent file. For more information on the ``sha1_hash``, see the big_number_ class. +``info_hash()`` will only return a valid hash if the torrent_info was read from a +``.torrent`` file or if an ``entry`` was created from it (through ``create_torrent``). name() comment() creation_date() creator() @@ -1250,7 +1346,7 @@ Its declaration looks like this:: entry write_resume_data() const; std::vector const& metadata() const; void force_reannounce() const; - void connect_peer(address const& adr) const; + void connect_peer(asio::ipv4::tcp::endpoint const& adr) const; void set_tracker_login(std::string const& username , std::string const& password) const; @@ -1258,11 +1354,17 @@ Its declaration looks like this:: std::vector const& trackers() const; void replace_trackers(std::vector const&); + void add_url_seed(std::string const& url); + void set_ratio(float ratio) const; void set_max_uploads(int max_uploads) const; void set_max_connections(int max_connections) const; void set_upload_limit(int limit) const; void set_download_limit(int limit) const; + + void set_peer_upload_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + void set_peer_download_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + void use_interface(char const* net_interface) const; void pause() const; @@ -1336,7 +1438,7 @@ connect_peer() :: - void connect_peer(address const& adr) const; + void connect_peer(asio::ipv4::tcp::endpoint const& adr) const; ``connect_peer()`` is a way to manually connect to peers that one believe is a part of the torrent. If the peer does not respond, or is not a member of this torrent, it will simply @@ -1377,6 +1479,16 @@ Note that setting a higher limit on a torrent then the global limit (``session:: will not override the global rate limit. The torrent can never upload more than the global rate limit. +set_peer_upload_limit() set_peer_download_limit() +------------------------------------------------- + + :: + + void set_peer_upload_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + void set_peer_download_limit(asio::ipv4::tcp::endpoint ip, int limit) const; + +Works like ``set_upload_limit`` and ``set_download_limit`` respectively, but controls individual +peer instead of the whole torrent. pause() resume() is_paused() ---------------------------- @@ -1445,6 +1557,21 @@ replace it. If you want an immediate effect, you have to call `force_reannounce()`_. +add_url_seed() +-------------- + + :: + + void add_url_seed(std::string const& url); + +``add_url_seed()`` adds another url to the torrent's list of url seeds. If the +given url already exists in that list, the call has no effect. The torrent +will connect to the server and try to download pieces from it, unless it's +paused, queued, checking or seeding. + +See `HTTP seeding`_ for more information. + + use_interface() --------------- @@ -1649,6 +1776,7 @@ It contains the following fields:: size_type total_payload_upload; size_type total_failed_bytes; + size_type total_redundant_bytes; float download_rate; float upload_rate; @@ -1662,6 +1790,8 @@ It contains the following fields:: int num_incomplete; const std::vector* pieces; + int num_pieces; + size_type total_done; size_type total_wanted_done; size_type total_wanted; @@ -1727,10 +1857,24 @@ data), these counters ignore any protocol overhead. has failed the piece hash test. In other words, this is just how much crap that has been downloaded. +``total_redundant_bytes`` is the number of bytes that has been downloaded even +though that data already was downloaded. The reason for this is that in some +situations the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a certain +timeout, libtorrent will re-request that block. Another situation when +libtorrent will re-request blocks is when the requests it sends out are not +replyed in FIFO-order (it will re-request blocks that are skipped by an out of +order block). This is supposed to be as low as possible. + ``pieces`` is the bitmask that represents which pieces we have (set to true) and the pieces we don't have. It's a pointer and may be set to 0 if the torrent isn't downloading or seeding. +``num_pieces`` is the number of pieces that has been downloaded. It is equivalent +to: ``std::accumulate(pieces->begin(), pieces->end())``. So you don't have to +count yourself. This can be used to see if anything has updated since last time +if you want to keep a graph of the pieces up to date. + ``download_rate`` and ``upload_rate`` are the total rates for all peers for this torrent. These will usually have better precision than summing the rates from all peers. The rates are given as the number of bytes per second. The @@ -1793,22 +1937,23 @@ It contains the following fields:: remote_choked = 0x8, supports_extensions = 0x10, local_connection = 0x20, - connecting = 0x40, - queued = 0x80 + handshake = 0x40, + connecting = 0x80, + queued = 0x100 }; unsigned int flags; - address ip; + asio::ipv4::tcp::endpoint ip; float up_speed; float down_speed; float payload_up_speed; float payload_down_speed; size_type total_download; size_type total_upload; - peer_id id; + peer_id pid; std::vector pieces; bool seed; int upload_limit; - int upload_ceiling; + int download_limit; size_type load_balancing; @@ -1819,6 +1964,15 @@ It contains the following fields:: int downloading_block_index; int downloading_progress; int downloading_total; + + std::string client; + + enum + { + standard_bittorrent = 0, + web_seed = 1 + }; + int connection_type; }; The ``flags`` attribute tells you in which state the peer is. It is set to @@ -1838,10 +1992,14 @@ any combination of the enums above. The following table describes each flag: +-------------------------+-------------------------------------------------------+ | ``local_connection`` | The connection was initiated by us, the peer has a | | | listen port open, and that port is the same as in the | -| | address_ of this peer. If this flag is not set, this | +| | address of this peer. If this flag is not set, this | | | peer connection was opened by this peer connecting to | | | us. | +-------------------------+-------------------------------------------------------+ +| ``handshake`` | The connection is opened, and waiting for the | +| | handshake. Until the handshake is done, the peer | +| | cannot be identified. | ++-------------------------+-------------------------------------------------------+ | ``connecting`` | The connection is in a half-open state (i.e. it is | | | being connected). | +-------------------------+-------------------------------------------------------+ @@ -1852,8 +2010,10 @@ any combination of the enums above. The following table describes each flag: __ extension_protocol.html -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. +The ``ip`` field is the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio_ documentation. + +.. _asio: http://asio.sf.net ``up_speed`` and ``down_speed`` contains the current upload and download speed we have to and from this peer (including any protocol messages). The transfer rates @@ -1864,7 +2024,7 @@ These figures are updated aproximately once every second. from and uploaded to this peer. These numbers do not include the protocol chatter, but only the payload data. -``id`` is the peer's id as used in the bit torrent protocol. This id can be used to +``pid`` is the peer's id as used in the bit torrent protocol. This id can be used to extract 'fingerprints' from the peer. Sometimes it can tell you which client the peer is using. See identify_client()_ @@ -1878,9 +2038,8 @@ or if the peer miss that piece (set to false). peer every second. It may be -1 if there's no limit. The upload limits of all peers should sum up to the upload limit set by ``session::set_upload_limit``. -``upload_ceiling`` is the current maximum allowed upload rate given the cownload -rate and share ratio. If the global upload rate is inlimited, the ``upload_limit`` -for every peer will be the same as their ``upload_ceiling``. +``download_limit`` is the number of bytes per second this peer is allowed to +receive. -1 means it's unlimited. ``load_balancing`` is a measurment of the balancing of free download (that we get) and free upload that we give. Every peer gets a certain amount of free upload, but @@ -1903,44 +2062,14 @@ block (or sub-piece) that is being downloaded. ``downloading_progress`` is the n of bytes of this block we have received from the peer, and ``downloading_total`` is the total number of bytes in this block. +``client`` is a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain a string +that may give away something about which software is running in the other end. +In the case of a web seed, the server type and version will be a part of this +string. - -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:: - - class address - { - public: - address(); - address(unsigned char a - , unsigned char b - , unsigned char c - , unsigned char d - , unsigned short port); - address(unsigned int addr, unsigned short port); - address(const std::string& addr, unsigned short port); - address(const address& a); - ~address(); - - std::string as_string() const; - unsigned int ip() const; - unsigned short port() const; - - bool operator<(const address& a) const; - bool operator!=(const address& a) const; - bool operator==(const address& a) const; - }; - -It is less-than comparable to make it possible to use it as a key in a map. ``as_string()`` may block -while it does the DNS lookup, it returns a string that points to the address represented by the object. - -``ip()`` will return the 32-bit ip-address as an integer. ``port()`` returns the port number. - - +``connection_type`` can currently be one of ``standard_bittorrent`` or +``web_seed``. These are currently the only implemented protocols. http_settings ============= @@ -1960,7 +2089,8 @@ that will be sent to the tracker. The user-agent is a good way to identify your std::string proxy_login; std::string proxy_password; std::string user_agent; - int tracker_timeout; + int tracker_completion_timeout; + int tracker_receive_timeout; int tracker_maximum_response_length; }; @@ -1978,10 +2108,18 @@ empty, the http proxy will be tried to be used without authentication. ``user_agent`` this is the client identification to the tracker. It will be followed by the string "(libtorrent)" to identify that this library is being used. This should be set to your client's name and version number. +This name will not only be used when making HTTP requests, but also when +sending extended headers to peers that support that extension. -``tracker_timeout`` is the number of seconds the tracker connection will -wait until it considers the tracker to have timed-out. Default value is 10 -seconds. +``tracker_completion_timeout`` is the number of seconds the tracker +connection will wait from when it sent the request until it considers the +tracker to have timed-out. Default value is 60 seconds. + +``tracker_receive_timeout`` is the number of seconds to wait to receive +any data from the tracker. If no data is received for this number of +seconds, the tracker will be considered as having timed out. If a tracker +is down, this is the kind of timeout that will occur. The default value +is 20 seconds. ``tracker_maximum_response_length`` is the maximum number of bytes in a tracker response. If a response size passes this number it will be rejected @@ -1998,6 +2136,8 @@ ip_filter The ``ip_filter`` class is a set of rules that uniquely categorizes all ip addresses as allowed or disallowed. The default constructor creates a single rule that allowes all addresses (0.0.0.0 - 255.255.255.255). +The ``address`` type here is ``asio::ipv4::address``. It can also be +accessed as ``libtorrent::address``. :: @@ -2112,8 +2252,9 @@ This class creates sha1-hashes. Its declaration looks like this:: { public: hasher(); + hasher(char const* data, unsigned int len); - void update(const char* data, unsigned int len); + void update(char const* data, unsigned int len); sha1_hash final(); void reset(); }; @@ -2125,6 +2266,9 @@ create the hash in memory. You can feed the hasher parts of it at a time. When You have fed the hasher with all the data, you call ``final()`` and it will return the sha1-hash of the data. +The constructor that takes a ``char const*`` and an integer will construct the +sha1 context and feed it the data passed in. + If you want to reuse the hasher object once you have created a hash, you have to call ``reset()`` to reinitialize it. @@ -2147,7 +2291,7 @@ This is the class declaration:: std::string to_string() const; - char id[2]; + char name[2]; char major_version; char minor_version; char revision_version; @@ -2458,6 +2602,25 @@ the tracker. It is generated with severity level ``warning``. }; +url_seed_alert +-------------- + +This alert is generated when a HTTP seed name lookup fails. This alert is +generated as severity level ``warning``. + +It contains ``url`` to the HTTP seed that failed along with an error message. + +:: + + struct url_seed_alert: alert + { + url_seed_alert(std::string const& h, const std::string& msg); + virtual std::auto_ptr clone() const; + + std::string url; + }; + + hash_failed_alert ----------------- @@ -2493,12 +2656,12 @@ to the torrent that this peer was a member of. struct peer_ban_alert: alert { peer_ban_alert( - address const& pip + asio::ipv4::tcp::endpoint const& pip , torrent_handle h , const std::string& msg); virtual std::auto_ptr clone() const; - address ip; + asio::ipv4::tcp::endpoint ip; torrent_handle handle; }; @@ -2515,12 +2678,12 @@ is generated as severity level ``debug``. struct peer_error_alert: alert { peer_error_alert( - address const& pip + asio::ipv4::tcp::endpoint const& pip , peer_id const& pid , const std::string& msg); virtual std::auto_ptr clone() const; - address ip; + asio::ipv4::tcp::endpoint ip; peer_id id; }; @@ -2540,13 +2703,13 @@ is a handle to the torrent the peer is a member of. `` invalid_request_alert( peer_request const& r , torrent_handle const& h - , address const& send + , asio::ipv4::tcp::endpoint const& send , peer_id const& pid , std::string const& msg); virtual std::auto_ptr clone() const; torrent_handle handle; - address ip; + asio::ipv4::tcp::endpoint ip; peer_request request; peer_id id; }; @@ -3292,6 +3455,20 @@ Don't have metadata: | | | doesn't have any metadata. | +-----------+---------------+----------------------------------------+ +HTTP seeding +------------ + +The HTTP seed extension implements `this specification`__. + +The libtorrent implementation assumes that, if the URL ends with a slash +('/'), the filename should be appended to it in order to request pieces from +that file. The way this works is that if the torrent is a single-file torrent, +only that filename is appended. If the torrent is a multi-file torrent, the +torrent's name '/' the file name is appended. This is the same directory +structure that libtorrent will download torrents into. + +__ http://www.getright.com/seedtorrent.html + filename checks =============== diff --git a/examples/Makefile.am b/examples/Makefile.am index 373075341..97c7081cc 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -14,5 +14,5 @@ make_torrent_LDADD = $(top_builddir)/src/libtorrent.la simple_client_SOURCES = simple_client.cpp simple_client_LDADD = $(top_builddir)/src/libtorrent.la -AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/asio/include @DEBUGFLAGS@ @PTHREAD_CFLAGS@ AM_LDFLAGS= ${LDLAGS} -L./ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 08ba06f5b..1e065acc3 100755 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -226,11 +226,11 @@ std::string progress_bar(float progress, int width, char const* code = "33") return std::string(bar.begin(), bar.end()); } -char const* peer_index(libtorrent::address addr, std::vector const& peers) +char const* peer_index(libtorrent::tcp::endpoint addr, std::vector const& peers) { using namespace libtorrent; std::vector::const_iterator i = std::find_if(peers.begin() - , peers.end(), boost::bind(std::equal_to

    () + , peers.end(), boost::bind(std::equal_to() , bind(&peer_info::ip, _1), addr)); if (i == peers.end()) return "+"; @@ -274,7 +274,11 @@ void print_peer_info(std::ostream& out, std::vector const out << progress_bar(0.f, 15); } - if (i->flags & peer_info::connecting) + if (i->flags & peer_info::handshake) + { + out << esc("31") << " waiting for handshake" << esc("0") << "\n"; + } + else if (i->flags & peer_info::connecting) { out << esc("31") << " connecting to peer" << esc("0") << "\n"; } @@ -284,7 +288,7 @@ void print_peer_info(std::ostream& out, std::vector const } else { - out << " " << identify_client(i->id) << "\n"; + out << " " << i->client << "\n"; } } } @@ -569,9 +573,9 @@ int main(int ac, char* av[]) int a, b, c, d; char dummy; in >> a >> dummy >> b >> dummy >> c >> dummy >> d >> dummy; - address start(a, b, c, d, 0); + address start((a << 24) + (b << 16) + (c << 8) + d); in >> a >> dummy >> b >> dummy >> c >> dummy >> d >> dummy; - address last(a, b, c, d, 0); + address last((a << 24) + (b << 16) + (c << 8) + d); int flags; in >> flags; if (flags <= 127) flags = ip_filter::blocked; @@ -681,6 +685,7 @@ int main(int ac, char* av[]) // loop through the alert queue to see if anything has happened. std::auto_ptr a; a = ses.pop_alert(); + std::string now = to_simple_string(second_clock::universal_time()); while (a.get()) { if (torrent_finished_alert* p = dynamic_cast(a.get())) @@ -693,20 +698,22 @@ int main(int ac, char* av[]) // all finished downloades are // moved into this directory //p->handle.move_storage("finished"); - events.push_back( - p->handle.get_torrent_info().name() + ": " + a->msg()); + events.push_back(now + ": " + + p->handle.get_torrent_info().name() + ": " + a->msg()); } else if (peer_error_alert* p = dynamic_cast(a.get())) { - events.push_back(identify_client(p->id) + ": " + a->msg()); + events.push_back(now + ": " + identify_client(p->pid) + + ": " + a->msg()); } else if (invalid_request_alert* p = dynamic_cast(a.get())) { - events.push_back(identify_client(p->id) + ": " + a->msg()); + events.push_back(now + ": " + identify_client(p->pid) + + ": " + a->msg()); } else { - events.push_back(a->msg()); + events.push_back(now + ": " + a->msg()); } if (events.size() >= 10) events.pop_front(); diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp index 5cd3595d2..0c9c0e33a 100755 --- a/examples/make_torrent.cpp +++ b/examples/make_torrent.cpp @@ -73,9 +73,12 @@ int main(int argc, char* argv[]) using namespace libtorrent; using namespace boost::filesystem; + path::default_name_check(no_check); + if (argc != 4) { - std::cerr << "usage: make_torrent \n"; + std::cerr << "usage: make_torrent " + " \n"; return 1; } diff --git a/include/Makefile.am b/include/Makefile.am index dc2e1ccf2..cbdebbfad 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,7 +1,6 @@ pkginclude_HEADERS = libtorrent/alert.hpp \ libtorrent/alert_types.hpp \ libtorrent/allocate_resources.hpp \ -libtorrent/async_gethostbyname.hpp \ libtorrent/bencode.hpp \ libtorrent/buffer.hpp \ libtorrent/config.hpp \ @@ -19,6 +18,8 @@ libtorrent/io.hpp \ libtorrent/ip_filter.hpp \ libtorrent/peer.hpp \ libtorrent/peer_connection.hpp \ +libtorrent/bt_peer_connection.hpp \ +libtorrent/web_peer_connection.hpp \ libtorrent/peer_id.hpp \ libtorrent/peer_info.hpp \ libtorrent/peer_request.hpp \ @@ -27,6 +28,7 @@ libtorrent/piece_picker.hpp \ libtorrent/policy.hpp \ libtorrent/resource_request.hpp \ libtorrent/session.hpp \ +libtorrent/session_settings.hpp \ libtorrent/size_type.hpp \ libtorrent/socket.hpp \ libtorrent/stat.hpp \ diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp index 50b24bc0d..a820ef225 100755 --- a/include/libtorrent/alert.hpp +++ b/include/libtorrent/alert.hpp @@ -57,6 +57,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" +#define TORRENT_MAX_ALERT_TYPES 10 + namespace libtorrent { class TORRENT_EXPORT alert @@ -112,13 +114,13 @@ namespace libtorrent { template< class Handler - , BOOST_PP_ENUM_PARAMS(5, class T) + , BOOST_PP_ENUM_PARAMS(TORRENT_MAX_ALERT_TYPES, class T) > void handle_alert_dispatch( const std::auto_ptr& alert_ , const Handler& handler , const std::type_info& typeid_ - , BOOST_PP_ENUM_BINARY_PARAMS(5, T, *p)) + , BOOST_PP_ENUM_BINARY_PARAMS(TORRENT_MAX_ALERT_TYPES, T, *p)) { if (typeid_ == typeid(T0)) handler(*static_cast(alert_.get())); @@ -127,7 +129,7 @@ namespace libtorrent { alert_ , handler , typeid_ - , BOOST_PP_ENUM_SHIFTED_PARAMS(5, p), (void_*)0 + , BOOST_PP_ENUM_SHIFTED_PARAMS(TORRENT_MAX_ALERT_TYPES, p), (void_*)0 ); } @@ -136,7 +138,7 @@ namespace libtorrent { const std::auto_ptr& alert_ , const Handler& handler , const std::type_info& typeid_ - , BOOST_PP_ENUM_PARAMS(5, void_* BOOST_PP_INTERCEPT)) + , BOOST_PP_ENUM_PARAMS(TORRENT_MAX_ALERT_TYPES, void_* BOOST_PP_INTERCEPT)) { throw unhandled_alert(); } @@ -144,7 +146,7 @@ namespace libtorrent { } // namespace detail template< - BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(5, class T, detail::void_) + BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(TORRENT_MAX_ALERT_TYPES, class T, detail::void_) > struct TORRENT_EXPORT handle_alert { @@ -159,7 +161,7 @@ namespace libtorrent { alert_ , handler , typeid(*alert_) - , BOOST_PP_ENUM(5, ALERT_POINTER_TYPE, _) + , BOOST_PP_ENUM(TORRENT_MAX_ALERT_TYPES, ALERT_POINTER_TYPE, _) ); #undef ALERT_POINTER_TYPE diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 2a91d8d79..7a5cb7fd3 100755 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -124,7 +124,7 @@ namespace libtorrent struct TORRENT_EXPORT peer_ban_alert: alert { - peer_ban_alert(address const& pip, torrent_handle h, std::string const& msg) + peer_ban_alert(tcp::endpoint const& pip, torrent_handle h, std::string const& msg) : alert(alert::info, msg) , ip(pip) , handle(h) @@ -133,30 +133,30 @@ namespace libtorrent virtual std::auto_ptr clone() const { return std::auto_ptr(new peer_ban_alert(*this)); } - address ip; + tcp::endpoint ip; torrent_handle handle; }; struct TORRENT_EXPORT peer_error_alert: alert { - peer_error_alert(address const& pip, peer_id const& pid, std::string const& msg) + peer_error_alert(tcp::endpoint const& pip, peer_id const& pid_, std::string const& msg) : alert(alert::debug, msg) , ip(pip) - , id(pid) + , pid(pid_) {} virtual std::auto_ptr clone() const { return std::auto_ptr(new peer_error_alert(*this)); } - address ip; - peer_id id; + tcp::endpoint ip; + peer_id pid; }; struct TORRENT_EXPORT chat_message_alert: alert { chat_message_alert( const torrent_handle& h - , const address& sender + , const tcp::endpoint& sender , const std::string& msg) : alert(alert::critical, msg) , handle(h) @@ -167,7 +167,7 @@ namespace libtorrent { return std::auto_ptr(new chat_message_alert(*this)); } torrent_handle handle; - address ip; + tcp::endpoint ip; }; struct TORRENT_EXPORT invalid_request_alert: alert @@ -175,23 +175,23 @@ namespace libtorrent invalid_request_alert( peer_request const& r , torrent_handle const& h - , address const& sender - , peer_id const& pid + , tcp::endpoint const& sender + , peer_id const& pid_ , std::string const& msg) : alert(alert::debug, msg) , handle(h) , ip(sender) , request(r) - , id(pid) + , pid(pid_) {} virtual std::auto_ptr clone() const { return std::auto_ptr(new invalid_request_alert(*this)); } torrent_handle handle; - address ip; + tcp::endpoint ip; peer_request request; - peer_id id; + peer_id pid; }; struct TORRENT_EXPORT torrent_finished_alert: alert @@ -209,6 +209,21 @@ namespace libtorrent torrent_handle handle; }; + struct TORRENT_EXPORT url_seed_alert: alert + { + url_seed_alert( + const std::string& url_ + , const std::string& msg) + : alert(alert::warning, msg) + , url(url_) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new url_seed_alert(*this)); } + + std::string url; + }; + struct TORRENT_EXPORT file_error_alert: alert { file_error_alert( diff --git a/include/libtorrent/allocate_resources.hpp b/include/libtorrent/allocate_resources.hpp index 75dc3e681..318f8939d 100644 --- a/include/libtorrent/allocate_resources.hpp +++ b/include/libtorrent/allocate_resources.hpp @@ -44,7 +44,6 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - class socket; class peer_connection; class torrent; @@ -59,7 +58,7 @@ namespace libtorrent /* void allocate_resources( int resources - , std::map, boost::shared_ptr >& connections + , std::map, boost::intrusive_ptr >& connections , resource_request peer_connection::* res); */ void allocate_resources( @@ -69,7 +68,7 @@ namespace libtorrent void allocate_resources( int resources - , std::map& connections + , std::map& connections , resource_request peer_connection::* res); } diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp new file mode 100755 index 000000000..e417db582 --- /dev/null +++ b/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,295 @@ +/* + +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. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + class torrent; + + namespace detail + { + struct session_impl; + } + + class TORRENT_EXPORT bt_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + bt_peer_connection( + detail::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote); + + // with this constructor we have been contacted and we still don't + // know which torrent the connection belongs to + bt_peer_connection( + detail::session_impl& ses + , boost::shared_ptr s); + + ~bt_peer_connection(); + + // called from the main loop when this connection has any + // work to do. + + void on_sent(asio::error const& error + , std::size_t bytes_transferred); + void on_receive(asio::error const& error + , std::size_t bytes_transferred); + + virtual void get_peer_info(peer_info& p) const; + + bool support_extensions() const { return m_supports_extensions; } + + bool supports_extension(extension_index ex) const + { return m_extension_messages[ex] > 0; } + + bool has_metadata() const; + + // 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_keepalive(); + 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_dht_port(int received); + + void on_extended(int received); + + void on_extended_handshake(); + void on_chat(); + void on_metadata(); + void on_peer_exchange(); + + typedef void (bt_peer_connection::*message_handler)(int received); + + // the following functions appends messages + // to the send buffer + void write_choke(); + void write_unchoke(); + void write_interested(); + void write_not_interested(); + void write_request(peer_request const& r); + void write_cancel(peer_request const& r); + void write_bitfield(std::vector const& bitfield); + void write_have(int index); + void write_piece(peer_request const& r); + void write_handshake(); + void write_extensions(); + void write_chat_message(const std::string& msg); + void write_metadata(std::pair req); + void write_metadata_request(std::pair req); + void write_keepalive(); + void write_dht_port(int listen_port); + void on_connected() {} + void on_tick(); + +#ifndef NDEBUG + void check_invariant() const; + boost::posix_time::ptime m_last_choke; +#endif + + private: + + bool dispatch_message(int received); + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + boost::optional downloading_piece_progress() const; + + // if we don't have all metadata + // this function will request a part of it + // from this peer + void request_metadata(); + + enum state + { + read_protocol_length = 0, + read_protocol_string, + read_info_hash, + read_peer_id, + + read_packet_size, + read_packet + }; + + std::string m_client_version; + + state m_state; + + // the timeout in seconds + int m_timeout; + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + msg_dht_port, + // extension protocol message + msg_extended = 20, + + num_supported_messages + }; + + static const message_handler m_message_handler[num_supported_messages]; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // seperately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + assert(s >= 0); + assert(l > 0); + } + int start; + int length; + }; + static bool range_below_zero(const range& r) + { return r.start < 0; } + std::deque m_payloads; + + // this is set to true if the handshake from + // the peer indicated that it supports the + // extension protocol + bool m_supports_extensions; + bool m_supports_dht_port; + + static const char* extension_names[num_supported_extensions]; + // contains the indices of the extension messages for each extension + // supported by the other end. A value of <= 0 means that the extension + // is not supported. + int m_extension_messages[num_supported_extensions]; + + // this is set to the current time each time we get a + // "I don't have metadata" message. + boost::posix_time::ptime m_no_metadata; + + // this is set to the time when we last sent + // a request for metadata to this peer + boost::posix_time::ptime m_metadata_request; + + // this is set to true when we send a metadata + // request to this peer, and reset to false when + // we receive a reply to our request. + bool m_waiting_metadata_request; + + // if we're waiting for a metadata request + // this was the request we sent + std::pair m_last_metadata_request; + + // the number of bytes of metadata we have received + // so far from this per, only counting the current + // request. Any previously finished requests + // that have been forwarded to the torrent object + // do not count. + int m_metadata_progress; + +#ifndef NDEBUG + bool m_in_constructor; +#endif + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + diff --git a/include/libtorrent/buffer.hpp b/include/libtorrent/buffer.hpp index bc5a02fe3..5dc2e558a 100644 --- a/include/libtorrent/buffer.hpp +++ b/include/libtorrent/buffer.hpp @@ -49,6 +49,14 @@ public: , end(end) {} + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } + + int left() const { assert(end > begin); return end - begin; } + char* begin; char* end; }; @@ -60,6 +68,14 @@ public: , end(end) {} + char operator[](int index) const + { + assert(begin + index < end); + return begin[index]; + } + + int left() const { assert(end > begin); return end - begin; } + char const* begin; char const* end; }; @@ -69,7 +85,7 @@ public: buffer(std::size_t n = 0); ~buffer(); - interval allocate(std::size_t n); + interval allocate(std::size_t n); void insert(char const* first, char const* last); void erase(std::size_t n); std::size_t size() const; @@ -94,7 +110,7 @@ private: char* m_last; char* m_write_cursor; char* m_read_cursor; - char* m_read_end; + char* m_read_end; bool m_empty; #ifdef TORRENT_BUFFER_DEBUG mutable std::vector m_debug; @@ -230,27 +246,28 @@ inline void buffer::insert(char const* first, char const* last) inline void buffer::erase(std::size_t n) { - assert(!m_empty); + INVARIANT_CHECK; - INVARIANT_CHECK; + if (n == 0) return; + assert(!m_empty); #ifndef NDEBUG int prev_size = size(); #endif assert(m_read_cursor <= m_read_end); - m_read_cursor += n; - if (m_read_cursor > m_read_end) - { - m_read_cursor = m_first + (m_read_cursor - m_read_end); - assert(m_read_cursor <= m_write_cursor); - } + m_read_cursor += n; + if (m_read_cursor > m_read_end) + { + m_read_cursor = m_first + (m_read_cursor - m_read_end); + assert(m_read_cursor <= m_write_cursor); + } - m_empty = m_read_cursor == m_write_cursor; + m_empty = m_read_cursor == m_write_cursor; - assert(prev_size - n == size()); + assert(prev_size - n == size()); #ifdef TORRENT_BUFFER_DEBUG - m_debug.erase(m_debug.begin(), m_debug.begin() + n); + m_debug.erase(m_debug.begin(), m_debug.begin() + n); #endif } diff --git a/include/libtorrent/debug.hpp b/include/libtorrent/debug.hpp index 71f849bce..e496b583c 100755 --- a/include/libtorrent/debug.hpp +++ b/include/libtorrent/debug.hpp @@ -123,7 +123,7 @@ namespace libtorrent m_file.open(dir / filename, std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); log("\n\n\n*** starting log ***\n"); } - virtual void log(const char* text) { assert(text); m_file << text; } + virtual void log(const char* text) { assert(text); m_file << text; m_file.flush(); } boost::filesystem::ofstream m_file; }; diff --git a/include/libtorrent/escape_string.hpp b/include/libtorrent/escape_string.hpp index 1f30f744c..e0e743e1e 100755 --- a/include/libtorrent/escape_string.hpp +++ b/include/libtorrent/escape_string.hpp @@ -40,6 +40,7 @@ namespace libtorrent { std::string TORRENT_EXPORT unescape_string(std::string const& s); std::string TORRENT_EXPORT escape_string(const char* str, int len); + std::string TORRENT_EXPORT escape_path(const char* str, int len); } #endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp index 4c7a78982..1b71c66f5 100755 --- a/include/libtorrent/file.hpp +++ b/include/libtorrent/file.hpp @@ -114,7 +114,7 @@ namespace libtorrent size_type write(const char*, size_type num_bytes); size_type read(char*, size_type num_bytes); - void seek(size_type pos, seek_mode m = begin); + size_type seek(size_type pos, seek_mode m = begin); size_type tell(); private: diff --git a/include/libtorrent/fingerprint.hpp b/include/libtorrent/fingerprint.hpp index 32aec9261..dbd0ac74e 100755 --- a/include/libtorrent/fingerprint.hpp +++ b/include/libtorrent/fingerprint.hpp @@ -55,14 +55,14 @@ namespace libtorrent assert(revision >= 0); assert(tag >= 0); assert(std::strlen(id_string) == 2); - id[0] = id_string[0]; - id[1] = id_string[1]; + name[0] = id_string[0]; + name[1] = id_string[1]; } std::string to_string() const { std::stringstream s; - s << "-" << id[0] << id[1] + s << "-" << name[0] << name[1] << version_to_char(major_version) << version_to_char(minor_version) << version_to_char(revision_version) @@ -70,7 +70,7 @@ namespace libtorrent return s.str(); } - char id[2]; + char name[2]; char major_version; char minor_version; char revision_version; diff --git a/include/libtorrent/http_tracker_connection.hpp b/include/libtorrent/http_tracker_connection.hpp index 26629a9c5..ef2370f69 100755 --- a/include/libtorrent/http_tracker_connection.hpp +++ b/include/libtorrent/http_tracker_connection.hpp @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -56,19 +57,62 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_id.hpp" #include "libtorrent/peer.hpp" #include "libtorrent/tracker_manager.hpp" -#include "libtorrent/async_gethostbyname.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" namespace libtorrent { + + class http_parser + { + public: + http_parser(); + template + T header(char const* key) const; + std::string const& protocol() const { return m_protocol; } + int status_code() const { return m_status_code; } + std::string message() const { return m_server_message; } + buffer::const_interval get_body(); + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + boost::tuple incoming(buffer::const_interval recv_buffer); + int body_start() const { return m_body_start_pos; } + private: + int m_recv_pos; + int m_status_code; + std::string m_protocol; + std::string m_server_message; - class TORRENT_EXPORT http_tracker_connection: public tracker_connection + int m_content_length; + enum { plain, gzip } m_content_encoding; + + enum { read_status, read_header, read_body } m_state; + + std::map m_header; + buffer::const_interval m_recv_buffer; + int m_body_start_pos; + + bool m_finished; + }; + + template + T http_parser::header(char const* key) const + { + std::map::const_iterator i + = m_header.find(key); + if (i == m_header.end()) return T(); + return boost::lexical_cast(i->second); + } + + class TORRENT_EXPORT http_tracker_connection + : public tracker_connection { friend class tracker_manager; public: http_tracker_connection( - tracker_manager& man + demuxer& d + , tracker_manager& man , tracker_request const& req , std::string const& hostname , unsigned short port @@ -76,18 +120,29 @@ namespace libtorrent , boost::weak_ptr c , const http_settings& stn , std::string const& password = ""); - virtual bool tick(); - virtual bool send_finished() const - { return m_send_buffer.empty(); } + virtual tracker_request const& tracker_req() const { return m_req; } private: + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + void on_response(); + void init_send_buffer( std::string const& hostname , std::string const& request); + void name_lookup(asio::error const& error); + void connected(asio::error const& error); + void sent(asio::error const& error); + void receive(asio::error const& error + , std::size_t bytes_transferred); + + virtual void on_timeout(); + void parse(const entry& e); peer_entry extract_peer_info(const entry& e); @@ -98,15 +153,14 @@ namespace libtorrent int m_content_length; std::string m_location; - dns_lookup m_name_lookup; - boost::shared_ptr m_socket; + host_resolver m_name_lookup; + host m_host; + int m_port; + boost::shared_ptr m_socket; int m_recv_pos; std::vector m_buffer; std::string m_send_buffer; - // used for time outs - boost::posix_time::ptime m_request_time; - std::string m_server_message; std::string m_server_protocol; @@ -117,8 +171,11 @@ namespace libtorrent // server string in http-reply std::string m_server; + + bool m_timed_out; }; } #endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp index 0d7dc3b5d..ed6b655fc 100644 --- a/include/libtorrent/ip_filter.hpp +++ b/include/libtorrent/ip_filter.hpp @@ -30,17 +30,23 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifndef IP_FILTER_HPP -#define IP_FILTER_HPP +#ifndef TORRENT_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP -#include "libtorrent/socket.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" #include #include namespace libtorrent { +inline bool operator<=(address const& lhs + , address const& rhs) +{ + return lhs < rhs || lhs == rhs; +} + class TORRENT_EXPORT ip_filter { public: diff --git a/include/libtorrent/peer.hpp b/include/libtorrent/peer.hpp index 842cf59f9..c404a611d 100755 --- a/include/libtorrent/peer.hpp +++ b/include/libtorrent/peer.hpp @@ -44,16 +44,16 @@ namespace libtorrent { std::string ip; int port; - peer_id id; + peer_id pid; bool operator==(const peer_entry& p) const { - return id == p.id; + return pid == p.pid; } bool operator<(const peer_entry& p) const { - return id < p.id; + return pid < p.pid; } }; diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index ff428f7ae..a65976d15 100755 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #include #include #include @@ -68,6 +69,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_request.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" // TODO: each time a block is 'taken over' // from another peer. That peer must be given @@ -82,6 +84,9 @@ namespace libtorrent struct session_impl; } + TORRENT_EXPORT void intrusive_ptr_add_ref(peer_connection const*); + TORRENT_EXPORT void intrusive_ptr_release(peer_connection const*); + struct TORRENT_EXPORT protocol_error: std::runtime_error { protocol_error(const std::string& msg): std::runtime_error(msg) {}; @@ -89,27 +94,26 @@ namespace libtorrent class TORRENT_EXPORT peer_connection : public boost::noncopyable - , public boost::enable_shared_from_this { friend class invariant_access; + friend void intrusive_ptr_add_ref(peer_connection const*); + friend void intrusive_ptr_release(peer_connection const*); public: - // this is the constructor where the we are teh active part. + // this is the constructor where the we are the active part. // The peer_conenction should handshake and verify that the // other end has the correct id peer_connection( detail::session_impl& ses - , selector& sel - , torrent* t - , boost::shared_ptr s - , address const& remote); + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote); // with this constructor we have been contacted and we still don't // know which torrent the connection belongs to peer_connection( detail::session_impl& ses - , selector& sel - , boost::shared_ptr s); + , boost::shared_ptr s); // this function is called once the torrent associated // with this peer connection has retrieved the meta- @@ -117,17 +121,15 @@ namespace libtorrent // this is called from the constructor. void init(); - ~peer_connection(); + void set_upload_limit(int limit); + void set_download_limit(int limit); + + virtual ~peer_connection(); // this adds an announcement in the announcement queue // it will let the peer know that we have the given piece void announce_piece(int index); - // called from the main loop when this connection has any - // work to do. - void send_data(); - void receive_data(); - // tells if this connection has data it want to send // and has enough upload bandwidth quota left to send it. bool can_write() const; @@ -140,20 +142,14 @@ namespace libtorrent // will send a keep-alive message to the peer void keep_alive(); - const peer_id& id() const { return m_peer_id; } + peer_id const& pid() const { return m_peer_id; } + void set_pid(const peer_id& pid) { m_peer_id = pid; } bool has_piece(int i) const; const std::deque& download_queue() const; const std::deque& request_queue() const; const std::deque& upload_queue() const; - // returns the block currently being - // downloaded. And the progress of that - // block. If the peer isn't downloading - // a piece for the moment, the boost::optional - // will be invalid. - boost::optional downloading_piece() const; - bool is_interesting() const { return m_interesting; } bool is_choked() const { return m_choked; } @@ -164,21 +160,19 @@ namespace libtorrent // may be zero if the connection is an incoming connection // and it hasn't received enough information to determine // which torrent it should be associated with - torrent* associated_torrent() const { return m_attached_to_torrent?m_torrent:0; } - - bool verify_piece(const peer_request& p) const; + boost::weak_ptr associated_torrent() const + { return m_torrent; } const stat& statistics() const { return m_statistics; } void add_stat(size_type downloaded, size_type uploaded); // is called once every second by the main loop - void second_tick(); + void second_tick(float tick_interval); - boost::shared_ptr get_socket() const { return m_socket; } - address const& remote() const { return m_remote; } + boost::shared_ptr get_socket() const { return m_socket; } + tcp::endpoint const& remote() const { return m_remote; } - const peer_id& get_peer_id() const { return m_peer_id; } - const std::vector& get_bitfield() const; + std::vector const& get_bitfield() const; // this will cause this peer_connection to be disconnected. // what it does is that it puts a reference to it in @@ -190,7 +184,7 @@ namespace libtorrent // this is called when the connection attempt has succeeded // and the peer_connection is supposed to set m_connecting // to false, and stop monitor writability - void connection_complete(); + void on_connection_complete(asio::error const& e); // returns true if this connection is still waiting to // finish the connection attempt @@ -206,20 +200,16 @@ namespace libtorrent // the library isn't using up the limitation of half-open // tcp connections. void connect(); - // This is called for every peer right after the upload // bandwidth has been distributed among them - // It will reset the used bandwidth to 0 and - // possibly add or remove the peer's socket - // from the socket monitor + // It will reset the used bandwidth to 0. void reset_upload_quota(); // free upload. size_type total_free_upload() const; void add_free_upload(size_type free_upload); - // trust management. void received_valid_data(); void received_invalid_data(); @@ -227,8 +217,6 @@ namespace libtorrent size_type share_diff() const; - bool support_extensions() const { return m_supports_extensions; } - // a connection is local if it was initiated by us. // if it was an incoming connection, it is remote bool is_local() const { return m_active; } @@ -236,24 +224,12 @@ namespace libtorrent void set_failed() { m_failed = true; } bool failed() const { return m_failed; } + int desired_queue_size() const { return m_desired_queue_size; } + #ifdef TORRENT_VERBOSE_LOGGING boost::shared_ptr m_logger; #endif - enum extension_index - { - extended_chat_message, - extended_metadata_message, - extended_peer_exchange_message, - extended_listen_port_message, - num_supported_extensions - }; - - bool supports_extension(extension_index ex) const - { return m_extension_messages[ex] != -1; } - - bool has_metadata() const; - // the message handlers are called // each time a recv() returns some new // data, the last time it will be called @@ -262,26 +238,18 @@ namespace libtorrent // 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_dht_port(int received); - - void on_extension_list(int received); - void on_extended(int received); - - void on_chat(); - void on_metadata(); - void on_peer_exchange(); - void on_listen_port(); - - typedef void (peer_connection::*message_handler)(int received); + void incoming_keepalive(); + void incoming_choke(); + void incoming_unchoke(); + void incoming_interested(); + void incoming_not_interested(); + void incoming_have(int piece_index); + void incoming_bitfield(std::vector const& bitfield); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, char const* data); + void incoming_piece_fragment(); + void incoming_cancel(peer_request const& r); + void incoming_dht_port(int listen_port); // the following functions appends messages // to the send buffer @@ -289,15 +257,10 @@ namespace libtorrent void send_unchoke(); void send_interested(); void send_not_interested(); - void send_request(piece_block block); - void send_cancel(piece_block block); - void send_bitfield(); - void send_have(int index); - void send_handshake(); - void send_extensions(); - void send_chat_message(const std::string& msg); - void send_metadata(std::pair req); - void send_metadata_request(std::pair req); + + // adds a block to the request queue + void add_request(piece_block const& b); + void cancel_request(piece_block const& b); // how much bandwidth we're using, how much we want, // and how much we are allowed to use. @@ -309,59 +272,104 @@ namespace libtorrent boost::posix_time::ptime m_last_choke; #endif + virtual void get_peer_info(peer_info& p) const = 0; + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + virtual boost::optional + downloading_piece_progress() const + { + #ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "downloading_piece_progress() dispatched to the base class!\n"; + #endif + return boost::optional(); + } + + protected: + + virtual void write_choke() = 0; + virtual void write_unchoke() = 0; + virtual void write_interested() = 0; + virtual void write_not_interested() = 0; + virtual void write_request(peer_request const& r) = 0; + virtual void write_cancel(peer_request const& r) = 0; + virtual void write_have(int index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + virtual void on_receive(asio::error const& error + , std::size_t bytes_transferred) = 0; + virtual void on_sent(asio::error const& error + , std::size_t bytes_transferred) = 0; + + void send_buffer(char const* begin, char const* end); + buffer::interval allocate_send_buffer(int size); + int send_buffer_size() const + { + return (int)m_send_buffer[0].size() + + (int)m_send_buffer[1].size(); + } + + buffer::const_interval receive_buffer() const + { + return buffer::const_interval(&m_recv_buffer[0] + , &m_recv_buffer[0] + m_recv_pos); + } + + void cut_receive_buffer(int size, int packet_size); + + void reset_recv_buffer(int packet_size); + int packet_size() const { return m_packet_size; } + + bool packet_finished() const + { + assert(m_recv_pos <= m_packet_size); + return m_packet_size == m_recv_pos; + } + + void setup_send(); + void setup_receive(); + + void attach_to_torrent(sha1_hash const& ih); + + bool verify_piece(peer_request const& p) const; + + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + stat m_statistics; + + // a back reference to the session + // the peer belongs to. + detail::session_impl& m_ses; + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + // called from the main loop when this connection has any + // work to do. + void on_send_data(asio::error const& error + , std::size_t bytes_transferred); + void on_receive_data(asio::error const& error + , std::size_t bytes_transferred); + private: + void fill_send_buffer(); void send_block_requests(); - bool dispatch_message(int received); - - // if we don't have all metadata - // this function will request a part of it - // from this peer - void request_metadata(); - - // this is called each time this peer generates some - // data to be sent. It will add this socket to - // the writibility monitor in the selector. - void send_buffer_updated(); - - // is used during handshake - enum state - { - read_protocol_length = 0, - read_protocol_string, - read_info_hash, - read_peer_id, - - read_packet_size, - read_packet - }; - - state m_state; // the timeout in seconds int m_timeout; - enum message_type - { - // standard messages - msg_choke = 0, - msg_unchoke, - msg_interested, - msg_not_interested, - msg_have, - msg_bitfield, - msg_request, - msg_piece, - msg_cancel, - msg_dht_port, - // extension protocol message - msg_extension_list = 20, - msg_extended, - - num_supported_messages - }; - - static const message_handler m_message_handler[num_supported_messages]; + // the time when we last got a part of a + // piece packet from this peer + boost::posix_time::ptime m_last_piece; int m_packet_size; int m_recv_pos; @@ -369,73 +377,37 @@ namespace libtorrent // this is the buffer where data that is // to be sent is stored until it gets - // consumed by send() -// std::vector m_send_buffer; - buffer m_send_buffer; - - // this is a queue of ranges that describes - // where in the send buffer actual payload - // data is located. This is currently - // only used to be able to gather statistics - // seperately on payload and protocol data. - struct range - { - range(int s, int l) - : start(s) - , length(l) - { - assert(s >= 0); - assert(l > 0); - } - int start; - int length; - }; - static bool range_below_zero(const range& r) - { return r.start < 0; } - std::deque m_payloads; + // consumed by send(). Since asio requires + // the memory buffer that is given to async. + // operations to remain valid until the operation + // finishes, there has to be two buffers. While + // waiting for a async_write operation on one + // buffer, the other is used to write data to + // be queued up. + buffer m_send_buffer[2]; + // the current send buffer is the one to write to. + // (m_current_send_buffer + 1) % 2 is the + // buffer we're currently waiting for. + int m_current_send_buffer; // timeouts boost::posix_time::ptime m_last_receive; boost::posix_time::ptime m_last_sent; - // the selector is used to add and remove this - // peer's socket from the writability monitor list. - selector& m_selector; - boost::shared_ptr m_socket; - address m_remote; + boost::shared_ptr m_socket; + tcp::endpoint m_remote; // this is the torrent this connection is // associated with. If the connection is an // incoming conncetion, this is set to zero // until the info_hash is received. Then it's // set to the torrent it belongs to. - torrent* m_torrent; - - // this is set to false until the peer_id - // is received from the other end. Or it is - // true from the start if the conenction - // was actively opened from our side. - bool m_attached_to_torrent; - - // a back reference to the session - // the peer belongs to. - detail::session_impl& m_ses; - + boost::weak_ptr m_torrent; // is true if it was we that connected to the peer // and false if we got an incomming connection // could be considered: true = local, false = remote bool m_active; - // this is true as long as this peer's - // socket is added to the selector to - // monitor writability. Each time we do - // something that generates data to be - // sent to this peer, we check this and - // if it's not added to the selector we - // add it. (this is done in send_buffer_updated()) - bool m_writability_monitored; - bool m_readability_monitored; - // remote peer's id peer_id m_peer_id; @@ -459,11 +431,6 @@ namespace libtorrent // this peer bool m_failed; - // this is set to true if the handshake from - // the peer indicated that it supports the - // extension protocol - bool m_supports_extensions; - // the pieces the other end have std::vector m_have_piece; @@ -477,11 +444,6 @@ namespace libtorrent // from this peer std::deque m_requests; - // a list of pieces that have become available - // and should be announced as available to - // the peer - std::vector m_announce_queue; - // the blocks we have reserved in the piece // picker and will send to this peer. std::deque m_request_queue; @@ -489,11 +451,10 @@ namespace libtorrent // the queue of blocks we have requested // from this peer std::deque m_download_queue; - - // statistics about upload and download speeds - // and total amount of uploads and downloads for - // this peer - stat m_statistics; + + // the number of request we should queue up + // at the remote end. + int m_desired_queue_size; // the amount of data this peer has been given // as free upload. This is distributed from @@ -511,8 +472,12 @@ namespace libtorrent // considered a bad peer and will be banned. int m_trust_points; - static const char* extension_names[num_supported_extensions]; - int m_extension_messages[num_supported_extensions]; + // if this is true, this peer is assumed to handle all piece + // requests in fifo order. All skipped blocks are re-requested + // immediately instead of having a looser requirement + // where blocks can be sent out of order. The default is to + // allow non-fifo order. + bool m_assume_fifo; // the number of invalid piece-requests // we have got from this peer. If the request @@ -523,10 +488,6 @@ namespace libtorrent // by sending choke, unchoke. int m_num_invalid_requests; - // the time when we last got a part of a - // piece packet from this peer - boost::posix_time::ptime m_last_piece; - // this is true if this connection has been added // to the list of connections that will be closed. bool m_disconnecting; @@ -539,23 +500,6 @@ namespace libtorrent // this peer the last time. boost::posix_time::ptime m_became_uninteresting; - // this is set to the current time each time we get a - // "I don't have metadata" message. - boost::posix_time::ptime m_no_metadata; - - // this is set to the time when we last sent - // a request for metadata to this peer - boost::posix_time::ptime m_metadata_request; - - // this is set to true when we send a metadata - // request to this peer, and reset to false when - // we receive a reply to our request. - bool m_waiting_metadata_request; - - // if we're waiting for a metadata request - // this was the request we sent - std::pair m_last_metadata_request; - // this is true until this socket has become // writable for the first time (i.e. the // connection completed). While connecting @@ -571,12 +515,24 @@ namespace libtorrent // connections. bool m_queued; - // the number of bytes of metadata we have received - // so far from this per, only counting the current - // request. Any previously finished requests - // that have been forwarded to the torrent object - // do not count. - int m_metadata_progress; + // these are true when there's a asynchronous write + // or read operation running. + bool m_writing; + // this is the number of bytes sent to the socket last + // time it was invoked. This is compared against the + // bytes_transferred in the callback function that tells + // how much actually was sent. Then the quota can be + // corrected according to the actual number of bytes sent + int m_last_write_size; + bool m_reading; + int m_last_read_size; + // reference counter for intrusive_ptr + mutable int m_refs; + +#ifndef NDEBUG + public: + bool m_in_constructor; +#endif }; } diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp index 05d20459f..7a6e50834 100755 --- a/include/libtorrent/peer_info.hpp +++ b/include/libtorrent/peer_info.hpp @@ -52,22 +52,23 @@ namespace libtorrent remote_choked = 0x8, supports_extensions = 0x10, local_connection = 0x20, - connecting = 0x40, - queued = 0x80 + handshake = 0x40, + connecting = 0x80, + queued = 0x100 }; unsigned int flags; - address ip; + tcp::endpoint ip; float up_speed; float down_speed; float payload_up_speed; float payload_down_speed; size_type total_download; size_type total_upload; - peer_id id; + peer_id pid; std::vector pieces; bool seed; // true if this is a seed - int upload_limit; // from peer_connection - int upload_ceiling; // from the global upload limiter + int upload_limit; + int download_limit; size_type load_balancing; @@ -89,6 +90,15 @@ namespace libtorrent int downloading_block_index; int downloading_progress; int downloading_total; + + std::string client; + + enum + { + standard_bittorrent = 0, + web_seed = 1 + }; + int connection_type; }; } diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp index 74a6d142c..b781251bc 100755 --- a/include/libtorrent/piece_picker.hpp +++ b/include/libtorrent/piece_picker.hpp @@ -49,13 +49,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_id.hpp" #include "libtorrent/socket.hpp" +#include "libtorrent/session_settings.hpp" #include "libtorrent/config.hpp" namespace libtorrent { class torrent; - class address; class peer_connection; struct TORRENT_EXPORT piece_block @@ -93,7 +93,7 @@ namespace libtorrent block_info(): num_downloads(0) {} // the peer this block was requested or // downloaded from - address peer; + tcp::endpoint peer; // the number of times this block has been downloaded int num_downloads; }; @@ -110,8 +110,11 @@ namespace libtorrent block_info info[max_blocks_per_piece]; }; - piece_picker(int blocks_per_piece, - int total_num_blocks); + piece_picker(int blocks_per_piece + , int total_num_blocks + , int sequenced_download_threshold); + + void set_sequenced_download_threshold(int sequenced_download_threshold); // this is called before any other method is called // after the local files has been checked. @@ -161,12 +164,12 @@ namespace libtorrent // decides to download a piece, it must mark it as being downloaded // itself, by using the mark_as_downloading() member function. // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! - // The last argument is the address of the peer that we'll download + // The last argument is the tcp::endpoint of the peer that we'll download // from. void pick_pieces(const std::vector& pieces , std::vector& interesting_blocks , int num_pieces, bool prefer_whole_pieces - , address peer) const; + , tcp::endpoint peer) const; // returns true if any client is currently downloading this // piece-block, or if it's queued for downloading by some client @@ -175,8 +178,8 @@ namespace libtorrent bool is_finished(piece_block block) const; // marks this piece-block as queued for downloading - void mark_as_downloading(piece_block block, const address& peer); - void mark_as_finished(piece_block block, const address& peer); + void mark_as_downloading(piece_block block, tcp::endpoint const& peer); + void mark_as_finished(piece_block block, tcp::endpoint const& peer); // if a piece had a hash-failure, it must be restored and // made available for redownloading @@ -195,12 +198,12 @@ namespace libtorrent // the hash-check yet int unverified_blocks() const; - void get_downloaders(std::vector
    & d, int index) const; + void get_downloaders(std::vector& d, int index) const; const std::vector& get_download_queue() const { return m_downloads; } - boost::optional
    get_downloader(piece_block block) const; + boost::optional get_downloader(piece_block block) const; // the number of filtered pieces we don't have int num_filtered() const { return m_num_filtered; } @@ -252,10 +255,20 @@ namespace libtorrent enum { we_have_index = 0x3ffff }; - bool operator!=(piece_pos p) + int priority(int limit) const + { + return peer_count >= (unsigned)limit ? limit : peer_count; + } + + bool ordered(int limit) const + { + return peer_count >= (unsigned)limit; + } + + bool operator!=(piece_pos p) const { return index != p.index || peer_count != p.peer_count; } - bool operator==(piece_pos p) + bool operator==(piece_pos p) const { return index == p.index && peer_count == p.peer_count; } }; @@ -280,7 +293,7 @@ namespace libtorrent , std::vector& interesting_blocks , std::vector& backup_blocks , int num_blocks, bool prefer_whole_pieces - , address peer) const; + , tcp::endpoint peer) const; // this vector contains all pieces we don't have. @@ -321,6 +334,10 @@ namespace libtorrent // the number of pieces we have that also are filtered int m_num_have_filtered; + + // the required popularity of a piece in order to download + // it in sequence instead of random order. + int m_sequenced_download_threshold; }; inline int piece_picker::blocks_in_piece(int index) const diff --git a/include/libtorrent/policy.hpp b/include/libtorrent/policy.hpp index a677b5523..126e7e895 100755 --- a/include/libtorrent/policy.hpp +++ b/include/libtorrent/policy.hpp @@ -57,7 +57,6 @@ namespace libtorrent { class torrent; - class address; class peer_connection; enum @@ -84,7 +83,7 @@ namespace libtorrent // this is called once for every peer we get from // the tracker - void peer_from_tracker(const address& remote, const peer_id& id); + void peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid); // called when an incoming connection is accepted // return false if the connection closed @@ -131,7 +130,7 @@ namespace libtorrent { enum connection_type { not_connectable,connectable }; - peer(const address& ip, connection_type t); + peer(const tcp::endpoint& ip, connection_type t); size_type total_download() const; size_type total_upload() const; @@ -140,7 +139,7 @@ namespace libtorrent // if it was a remote (incoming) connection, type is // set thereafter. If it was a peer we got from the // tracker, type is set to local_connection. - address id; + tcp::endpoint ip; connection_type type; // the time when this peer was optimistically unchoked diff --git a/include/libtorrent/resource_request.hpp b/include/libtorrent/resource_request.hpp index 76856596a..7401163b5 100755 --- a/include/libtorrent/resource_request.hpp +++ b/include/libtorrent/resource_request.hpp @@ -1,6 +1,6 @@ /* -Copyright (c) 2003, Magnus Jonsson +Copyright (c) 2003, Magnus Jonsson, Arvid Norberg All rights reserved. Redistribution and use in source and binary forms, with or without @@ -60,7 +60,8 @@ namespace libtorrent { assert(given <= max); assert(given >= min); - assert(given >= used); +// TODO: TEMP! +// assert(given >= used); return given - used; } diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp index 8676e18e1..41f008a39 100755 --- a/include/libtorrent/session.hpp +++ b/include/libtorrent/session.hpp @@ -48,6 +48,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -69,6 +70,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" #if !defined(NDEBUG) && defined(_MSC_VER) # include @@ -77,9 +79,17 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - class torrent; + enum extension_index + { + extended_handshake, + extended_chat_message, + extended_metadata_message, + extended_peer_exchange_message, + num_supported_extensions + }; + namespace detail { // workaround for microsofts @@ -123,7 +133,7 @@ namespace libtorrent std::vector piece_map; std::vector unfinished_pieces; - std::vector
    peers; + std::vector peers; entry resume_data; // this is true if this torrent is being processed (checked) @@ -170,12 +180,10 @@ namespace libtorrent struct session_impl: boost::noncopyable { friend class invariant_access; - // TODO: maybe this should be changed to a sorted vector - // using lower_bound? - typedef std::map, boost::shared_ptr > + typedef std::map, boost::intrusive_ptr > connection_map; typedef std::map > torrent_map; - typedef std::deque > + typedef std::deque > connection_queue; session_impl( @@ -187,10 +195,16 @@ namespace libtorrent void open_listen_port(); + void async_accept(); + void on_incoming_connection(boost::shared_ptr const& s + , boost::weak_ptr const& as, asio::error const& e); + // must be locked to access the data // in this struct - mutable boost::mutex m_mutex; - torrent* find_torrent(const sha1_hash& info_hash); + typedef boost::recursive_mutex mutex_t; + mutable mutex_t m_mutex; + + boost::weak_ptr find_torrent(const sha1_hash& info_hash); peer_id const& get_peer_id() const { return m_peer_id; } tracker_manager m_tracker_manager; @@ -201,15 +215,11 @@ namespace libtorrent // is reached. void process_connection_queue(); - void connection_failed(boost::shared_ptr const& s - , address const& a, char const* message); + void close_connection(boost::intrusive_ptr const& p); + void connection_completed(boost::intrusive_ptr const& p); + void connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message); - // this is where all active sockets are stored. - // the selector can sleep while there's no activity on - // them - selector m_selector; - - // this maps sockets to their peer_connection // object. It is the complete list of all connected // peers. @@ -225,13 +235,7 @@ namespace libtorrent // waiting for one slot in the half-open queue to open up. connection_queue m_connection_queue; - // this is a list of iterators into the m_connections map - // that should be disconnected as soon as possible. - // It is used to delay disconnections to avoid troubles - // in loops that iterate over them. - std::vector > m_disconnect_peer; - - // filters incomming connections + // filters incoming connections ip_filter m_ip_filter; // the peer id that is generated at the start of the session @@ -250,18 +254,24 @@ namespace libtorrent // if the ip is set to zero, it means // that we should let the os decide which // interface to listen on - address m_listen_interface; + tcp::endpoint m_listen_interface; - boost::shared_ptr m_listen_socket; + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + demuxer m_selector; + + boost::shared_ptr m_listen_socket; // the entries in this array maps the // extension index (as specified in peer_connection) - bool m_extension_enabled[peer_connection::num_supported_extensions]; + bool m_extension_enabled[num_supported_extensions]; bool extensions_enabled() const; // the settings for the client - http_settings m_settings; + session_settings m_settings; + http_settings m_http_settings; // set to true when the session object // is being destructed and the thread @@ -293,8 +303,11 @@ namespace libtorrent // does the actual disconnections // that are queued up in m_disconnect_peer - void purge_connections(); + void second_tick(asio::error const& e); + boost::posix_time::ptime m_last_tick; + // the timer used to fire the second_tick + deadline_timer m_timer; #ifndef NDEBUG void check_invariant(const char *place = 0); #endif @@ -330,7 +343,7 @@ namespace libtorrent { public: - session(fingerprint const& print = fingerprint("LT", 0, 9, 1, 0)); + session(fingerprint const& print = fingerprint("LT", 0, 9, 2, 0)); session( fingerprint const& print , std::pair listen_port_range @@ -348,7 +361,7 @@ namespace libtorrent , bool compact_mode = true , int block_size = 16 * 1024); - // TODO: depricated, this is for backwards compatibility only + // TODO: deprecated, this is for backwards compatibility only torrent_handle add_torrent( entry const& e , boost::filesystem::path const& save_path @@ -370,11 +383,11 @@ namespace libtorrent session_status status() const; - void enable_extension(peer_connection::extension_index i); + void enable_extension(extension_index i); void disable_extensions(); void set_ip_filter(ip_filter const& f); - void set_peer_id(peer_id const& id); + void set_peer_id(peer_id const& pid); void set_key(int key); bool is_listening() const; diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp new file mode 100644 index 000000000..6ff4bf20a --- /dev/null +++ b/include/libtorrent/session_settings.hpp @@ -0,0 +1,68 @@ +/* + +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. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +namespace libtorrent +{ + + struct TORRENT_EXPORT session_settings + { + session_settings() + : piece_timeout(120) + , request_queue_time(3.f) + , sequenced_download_threshold(7) + {} + + // the number of seconds from a request is sent until + // it times out if no piece response is returned. + int piece_timeout; + + // the length of the request queue given in the number + // of seconds it should take for the other end to send + // all the pieces. i.e. the actual number of requests + // depends on the download rate and this number. + float request_queue_time; + + // this is the limit on how popular a piece has to be + // (popular == inverse of rarity) to be downloaded + // in sequence instead of in random (rarest first) order. + // it can be used to tweak disk performance in settings + // where the random download property is less necessary. + // for example, if the threshold is 7, all pieces which 7 + // or more peers have, will be downloaded in index order. + int sequenced_download_threshold; + }; +} + +#endif diff --git a/include/libtorrent/socket.hpp b/include/libtorrent/socket.hpp index 7b6375971..6a0d2b7ec 100755 --- a/include/libtorrent/socket.hpp +++ b/include/libtorrent/socket.hpp @@ -37,268 +37,54 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(push, 1) #endif -#include -#include -#include -#include -#include +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#include + +#ifdef __OBJC__ +#undef Protocol +#endif #ifdef _MSC_VER #pragma warning(pop) #endif - -#include -#include -#include - -#include "libtorrent/config.hpp" - namespace libtorrent { +/* + namespace asio = boost::asio; - class TORRENT_EXPORT network_error : public std::exception - { - public: - network_error(int error_code): m_error_code(error_code) {} - virtual const char* what() const throw(); - int error_code() const - { return m_error_code; } - private: - int m_error_code; - }; + using boost::asio::ipv4::tcp; + using boost::asio::ipv4::address; + using boost::asio::stream_socket; + using boost::asio::datagram_socket; + using boost::asio::socket_acceptor; + using boost::asio::demuxer; + using boost::asio::ipv4::host_resolver; + using boost::asio::async_write; + using boost::asio::ipv4::host; + using boost::asio::deadline_timer; +*/ + namespace asio = ::asio; - class socket; - - class TORRENT_EXPORT address - { - friend class socket; - public: - address(); - address( - unsigned char a - , unsigned char b - , unsigned char c - , unsigned char d - , unsigned short port); - - address(unsigned int addr, unsigned short port); - - address(const char* addr, unsigned short port); - address(const address& a); - ~address(); - - std::string as_string() const; - unsigned int ip() const { return m_ip; } - - bool operator<=(const address& a) const - { if (ip() == a.ip()) return port <= a.port; else return ip() <= a.ip(); } - bool operator<(const address& a) const - { if (ip() == a.ip()) return port < a.port; else return ip() < a.ip(); } - bool operator!=(const address& a) const - { return ip() != a.ip() || port != a.port; } - bool operator==(const address& a) const - { return ip() == a.ip() && port == a.port; } - - unsigned short port; - - BOOST_STATIC_CONSTANT(unsigned short, any_port = 0); - BOOST_STATIC_CONSTANT(unsigned int, any_addr = 0); - - private: - - unsigned int m_ip; - }; - - class TORRENT_EXPORT socket: public boost::noncopyable - { - friend class address; - friend class selector; - public: - - enum type - { - tcp = 0, - udp - }; - - socket(type t, bool blocking = true, unsigned short receive_port = 0); - virtual ~socket(); - - void connect( - const address& addr - , address const& bind_to - = address(address::any_addr, address::any_port)); - void close(); - - void set_blocking(bool blocking); - bool is_blocking() { return m_blocking; } - - const address& sender() const { assert(m_sender != address()); return m_sender; } - address name() const; - - void listen(libtorrent::address const& iface, int queue); - boost::shared_ptr accept(); - - template int send(const T& buffer); - template int send_to(const address& addr, const T& buffer); - template int receive(T& buf); - - int send(const char* buffer, int size); - int send_to(const address& addr, const char* buffer, int size); - int receive(char* buffer, int size); - - void set_receive_bufsize(int size); - void set_send_bufsize(int size); - - bool is_readable() const; - bool is_writable() const; - bool has_error() const; - - enum error_code - { - netdown, - fault, - access, - address_in_use, - address_not_available, - in_progress, - interrupted, - invalid, - net_reset, - not_connected, - no_buffers, - operation_not_supported, - not_socket, - shutdown, - would_block, - connection_reset, - timed_out, - connection_aborted, - message_size, - not_ready, - no_support, - connection_refused, - is_connected, - net_unreachable, - not_initialized, - host_not_found, - unknown_error - }; - - error_code last_error() const; - - private: - - socket(int sock, const address& sender, bool blocking); - - int m_socket; - address m_sender; - bool m_blocking; - -#ifndef NDEBUG - bool m_connected; // indicates that this socket has been connected - type m_type; -#endif - - }; - - template - inline int socket::send(const T& buf) - { - return send(reinterpret_cast(&buf), sizeof(buf)); - } - - template - inline int socket::send_to(const address& addr, const T& buf) - { - return send_to(addr, reinterpret_cast(&buf), sizeof(buf)); - } - - template - inline int socket::receive(T& buf) - { - return receive(reinterpret_cast(&buf), sizeof(T)); - } - - - // timeout is given in microseconds - // modified is cleared and filled with the sockets that is ready for reading or writing - // or have had an error - - - - - class TORRENT_EXPORT selector - { - public: - - void monitor_readability(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - assert(std::find(m_readable.begin(), m_readable.end(), s) == m_readable.end()); - m_readable.push_back(s); - } - void monitor_writability(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - assert(std::find(m_writable.begin(), m_writable.end(), s) == m_writable.end()); - m_writable.push_back(s); - } - void monitor_errors(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - assert(std::find(m_error.begin(), m_error.end(), s) == m_error.end()); - m_error.push_back(s); - } - - void remove(boost::shared_ptr s); - - void remove_writable(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - m_writable.erase(std::find(m_writable.begin(), m_writable.end(), s)); - } - - void remove_readable(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - m_readable.erase(std::find(m_readable.begin(), m_readable.end(), s)); - } - - bool is_writability_monitored(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - return std::find(m_writable.begin(), m_writable.end(), s) - != m_writable.end(); - } - - bool is_readability_monitored(boost::shared_ptr s) - { - boost::mutex::scoped_lock l(m_mutex); - return std::find(m_readable.begin(), m_readable.end(), s) - != m_readable.end(); - } - - void wait(int timeout - , std::vector >& readable - , std::vector >& writable - , std::vector >& error); - - int count_read_monitors() const - { - boost::mutex::scoped_lock l(m_mutex); - return (int)m_readable.size(); - } - - private: - - mutable boost::mutex m_mutex; - std::vector > m_readable; - std::vector > m_writable; - std::vector > m_error; - }; + using asio::ipv4::tcp; + using asio::ipv4::udp; + typedef asio::ipv4::tcp::socket stream_socket; + using asio::ipv4::address; + typedef asio::ipv4::udp::socket datagram_socket; + typedef asio::ipv4::tcp::acceptor socket_acceptor; + typedef asio::io_service demuxer; + using asio::ipv4::host_resolver; + using asio::ipv4::host; + using asio::async_write; + using asio::deadline_timer; } #endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/stat.hpp b/include/libtorrent/stat.hpp index 36c54634c..50b4b7d4d 100755 --- a/include/libtorrent/stat.hpp +++ b/include/libtorrent/stat.hpp @@ -112,7 +112,7 @@ namespace libtorrent } // should be called once every second - void second_tick(); + void second_tick(float tick_interval); float upload_rate() const { return m_mean_upload_rate; } float download_rate() const { return m_mean_download_rate; } @@ -152,11 +152,11 @@ namespace libtorrent #endif // history of download/upload speeds a few seconds back - int m_download_rate_history[history]; - int m_upload_rate_history[history]; + float m_download_rate_history[history]; + float m_upload_rate_history[history]; - int m_download_payload_rate_history[history]; - int m_upload_payload_rate_history[history]; + float m_download_payload_rate_history[history]; + float m_upload_payload_rate_history[history]; // the accumulators we are adding the downloads/uploads // to this second. This only counts the actual payload diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index 0daa68682..5b6e046bf 100755 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -130,8 +130,9 @@ namespace libtorrent ~piece_manager(); bool check_fastresume(detail::piece_checker_data& d - , std::vector& pieces, bool compact_mode); - std::pair check_files(std::vector& pieces); + , std::vector& pieces, int& num_pieces, bool compact_mode); + std::pair check_files(std::vector& pieces + , int& num_pieces); void release_files(); diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index c218fb57f..68404a5c3 100755 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -65,6 +65,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/resource_request.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/escape_string.hpp" namespace libtorrent { @@ -74,10 +75,6 @@ namespace libtorrent class piece_manager; - std::string escape_string(const char* str, int len); - std::string unescape_string(std::string const& s); - - namespace detail { struct session_impl; @@ -101,9 +98,10 @@ namespace libtorrent , detail::checker_impl& checker , torrent_info const& tf , boost::filesystem::path const& save_path - , address const& net_interface + , tcp::endpoint const& net_interface , bool compact_mode - , int block_size); + , int block_size + , session_settings const& s); // used with metadata-less torrents // (the metadata is downloaded from the peers) @@ -113,9 +111,10 @@ namespace libtorrent , char const* tracker_url , sha1_hash const& info_hash , boost::filesystem::path const& save_path - , address const& net_interface + , tcp::endpoint const& net_interface , bool compact_mode - , int block_size); + , int block_size + , session_settings const& s); ~torrent(); @@ -138,7 +137,7 @@ namespace libtorrent // caclulate the upload/download and number // of connections this torrent needs. And prepare // it for being used by allocate_resources. - void second_tick(stat& accumulator); + void second_tick(stat& accumulator, float tick_interval); // debug purpose only void print(std::ostream& os) const; @@ -165,19 +164,15 @@ namespace libtorrent bool is_piece_filtered(int index) const; void filtered_pieces(std::vector& bitmask) const; - // idea from Arvid and MooPolice - // todo refactoring and improving the function body - // marks the file with the given index as filtered - // it will not be downloaded - void filter_file(int index, bool filter); void filter_files(std::vector const& files); - torrent_status status() const; void use_interface(const char* net_interface); - address const& get_interface() const { return m_net_interface; } - peer_connection& connect_to_peer(const address& a); + tcp::endpoint const& get_interface() const { return m_net_interface; } + + void connect_to_url_seed(std::string const& url); + peer_connection& connect_to_peer(tcp::endpoint const& a); void set_ratio(float ratio) { assert(ratio >= 0.0f); m_ratio = ratio; } @@ -187,6 +182,14 @@ namespace libtorrent // -------------------------------------------- // PEER MANAGEMENT + + // add or remove a url that will be attempted for + // finding the file(s) in this torrent. + void add_url_seed(std::string const& url) + { m_web_seeds.insert(url); } + + void remove_url_seed(std::string const& url) + { m_web_seeds.erase(url); } // used by peer_connection to attach itself to a torrent // since incoming connections don't know what torrent @@ -196,10 +199,9 @@ namespace libtorrent // this will remove the peer and make sure all // the pieces it had have their reference counter // decreased in the piece_picker - // called from the peer_connection destructor void remove_peer(peer_connection* p); - peer_connection* connection_for(const address& a) + peer_connection* connection_for(tcp::endpoint const& a) { peer_iterator i = m_connections.find(a); if (i == m_connections.end()) return 0; @@ -210,8 +212,8 @@ namespace libtorrent int num_peers() const { return (int)m_connections.size(); } int num_seeds() const; - typedef std::map::iterator peer_iterator; - typedef std::map::const_iterator const_peer_iterator; + typedef std::map::iterator peer_iterator; + typedef std::map::const_iterator const_peer_iterator; const_peer_iterator begin() const { return m_connections.begin(); } const_peer_iterator end() const { return m_connections.end(); } @@ -264,9 +266,9 @@ namespace libtorrent // the tracker void set_tracker_login(std::string const& name, std::string const& pw); - // the address of the tracker that we managed to + // the tcp::endpoint of the tracker that we managed to // announce ourself at the last time we tried to announce - const address& current_tracker() const; + const tcp::endpoint& current_tracker() const; // -------------------------------------------- // PIECE MANAGEMENT @@ -300,7 +302,7 @@ namespace libtorrent m_picker->dec_refcount(index); } - int block_size() const { return m_block_size; } + int block_size() const { assert(m_block_size > 0); return m_block_size; } // this will tell all peers that we just got his piece // and also let the piece picker know that we have this piece @@ -313,6 +315,10 @@ namespace libtorrent // the download. It will post an event, disconnect // all seeds and let the tracker know we're finished. void completed(); + + // this is the asio callback that is called when a name + // lookup for a web seed is completed. + void on_name_lookup(asio::error const& e, int port, std::string url, host h); // this is called when the torrent has finished. i.e. // all the pieces we have not filtered have been downloaded. @@ -326,6 +332,8 @@ namespace libtorrent // each time a piece has failed the hash // test void piece_failed(int index); + void received_redundant_data(int num_bytes) + { assert(num_bytes > 0); m_total_redundant_bytes += num_bytes; } float priority() const { return m_priority; } @@ -384,6 +392,9 @@ namespace libtorrent resource_request m_uploads_quota; resource_request m_connections_quota; + void set_peer_upload_limit(tcp::endpoint ip, int limit); + void set_peer_download_limit(tcp::endpoint ip, int limit); + void set_upload_limit(int limit); void set_download_limit(int limit); void set_max_uploads(int limit); @@ -454,15 +465,25 @@ namespace libtorrent // is optional and may be -1. int m_complete; int m_incomplete; - + #ifndef NDEBUG public: #endif - std::map m_connections; + std::map m_connections; #ifndef NDEBUG private: #endif + // The list of web seeds in this torrent. Seeds + // with fatal errors are removed from the set + std::set m_web_seeds; + // The set of url seeds that are currently having + // their hostnames resolved. + std::map m_resolving_web_seeds; + + // used to resolve the names of web seeds + host_resolver m_host_resolver; + // this is the upload and download statistics for the whole torrent. // it's updated from all its peers once every second. libtorrent::stat m_stat; @@ -518,13 +539,14 @@ namespace libtorrent // the number of bytes that has been // downloaded that failed the hash-test size_type m_total_failed_bytes; + size_type m_total_redundant_bytes; std::string m_username; std::string m_password; // the network interface all outgoing connections // are opened through - address m_net_interface; + tcp::endpoint m_net_interface; // the max number of bytes this torrent // can upload per second @@ -569,6 +591,15 @@ namespace libtorrent // them from altering the piece-picker before it // has been initialized with files_checked(). bool m_connections_initialized; + + session_settings const& m_settings; + +#ifndef NDEBUG + // this is the amount downloaded when this torrent + // is started. i.e. + // total_done - m_initial_done <= total_payload_download + size_type m_initial_done; +#endif }; inline boost::posix_time::ptime torrent::next_announce() const diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index c67034d6a..b01a862e2 100755 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -81,6 +81,8 @@ namespace libtorrent , total_upload(0) , total_payload_download(0) , total_payload_upload(0) + , total_failed_bytes(0) + , total_redundant_bytes(0) , download_rate(0) , upload_rate(0) , download_payload_rate(0) @@ -89,6 +91,7 @@ namespace libtorrent , num_complete(-1) , num_incomplete(-1) , pieces(0) + , num_pieces(0) , total_done(0) , total_wanted_done(0) , total_wanted(0) @@ -130,6 +133,10 @@ namespace libtorrent // has failed their hash test size_type total_failed_bytes; + // the number of payload bytes that + // has been received redundantly. + size_type total_redundant_bytes; + // current transfer rate // payload plus protocol float download_rate; @@ -153,6 +160,11 @@ namespace libtorrent int num_incomplete; const std::vector* pieces; + + // this is the number of pieces the client has + // downloaded. it is equal to: + // std::accumulate(pieces->begin(), pieces->end()); + int num_pieces; // the number of bytes of the file we have // including pieces that may have been filtered @@ -196,7 +208,7 @@ namespace libtorrent int blocks_in_piece; std::bitset requested_blocks; std::bitset finished_blocks; - address peer[max_blocks_per_piece]; + tcp::endpoint peer[max_blocks_per_piece]; int num_downloads[max_blocks_per_piece]; }; @@ -209,13 +221,15 @@ namespace libtorrent torrent_handle(): m_ses(0), m_chk(0) {} void get_peer_info(std::vector& v) const; - bool send_chat_message(address ip, std::string message) const; + bool send_chat_message(tcp::endpoint ip, std::string message) const; torrent_status status() const; void get_download_queue(std::vector& queue) const; std::vector const& trackers() const; void replace_trackers(std::vector const&) const; + void add_url_seed(std::string const& url); + bool has_metadata() const; const torrent_info& get_torrent_info() const; bool is_valid() const; @@ -225,7 +239,6 @@ namespace libtorrent void pause() const; void resume() const; - // marks the piece with the given index as filtered // it will not be downloaded void filter_piece(int index, bool filter) const; @@ -233,8 +246,6 @@ namespace libtorrent bool is_piece_filtered(int index) const; std::vector filtered_pieces() const; - //idea from Arvid and MooPolice - //todo refactoring and improving the function body // marks the file with the given index as filtered // it will not be downloaded void filter_file(int index, bool filter) const; @@ -247,7 +258,7 @@ namespace libtorrent entry write_resume_data() const; // kind of similar to get_torrent_info() but this - // is low level, returning the exact info-part of + // is lower level, returning the exact info-part of // the .torrent file. When hashed, this buffer // will produce the info hash. The reference is valid // only as long as the torrent is running. @@ -270,8 +281,11 @@ namespace libtorrent void set_upload_limit(int limit) const; void set_download_limit(int limit) const; + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + // manually connect a peer - void connect_peer(address const& adr) const; + void connect_peer(tcp::endpoint const& adr) const; // valid ratios are 0 (infinite ratio) or [ 1.0 , inf ) // the ratio is uploaded / downloaded. less than 1 is not allowed diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index e9be8e17a..80a24654f 100755 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -54,6 +54,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" #include "libtorrent/size_type.hpp" +#include "libtorrent/peer_request.hpp" #include "libtorrent/config.hpp" namespace libtorrent @@ -65,6 +66,13 @@ namespace libtorrent size_type size; }; + struct TORRENT_EXPORT file_slice + { + int file_index; + size_type offset; + size_type size; + }; + struct TORRENT_EXPORT announce_entry { announce_entry(std::string const& u): url(u), tier(0) {} @@ -77,11 +85,6 @@ namespace libtorrent virtual const char* what() const throw() { return "invalid torrent file"; } }; - // TODO: add a check to see if filenames are accepted on the - // current platform. - // also add a filename converter function that will transform - // invalid filenames to valid filenames on the current platform - class TORRENT_EXPORT torrent_info { public: @@ -99,6 +102,12 @@ namespace libtorrent void set_hash(int index, sha1_hash const& h); void add_tracker(std::string const& url, int tier = 0); void add_file(boost::filesystem::path file, size_type size); + void add_url_seed(std::string const& url); + + std::vector map_block(int piece, size_type offset, int size) const; + peer_request map_file(int file, size_type offset, int size) const; + + std::vector const& url_seeds() const { return m_url_seeds; } typedef std::vector::const_iterator file_iterator; typedef std::vector::const_reverse_iterator reverse_file_iterator; @@ -153,6 +162,8 @@ namespace libtorrent // the urls to the trackers std::vector m_urls; + std::vector m_url_seeds; + // the length of one piece // if this is 0, the torrent_info is // in an uninitialized state @@ -168,7 +179,7 @@ namespace libtorrent size_type m_total_size; // the hash that identifies this torrent - // it is mutable because it's calculated + // is mutable because it's calculated // lazily mutable sha1_hash m_info_hash; diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index 9acc5567a..c51804f1d 100755 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -46,6 +46,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include +#include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -62,8 +66,8 @@ namespace libtorrent { struct request_callback; class tracker_manager; - -// address parse_url(std::string const& url); + struct timeout_handler; + struct tracker_connection; // encodes a string using the base64 scheme TORRENT_EXPORT std::string base64encode(const std::string& s); @@ -71,6 +75,9 @@ namespace libtorrent // returns -1 if gzip header is invalid or the header size in bytes TORRENT_EXPORT int gzip_header(const char* buf, int size); + TORRENT_EXPORT boost::tuple + parse_url_components(std::string url); + struct TORRENT_EXPORT tracker_request { tracker_request() @@ -95,7 +102,7 @@ namespace libtorrent }; sha1_hash info_hash; - peer_id id; + peer_id pid; size_type downloaded; size_type uploaded; size_type left; @@ -125,7 +132,7 @@ namespace libtorrent , int response_code , const std::string& description) = 0; - address m_tracker_address; + tcp::endpoint m_tracker_address; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) virtual void debug_log(const std::string& line) = 0; @@ -140,23 +147,71 @@ namespace libtorrent , request_callback* requester , int maximum_tracker_response_length); - struct TORRENT_EXPORT tracker_connection: boost::noncopyable - { - tracker_connection(boost::weak_ptr r) - : m_requester(r) - {} + TORRENT_EXPORT void intrusive_ptr_add_ref(timeout_handler const*); + TORRENT_EXPORT void intrusive_ptr_release(timeout_handler const*); + + struct TORRENT_EXPORT timeout_handler + : boost::noncopyable + { + friend void intrusive_ptr_add_ref(timeout_handler const*); + friend void intrusive_ptr_release(timeout_handler const*); + + timeout_handler(demuxer& d); + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + + virtual void on_timeout() = 0; + virtual ~timeout_handler() {} + + private: + + void timeout_callback(asio::error const&); + + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + demuxer& m_demuxer; + // used for timeouts + // this is set when the request has been sent + boost::posix_time::ptime m_start_time; + // this is set every time something is received + boost::posix_time::ptime m_read_time; + // the asio async operation + asio::deadline_timer m_timeout; + + int m_completion_timeout; + int m_read_timeout; + + typedef boost::mutex mutex_t; + mutable mutex_t m_mutex; + mutable int m_refs; + }; + + struct TORRENT_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request req + , demuxer& d + , boost::weak_ptr r); - virtual bool tick() = 0; - virtual bool send_finished() const = 0; - bool has_requester() const { return !m_requester.expired(); } request_callback& requester(); virtual ~tracker_connection() {} - virtual tracker_request const& tracker_req() const = 0; + + tracker_request const& tracker_req() const { return m_req; } + bool has_requester() const { return !m_requester.expired(); } + + void fail(int code, char const* msg); + void fail_timeout(); + void close(); protected: - boost::weak_ptr m_requester; - + private: + tracker_manager& m_man; + const tracker_request m_req; }; class TORRENT_EXPORT tracker_manager: boost::noncopyable @@ -165,19 +220,23 @@ namespace libtorrent tracker_manager(const http_settings& s) : m_settings(s) {} - - void tick(); + void queue_request( - tracker_request r + demuxer& d + , tracker_request r , std::string const& auth , boost::weak_ptr c = boost::weak_ptr()); void abort_all_requests(); - bool send_finished() const; + void remove_request(tracker_connection const*); + private: - typedef std::list > + typedef boost::recursive_mutex mutex_t; + mutex_t m_mutex; + + typedef std::list > tracker_connections_t; tracker_connections_t m_connections; const http_settings& m_settings; diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp index c986799da..a3f6828aa 100755 --- a/include/libtorrent/udp_tracker_connection.hpp +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -56,7 +56,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_id.hpp" #include "libtorrent/peer.hpp" #include "libtorrent/tracker_manager.hpp" -#include "libtorrent/async_gethostbyname.hpp" #include "libtorrent/config.hpp" namespace libtorrent @@ -67,45 +66,55 @@ namespace libtorrent public: udp_tracker_connection( - tracker_request const& req + demuxer& d + , tracker_manager& man + , tracker_request const& req , std::string const& hostname , unsigned short port , boost::weak_ptr c , const http_settings& stn); - virtual bool tick(); - virtual bool send_finished() const; - virtual tracker_request const& tracker_req() const - { return m_request; } - private: enum action_t { - connect, - announce, - scrape, - error + action_connect, + action_announce, + action_scrape, + action_error }; + boost::intrusive_ptr self() + { return boost::intrusive_ptr(this); } + + void name_lookup(asio::error const& error); + void timeout(asio::error const& error); + void send_udp_connect(); + void connect_response(asio::error const& error, std::size_t bytes_transferred); + void send_udp_announce(); + void announce_response(asio::error const& error, std::size_t bytes_transferred); + void send_udp_scrape(); - bool parse_connect_response(const char* buf, int ret); - bool parse_announce_response(const char* buf, int ret); - bool parse_scrape_response(const char* buf, int ret); + void scrape_response(asio::error const& error, std::size_t bytes_transferred); - dns_lookup m_name_lookup; - boost::shared_ptr m_socket; + virtual void on_timeout(); - // used for time outs - boost::posix_time::ptime m_request_time; + tracker_manager& m_man; + + host_resolver m_name_lookup; + host m_host; + int m_port; + boost::shared_ptr m_socket; + udp::endpoint m_target; + udp::endpoint m_sender; - tracker_request m_request; int m_transaction_id; boost::int64_t m_connection_id; const http_settings& m_settings; int m_attempts; + std::vector m_buffer; }; } diff --git a/include/libtorrent/version.hpp b/include/libtorrent/version.hpp index 5e9502a79..8a5132a07 100755 --- a/include/libtorrent/version.hpp +++ b/include/libtorrent/version.hpp @@ -33,6 +33,6 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_VERSION_HPP_INCLUDED #define TORRENT_VERSION_HPP_INCLUDED -#define LIBTORRENT_VERSION "0.9.0.0" +#define LIBTORRENT_VERSION "0.9.2.0" #endif diff --git a/include/libtorrent/web_peer_connection.hpp b/include/libtorrent/web_peer_connection.hpp new file mode 100755 index 000000000..7001fe623 --- /dev/null +++ b/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,170 @@ +/* + +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. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/allocate_resources.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +// parse_url +#include "libtorrent/tracker_manager.hpp" +// http_parser +#include "libtorrent/http_tracker_connection.hpp" + +namespace libtorrent +{ + class torrent; + + namespace detail + { + struct session_impl; + } + + class TORRENT_EXPORT web_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + web_peer_connection( + detail::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , std::string const& url); + + ~web_peer_connection(); + + // called from the main loop when this connection has any + // work to do. + void on_sent(asio::error const& error + , std::size_t bytes_transferred); + void on_receive(asio::error const& error + , std::size_t bytes_transferred); + + std::string const& url() const { return m_url; } + + virtual void get_peer_info(peer_info& p) const; + + // the following functions appends messages + // to the send buffer + void write_choke() {} + void write_unchoke() {} + void write_interested() {} + void write_not_interested() {} + void write_request(peer_request const& r); + void write_cancel(peer_request const& r) {} + void write_have(int index) {} + void write_piece(peer_request const& r) {} + void write_keepalive() {} + void on_connected(); + +#ifndef NDEBUG + void check_invariant() const; + boost::posix_time::ptime m_last_choke; +#endif + + private: + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + boost::optional downloading_piece_progress() const; + + // this has one entry per bittorrent request + std::deque m_requests; + // this has one entry per http-request + // (might be more than the bt requests) + std::deque m_file_requests; + + std::string m_server_string; + http_parser m_parser; + std::string m_host; + int m_port; + std::string m_path; + std::string m_url; + + // the first request will contain a little bit more data + // than subsequent ones, things that aren't critical are left + // out to save bandwidth. + bool m_first_request; + + // this is used for intermediate storage of pieces + // that is received in more than on HTTP responses + std::vector m_piece; + // the mapping of the data in the m_piece buffer + peer_request m_intermediate_piece; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + diff --git a/src/Makefile.am b/src/Makefile.am index 3bf252588..4e89013b2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,9 @@ lib_LTLIBRARIES = libtorrent.la -libtorrent_la_SOURCES = allocate_resources.cpp async_gethostbyname.cpp \ +libtorrent_la_SOURCES = allocate_resources.cpp \ entry.cpp escape_string.cpp \ -peer_connection.cpp piece_picker.cpp policy.cpp \ -session.cpp sha1.cpp socket.cpp stat.cpp \ +peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ +piece_picker.cpp policy.cpp session.cpp sha1.cpp stat.cpp \ storage.cpp torrent.cpp torrent_handle.cpp \ torrent_info.cpp tracker_manager.cpp \ http_tracker_connection.cpp udp_tracker_connection.cpp \ @@ -13,7 +13,6 @@ noinst_HEADERS = \ $(top_srcdir)/include/libtorrent/alert.hpp \ $(top_srcdir)/include/libtorrent/alert_types.hpp \ $(top_srcdir)/include/libtorrent/allocate_resources.hpp \ -$(top_srcdir)/include/libtorrent/async_gethostbyname.hpp \ $(top_srcdir)/include/libtorrent/bencode.hpp \ $(top_srcdir)/include/libtorrent/buffer.hpp \ $(top_srcdir)/include/libtorrent/debug.hpp \ @@ -30,6 +29,8 @@ $(top_srcdir)/include/libtorrent/io.hpp \ $(top_srcdir)/include/libtorrent/ip_filter.hpp \ $(top_srcdir)/include/libtorrent/peer.hpp \ $(top_srcdir)/include/libtorrent/peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/bt_peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/web_peer_connection.hpp \ $(top_srcdir)/include/libtorrent/peer_id.hpp \ $(top_srcdir)/include/libtorrent/peer_info.hpp \ $(top_srcdir)/include/libtorrent/peer_request.hpp \ @@ -54,5 +55,5 @@ $(top_srcdir)/include/libtorrent/version.hpp libtorrent_la_LDFLAGS = $(LDFLAGS) -version-info 1:0:1 libtorrent_la_LIBADD = @ZLIB@ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ -AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CXXFLAGS= -ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/asio/include @ZLIBINCL@ @DEBUGFLAGS@ @PTHREAD_CFLAGS@ AM_LDFLAGS= $(LDFLAGS) -L./ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ diff --git a/src/allocate_resources.cpp b/src/allocate_resources.cpp index f8598b89b..203e52a9b 100644 --- a/src/allocate_resources.cpp +++ b/src/allocate_resources.cpp @@ -69,10 +69,12 @@ namespace libtorrent { assert(a >= 0); assert(b >= 0); + assert(a <= resource_request::inf); + assert(b <= resource_request::inf); assert(resource_request::inf + resource_request::inf < 0); int sum = a + b; - if(sum < 0) + if (sum < 0) sum = resource_request::inf; assert(sum >= a && sum >= b); @@ -121,7 +123,6 @@ namespace libtorrent assert(m_resources >= 0); for (It i = m_start, end(m_end); i != end; ++i) { - assert(((*i).*m_res).used >= 0); assert(((*i).*m_res).max >= 0); assert(((*i).*m_res).given >= 0); } @@ -165,7 +166,7 @@ namespace libtorrent , res); #endif - if(resources == resource_request::inf) + if (resources == resource_request::inf) { // No competition for resources. // Just give everyone what they want. @@ -224,15 +225,18 @@ namespace libtorrent for (It i = start; i != end && resources_to_distribute > 0; ++i) { resource_request& r = (*i).*res; - if(r.given == r.max) continue; + if (r.given == r.max) continue; assert(r.given < r.max); size_type used = (size_type)r.used + 1; + if (used < 1) used = 1; size_type to_give = used * kNumer / kDenom; if(to_give > resource_request::inf) to_give = resource_request::inf; + assert(to_give >= 0); resources_to_distribute -= give(r, (int)to_give); + assert(resources_to_distribute >= 0); } assert(resources_to_distribute >= 0); @@ -240,13 +244,14 @@ namespace libtorrent } peer_connection& pick_peer( - std::pair, boost::shared_ptr > const& p) + std::pair + , boost::intrusive_ptr > const& p) { return *p.second; } peer_connection& pick_peer2( - std::pair const& p) + std::pair const& p) { return *p.second; } @@ -262,11 +267,11 @@ namespace libtorrent /* void allocate_resources( int resources - , std::map, boost::shared_ptr >& c + , std::map, boost::intrusive_ptr >& c , resource_request peer_connection::* res) { - typedef std::map, boost::shared_ptr >::iterator orig_iter; - typedef std::pair, boost::shared_ptr > in_param; + typedef std::map, boost::intrusive_ptr >::iterator orig_iter; + typedef std::pair, boost::intrusive_ptr > in_param; typedef boost::transform_iterator new_iter; allocate_resources_impl( @@ -297,7 +302,7 @@ namespace libtorrent struct iterator_wrapper2 { - typedef std::map::iterator orig_iter; + typedef std::map::iterator orig_iter; orig_iter iter; @@ -326,7 +331,7 @@ namespace libtorrent void allocate_resources( int resources - , std::map& c + , std::map& c , resource_request peer_connection::* res) { allocate_resources_impl( @@ -356,11 +361,11 @@ namespace libtorrent void allocate_resources( int resources - , std::map& c + , std::map& c , resource_request peer_connection::* res) { - typedef std::map::iterator orig_iter; - typedef std::pair in_param; + typedef std::map::iterator orig_iter; + typedef std::pair in_param; typedef boost::transform_iterator new_iter; allocate_resources_impl( diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp new file mode 100755 index 000000000..aa0d902cb --- /dev/null +++ b/src/bt_peer_connection.cpp @@ -0,0 +1,1537 @@ +/* + +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 +#include +#include +#include +#include + +#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 tor + , shared_ptr 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 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 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::max()) + p.upload_limit = -1; + else + p.upload_limit = m_ul_bandwidth_quota.given; + + if (m_dl_bandwidth_quota.given == std::numeric_limits::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 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 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 bt_peer_connection::downloading_piece_progress() const + { + boost::shared_ptr 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(); + + 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 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(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 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 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(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 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 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 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 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(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(packet_type) + + " size: " + boost::lexical_cast(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 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 t = associated_torrent().lock(); + assert(t); + + if (t->valid_metadata()) + { + std::pair 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 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 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 const& bitfield) + { + INVARIANT_CHECK; + + boost::shared_ptr 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 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) + { + const int packet_size = 4 + 5 + 4 + r.length; + + boost::shared_ptr 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 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"); + } + + 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::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 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(); + } +#endif + +} + diff --git a/src/entry.cpp b/src/entry.cpp index ead952acf..6e5683ca6 100755 --- a/src/entry.cpp +++ b/src/entry.cpp @@ -62,7 +62,7 @@ namespace std::pair const& e) const { - return e.first == m_str; + return m_str && e.first == m_str; } char const* m_str; }; diff --git a/src/escape_string.cpp b/src/escape_string.cpp index 775fc6173..80d134ed7 100755 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -115,4 +115,34 @@ namespace libtorrent } return ret.str(); } + + std::string escape_path(const char* str, int len) + { + assert(str != 0); + assert(len >= 0); + static const char unreserved_chars[] = "/-_.!~*()" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789"; + + std::stringstream ret; + ret << std::hex << std::setfill('0'); + for (int i = 0; i < len; ++i) + { + if (std::count( + unreserved_chars + , unreserved_chars+sizeof(unreserved_chars)-1 + , *str)) + { + ret << *str; + } + else + { + ret << '%' + << std::setw(2) + << (int)static_cast(*str); + } + ++str; + } + return ret.str(); + } } diff --git a/src/file.cpp b/src/file.cpp index 68e56fd1f..dd78e461c 100755 --- a/src/file.cpp +++ b/src/file.cpp @@ -167,7 +167,11 @@ namespace libtorrent m_fd = ::open( utf8_native(path.native_file_string()).c_str() , map_open_mode(mode) +#ifdef _WIN32 , S_IREAD | S_IWRITE); +#else + , S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif #endif if (m_fd == -1) { @@ -218,7 +222,7 @@ namespace libtorrent return ret; } - void seek(size_type offset, int m) + size_type seek(size_type offset, int m) { assert(m_open_mode); assert(m_fd != -1); @@ -242,7 +246,7 @@ namespace libtorrent << " seekdir: " << seekdir; throw file_error(msg.str()); } - + return ret; } size_type tell() @@ -291,9 +295,9 @@ namespace libtorrent return m_impl->read(buf, num_bytes); } - void file::seek(size_type pos, file::seek_mode m) + size_type file::seek(size_type pos, file::seek_mode m) { - m_impl->seek(pos, m.m_val); + return m_impl->seek(pos, m.m_val); } size_type file::tell() diff --git a/src/file_win.cpp b/src/file_win.cpp index b726a57da..3900b9949 100644 --- a/src/file_win.cpp +++ b/src/file_win.cpp @@ -233,7 +233,7 @@ namespace libtorrent return bytes_read; } - void seek(size_type pos, seek_mode from_where) + size_type seek(size_type pos, seek_mode from_where) { assert(pos >= 0 || from_where != seek_begin); assert(pos <= 0 || from_where != seek_end); @@ -333,9 +333,9 @@ namespace libtorrent return m_impl->read(buffer, num_bytes); } - void file::seek(size_type pos, seek_mode m) + size_type file::seek(size_type pos, seek_mode m) { - m_impl->seek(pos,impl::seek_mode(m.m_val)); + return m_impl->seek(pos,impl::seek_mode(m.m_val)); } size_type file::tell() diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp index 4cc7815f5..5b0801bdf 100755 --- a/src/http_tracker_connection.cpp +++ b/src/http_tracker_connection.cpp @@ -38,16 +38,25 @@ POSSIBILITY OF SUCH DAMAGE. #include "zlib.h" +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "libtorrent/tracker_manager.hpp" #include "libtorrent/http_tracker_connection.hpp" -#include "libtorrent/udp_tracker_connection.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/bencode.hpp" #include "libtorrent/torrent.hpp" #include "libtorrent/io.hpp" -#include "libtorrent/async_gethostbyname.hpp" using namespace libtorrent; +using boost::bind; namespace { @@ -77,9 +86,152 @@ using namespace boost::posix_time; namespace libtorrent { + http_parser::http_parser() + : m_recv_pos(0) + , m_status_code(-1) + , m_content_length(-1) + , m_content_encoding(plain) + , m_state(read_status) + , m_recv_buffer(0, 0) + , m_body_start_pos(0) + , m_finished(false) + {} + + boost::tuple http_parser::incoming(buffer::const_interval recv_buffer) + { + m_recv_buffer = recv_buffer; + boost::tuple ret(0, 0); + + char const* pos = recv_buffer.begin + m_recv_pos; + if (m_state == read_status) + { + assert(!m_finished); + char const* newline = std::find(pos, recv_buffer.end, '\n'); + // if we don't have a full line yet, wait. + if (newline == recv_buffer.end) return ret; + + if (newline == pos) + throw std::runtime_error("unexpected newline in HTTP response"); + + std::istringstream line(std::string(pos, newline - 1)); + ++newline; + int incoming = (int)std::distance(pos, newline); + m_recv_pos += incoming; + boost::get<1>(ret) += incoming; + pos = newline; + + line >> m_protocol; + if (m_protocol.substr(0, 5) != "HTTP/") + { + throw std::runtime_error("unknown protocol in HTTP response: " + + m_protocol); + } + line >> m_status_code; + std::getline(line, m_server_message); + m_state = read_header; + } + + if (m_state == read_header) + { + assert(!m_finished); + char const* newline = std::find(pos, recv_buffer.end, '\n'); + std::string line; + + while (newline != recv_buffer.end && m_state == read_header) + { + if (newline == pos) + throw std::runtime_error("unexpected newline in HTTP response"); + + line.assign(pos, newline - 1); + m_recv_pos += newline - pos; + boost::get<1>(ret) += newline - pos; + pos = newline; + + std::string::size_type separator = line.find(": "); + if (separator == std::string::npos) + { + ++pos; + ++m_recv_pos; + boost::get<1>(ret) += 1; + + m_state = read_body; + m_body_start_pos = m_recv_pos; + break; + } + + std::string name = line.substr(0, separator); + std::string value = line.substr(separator + 2, std::string::npos); + m_header.insert(std::make_pair(name, value)); + + if (name == "Content-Length") + { + try + { + m_content_length = boost::lexical_cast(value); + } + catch(boost::bad_lexical_cast&) {} + } + else if (name == "Content-Encoding") + { + if (value == "gzip" || value == "x-gzip") + { + m_content_encoding = gzip; + } + else + { + std::string error_str = "unknown content encoding in response: \""; + error_str += value; + error_str += "\""; + throw std::runtime_error(error_str); + } + } + // TODO: make sure we don't step outside of the buffer + ++pos; + ++m_recv_pos; + assert(m_recv_pos <= (int)recv_buffer.left()); + newline = std::find(pos, recv_buffer.end, '\n'); + } + } + + if (m_state == read_body) + { + int incoming = recv_buffer.end - pos; + if (m_recv_pos - m_body_start_pos + incoming > m_content_length + && m_content_length >= 0) + incoming = m_content_length - m_recv_pos + m_body_start_pos; + + assert(incoming >= 0); + m_recv_pos += incoming; + boost::get<0>(ret) += incoming; + + if (m_content_length >= 0 + && m_recv_pos - m_body_start_pos >= m_content_length) + { + m_finished = true; + } + } + return ret; + } + + buffer::const_interval http_parser::get_body() + { + char const* body_begin = m_recv_buffer.begin + m_body_start_pos; + char const* body_end = m_recv_buffer.begin + m_recv_pos; + + m_recv_pos = 0; + m_body_start_pos = 0; + m_status_code = -1; + m_content_length = -1; + m_finished = false; + m_state = read_status; + m_header.clear(); + + return buffer::const_interval(body_begin, body_end); + } http_tracker_connection::http_tracker_connection( - tracker_manager& man + demuxer& d + , tracker_manager& man , tracker_request const& req , std::string const& hostname , unsigned short port @@ -87,20 +239,21 @@ namespace libtorrent , boost::weak_ptr c , const http_settings& stn , std::string const& auth) - : tracker_connection(c) + : tracker_connection(man, req, d, c) , m_man(man) , m_state(read_status) , m_content_encoding(plain) , m_content_length(0) + , m_name_lookup(d) + , m_port(port) , m_recv_pos(0) - , m_request_time(second_clock::universal_time()) + , m_buffer(http_buffer_size) , m_settings(stn) - , m_req(req) , m_password(auth) , m_code(0) + , m_timed_out(false) { const std::string* connect_to_host; - int connect_to_port = port; bool using_proxy = false; m_send_buffer.assign("GET "); @@ -117,7 +270,7 @@ namespace libtorrent m_send_buffer += ":"; m_send_buffer += boost::lexical_cast(port); } - connect_to_port = m_settings.proxy_port != 0 + m_port = m_settings.proxy_port != 0 ? m_settings.proxy_port : 80 ; } else @@ -125,7 +278,7 @@ namespace libtorrent connect_to_host = &hostname; } - if (m_req.kind == tracker_request::scrape_request) + if (tracker_req().kind == tracker_request::scrape_request) { // find and replace "announce" with "scrape" // in request @@ -133,7 +286,7 @@ namespace libtorrent std::size_t pos = request.find("announce"); if (pos == std::string::npos) throw std::runtime_error("scrape is not available on url: '" - + m_req.url +"'"); + + tracker_req().url +"'"); request.replace(pos, 8, "scrape"); } @@ -151,11 +304,11 @@ namespace libtorrent m_send_buffer += escape_string( reinterpret_cast(req.info_hash.begin()), 20); - if (m_req.kind == tracker_request::announce_request) + if (tracker_req().kind == tracker_request::announce_request) { m_send_buffer += "&peer_id="; m_send_buffer += escape_string( - reinterpret_cast(req.id.begin()), 20); + reinterpret_cast(req.pid.begin()), 20); m_send_buffer += "&port="; m_send_buffer += boost::lexical_cast(req.listen_port); @@ -191,7 +344,8 @@ namespace libtorrent m_send_buffer += " HTTP/1.0\r\nAccept-Encoding: gzip\r\n" "User-Agent: "; - m_send_buffer += m_settings.user_agent; + m_send_buffer += escape_string(m_settings.user_agent.c_str() + , m_settings.user_agent.length()); m_send_buffer += " (libtorrent)\r\n" "Host: "; m_send_buffer += hostname; @@ -221,139 +375,147 @@ namespace libtorrent } #endif - m_name_lookup = dns_lookup(connect_to_host->c_str(), connect_to_port); - m_socket.reset(new socket(socket::tcp, false)); + m_name_lookup.async_by_name(m_host, *connect_to_host + , bind(&http_tracker_connection::name_lookup, self(), _1)); + set_timeout(m_settings.tracker_completion_timeout + , m_settings.tracker_receive_timeout); } - // returns true if this connection is finished and should be removed from - // the connections list. - bool http_tracker_connection::tick() + void http_tracker_connection::on_timeout() { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - try + m_timed_out = true; + m_socket.reset(); + m_name_lookup.cancel(); + fail_timeout(); + } + + void http_tracker_connection::name_lookup(asio::error const& error) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + + if (error) { -#endif - - if (m_name_lookup.running()) - { - if (!m_name_lookup.finished()) return false; - - if (m_name_lookup.failed()) - { - if (has_requester()) requester().tracker_request_error( - m_req, -1, m_name_lookup.error()); - return true; - } - address a(m_name_lookup.ip()); - if (has_requester()) requester().m_tracker_address = a; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("name lookup successful"); -#endif - - m_socket->connect(a); + fail(-1, error.what()); + return; + } - // clear the lookup entry so it will not be - // marked as running anymore - m_name_lookup = dns_lookup(); - } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker name lookup successful"); +#endif + restart_read_timeout(); + m_socket.reset(new stream_socket(m_name_lookup.io_service())); + tcp::endpoint a(m_port, m_host.address(0)); + if (has_requester()) requester().m_tracker_address = a; + m_socket->async_connect(a, bind(&http_tracker_connection::connected, self(), _1)); + } + catch (std::exception& e) + { + assert(false); + fail(-1, e.what()); + } - using namespace boost::posix_time; - - time_duration d = second_clock::universal_time() - m_request_time; - if (d > seconds(m_settings.tracker_timeout) || - (!has_requester() && d > seconds(m_settings.stop_tracker_timeout))) + void http_tracker_connection::connected(asio::error const& error) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + if (error) { - if (has_requester()) requester().tracker_request_timed_out(m_req); - return true; + fail(-1, error.what()); + return; } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection tick"); + if (has_requester()) requester().debug_log("tracker connection successful"); #endif - // if we have a send buffer and the socket is ready for writing - // send the buffer - if (!m_send_buffer.empty() && m_socket->is_writable()) + restart_read_timeout(); + async_write(*m_socket, asio::buffer(m_send_buffer.c_str() + , m_send_buffer.size()), bind(&http_tracker_connection::sent + , self(), _1)); + } + catch (std::exception& e) + { + assert(false); + fail(-1, e.what()); + } + + void http_tracker_connection::sent(asio::error const& error) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + if (error) { - int sent = m_socket->send(m_send_buffer.c_str(),(int)m_send_buffer.size()); - - if (sent == (int)m_send_buffer.size()) - { - m_send_buffer.clear(); - } - else if (sent > 0) - { - m_send_buffer.erase( - m_send_buffer.begin() - , m_send_buffer.begin() + sent); - } - - if (sent != 0) - m_request_time = second_clock::universal_time(); + fail(-1, error.what()); + return; } - if (m_socket->has_error()) - { - if (has_requester()) requester().tracker_request_error( - m_req, -1, "connection refused"); - return true; - } - - // if the socket isn't ready for reading, there's no point in continuing - // trying to read from it - if (!m_socket->is_readable()) return false; - m_request_time = second_clock::universal_time(); - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("tracker connection socket readable"); + if (has_requester()) requester().debug_log("tracker send data completed"); #endif + restart_read_timeout(); + assert(m_buffer.size() - m_recv_pos > 0); + m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive + , self(), _1, _2)); + } + catch (std::exception& e) + { + assert(false); + fail(-1, e.what()); + }; // msvc 7.1 seems to require this semi-colon + + + void http_tracker_connection::receive(asio::error const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (m_timed_out) return; + + if (error) + { + if (error == asio::error::eof) + { + on_response(); + close(); + return; + } + + fail(-1, error.what()); + return; + } + + restart_read_timeout(); + assert(bytes_transferred > 0); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) requester().debug_log("tracker connection reading " + + boost::lexical_cast(bytes_transferred)); +#endif + + m_recv_pos += bytes_transferred; // if the receive buffer is full, expand it with http_buffer_size if ((int)m_buffer.size() == m_recv_pos) { - if ((int)m_buffer.size() > m_settings.tracker_maximum_response_length) + if ((int)m_buffer.size() >= m_settings.tracker_maximum_response_length) { - if (has_requester()) - { - requester().tracker_request_error(m_req, 200 - , "too large tracker response"); - } - return true; + fail(200, "too large tracker response"); + return; } assert(http_buffer_size > 0); - m_buffer.resize(m_buffer.size() + http_buffer_size); + if ((int)m_buffer.size() + http_buffer_size + > m_settings.tracker_maximum_response_length) + m_buffer.resize(m_settings.tracker_maximum_response_length); + else + m_buffer.resize(m_buffer.size() + http_buffer_size); } - - assert(m_recv_pos >= 0); - assert(m_recv_pos < (int)m_buffer.size()); - int received = m_socket->receive(&m_buffer[m_recv_pos], (int)m_buffer.size() - m_recv_pos); - - assert(received <= (int)m_buffer.size() - m_recv_pos); - - if (received > 0) m_recv_pos += received; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("received: " + boost::lexical_cast(m_recv_pos)); -#endif - if (m_state == read_status) { - if (received <= 0) - { - if (has_requester()) - { - requester().tracker_request_error(m_req, -1 - , "invalid tracker response, connection closed"); - } - return true; - } - std::vector::iterator end = m_buffer.begin()+m_recv_pos; std::vector::iterator newline = std::find(m_buffer.begin(), end, '\n'); // if we don't have a full line yet, wait. - if (newline == end) return false; + if (newline == end) return; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (has_requester()) requester().debug_log(std::string(m_buffer.begin(), newline)); @@ -369,8 +531,8 @@ namespace libtorrent if (m_server_protocol.substr(0, 5) != "HTTP/") { std::string error_msg = "unknown protocol in response: " + m_server_protocol; - if (has_requester()) requester().tracker_request_error(m_req, -1, error_msg.c_str()); - return true; + fail(-1, error_msg.c_str()); + return; } line >> m_code; std::getline(line, m_server_message); @@ -379,16 +541,7 @@ namespace libtorrent if (m_state == read_header) { - if (received <= 0) - { - if (has_requester()) - requester().tracker_request_error(m_req, -1 - , "invalid tracker response, connection closed " - "while reading header"); - return true; - } - - std::vector::iterator end = m_buffer.begin()+m_recv_pos; + std::vector::iterator end = m_buffer.begin() + m_recv_pos; std::vector::iterator newline = std::find(m_buffer.begin(), end, '\n'); std::string line; @@ -410,31 +563,19 @@ namespace libtorrent } catch(boost::bad_lexical_cast&) { - if (has_requester()) - { - requester().tracker_request_error(m_req, -1, - "invalid content-length in tracker response"); - } - return true; + fail(-1, "invalid content-length in tracker response"); + return; } if (m_content_length > m_settings.tracker_maximum_response_length) { - if (has_requester()) - { - requester().tracker_request_error(m_req, -1 - , "content-length is greater than maximum response length"); - } - return true; + fail(-1, "content-length is greater than maximum response length"); + return; } if (m_content_length < minimum_tracker_response_length && m_code == 200) { - if (has_requester()) - { - requester().tracker_request_error(m_req, -1 - , "content-length is smaller than minimum response length"); - } - return true; + fail(-1, "content-length is smaller than minimum response length"); + return; } } else if (line.substr(0, 18) == "Content-Encoding: ") @@ -448,10 +589,8 @@ namespace libtorrent std::string error_str = "unknown content encoding in response: \""; error_str += line.substr(18, line.length() - 18 - 2); error_str += "\""; - if (has_requester()) - requester().tracker_request_error(m_req, -1 - , error_str.c_str()); - return true; + fail(-1, error_str.c_str()); + return; } } else if (line.substr(0, 10) == "Location: ") @@ -475,23 +614,24 @@ namespace libtorrent std::string error_str = "got redirection response ("; error_str += boost::lexical_cast(m_code); error_str += ") without 'Location' header"; - if (has_requester()) - requester().tracker_request_error(m_req - , m_code, error_str.c_str()); - return true; + fail(-1, error_str.c_str()); + return; } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (has_requester()) requester().debug_log("Redirecting to \"" + m_location + "\""); #endif + tracker_request req = tracker_req(); std::string::size_type i = m_location.find('?'); if (i == std::string::npos) - m_req.url = m_location; + req.url = m_location; else - m_req.url.assign(m_location.begin(), m_location.begin() + i); + req.url.assign(m_location.begin(), m_location.begin() + i); - m_man.queue_request(m_req, m_password, m_requester); - return true; + m_man.queue_request(m_socket->io_service(), req + , m_password, m_requester); + close(); + return; } } @@ -508,54 +648,69 @@ namespace libtorrent if (m_state == read_body) { - if (m_recv_pos == m_content_length || received <= 0) + if (m_recv_pos == m_content_length) { - // GZIP - if (m_content_encoding == gzip) - { - boost::shared_ptr r = m_requester.lock(); - if (!r) return true; - if (inflate_gzip(m_buffer, m_req, r.get(), - m_settings.tracker_maximum_response_length)) - return true; - } - - // handle tracker response - try - { - entry e = bdecode(m_buffer.begin(), m_buffer.end()); - parse(e); - } - catch (std::exception&) - { - std::string error_str(m_buffer.begin(), m_buffer.end()); - if (has_requester()) requester().tracker_request_error(m_req, m_code, error_str); - } - - return true; + on_response(); + close(); + return; } - return false; } else if (m_recv_pos > m_content_length && m_content_length > 0) { - if (has_requester()) - { - requester().tracker_request_error(m_req, -1 - , "invalid tracker response (body > content_length)"); - } - return true; + fail(-1, "invalid tracker response (body > content_length)"); + return; } - return false; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - } - catch (std::exception&) + assert(m_buffer.size() - m_recv_pos > 0); + m_socket->async_read_some(asio::buffer(&m_buffer[m_recv_pos] + , m_buffer.size() - m_recv_pos), bind(&http_tracker_connection::receive + , self(), _1, _2)); + } + catch (std::exception& e) + { + assert(false); + fail(-1, e.what()); + }; + + void http_tracker_connection::on_response() + { + // GZIP + if (m_content_encoding == gzip) { - if (has_requester()) - requester().debug_log(std::string(m_buffer.begin(), m_buffer.end())); - throw; + boost::shared_ptr r = m_requester.lock(); + + if (!r) + { + close(); + return; + } + if (inflate_gzip(m_buffer, m_req, r.get(), + m_settings.tracker_maximum_response_length)) + { + close(); + return; + } } -#endif + + // handle tracker response + try + { + entry e = bdecode(m_buffer.begin(), m_buffer.end()); + parse(e); + } + catch (std::exception& e) + { + std::string error_str(e.what()); + error_str += ": "; + error_str.append(m_buffer.begin(), m_buffer.end()); + fail(m_code, error_str.c_str()); + } + #ifndef NDEBUG + catch (...) + { + assert(false); + } + #endif } peer_entry http_tracker_connection::extract_peer_info(const entry& info) @@ -568,12 +723,12 @@ namespace libtorrent { if (i->string().length() != 20) throw std::runtime_error("invalid response from tracker"); - std::copy(i->string().begin(), i->string().end(), ret.id.begin()); + std::copy(i->string().begin(), i->string().end(), ret.pid.begin()); } else { // if there's no peer_id, just initialize it to a bunch of zeroes - std::fill_n(ret.id.begin(), 20, 0); + std::fill_n(ret.pid.begin(), 20, 0); } // extract ip @@ -600,8 +755,7 @@ namespace libtorrent { entry const& failure = e["failure reason"]; - if (has_requester()) requester().tracker_request_error( - m_req, m_code, failure.string().c_str()); + fail(m_code, failure.string().c_str()); return; } catch (type_error const&) {} @@ -616,10 +770,10 @@ namespace libtorrent std::vector peer_list; - if (m_req.kind == tracker_request::scrape_request) + if (tracker_req().kind == tracker_request::scrape_request) { std::string ih; - std::copy(m_req.info_hash.begin(), m_req.info_hash.end() + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() , std::back_inserter(ih)); entry scrape_data = e["files"][ih]; int complete = scrape_data["complete"].integer(); @@ -640,7 +794,7 @@ namespace libtorrent if (std::distance(i, peers.end()) < 6) break; peer_entry p; - p.id.clear(); + p.pid.clear(); std::stringstream ip_str; ip_str << (int)detail::read_uint8(i) << "."; ip_str << (int)detail::read_uint8(i) << "."; @@ -661,7 +815,7 @@ namespace libtorrent } } - // look for optional crape info + // look for optional scrape info int complete = -1; int incomplete = -1; diff --git a/src/identify_client.cpp b/src/identify_client.cpp index 5fb9efd9e..f14ef672f 100755 --- a/src/identify_client.cpp +++ b/src/identify_client.cpp @@ -71,8 +71,8 @@ namespace || id[7] != '-') return boost::optional(); - ret.id[0] = id[1]; - ret.id[1] = id[2]; + ret.name[0] = id[1]; + ret.name[1] = id[2]; ret.major_version = decode_digit(id[3]); ret.minor_version = decode_digit(id[4]); ret.revision_version = decode_digit(id[5]); @@ -108,8 +108,8 @@ namespace ret.revision_version = id[3]; } - ret.id[0] = id[0]; - ret.id[1] = 0; + ret.name[0] = id[0]; + ret.name[1] = 0; ret.tag_version = 0; return boost::optional(ret); @@ -130,8 +130,8 @@ namespace fingerprint ret("..", 0, 0, 0, 0); - ret.id[0] = id[0]; - ret.id[1] = 0; + ret.name[0] = id[0]; + ret.name[1] = 0; ret.major_version = decode_digit(id[1]); ret.minor_version = decode_digit(id[3]); ret.revision_version = decode_digit(id[5]); @@ -166,6 +166,7 @@ namespace , map_entry("SZ", "Shareaza") , map_entry("T", "BitTornado") , map_entry("TN", "Torrent.NET") + , map_entry("TR", "Transmission") , map_entry("TS", "TorrentStorm") , map_entry("U", "UPnP") , map_entry("UT", "MicroTorrent") @@ -188,7 +189,7 @@ namespace const int size = sizeof(name_map)/sizeof(name_map[0]); map_entry* i = std::lower_bound(name_map, name_map + size - , map_entry(f.id, ""), &compare_first_string); + , map_entry(f.name, ""), &compare_first_string); #ifndef NDEBUG for (int i = 1; i < size; ++i) @@ -198,19 +199,19 @@ namespace } #endif - if (i < name_map + size && std::equal(f.id, f.id + 2, i->first)) + if (i < name_map + size && std::equal(f.name, f.name + 2, i->first)) identity << i->second; else { - identity << f.id[0]; - if (f.id[1] != 0) identity << f.id[1]; + identity << f.name[0]; + if (f.name[1] != 0) identity << f.name[1]; } identity << " " << (int)f.major_version << "." << (int)f.minor_version << "." << (int)f.revision_version; - if (f.id[1] != 0) + if (f.name[1] != 0) identity << "." << (int)f.tag_version; return identity.str(); diff --git a/src/ip_filter.cpp b/src/ip_filter.cpp index 5413e7905..21d840726 100644 --- a/src/ip_filter.cpp +++ b/src/ip_filter.cpp @@ -37,10 +37,11 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + ip_filter::ip_filter() { // make the entire ip-range non-blocked - m_access_list.insert(range(address(0,0,0,0,0), 0)); + m_access_list.insert(range(address(0UL), 0)); } void ip_filter::add_rule(address first, address last, int flags) @@ -70,7 +71,7 @@ namespace libtorrent if (i->start != first && first_access != flags) { - i = m_access_list.insert(i, range(address(first.ip(), 0), flags)); + i = m_access_list.insert(i, range(first, flags)); } else if (i != m_access_list.begin() && prior(i)->access == flags) { @@ -100,16 +101,16 @@ namespace libtorrent } else if (first_access != flags) { - m_access_list.insert(i, range(address(first.ip(), 0), flags)); + m_access_list.insert(i, range(first, flags)); } - if ((j != m_access_list.end() && j->start.ip() - 1 != last.ip()) - || (j == m_access_list.end() && last.ip() != 0xffffffff)) + if ((j != m_access_list.end() && j->start.to_ulong() - 1 != last.to_ulong()) + || (j == m_access_list.end() && last.to_ulong() != 0xffffffff)) { - assert(j == m_access_list.end() || last.ip() < j->start.ip() - 1); + assert(j == m_access_list.end() || last.to_ulong() < j->start.to_ulong() - 1); // std::cout << " -- last_access: " << last_access << "\n"; if (last_access != flags) - j = m_access_list.insert(j, range(address(last.ip() + 1, 0), last_access)); + j = m_access_list.insert(j, range(address(last.to_ulong() + 1), last_access)); } if (j != m_access_list.end() && j->access == flags) m_access_list.erase(j); @@ -138,14 +139,13 @@ namespace libtorrent { ip_range r; r.first = i->start; - assert(r.first.port == 0); r.flags = i->access; ++i; if (i == end) - r.last = address(0xffffffff, 0); + r.last = address(0xffffffff); else - r.last = address(i->start.ip() - 1, 0); + r.last = address(i->start.to_ulong() - 1); ret.push_back(r); } diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 29194ce76..22f21fa05 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "libtorrent/peer_connection.hpp" #include "libtorrent/session.hpp" @@ -46,97 +47,92 @@ POSSIBILITY OF SUCH DAMAGE. #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* peer_connection::extension_names[] = - { "chat", "metadata", "peer_exchange", "listen_port" }; - - const peer_connection::message_handler peer_connection::m_message_handler[] = + void intrusive_ptr_add_ref(peer_connection const* c) { - &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, - &peer_connection::on_dht_port, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - &peer_connection::on_extension_list, - &peer_connection::on_extended - }; + assert(c->m_refs >= 0); + assert(c != 0); + ++c->m_refs; + } + void intrusive_ptr_release(peer_connection const* c) + { + assert(c->m_refs > 0); + assert(c != 0); + --c->m_refs; + if (c->m_refs == 0) + delete c; + } peer_connection::peer_connection( detail::session_impl& ses - , selector& sel - , torrent* t - , boost::shared_ptr s - , address const& remote) + , boost::weak_ptr tor + , shared_ptr s + , tcp::endpoint const& remote) : #ifndef NDEBUG m_last_choke(boost::posix_time::second_clock::universal_time() - hours(1)) - , + , #endif - m_state(read_protocol_length) + m_ses(ses) , m_timeout(120) - , m_packet_size(1) + , m_last_piece(second_clock::universal_time()) + , m_packet_size(0) , m_recv_pos(0) + , m_current_send_buffer(0) , m_last_receive(second_clock::universal_time()) , m_last_sent(second_clock::universal_time()) - , m_selector(sel) , m_socket(s) , m_remote(remote) - , m_torrent(t) - , m_attached_to_torrent(true) - , m_ses(ses) + , m_torrent(tor) , m_active(true) - , m_writability_monitored(true) - , m_readability_monitored(true) , m_peer_interested(false) , m_peer_choked(true) , m_interesting(false) , m_choked(true) , m_failed(false) - , m_supports_extensions(false) , m_num_pieces(0) + , m_desired_queue_size(2) , m_free_upload(0) , m_trust_points(0) + , m_assume_fifo(false) , m_num_invalid_requests(0) - , m_last_piece(second_clock::universal_time()) , m_disconnecting(false) , m_became_uninterested(second_clock::universal_time()) , m_became_uninteresting(second_clock::universal_time()) - , 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_connecting(true) , m_queued(true) - , m_metadata_progress(0) + , m_writing(false) + , m_last_write_size(0) + , m_reading(false) + , m_last_read_size(0) + , m_refs(0) +#ifndef NDEBUG + , m_in_constructor(true) +#endif { - m_selector.monitor_writability(m_socket); - m_selector.monitor_readability(m_socket); - INVARIANT_CHECK; +#ifdef TORRENT_VERBOSE_LOGGING + m_logger = m_ses.create_log(m_remote.address().to_string() + "_" + + boost::lexical_cast(m_remote.port())); + (*m_logger) << "*** OUTGOING CONNECTION\n"; +#endif + boost::shared_ptr t = m_torrent.lock(); + assert(t); // these numbers are used the first second of connection. // then the given upload limits will be applied by running // allocate_resources(). - m_ul_bandwidth_quota.min = 10; m_ul_bandwidth_quota.max = resource_request::inf; - if (m_torrent->m_ul_bandwidth_quota.given == resource_request::inf) + if (t->m_ul_bandwidth_quota.given == resource_request::inf) { m_ul_bandwidth_quota.given = resource_request::inf; } @@ -149,7 +145,7 @@ namespace libtorrent m_dl_bandwidth_quota.min = 10; m_dl_bandwidth_quota.max = resource_request::inf; - if (m_torrent->m_dl_bandwidth_quota.given == resource_request::inf) + if (t->m_dl_bandwidth_quota.given == resource_request::inf) { m_dl_bandwidth_quota.given = resource_request::inf; } @@ -159,89 +155,65 @@ namespace libtorrent m_dl_bandwidth_quota.given = 400; } - assert(!m_socket->is_blocking()); - assert(m_torrent != 0); - -#ifdef TORRENT_VERBOSE_LOGGING - m_logger = m_ses.create_log(m_remote.as_string() + "_" - + boost::lexical_cast(m_remote.port)); - (*m_logger) << "*** OUTGOING CONNECTION\n"; -#endif - std::fill(m_peer_id.begin(), m_peer_id.end(), 0); - // 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, -1); - - send_handshake(); - - // start in the state where we are trying to read the - // handshake from the other side - m_recv_buffer.resize(1); - - // assume the other end has no pieces - if (m_torrent->ready_for_connections()) - { + if (t->ready_for_connections()) init(); - send_bitfield(); - } } peer_connection::peer_connection( detail::session_impl& ses - , selector& sel - , boost::shared_ptr s) + , boost::shared_ptr s) : #ifndef NDEBUG m_last_choke(boost::posix_time::second_clock::universal_time() - hours(1)) - , + , #endif - m_state(read_protocol_length) + m_ses(ses) , m_timeout(120) - , m_packet_size(1) + , m_last_piece(second_clock::universal_time()) + , m_packet_size(0) , m_recv_pos(0) + , m_current_send_buffer(0) , m_last_receive(second_clock::universal_time()) , m_last_sent(second_clock::universal_time()) - , m_selector(sel) , m_socket(s) - , m_remote(s->sender()) - , m_torrent(0) - , m_attached_to_torrent(false) - , m_ses(ses) , m_active(false) - , m_writability_monitored(false) - , m_readability_monitored(true) - , m_peer_id() , m_peer_interested(false) , m_peer_choked(true) , m_interesting(false) , m_choked(true) , m_failed(false) - , m_supports_extensions(false) , m_num_pieces(0) + , m_desired_queue_size(2) , m_free_upload(0) , m_trust_points(0) + , m_assume_fifo(false) , m_num_invalid_requests(0) - , m_last_piece(second_clock::universal_time()) , m_disconnecting(false) , m_became_uninterested(second_clock::universal_time()) , m_became_uninteresting(second_clock::universal_time()) - , 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_connecting(false) , m_queued(false) - , m_metadata_progress(0) + , m_writing(false) + , m_last_write_size(0) + , m_reading(false) + , m_last_read_size(0) + , m_refs(0) +#ifndef NDEBUG + , m_in_constructor(true) +#endif { - m_selector.monitor_readability(m_socket); - INVARIANT_CHECK; + m_remote = m_socket->remote_endpoint(); + +#ifdef TORRENT_VERBOSE_LOGGING + assert(m_socket->remote_endpoint() == remote()); + m_logger = m_ses.create_log(remote().address().to_string() + "_" + + boost::lexical_cast(remote().port())); + (*m_logger) << "*** INCOMING CONNECTION\n"; +#endif + // upload bandwidth will only be given to connections // that are part of a torrent. Since this is an incoming @@ -277,43 +249,25 @@ namespace libtorrent m_dl_bandwidth_quota.given = 400; } - assert(!m_socket->is_blocking()); - std::fill(m_peer_id.begin(), m_peer_id.end(), 0); - -#ifdef TORRENT_VERBOSE_LOGGING - assert(m_socket->sender() == remote()); - m_logger = m_ses.create_log(remote().as_string() + "_" - + boost::lexical_cast(remote().port)); - (*m_logger) << "*** INCOMING 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, -1); - - // 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 - m_recv_buffer.resize(1); } void peer_connection::init() { - assert(m_torrent); - assert(m_torrent->valid_metadata()); - assert(m_torrent->ready_for_connections()); + INVARIANT_CHECK; - m_have_piece.resize(m_torrent->torrent_file().num_pieces(), false); + boost::shared_ptr t = m_torrent.lock(); + assert(t); + assert(t->valid_metadata()); + assert(t->ready_for_connections()); + + m_have_piece.resize(t->torrent_file().num_pieces(), false); // now that we have a piece_picker, // update it with this peers pieces // build a vector of all pieces + m_num_pieces = 0; std::vector piece_list; for (int i = 0; i < (int)m_have_piece.size(); ++i) { @@ -324,19 +278,16 @@ namespace libtorrent } } - // 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) + for (std::vector::reverse_iterator i = piece_list.rbegin(); + i != piece_list.rend(); ++i) { int index = *i; - m_torrent->peer_has(index); - if (!m_torrent->have_piece(index) - && !m_torrent->picker().is_filtered(index)) + t->peer_has(index); + if (!t->have_piece(index) + && !t->picker().is_filtered(index)) interesting = true; } @@ -346,22 +297,23 @@ namespace libtorrent (*m_logger) << " *** THIS IS A SEED ***\n"; #endif // if we're a seed too, disconnect - if (m_torrent->is_seed()) + if (t->is_seed()) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " we're also a seed, disconnecting\n"; #endif - throw protocol_error("seed to seed connection redundant, disconnecting"); + throw std::runtime_error("seed to seed connection redundant, disconnecting"); } } if (interesting) - m_torrent->get_policy().peer_is_interesting(*this); - + t->get_policy().peer_is_interesting(*this); } peer_connection::~peer_connection() { + INVARIANT_CHECK; + #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; if (m_logger) @@ -370,40 +322,29 @@ namespace libtorrent << " *** CONNECTION CLOSED\n"; } #endif - m_disconnecting = true; - m_selector.remove(m_socket); - if (m_attached_to_torrent) - { - assert(m_torrent != 0); - m_torrent->remove_peer(this); - } #ifndef NDEBUG - else - { - if (m_torrent) - { - torrent::peer_iterator i = m_torrent->m_connections.find( - remote()); - assert(i == m_torrent->m_connections.end()); - } - } + boost::shared_ptr t = m_torrent.lock(); + if (t) assert(t->connection_for(remote()) != this); #endif } void peer_connection::announce_piece(int index) { - assert(m_torrent); - assert(m_torrent->valid_metadata()); - assert(index >= 0 && index < m_torrent->torrent_file().num_pieces()); - m_announce_queue.push_back(index); + // optimization, don't send have messages + // to peers that already have the piece + if (has_piece(index)) return; + write_have(index); } bool peer_connection::has_piece(int i) const { - assert(m_torrent); - assert(m_torrent->valid_metadata()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + assert(t->valid_metadata()); assert(i >= 0); - assert(i < m_torrent->torrent_file().num_pieces()); + assert(i < t->torrent_file().num_pieces()); return m_have_piece[i]; } @@ -424,6 +365,8 @@ namespace libtorrent void peer_connection::add_stat(size_type downloaded, size_type uploaded) { + INVARIANT_CHECK; + m_statistics.add_stat(downloaded, uploaded); } @@ -434,14 +377,20 @@ namespace libtorrent void peer_connection::received_valid_data() { + INVARIANT_CHECK; + m_trust_points++; + // TODO: make this limit user settable if (m_trust_points > 20) m_trust_points = 20; } void peer_connection::received_invalid_data() { + INVARIANT_CHECK; + // we decrease more than we increase, to keep the // allowed failed/passed ratio low. + // TODO: make this limit user settable m_trust_points -= 2; if (m_trust_points < -7) m_trust_points = -7; } @@ -458,6 +407,8 @@ namespace libtorrent void peer_connection::add_free_upload(size_type free_upload) { + INVARIANT_CHECK; + m_free_upload += free_upload; } @@ -465,135 +416,119 @@ namespace libtorrent { m_ul_bandwidth_quota.used = 0; m_dl_bandwidth_quota.used = 0; - if (!m_readability_monitored) - { - assert(!m_selector.is_readability_monitored(m_socket)); - m_selector.monitor_readability(m_socket); - m_readability_monitored = true; - } - send_buffer_updated(); - } - - void peer_connection::send_handshake() - { - INVARIANT_CHECK; - - assert(m_send_buffer.size() == 0); - - // add handshake to the send buffer - const char version_string[] = "BitTorrent protocol"; - const int string_len = sizeof(version_string)-1; - - m_send_buffer.reserve(1 + string_len + 8 + 20 + 20); - - buffer::interval i = m_send_buffer.allocate(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; - i.begin += 8; - - // info hash - std::copy( - m_torrent->torrent_file().info_hash().begin() - , m_torrent->torrent_file().info_hash().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 - - send_buffer_updated(); + assert(m_ul_bandwidth_quota.left() >= 0); + assert(m_dl_bandwidth_quota.left() >= 0); + setup_send(); + setup_receive(); } // verifies a piece to see if it is valid (is within a valid range) // and if it can correspond to a request generated by libtorrent. bool peer_connection::verify_piece(const peer_request& p) const { - assert(m_torrent->valid_metadata()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(t->valid_metadata()); return p.piece >= 0 - && p.piece < m_torrent->torrent_file().num_pieces() + && p.piece < t->torrent_file().num_pieces() && p.length > 0 && p.start >= 0 - && (p.length == m_torrent->block_size() - || (p.length < m_torrent->block_size() - && p.piece == m_torrent->torrent_file().num_pieces()-1 - && p.start + p.length == m_torrent->torrent_file().piece_size(p.piece))) - && p.start + p.length <= m_torrent->torrent_file().piece_size(p.piece) - && p.start % m_torrent->block_size() == 0; + && (p.length == t->block_size() + || (p.length < t->block_size() + && p.piece == t->torrent_file().num_pieces()-1 + && p.start + p.length == t->torrent_file().piece_size(p.piece))) + && p.start + p.length <= t->torrent_file().piece_size(p.piece) + && p.start % t->block_size() == 0; } - - boost::optional peer_connection::downloading_piece() const + + struct disconnect_torrent { - // are we currently receiving a 'piece' message? - if (m_state != read_packet - || m_recv_pos < 9 - || m_recv_buffer[0] != msg_piece) - return boost::optional(); + disconnect_torrent(boost::weak_ptr& t): m_t(&t) {} + ~disconnect_torrent() { if (m_t) m_t->reset(); } + void cancel() { m_t = 0; } + private: + boost::weak_ptr* m_t; + }; + + void peer_connection::attach_to_torrent(sha1_hash const& ih) + { + INVARIANT_CHECK; - const char* ptr = &m_recv_buffer[1]; - peer_request r; - r.piece = detail::read_int32(ptr); - r.start = detail::read_int32(ptr); - r.length = m_packet_size - 9; + assert(!m_disconnecting); + m_torrent = m_ses.find_torrent(ih); - // is any of the piece message header data invalid? - if (!verify_piece(r)) - return boost::optional(); + boost::shared_ptr t = m_torrent.lock(); - piece_block_progress p; + if (t && t->is_aborted()) { m_torrent.reset(); t.reset(); } - p.piece_index = r.piece; - p.block_index = r.start / m_torrent->block_size(); - p.bytes_downloaded = m_recv_pos - 9; - p.full_block_bytes = r.length; + if (!t) + { + // we couldn't find the torrent! +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " couldn't find a torrent with the given info_hash\n"; +#endif + throw std::runtime_error("got info-hash that is not in our session"); + } - return boost::optional(p); + disconnect_torrent disconnect(m_torrent); + if (t->is_paused()) + { + // paused torrents will not accept + // incoming connections +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << " rejected connection to paused torrent\n"; +#endif + throw std::runtime_error("connection rejected by paused torrent"); + } + + // check to make sure we don't have another connection with the same + // info_hash and peer_id. If we do. close this connection. + t->attach_peer(this); + + // if the torrent isn't ready to accept + // connections yet, we'll have to wait with + // our initialization + if (t->ready_for_connections()) init(); + + // assume the other end has no pieces + // if we don't have valid metadata yet, + // leave the vector unallocated + assert(m_num_pieces == 0); + std::fill(m_have_piece.begin(), m_have_piece.end(), false); + disconnect.cancel(); } - // message handlers + // ----------------------------- + // --------- KEEPALIVE --------- + // ----------------------------- + + void peer_connection::incoming_keepalive() + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + using namespace boost::posix_time; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " <== KEEPALIVE\n"; +#endif + } + // ----------------------------- // ----------- CHOKE ----------- // ----------------------------- - void peer_connection::on_choke(int received) + void peer_connection::incoming_choke() { INVARIANT_CHECK; - assert(received > 0); - 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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -601,27 +536,27 @@ namespace libtorrent << " <== CHOKE\n"; #endif m_peer_choked = true; - m_torrent->get_policy().choked(*this); + t->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); + t->picker().abort_download(*i); } for (std::deque::const_iterator i = m_request_queue.begin() , end(m_request_queue.end()); i != end; ++i) { // since this piece was skipped, clear it and allow it to // be requested from other peers - m_torrent->picker().abort_download(*i); + t->picker().abort_download(*i); } m_download_queue.clear(); m_request_queue.clear(); #ifndef NDEBUG -// m_torrent->picker().integrity_check(m_torrent); +// t->picker().integrity_check(m_torrent); #endif } @@ -629,15 +564,12 @@ namespace libtorrent // ---------- UNCHOKE ---------- // ----------------------------- - void peer_connection::on_unchoke(int received) + void peer_connection::incoming_unchoke() { INVARIANT_CHECK; - assert(received > 0); - 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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -645,22 +577,19 @@ namespace libtorrent << " <== UNCHOKE\n"; #endif m_peer_choked = false; - m_torrent->get_policy().unchoked(*this); + t->get_policy().unchoked(*this); } // ----------------------------- // -------- INTERESTED --------- // ----------------------------- - void peer_connection::on_interested(int received) + void peer_connection::incoming_interested() { INVARIANT_CHECK; - assert(received > 0); - 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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -668,57 +597,46 @@ namespace libtorrent << " <== INTERESTED\n"; #endif m_peer_interested = true; - m_torrent->get_policy().interested(*this); + t->get_policy().interested(*this); } // ----------------------------- // ------ NOT INTERESTED ------- // ----------------------------- - void peer_connection::on_not_interested(int received) + void peer_connection::incoming_not_interested() { INVARIANT_CHECK; - assert(received > 0); - 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; - m_became_uninterested = second_clock::universal_time(); // clear the request queue if the client isn't interested m_requests.clear(); - send_buffer_updated(); + setup_send(); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; (*m_logger) << to_simple_string(second_clock::universal_time()) << " <== NOT_INTERESTED\n"; #endif + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + m_peer_interested = false; - m_torrent->get_policy().not_interested(*this); + t->get_policy().not_interested(*this); } // ----------------------------- // ----------- HAVE ------------ // ----------------------------- - void peer_connection::on_have(int received) + void peer_connection::incoming_have(int index) { INVARIANT_CHECK; - assert(received > 0); - 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_int32(ptr); - // if we got an invalid message, abort - if (index >= (int)m_have_piece.size() || index < 0) - throw protocol_error("have message with higher index than the number of pieces"); + boost::shared_ptr t = m_torrent.lock(); + assert(t); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -726,10 +644,15 @@ namespace libtorrent << " <== HAVE [ piece: " << index << "]\n"; #endif + // if we got an invalid message, abort + if (index >= (int)m_have_piece.size() || index < 0) + throw protocol_error("got 'have'-message with higher index " + "than the number of pieces"); + if (m_have_piece[index]) { #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " got redundant HAVE message for index: " << index << "\n"; + (*m_logger) << " got redundant HAVE message for index: " << index << "\n"; #endif } else @@ -738,18 +661,18 @@ namespace libtorrent // only update the piece_picker if // we have the metadata - if (m_torrent->valid_metadata()) + if (t->valid_metadata()) { ++m_num_pieces; - m_torrent->peer_has(index); + t->peer_has(index); - if (!m_torrent->have_piece(index) + if (!t->have_piece(index) && !is_interesting() - && !m_torrent->picker().is_filtered(index)) - m_torrent->get_policy().peer_is_interesting(*this); + && !t->picker().is_filtered(index)) + t->get_policy().peer_is_interesting(*this); } - if (m_torrent->is_seed() && is_seed()) + if (t->is_seed() && is_seed()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } @@ -760,20 +683,12 @@ namespace libtorrent // --------- BITFIELD ---------- // ----------------------------- - void peer_connection::on_bitfield(int received) + void peer_connection::incoming_bitfield(std::vector const& bitfield) { INVARIANT_CHECK; - assert(received > 0); - assert(m_torrent); - // if we don't have the metedata, we cannot - // verify the bitfield size - if (m_torrent->valid_metadata() - && m_packet_size - 1 != ((int)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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -781,15 +696,20 @@ namespace libtorrent << " <== BITFIELD\n"; #endif + // if we don't have the metedata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && (bitfield.size() / 8) != (m_have_piece.size() / 8)) + throw protocol_error("got bitfield with invalid size"); + // if we don't have metadata yet // just remember the bitmask // don't update the piecepicker // (since it doesn't exist yet) - if (!m_torrent->valid_metadata()) + if (!t->valid_metadata()) { - m_have_piece.resize((m_packet_size - 1) * 8, false); - for (int i = 0; i < (int)m_have_piece.size(); ++i) - m_have_piece[i] = (m_recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7)))) != 0; + m_have_piece = bitfield; + m_num_pieces = std::count(bitfield.begin(), bitfield.end(), true); return; } @@ -797,7 +717,7 @@ namespace libtorrent std::vector piece_list; for (int i = 0; i < (int)m_have_piece.size(); ++i) { - bool have = (m_recv_buffer[1 + (i>>3)] & (1 << (7 - (i&7)))) != 0; + bool have = bitfield[i]; if (have && !m_have_piece[i]) { m_have_piece[i] = true; @@ -809,23 +729,20 @@ namespace libtorrent // this should probably not be allowed m_have_piece[i] = false; --m_num_pieces; - m_torrent->peer_lost(i); + t->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) + for (std::vector::reverse_iterator i = piece_list.rbegin(); + i != piece_list.rend(); ++i) { int index = *i; - m_torrent->peer_has(index); - if (!m_torrent->have_piece(index) - && !m_torrent->picker().is_filtered(index)) + t->peer_has(index); + if (!t->have_piece(index) + && !t->picker().is_filtered(index)) interesting = true; } @@ -835,36 +752,27 @@ namespace libtorrent (*m_logger) << " *** THIS IS A SEED ***\n"; #endif // if we're a seed too, disconnect - if (m_torrent->is_seed()) + if (t->is_seed()) { throw protocol_error("seed to seed connection redundant, disconnecting"); } } - if (interesting) m_torrent->get_policy().peer_is_interesting(*this); + if (interesting) t->get_policy().peer_is_interesting(*this); } // ----------------------------- // ---------- REQUEST ---------- // ----------------------------- - void peer_connection::on_request(int received) + void peer_connection::incoming_request(peer_request const& r) { INVARIANT_CHECK; - assert(received > 0); - 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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); - peer_request r; - const char* ptr = &m_recv_buffer[1]; - r.piece = detail::read_int32(ptr); - r.start = detail::read_int32(ptr); - r.length = detail::read_int32(ptr); - - if (!m_torrent->valid_metadata()) + if (!t->valid_metadata()) { // if we don't have valid metadata yet, // we shouldn't get a request @@ -876,8 +784,8 @@ namespace libtorrent "s: " << r.start << " | " "l: " << r.length << " | " "i: " << m_peer_interested << " | " - "t: " << (int)m_torrent->torrent_file().piece_size(r.piece) << " | " - "n: " << m_torrent->torrent_file().num_pieces() << " ]\n"; + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif return; } @@ -896,8 +804,8 @@ namespace libtorrent "s: " << r.start << " | " "l: " << r.length << " | " "i: " << m_peer_interested << " | " - "t: " << (int)m_torrent->torrent_file().piece_size(r.piece) << " | " - "n: " << m_torrent->torrent_file().num_pieces() << " ]\n"; + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif return; } @@ -906,12 +814,12 @@ namespace libtorrent // is legal and that the peer // is not choked if (r.piece >= 0 - && r.piece < m_torrent->torrent_file().num_pieces() - && m_torrent->have_piece(r.piece) + && r.piece < t->torrent_file().num_pieces() + && t->have_piece(r.piece) && r.start >= 0 - && r.start < m_torrent->torrent_file().piece_size(r.piece) + && r.start < t->torrent_file().piece_size(r.piece) && r.length > 0 - && r.length + r.start <= m_torrent->torrent_file().piece_size(r.piece) + && r.length + r.start <= t->torrent_file().piece_size(r.piece) && m_peer_interested) { // if we have choked the client @@ -920,7 +828,7 @@ namespace libtorrent return; m_requests.push_back(r); - send_buffer_updated(); + setup_send(); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; (*m_logger) << to_simple_string(second_clock::universal_time()) @@ -937,60 +845,49 @@ namespace libtorrent "s: " << r.start << " | " "l: " << r.length << " | " "i: " << m_peer_interested << " | " - "t: " << (int)m_torrent->torrent_file().piece_size(r.piece) << " | " - "n: " << m_torrent->torrent_file().num_pieces() << " ]\n"; + "t: " << (int)t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; #endif ++m_num_invalid_requests; - if (m_torrent->alerts().should_post(alert::debug)) + if (t->alerts().should_post(alert::debug)) { - m_torrent->alerts().post_alert(invalid_request_alert( + t->alerts().post_alert(invalid_request_alert( r - , m_torrent->get_handle() + , t->get_handle() , m_remote , m_peer_id - , "peer sent an illegal request, ignoring")); + , "peer sent an illegal piece request, ignoring")); } } } + void peer_connection::incoming_piece_fragment() + { + m_last_piece = second_clock::universal_time(); + } + // ----------------------------- // ----------- PIECE ----------- // ----------------------------- - void peer_connection::on_piece(int received) + void peer_connection::incoming_piece(peer_request const& p, char const* data) { INVARIANT_CHECK; - assert(received > 0); - // classify the received data as protocol chatter - // or data payload for the statistics - 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)); - } + boost::shared_ptr t = m_torrent.lock(); + assert(t); - m_last_piece = second_clock::universal_time(); - if (m_recv_pos < m_packet_size) return; - - const char* ptr = &m_recv_buffer[1]; - peer_request p; - p.piece = detail::read_int32(ptr); - p.start = detail::read_int32(ptr); - p.length = m_packet_size - 9; +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " <== PIECE [ piece: " << p.piece << " | " + "b: " << p.start / t->block_size() << " | " + "s: " << p.start << " | " + "l: " << p.length << " | " + "ds: " << statistics().download_rate() << " | " + "qs: " << m_desired_queue_size << " ]\n"; +#endif if (!verify_piece(p)) { @@ -1001,129 +898,134 @@ namespace libtorrent "start: " << p.start << " | " "length: " << p.length << " ]\n"; #endif - throw protocol_error("invalid piece packet"); + throw protocol_error("got invalid piece packet"); } using namespace boost::posix_time; - piece_picker& picker = m_torrent->picker(); + piece_picker& picker = t->picker(); - piece_block block_finished(p.piece, p.start / m_torrent->block_size()); + piece_block block_finished(p.piece, p.start / t->block_size()); std::deque::iterator b = std::find( m_download_queue.begin() , m_download_queue.end() , block_finished); - std::deque::iterator i; if (b != m_download_queue.end()) { -/* - for (i = m_download_queue.begin(); - i != b; ++i) + if (m_assume_fifo) { + for (i = m_download_queue.begin(); + i != b; ++i) + { #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " *** SKIPPED_PIECE [ piece: " << i->piece_index << " | " - "b: " << i->block_index << " ] ***\n"; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " *** SKIPPED_PIECE [ piece: " << i->piece_index << " | " + "b: " << i->block_index << " ] ***\n"; #endif - // since this piece was skipped, clear it and allow it to - // be requested from other peers - picker.abort_download(*i); + // since this piece was skipped, clear it and allow it to + // be requested from other peers + // TODO: send cancel? + picker.abort_download(*i); + } + + // remove the request that just finished + // from the download queue plus the + // skipped blocks. + m_download_queue.erase(m_download_queue.begin() + , boost::next(b)); + } + else + { + m_download_queue.erase(b); } -*/ -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " <== PIECE [ piece: " << p.piece << " | " - "b: " << p.start / m_torrent->block_size() << " | " - "s: " << p.start << " | " - "l: " << p.length << " ]\n"; -#endif -/* - // remove the request that just finished - // from the download queue plus the - // skipped blocks. - m_download_queue.erase(m_download_queue.begin() - , boost::next(b)); -*/ - m_download_queue.erase(b); send_block_requests(); } else { // cancel the block from the // peer that has taken over it. - boost::optional
    peer = m_torrent->picker().get_downloader(block_finished); + boost::optional peer + = t->picker().get_downloader(block_finished); if (peer) { - peer_connection* pc = m_torrent->connection_for(*peer); + peer_connection* pc = t->connection_for(*peer); if (pc && pc != this) { - pc->send_cancel(block_finished); + pc->cancel_request(block_finished); } } else { - if (m_torrent->alerts().should_post(alert::debug)) + if (t->alerts().should_post(alert::debug)) { - m_torrent->alerts().post_alert( + t->alerts().post_alert( peer_error_alert( m_remote , m_peer_id , "got a block that was not requested")); } #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " *** The block we just got was not requested ***\n"; + (*m_logger) << " *** The block we just got was not in the " + "request queue ***\n"; #endif } } // if the block we got is already finished, then ignore it - if (picker.is_finished(block_finished)) return; + if (picker.is_finished(block_finished)) + { + t->received_redundant_data(p.length); + return; + } - m_torrent->filesystem().write(&m_recv_buffer[9], p.piece, p.start, p.length); + t->filesystem().write(data, p.piece, p.start, p.length); - bool was_seed = m_torrent->is_seed(); - bool was_finished = picker.num_filtered() + m_torrent->num_pieces() - == m_torrent->torrent_file().num_pieces(); + bool was_seed = t->is_seed(); + bool was_finished = picker.num_filtered() + t->num_pieces() + == t->torrent_file().num_pieces(); picker.mark_as_finished(block_finished, m_remote); - m_torrent->get_policy().block_finished(*this, block_finished); + t->get_policy().block_finished(*this, block_finished); + + // if the piece failed, this connection may be closed, and + // detached from the torrent. In that case m_torrent will + // be set to 0. So, we need to temporarily save it in this function // did we just finish the piece? if (picker.is_piece_finished(p.piece)) { - bool verified = m_torrent->verify_piece(p.piece); + bool verified = t->verify_piece(p.piece); if (verified) { - m_torrent->announce_piece(p.piece); - assert(m_torrent->valid_metadata()); + t->announce_piece(p.piece); + assert(t->valid_metadata()); if (!was_finished - && picker.num_filtered() + m_torrent->num_pieces() - == m_torrent->torrent_file().num_pieces()) + && picker.num_filtered() + t->num_pieces() + == t->torrent_file().num_pieces()) { // torrent finished // i.e. all the pieces we're interested in have // been downloaded. Release the files (they will open // in read only mode if needed) - m_torrent->finished(); + t->finished(); } } else { - m_torrent->piece_failed(p.piece); + t->piece_failed(p.piece); } - m_torrent->get_policy().piece_finished(p.piece, verified); + t->get_policy().piece_finished(p.piece, verified); - if (!was_seed && m_torrent->is_seed()) + if (!was_seed && t->is_seed()) { assert(verified); - m_torrent->completed(); + t->completed(); } - } } @@ -1131,60 +1033,41 @@ namespace libtorrent // ---------- CANCEL ----------- // ----------------------------- - void peer_connection::on_cancel(int received) + void peer_connection::incoming_cancel(peer_request const& r) { INVARIANT_CHECK; - assert(received > 0); - 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_int32(ptr); - r.start = detail::read_int32(ptr); - r.length = detail::read_int32(ptr); - - std::deque::iterator i - = std::find(m_requests.begin(), m_requests.end(), r); - if (i != m_requests.end()) - { - m_requests.erase(i); - } - - if (!can_write() && m_writability_monitored) - { - m_writability_monitored = false; - m_selector.remove_writable(m_socket); - } - #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; (*m_logger) << to_simple_string(second_clock::universal_time()) << " <== CANCEL [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; #endif + + std::deque::iterator i + = std::find(m_requests.begin(), m_requests.end(), r); + + if (i != m_requests.end()) + { + m_requests.erase(i); + } + else + { +#ifdef TORRENT_VERBOSE_LOGGING + using namespace boost::posix_time; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " *** GOT CANCEL NOT IN THE QUEUE\n"; +#endif + } } // ----------------------------- // --------- DHT PORT ---------- // ----------------------------- - void peer_connection::on_dht_port(int received) + void peer_connection::incoming_dht_port(int listen_port) { INVARIANT_CHECK; - assert(received > 0); - if (m_packet_size != 3) - throw protocol_error("'dht_port' message size != 3"); - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) return; - - const char* ptr = &m_recv_buffer[1]; - int listen_port = detail::read_uint16(ptr); - (void)listen_port; - #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; (*m_logger) << to_simple_string(second_clock::universal_time()) @@ -1192,333 +1075,41 @@ namespace libtorrent #endif } - // ----------------------------- - // ------ EXTENSION LIST ------- - // ----------------------------- - - void peer_connection::on_extension_list(int received) + void peer_connection::add_request(piece_block const& block) { INVARIANT_CHECK; - assert(m_torrent); - assert(received > 0); - if (m_packet_size > 100 * 1000) - { - // 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; + boost::shared_ptr t = m_torrent.lock(); + assert(t); - try - { - entry e = bdecode(m_recv_buffer.begin()+1, m_recv_buffer.end()); -#ifdef TORRENT_VERBOSE_LOGGING - entry::dictionary_type& extensions = e.dict(); - std::stringstream ext; - e.print(ext); - (*m_logger) << ext.str(); -#endif + assert(t->valid_metadata()); + assert(block.piece_index >= 0); + assert(block.piece_index < t->torrent_file().num_pieces()); + assert(block.block_index >= 0); + assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); + assert(!t->picker().is_downloading(block)); - for (int i = 0; i < num_supported_extensions; ++i) - { - entry* f = e.find_key(extension_names[i]); - if (f) - { - m_extension_messages[i] = (int)f->integer(); - } - } -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "supported extensions:\n"; - for (entry::dictionary_type::const_iterator i = extensions.begin(); - i != extensions.end(); - ++i) - { - (*m_logger) << i->first << "\n"; - } -#endif - } - catch(invalid_encoding&) - { - throw protocol_error("'extensions' packet contains invalid bencoding"); - } - catch(type_error&) - { - throw protocol_error("'extensions' packet contains incorrect types"); - } + t->picker().mark_as_downloading(block, m_remote); + m_request_queue.push_back(block); + send_block_requests(); } - // ----------------------------- - // --------- EXTENDED ---------- - // ----------------------------- - - void peer_connection::on_extended(int received) + void peer_connection::cancel_request(piece_block const& block) { INVARIANT_CHECK; - assert(received > 0); - m_statistics.received_bytes(0, received); - if (m_packet_size < 5) - throw protocol_error("'extended' message smaller than 5 bytes"); + boost::shared_ptr t = m_torrent.lock(); + assert(t); - if (m_torrent == 0) - throw protocol_error("'extended' message sent before proper handshake"); - - - if (m_recv_pos < 5) return; - - const char* ptr = &m_recv_buffer[1]; - int extended_id = detail::read_int32(ptr); - - 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_chat_message: - on_chat(); break; - case extended_metadata_message: - on_metadata(); break; - case extended_peer_exchange_message: - on_peer_exchange(); break; - case extended_listen_port_message: - on_listen_port(); break; - default: - throw protocol_error("unknown extended message id: " - + boost::lexical_cast(extended_id)); - }; - } - - // ----------------------------- - // ----------- CHAT ------------ - // ----------------------------- - - void peer_connection::on_chat() - { - if (m_packet_size > 2 * 1024) - throw protocol_error("CHAT message larger than 2 kB"); - - if (m_recv_pos < m_packet_size) return; - try - { - entry d = bdecode(m_recv_buffer.begin()+5, m_recv_buffer.end()); - const std::string& str = d["msg"].string(); - - if (m_torrent->alerts().should_post(alert::critical)) - { - m_torrent->alerts().post_alert( - chat_message_alert( - m_torrent->get_handle() - , m_remote, str)); - } - - } - catch (invalid_encoding&) - { - // TODO: make these non-fatal errors - // they should just ignore the chat message - // and report the error via an alert - throw protocol_error("invalid bencoding in CHAT message"); - } - catch (type_error&) - { - throw protocol_error("invalid types in bencoded CHAT message"); - } - return; - } - - // ----------------------------- - // --------- METADATA ---------- - // ----------------------------- - - void peer_connection::on_metadata() - { - assert(m_torrent); - - if (m_packet_size > 500 * 1024) - throw protocol_error("metadata message larger than 500 kB"); - - if (m_recv_pos < 6) return; - - std::vector::iterator ptr = m_recv_buffer.begin() + 5; - int type = detail::read_uint8(ptr); - - switch (type) - { - case 0: // request - { - if (m_recv_pos < m_packet_size) return; - - int start = detail::read_uint8(ptr); - int size = detail::read_uint8(ptr) + 1; - - if (m_packet_size != 8) - { - // invalid metadata request - throw protocol_error("invalid metadata request"); - } - - send_metadata(std::make_pair(start, size)); - } - break; - case 1: // data - { - if (m_recv_pos < 14) return; - int total_size = detail::read_int32(ptr); - int offset = detail::read_int32(ptr); - int data_size = m_packet_size - 5 - 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"); - - m_torrent->metadata_progress(total_size, m_recv_pos - 14 - - m_metadata_progress); - m_metadata_progress = m_recv_pos - 14; - if (m_recv_pos < m_packet_size) 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; - m_torrent->received_metadata(&m_recv_buffer[5+9], data_size, offset, total_size); - m_metadata_progress = 0; - } - break; - case 2: // have no data - if (m_recv_pos < m_packet_size) return; - - m_no_metadata = second_clock::universal_time(); - if (m_waiting_metadata_request) - m_torrent->cancel_metadata_request(m_last_metadata_request); - m_waiting_metadata_request = false; - break; - default: - throw protocol_error("unknown metadata extension message: " - + boost::lexical_cast(type)); - } - - } - - // ----------------------------- - // ------ PEER EXCHANGE -------- - // ----------------------------- - - void peer_connection::on_peer_exchange() - { - - } - - // ----------------------------- - // ------- LISTEN PORT --------- - // ----------------------------- - - void peer_connection::on_listen_port() - { - using namespace boost::posix_time; - assert(m_torrent); - - if (m_packet_size != 7) - throw protocol_error("invalid listen_port message"); - - if (is_local()) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << to_simple_string(second_clock::universal_time()) - << "<== LISTEN_PORT [ UNEXPECTED ]\n"; -#endif - return; - } - - const char* ptr = &m_recv_buffer[5]; - unsigned short port = detail::read_uint16(ptr); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << to_simple_string(second_clock::universal_time()) - << "<== LISTEN_PORT [ port: " << port << " ]\n"; -#endif - - address adr = m_remote; - adr.port = port; - m_torrent->get_policy().peer_from_tracker(adr, m_peer_id); - } - - bool peer_connection::has_metadata() const - { - using namespace boost::posix_time; - return second_clock::universal_time() - m_no_metadata > minutes(5); - } - - - void peer_connection::disconnect() - { - if (m_disconnecting) return; - - assert((m_ses.m_connections.find(m_socket) != m_ses.m_connections.end()) - == !m_connecting); - - m_disconnecting = true; - - assert(std::find(m_ses.m_disconnect_peer.begin() - , m_ses.m_disconnect_peer.end(), shared_from_this()) - == m_ses.m_disconnect_peer.end()); - - m_ses.m_disconnect_peer.push_back(shared_from_this()); - } - - bool peer_connection::dispatch_message(int received) - { - INVARIANT_CHECK; - - assert(received > 0); - assert(m_recv_pos >= received); - assert(m_recv_pos > 0); - assert(m_torrent != 0); - - int packet_type = m_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(packet_type) - + " size: " + boost::lexical_cast(m_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 (m_recv_pos < m_packet_size) return false; - - assert(m_recv_pos == m_packet_size); - return true; - } - - void peer_connection::send_cancel(piece_block block) - { - INVARIANT_CHECK; - - assert(m_torrent->valid_metadata()); + assert(t->valid_metadata()); assert(block.piece_index >= 0); - assert(block.piece_index < m_torrent->torrent_file().num_pieces()); + assert(block.piece_index < t->torrent_file().num_pieces()); assert(block.block_index >= 0); - assert(block.block_index < m_torrent->torrent_file().piece_size(block.piece_index)); - assert(m_torrent->picker().is_downloading(block)); + assert(block.block_index < t->torrent_file().piece_size(block.piece_index)); + assert(t->picker().is_downloading(block)); - m_torrent->picker().abort_download(block); + t->picker().abort_download(block); std::deque::iterator it = std::find(m_download_queue.begin(), m_download_queue.end(), block); @@ -1528,6 +1119,9 @@ namespace libtorrent assert(it != m_request_queue.end()); if (it == m_request_queue.end()) return; m_request_queue.erase(it); + // since we found it in the request queue, it means it hasn't been + // sent yet, so we don't have to send a cancel. + return; } else { @@ -1536,28 +1130,19 @@ namespace libtorrent send_block_requests(); - int block_offset = block.block_index * m_torrent->block_size(); + int block_offset = block.block_index * t->block_size(); int block_size - = std::min((int)m_torrent->torrent_file().piece_size(block.piece_index)-block_offset, - m_torrent->block_size()); + = std::min((int)t->torrent_file().piece_size(block.piece_index)-block_offset, + t->block_size()); assert(block_size > 0); - assert(block_size <= m_torrent->block_size()); + assert(block_size <= t->block_size()); - char buf[] = {0,0,0,13, msg_cancel}; + peer_request r; + r.piece = block.piece_index; + r.start = block_offset; + r.length = block_size; - buffer::interval i = m_send_buffer.allocate(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(block.piece_index, i.begin); - // begin - detail::write_int32(block_offset, i.begin); - // length - detail::write_int32(block_size, i.begin); - - assert(i.begin == i.end); + write_cancel(r); #ifdef TORRENT_VERBOSE_LOGGING using namespace boost::posix_time; @@ -1565,275 +1150,6 @@ namespace libtorrent << " ==> CANCEL [ piece: " << block.piece_index << " | s: " << block_offset << " | l: " << block_size << " | " << block.block_index << " ]\n"; #endif - - send_buffer_updated(); - } - - void peer_connection::send_block_requests() - { - // TODO: calculate the desired request queue each tick instead. - // TODO: make this constant user-settable - const int queue_time = 3; // seconds - // (if the latency is more than this, the download will stall) - // so, the queue size is 5 * down_rate / 16 kiB (16 kB is the size of each request) - // the minimum request size is 2 and the maximum is 48 - // the block size doesn't have to be 16. So we first query the torrent for it - const int block_size = m_torrent->block_size(); - assert(block_size > 0); - - int desired_queue_size = static_cast(queue_time - * statistics().download_rate() / block_size); - if (desired_queue_size > max_request_queue) desired_queue_size = max_request_queue; - if (desired_queue_size < min_request_queue) desired_queue_size = min_request_queue; - - if ((int)m_download_queue.size() >= desired_queue_size) return; - - while (!m_request_queue.empty() - && (int)m_download_queue.size() < desired_queue_size) - { - piece_block block = m_request_queue.front(); - m_request_queue.pop_front(); - m_download_queue.push_back(block); - - int block_offset = block.block_index * m_torrent->block_size(); - int block_size - = std::min((int)m_torrent->torrent_file().piece_size(block.piece_index)-block_offset, - m_torrent->block_size()); - assert(block_size > 0); - assert(block_size <= m_torrent->block_size()); - - char buf[] = {0,0,0,13, msg_request}; - - buffer::interval i = m_send_buffer.allocate(17); - - std::copy(buf, buf + 5, i.begin); - i.begin += 5; - - // index - detail::write_int32(block.piece_index, i.begin); - // begin - detail::write_int32(block_offset, i.begin); - // length - detail::write_int32(block_size, i.begin); - - assert(i.begin == i.end); - using namespace boost::posix_time; - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " ==> REQUEST [ " - "piece: " << block.piece_index << " | " - "b: " << block.block_index << " | " - "s: " << block_offset << " | " - "l: " << block_size << " ]\n"; - - peer_request r; - r.piece = block.piece_index; - r.start = block_offset; - r.length = block_size; - assert(verify_piece(r)); -#endif - } - m_last_piece = second_clock::universal_time(); - send_buffer_updated(); - - } - - void peer_connection::send_request(piece_block block) - { - INVARIANT_CHECK; - - assert(m_torrent->valid_metadata()); - assert(block.piece_index >= 0); - assert(block.piece_index < m_torrent->torrent_file().num_pieces()); - assert(block.block_index >= 0); - assert(block.block_index < m_torrent->torrent_file().piece_size(block.piece_index)); - assert(!m_torrent->picker().is_downloading(block)); - - m_torrent->picker().mark_as_downloading(block, m_remote); - m_request_queue.push_back(block); - send_block_requests(); - } - - void peer_connection::send_metadata(std::pair req) - { - assert(req.first >= 0); - assert(req.second > 0); - assert(req.second <= 256); - assert(req.first + req.second <= 256); - assert(m_torrent); - INVARIANT_CHECK; - - // abort if the peer doesn't support the metadata extension - if (!supports_extension(extended_metadata_message)) return; - - if (m_torrent->valid_metadata()) - { - std::pair offset - = req_to_offset(req, (int)m_torrent->metadata().size()); - - buffer::interval i = m_send_buffer.allocate(18 + offset.second); - - // yes, we have metadata, send it - detail::write_uint32(5 + 9 + offset.second, i.begin); - detail::write_uint8(msg_extended, i.begin); - detail::write_int32(m_extension_messages[extended_metadata_message], i.begin); - // means 'data packet' - detail::write_uint8(1, i.begin); - detail::write_uint32((int)m_torrent->metadata().size(), i.begin); - detail::write_uint32(offset.first, i.begin); - std::vector const& metadata = m_torrent->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 = m_send_buffer.allocate(10); - // we don't have the metadata, reply with - // don't have-message - detail::write_uint32(1 + 4 + 1, i.begin); - detail::write_uint8(msg_extended, i.begin); - detail::write_int32(m_extension_messages[extended_metadata_message], i.begin); - // means 'have no data' - detail::write_uint8(2, i.begin); - assert(i.begin == i.end); - } - send_buffer_updated(); - } - - void peer_connection::send_metadata_request(std::pair req) - { - assert(req.first >= 0); - assert(req.second > 0); - assert(req.first + req.second <= 256); - assert(m_torrent); - assert(!m_torrent->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 = m_send_buffer.allocate(12); - - detail::write_uint32(1 + 4 + 3, i.begin); - detail::write_uint8(msg_extended, i.begin); - detail::write_int32(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); - send_buffer_updated(); - } - - void peer_connection::send_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 message; - bencode(std::back_inserter(message), e); - - buffer::interval i = m_send_buffer.allocate(message.size() + 9); - - detail::write_uint32(1 + 4 + (int)message.size(), i.begin); - detail::write_uint8(msg_extended, i.begin); - detail::write_int32(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); - send_buffer_updated(); - } - - void peer_connection::send_bitfield() - { - INVARIANT_CHECK; - - if (m_torrent->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)m_have_piece.size(); ++i) - { - if (m_torrent->have_piece(i)) (*m_logger) << "1"; - else (*m_logger) << "0"; - } - (*m_logger) << "\n"; -#endif - const int packet_size = ((int)m_have_piece.size() + 7) / 8 + 5; - - buffer::interval i = m_send_buffer.allocate(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)m_have_piece.size(); ++c) - { - if (m_torrent->have_piece(c)) - i.begin[c >> 3] |= 1 << (7 - (c & 7)); - } - assert(i.end - i.begin == ((int)m_have_piece.size() + 7) / 8); - send_buffer_updated(); - } - - void peer_connection::send_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 extension_list(entry::dictionary_t); - - for (int i = 0; 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; - } - - std::vector msg; - bencode(std::back_inserter(msg), extension_list); - - // make room for message - buffer::interval i = m_send_buffer.allocate(5 + msg.size()); - - // write the length of the message - detail::write_int32((int)msg.size() + 1, i.begin); - - detail::write_uint8(msg_extension_list, i.begin); - - std::copy(msg.begin(), msg.end(), i.begin); - i.begin += msg.size(); - assert(i.begin == i.end); - - send_buffer_updated(); } void peer_connection::send_choke() @@ -1841,8 +1157,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_choked) return; - char msg[] = {0,0,0,1,msg_choke}; - m_send_buffer.insert(msg, msg + sizeof(msg)); + write_choke(); m_choked = true; #ifdef TORRENT_VERBOSE_LOGGING @@ -1856,7 +1171,6 @@ namespace libtorrent #endif m_num_invalid_requests = 0; m_requests.clear(); - send_buffer_updated(); } void peer_connection::send_unchoke() @@ -1873,8 +1187,7 @@ namespace libtorrent #endif if (!m_choked) return; - char msg[] = {0,0,0,1,msg_unchoke}; - m_send_buffer.insert(msg, msg + sizeof(msg)); + write_unchoke(); m_choked = false; #ifdef TORRENT_VERBOSE_LOGGING @@ -1882,7 +1195,6 @@ namespace libtorrent (*m_logger) << to_simple_string(second_clock::universal_time()) << " ==> UNCHOKE\n"; #endif - send_buffer_updated(); } void peer_connection::send_interested() @@ -1890,8 +1202,7 @@ namespace libtorrent INVARIANT_CHECK; if (m_interesting) return; - char msg[] = {0,0,0,1,msg_interested}; - m_send_buffer.insert(msg, msg + sizeof(msg)); + write_interested(); m_interesting = true; #ifdef TORRENT_VERBOSE_LOGGING @@ -1899,7 +1210,6 @@ namespace libtorrent (*m_logger) << to_simple_string(second_clock::universal_time()) << " ==> INTERESTED\n"; #endif - send_buffer_updated(); } void peer_connection::send_not_interested() @@ -1907,8 +1217,7 @@ namespace libtorrent INVARIANT_CHECK; if (!m_interesting) return; - char msg[] = {0,0,0,1,msg_not_interested}; - m_send_buffer.insert(msg, msg + sizeof(msg)); + write_not_interested(); m_interesting = false; m_became_uninteresting = second_clock::universal_time(); @@ -1918,37 +1227,129 @@ namespace libtorrent (*m_logger) << to_simple_string(second_clock::universal_time()) << " ==> NOT_INTERESTED\n"; #endif - send_buffer_updated(); } - void peer_connection::send_have(int index) + void peer_connection::send_block_requests() { - assert(m_torrent->valid_metadata()); - assert(index >= 0); - assert(index < m_torrent->torrent_file().num_pieces()); INVARIANT_CHECK; - // optimization, don't send have messages - // to peers that already have the piece - if (m_have_piece[index]) return; + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + assert(!has_peer_choked()); - const int packet_size = 9; - char msg[packet_size] = {0,0,0,5,msg_have}; - char* ptr = msg + 5; - detail::write_int32(index, ptr); - m_send_buffer.insert(msg, msg + packet_size); + if ((int)m_download_queue.size() >= m_desired_queue_size) return; + + while (!m_request_queue.empty() + && (int)m_download_queue.size() < m_desired_queue_size) + { + piece_block block = m_request_queue.front(); + m_request_queue.pop_front(); + m_download_queue.push_back(block); + + int block_offset = block.block_index * t->block_size(); + int block_size = std::min((int)t->torrent_file().piece_size( + block.piece_index) - block_offset, t->block_size()); + assert(block_size > 0); + assert(block_size <= t->block_size()); + + peer_request r; + r.piece = block.piece_index; + r.start = block_offset; + r.length = block_size; + + assert(verify_piece(r)); + write_request(r); + + using namespace boost::posix_time; #ifdef TORRENT_VERBOSE_LOGGING - using namespace boost::posix_time; - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " ==> HAVE [ piece: " << index << " ]\n"; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " ==> REQUEST [ " + "piece: " << block.piece_index << " | " + "b: " << block.block_index << " | " + "s: " << block_offset << " | " + "l: " << block_size << " | " + "ds: " << statistics().download_rate() << " | " + "qs: " << m_desired_queue_size << " ]\n"; #endif - send_buffer_updated(); + } + m_last_piece = second_clock::universal_time(); + } + + + void close_socket_ignore_error(boost::shared_ptr s) + { + s->close(asio::ignore_error()); + } + + void peer_connection::disconnect() + { + boost::intrusive_ptr me(this); + + INVARIANT_CHECK; + + if (m_disconnecting) return; + m_disconnecting = true; + m_ses.m_selector.post(boost::bind(&close_socket_ignore_error, m_socket)); + + boost::shared_ptr t = m_torrent.lock(); + + if (t) + { + if (t->valid_metadata()) + { + piece_picker& picker = t->picker(); + + while (!m_download_queue.empty()) + { + picker.abort_download(m_download_queue.back()); + m_download_queue.pop_back(); + } + while (!m_request_queue.empty()) + { + picker.abort_download(m_request_queue.back()); + m_request_queue.pop_back(); + } + } +#ifndef NDEBUG + else + { + assert(m_download_queue.empty()); + assert(m_request_queue.empty()); + } +#endif + t->remove_peer(this); + m_torrent.reset(); + } + + m_ses.close_connection(me); + } + + void peer_connection::set_upload_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + if (limit < 10) limit = 10; + m_ul_bandwidth_quota.max = limit; + } + + void peer_connection::set_download_limit(int limit) + { + assert(limit >= -1); + if (limit == -1) limit = std::numeric_limits::max(); + if (limit < 10) limit = 10; + m_dl_bandwidth_quota.max = limit; } size_type peer_connection::share_diff() const { - float ratio = m_torrent->ratio(); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + float ratio = t->ratio(); // if we have an infinite ratio, just say we have downloaded // much more than we have uploaded. And we'll keep uploading. @@ -1960,15 +1361,58 @@ namespace libtorrent - m_statistics.total_payload_upload(); } - void peer_connection::second_tick() + void peer_connection::cut_receive_buffer(int size, int packet_size) + { + INVARIANT_CHECK; + + assert((int)m_recv_buffer.size() >= size); + + std::copy(m_recv_buffer.begin() + size, m_recv_buffer.begin() + m_recv_pos, m_recv_buffer.begin()); + + assert(m_recv_pos >= size); + m_recv_pos -= size; + +#ifndef NDEBUG + std::fill(m_recv_buffer.begin() + m_recv_pos, m_recv_buffer.end(), 0); +#endif + + m_packet_size = packet_size; + m_recv_buffer.resize(m_packet_size); + } + + void peer_connection::second_tick(float tick_interval) { INVARIANT_CHECK; ptime now(second_clock::universal_time()); + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + on_tick(); + + if (!t->valid_metadata()) return; + + // calculate the desired download queue size + const float queue_time = m_ses.m_settings.request_queue_time; + // (if the latency is more than this, the download will stall) + // so, the queue size is queue_time * down_rate / 16 kiB + // (16 kB is the size of each request) + // the minimum number of requests is 2 and the maximum is 48 + // the block size doesn't have to be 16. So we first query the + // torrent for it + const int block_size = t->block_size(); + assert(block_size > 0); - // TODO: the timeout should be user-settable + int m_desired_queue_size = static_cast(queue_time + * statistics().download_rate() / block_size); + if (m_desired_queue_size > max_request_queue) m_desired_queue_size + = max_request_queue; + if (m_desired_queue_size < min_request_queue) m_desired_queue_size + = min_request_queue; + if (!m_download_queue.empty() - && now - m_last_piece > seconds(15)) + && now - m_last_piece > seconds(m_ses.m_settings.piece_timeout)) { // this peer isn't sending the pieces we've // requested (this has been observed by BitComet) @@ -1976,10 +1420,11 @@ namespace libtorrent // re-request the blocks. #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << to_simple_string(now) - << " *** IGNORED_REQUESTS [ " << (int)m_download_queue.size() << " ] ***\n"; + << " *** PIECE_REQUESTS TIMED OUT [ " << (int)m_download_queue.size() + << " " << to_simple_string(now - m_last_piece) << "] ***\n"; #endif - piece_picker& picker = m_torrent->picker(); + piece_picker& picker = t->picker(); for (std::deque::const_iterator i = m_download_queue.begin() , end(m_download_queue.end()); i != end; ++i) { @@ -1997,28 +1442,14 @@ namespace libtorrent m_download_queue.clear(); m_request_queue.clear(); + + m_assume_fifo = true; // this will trigger new picking of pieces - m_torrent->get_policy().unchoked(*this); - } - - // 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() - && supports_extension(extended_metadata_message) - && !m_waiting_metadata_request - && has_metadata()) - { - assert(m_torrent); - m_last_metadata_request = m_torrent->metadata_request(); - send_metadata_request(m_last_metadata_request); - m_waiting_metadata_request = true; - m_metadata_request = now; + t->get_policy().unchoked(*this); } - m_statistics.second_tick(); + m_statistics.second_tick(tick_interval); m_ul_bandwidth_quota.used = std::min( (int)ceil(statistics().upload_rate()) , m_ul_bandwidth_quota.given); @@ -2030,19 +1461,20 @@ namespace libtorrent // maintain the share ratio given by m_ratio // with all peers. - if (m_torrent->is_seed() || is_choked() || m_torrent->ratio() == 0.0f) + if (t->is_seed() || is_choked() || t->ratio() == 0.0f) { // if we have downloaded more than one piece more // than we have uploaded OR if we are a seed // have an unlimited upload rate - if(!m_send_buffer.empty() || (!m_requests.empty() && !is_choked())) + if(!m_send_buffer[m_current_send_buffer].empty() + || (!m_requests.empty() && !is_choked())) m_ul_bandwidth_quota.max = resource_request::inf; else m_ul_bandwidth_quota.max = m_ul_bandwidth_quota.min; } else { - size_type bias = 0x10000+2*m_torrent->block_size() + m_free_upload; + size_type bias = 0x10000+2*t->block_size() + m_free_upload; double break_even_time = 15; // seconds. size_type have_uploaded = m_statistics.total_payload_upload(); @@ -2052,8 +1484,8 @@ namespace libtorrent size_type soon_downloaded = have_downloaded + (size_type)(download_speed * break_even_time*1.5); - if(m_torrent->ratio() != 1.f) - soon_downloaded = (size_type)(soon_downloaded*(double)m_torrent->ratio()); + if(t->ratio() != 1.f) + soon_downloaded = (size_type)(soon_downloaded*(double)t->ratio()); double upload_speed_limit = (soon_downloaded - have_uploaded + bias) / break_even_time; @@ -2070,8 +1502,7 @@ namespace libtorrent if (m_ul_bandwidth_quota.used > m_ul_bandwidth_quota.given) m_ul_bandwidth_quota.used = m_ul_bandwidth_quota.given; - send_buffer_updated(); - + fill_send_buffer(); /* size_type diff = share_diff(); @@ -2111,429 +1542,40 @@ namespace libtorrent */ } - // -------------------------- - // RECEIVE DATA - // -------------------------- - - // throws exception when the client should be disconnected - void peer_connection::receive_data() + void peer_connection::fill_send_buffer() { INVARIANT_CHECK; - assert(!m_socket->is_blocking()); - assert(m_packet_size > 0); - assert(m_socket->is_readable()); - assert(can_read()); - assert(m_selector.is_readability_monitored(m_socket)); + if (!can_write()) return; - if (m_disconnecting) return; - - for(;;) - { - assert(m_packet_size > 0); - int max_receive = std::min( - m_dl_bandwidth_quota.left() - , m_packet_size - m_recv_pos); - int received = m_socket->receive( - &m_recv_buffer[m_recv_pos], max_receive); - - // connection closed - if (received == 0) - { - throw protocol_error("connection closed by remote host"); - } - - // an error - if (received < 0) - { - // would_block means that no data was ready to be received - // returns to exit the loop - if (m_socket->last_error() == socket::would_block) - return; - - // the connection was closed - throw network_error(m_socket->last_error()); - } - - if (received > 0) - { - m_last_receive = second_clock::universal_time(); - - m_recv_pos += received; - m_dl_bandwidth_quota.used += received; - if (!can_read()) - { - assert(m_readability_monitored); - assert(m_selector.is_readability_monitored(m_socket)); - m_selector.remove_readable(m_socket); - m_readability_monitored = false; - } - - switch(m_state) - { - case read_protocol_length: - - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) break; - assert(m_recv_pos == m_packet_size); - - m_packet_size = reinterpret_cast(m_recv_buffer[0]); - -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " protocol length: " << m_packet_size << "\n"; -#endif - if (m_packet_size > 100 || m_packet_size <= 0) - { - std::stringstream s; - s << "incorrect protocol length (" - << m_packet_size - << ") should be 19."; - throw protocol_error(s.str()); - } - - m_state = read_protocol_string; - m_recv_buffer.resize(m_packet_size); - m_recv_pos = 0; - - break; - - - case read_protocol_string: - { - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) break; - assert(m_recv_pos == m_packet_size); -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " protocol: '" << std::string(m_recv_buffer.begin() - , m_recv_buffer.end()) << "'\n"; -#endif - const char protocol_string[] = "BitTorrent protocol"; - if (!std::equal(m_recv_buffer.begin(), m_recv_buffer.end() - , protocol_string)) - { - const char cmd[] = "version"; - if (m_recv_buffer.size() == 7 && std::equal(m_recv_buffer.begin(), m_recv_buffer.end(), cmd)) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "sending libtorrent version\n"; -#endif - m_socket->send("libtorrent version " LIBTORRENT_VERSION "\n", 27); - throw protocol_error("closing"); - } -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "incorrect protocol name\n"; -#endif - std::stringstream s; - s << "got invalid protocol name: '" - << std::string(m_recv_buffer.begin(), m_recv_buffer.end()) - << "'"; - throw protocol_error(s.str()); - } - - m_state = read_info_hash; - m_packet_size = 28; - m_recv_pos = 0; - m_recv_buffer.resize(28); - } - break; - - - case read_info_hash: - { - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) break; - assert(m_recv_pos == m_packet_size); - // the use of this bit collides with Mainline - // the new way of identifying support for the extensions - // is in the peer_id -// if ((m_recv_buffer[7] & 0x01) && m_ses.extensions_enabled()) -// m_supports_extensions = true; - - // ok, now we have got enough of the handshake. Is this connection - // attached to a torrent? - if (m_torrent == 0) - { - - // 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(m_recv_buffer.begin() + 8, m_recv_buffer.begin() + 28, (char*)info_hash.begin()); - - m_torrent = m_ses.find_torrent(info_hash); - if (m_torrent && m_torrent->is_aborted()) m_torrent = 0; - if (m_torrent == 0) - { - // we couldn't find the torrent! -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " couldn't find a torrent with the given info_hash\n"; -#endif - throw protocol_error("got info-hash that is not in our session"); - } - - if (m_torrent->is_paused()) - { - // paused torrents will not accept - // incoming connections -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " rejected connection to paused torrent\n"; -#endif - throw protocol_error("connection rejected by paused torrent"); - } - - // if the torrent isn't ready to accept - // connections yet, we'll have to wait with - // our initialization - if (m_torrent->ready_for_connections()) init(); - - // assume the other end has no pieces - // if we don't have valid metadata yet, - // leave the vector unallocated - std::fill(m_have_piece.begin(), m_have_piece.end(), false); - - // yes, we found the torrent - // reply with our handshake - send_handshake(); - send_bitfield(); - } - else - { - // verify info hash - if (!std::equal(m_recv_buffer.begin()+8, m_recv_buffer.begin() + 28, (const char*)m_torrent->torrent_file().info_hash().begin())) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " received invalid info_hash\n"; -#endif - throw protocol_error("invalid info-hash in handshake"); - } - } - - m_state = read_peer_id; - m_packet_size = 20; - m_recv_pos = 0; - m_recv_buffer.resize(20); -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " info_hash received\n"; -#endif - break; - } - - - case read_peer_id: - { - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) break; - assert(m_recv_pos == m_packet_size); - assert(m_packet_size == 20); - -#ifdef TORRENT_VERBOSE_LOGGING - { - peer_id tmp; - std::copy(m_recv_buffer.begin(), m_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 - std::copy(m_recv_buffer.begin(), m_recv_buffer.begin() + 20, (char*)m_peer_id.begin()); - - if (std::memcmp(&m_peer_id[17], "ext", 3) == 0) - m_supports_extensions = true; - - // disconnect if the peer has the same peer-id as ourself - // since it most likely is ourself then - if (m_peer_id == m_ses.get_peer_id()) - throw protocol_error("closing connection to ourself"); - - if (m_supports_extensions) send_extensions(); - - if (!m_active) - { - // check to make sure we don't have another connection with the same - // info_hash and peer_id. If we do. close this connection. - m_torrent->attach_peer(this); - m_attached_to_torrent = true; - assert(m_torrent->get_policy().has_connection(this)); - } - - m_state = read_packet_size; - m_packet_size = 4; - m_recv_pos = 0; - m_recv_buffer.resize(4); - - break; - } - - - case read_packet_size: - { - m_statistics.received_bytes(0, received); - if (m_recv_pos < m_packet_size) break; - assert(m_recv_pos == m_packet_size); - - // convert from big endian to native byte order - const char* ptr = &m_recv_buffer[0]; - m_packet_size = detail::read_int32(ptr); - // don't accept packets larger than 1 MB - if (m_packet_size > 1024*1024 || m_packet_size < 0) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << " packet too large (packet_size > 1 Megabyte), abort\n"; -#endif - // packet too large - throw protocol_error("packet > 1 MB"); - } - - if (m_packet_size == 0) - { - // keepalive message - m_state = read_packet_size; - m_packet_size = 4; - } - else - { - m_state = read_packet; - m_recv_buffer.resize(m_packet_size); - } - m_recv_pos = 0; - assert(m_packet_size > 0); - break; - } - case read_packet: - - if (dispatch_message(received)) - { - m_state = read_packet_size; - m_packet_size = 4; - m_recv_buffer.resize(4); - m_recv_pos = 0; - assert(m_packet_size > 0); - } - break; - } - - // if we have used all our download quota, - // break the receive loop - if (!can_read()) break; - } - } - assert(m_packet_size > 0); - } - - - bool peer_connection::can_write() const - { - // if we have requests or pending data to be sent or announcements to be made - // we want to send data - return ((!m_requests.empty() && !m_choked) - || !m_send_buffer.empty()) - && m_ul_bandwidth_quota.left() > 0; - } - - bool peer_connection::can_read() const - { - return m_dl_bandwidth_quota.left() > 0; - } - - void peer_connection::connect() - { - INVARIANT_CHECK; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "CONNECT: " << m_remote.as_string() << "\n"; -#endif - - m_queued = false; - assert(m_connecting); - assert(associated_torrent()); - m_socket->connect(m_remote, associated_torrent()->get_interface()); - - if (m_torrent->alerts().should_post(alert::debug)) - { - m_torrent->alerts().post_alert(peer_error_alert( - m_remote, m_peer_id, "connecting to peer")); - } - } - - void peer_connection::connection_complete() - { - INVARIANT_CHECK; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "COMPLETED: " << m_remote.as_string() << "\n"; -#endif - - m_connecting = false; - // this means the connection just succeeded - -// assert(!can_write()); -// assert(m_writability_monitored); - if (!can_write() && m_writability_monitored) - { - m_writability_monitored = false; - m_selector.remove_writable(m_socket); - } - } - - // -------------------------- - // SEND DATA - // -------------------------- - - // throws exception when the client should be disconnected - void peer_connection::send_data() - { - INVARIANT_CHECK; - - assert(!m_connecting); - assert(!m_disconnecting); - assert(m_socket->is_writable()); - assert(can_write()); + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; // only add new piece-chunks if the send buffer is small enough // otherwise there will be no end to how large it will be! // TODO: the buffer size should probably be dependent on the transfer speed while (!m_requests.empty() - && ((int)m_send_buffer.size() < m_torrent->block_size() * 6) + && (send_buffer_size() < t->block_size() * 6) && !m_choked) { - assert(m_torrent->valid_metadata()); + assert(t->valid_metadata()); peer_request& r = m_requests.front(); assert(r.piece >= 0); assert(r.piece < (int)m_have_piece.size()); - assert(m_torrent != 0); - assert(m_torrent->have_piece(r.piece)); - assert(r.start + r.length <= m_torrent->torrent_file().piece_size(r.piece)); + assert(t->have_piece(r.piece)); + assert(r.start + r.length <= t->torrent_file().piece_size(r.piece)); assert(r.length > 0 && r.start >= 0); - const int packet_size = 4 + 5 + 4 + r.length; + write_piece(r); - buffer::interval i = m_send_buffer.allocate(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); - - m_torrent->filesystem().read( - i.begin - , r.piece - , r.start - , r.length); - assert(i.begin + r.length == i.end); #ifdef TORRENT_VERBOSE_LOGGING - using namespace boost::posix_time; - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " ==> PIECE [ piece: " << r.piece << " | s: " << r.start << " | l: " << r.length << " ]\n"; + using namespace boost::posix_time; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " ==> PIECE [ piece: " << r.piece << " | s: " << r.start + << " | l: " << r.length << " ]\n"; #endif - m_payloads.push_back(range(m_send_buffer.size() - r.length, r.length)); m_requests.erase(m_requests.begin()); if (m_requests.empty() @@ -2549,114 +1591,353 @@ namespace libtorrent send_unchoke(); } } + } - if (!m_announce_queue.empty()) - { - assert(m_torrent->valid_metadata()); - for (std::vector::iterator i = m_announce_queue.begin(); - i != m_announce_queue.end(); ++i) - { - send_have(*i); - } - m_announce_queue.clear(); - } + void peer_connection::setup_send() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - assert(m_ul_bandwidth_quota.used <= m_ul_bandwidth_quota.given); + INVARIANT_CHECK; + + if (m_writing) return; + if (!can_write()) return; + + assert(!m_writing); // send the actual buffer - if (!m_send_buffer.empty()) + if (!m_send_buffer[m_current_send_buffer].empty()) { int amount_to_send - = std::min(m_ul_bandwidth_quota.left(), (int)m_send_buffer.size()); + = std::min(m_ul_bandwidth_quota.left() + , (int)m_send_buffer[m_current_send_buffer].size()); assert(amount_to_send > 0); - buffer::interval_type send_buffer = m_send_buffer.data(); + buffer::interval_type send_buffer + = m_send_buffer[m_current_send_buffer].data(); + // swap the send buffer for the double buffered effect + m_current_send_buffer = (m_current_send_buffer + 1) & 1; + // we have data that's scheduled for sending int to_send = std::min(int(send_buffer.first.end - send_buffer.first.begin) , amount_to_send); - int sent = m_socket->send(send_buffer.first.begin, to_send); - if (sent == send_buffer.first.end - send_buffer.first.end - && send_buffer.second.begin != send_buffer.second.end - && to_send > sent) - { - to_send -= sent; - to_send = std::min(to_send, int(send_buffer.second.end - - send_buffer.second.begin)); - int ret = m_socket->send(send_buffer.second.begin, to_send); - if (ret > 0) sent += ret; - } - - if (sent > 0) - { - m_ul_bandwidth_quota.used += sent; - m_send_buffer.erase(sent); + boost::array bufs; + assert(to_send >= 0); + bufs[0] = asio::buffer(send_buffer.first.begin, to_send); - // manage the payload markers - int amount_payload = 0; - if (!m_payloads.empty()) - { - for (std::deque::iterator i = m_payloads.begin(); - i != m_payloads.end(); ++i) - { - i->start -= sent; - 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()); + to_send = std::min(int(send_buffer.second.end - send_buffer.second.begin) + , amount_to_send - to_send); + assert(to_send >= 0); + bufs[1] = asio::buffer(send_buffer.second.begin, to_send); - assert(amount_payload <= sent); - m_statistics.sent_bytes(amount_payload, sent - amount_payload); - } - else - { - assert(sent == -1); - throw network_error(m_socket->last_error()); - } + assert(m_ul_bandwidth_quota.left() >= int(buffer_size(bufs[0]) + buffer_size(bufs[1]))); + m_socket->async_write_some(bufs, bind(&peer_connection::on_send_data + , self(), _1, _2)); + m_writing = true; + m_last_write_size = amount_to_send; + m_ul_bandwidth_quota.used += m_last_write_size; + } + } - m_last_sent = second_clock::universal_time(); + void peer_connection::setup_receive() + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (m_reading) return; + if (!can_read()) return; + + assert(m_packet_size > 0); + int max_receive = std::min( + m_dl_bandwidth_quota.left() + , m_packet_size - m_recv_pos); + + assert(m_recv_pos >= 0); + assert(m_packet_size > 0); + assert(m_dl_bandwidth_quota.left() > 0); + assert(max_receive > 0); + + assert(can_read()); + m_socket->async_read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , max_receive), bind(&peer_connection::on_receive_data, self(), _1, _2)); + m_reading = true; + m_last_read_size = max_receive; + m_dl_bandwidth_quota.used += max_receive; + assert(m_dl_bandwidth_quota.used <= m_dl_bandwidth_quota.given); + } + + void peer_connection::reset_recv_buffer(int packet_size) + { + assert(packet_size > 0); + m_recv_pos = 0; + m_packet_size = packet_size; + m_recv_buffer.resize(m_packet_size); + } + + void peer_connection::send_buffer(char const* begin, char const* end) + { + m_send_buffer[m_current_send_buffer].insert(begin, end); + setup_send(); + } + +// TODO: change this interface to automatically call setup_send() when the +// return value is destructed + buffer::interval peer_connection::allocate_send_buffer(int size) + { + return m_send_buffer[m_current_send_buffer].allocate(size); + } + + template + struct set_to_zero + { + set_to_zero(T& v, bool cond): m_val(v), m_cond(cond) {} + void fire() { if (!m_cond) return; m_cond = false; m_val = 0; } + ~set_to_zero() { if (m_cond) m_val = 0; } + private: + T& m_val; + bool m_cond; + }; + + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + // throws exception when the client should be disconnected + void peer_connection::on_receive_data(const asio::error& error + , std::size_t bytes_transferred) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + assert(m_reading); + assert(m_last_read_size > 0); + m_reading = false; + // correct the dl quota usage, if not all of the buffer was actually read + m_dl_bandwidth_quota.used -= m_last_read_size - bytes_transferred; + m_last_read_size = 0; + + if (error) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "**ERROR**: " << error.what() << "\n"; +#endif + on_receive(error, bytes_transferred); + throw std::runtime_error(error.what()); } - assert(m_writability_monitored); - send_buffer_updated(); + if (m_disconnecting) return; + + assert(m_packet_size > 0); + assert(bytes_transferred > 0); + + m_last_receive = second_clock::universal_time(); + m_recv_pos += bytes_transferred; + + // this will reset the m_recv_pos to 0 if the + // entire packet was received + // it is important that this is done before + // setup_receive() is called. Therefore, fire() is + // called before setup_receive(). + assert(m_recv_pos <= m_packet_size); + set_to_zero reset(m_recv_pos, m_recv_pos == m_packet_size); + + { + INVARIANT_CHECK; + on_receive(error, bytes_transferred); + } + + assert(m_packet_size > 0); + + // do the reset immediately + reset.fire(); + + setup_receive(); } + catch (std::exception& e) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), e.what()); +// disconnect(); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); +// disconnect(); + } + + bool peer_connection::can_write() const + { + INVARIANT_CHECK; + + // if we have requests or pending data to be sent or announcements to be made + // we want to send data + return ((!m_requests.empty() && !m_choked) + || !m_send_buffer[m_current_send_buffer].empty()) + && m_ul_bandwidth_quota.left() > 0 + && !m_connecting; + } + + bool peer_connection::can_read() const + { + INVARIANT_CHECK; + + return m_dl_bandwidth_quota.left() > 0 && !m_connecting; + } + + void peer_connection::connect() + { + INVARIANT_CHECK; + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "CONNECTING: " << m_remote.address().to_string() << "\n"; +#endif + + boost::shared_ptr t = m_torrent.lock(); + assert(t); + + m_queued = false; + assert(m_connecting); + m_socket->open(asio::ipv4::tcp()); + m_socket->bind(t->get_interface()); + m_socket->async_connect(m_remote + , bind(&peer_connection::on_connection_complete, self(), _1)); + + if (t->alerts().should_post(alert::debug)) + { + t->alerts().post_alert(peer_error_alert( + m_remote, m_peer_id, "connecting to peer")); + } + } + + void peer_connection::on_connection_complete(asio::error const& e) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "CONNECTION FAILED: " << m_remote.address().to_string() << "\n"; +#endif + m_ses.connection_failed(m_socket, m_remote, e.what()); +// disconnect(); + return; + } + + // this means the connection just succeeded + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "COMPLETED: " << m_remote.address().to_string() << "\n"; +#endif + + m_connecting = false; + m_ses.connection_completed(self()); + on_connected(); + } + catch (std::exception& ex) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), ex.what()); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); + } + + // -------------------------- + // SEND DATA + // -------------------------- + + // throws exception when the client should be disconnected + void peer_connection::on_send_data(asio::error const& error + , std::size_t bytes_transferred) try + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + assert(m_writing); + assert(m_last_write_size > 0); + m_writing = false; + // correct the ul quota usage, if not all of the buffer was sent + m_ul_bandwidth_quota.used -= m_last_write_size - bytes_transferred; + m_last_write_size = 0; + + if (error) + throw std::runtime_error(error.what()); + if (m_disconnecting) return; + + assert(!m_connecting); +// assert(bytes_transferred > 0); + + int sending_buffer = (m_current_send_buffer + 1) & 1; + m_send_buffer[sending_buffer].erase(bytes_transferred); + + m_last_sent = second_clock::universal_time(); + + on_sent(error, bytes_transferred); + fill_send_buffer(); + if (!m_send_buffer[sending_buffer].empty()) + { + // if the send operation didn't send all of the data in the buffer. + // send it again. + m_current_send_buffer = sending_buffer; + } + setup_send(); + } + catch (std::exception& e) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), e.what()); + } + catch (...) + { + // all exceptions should derive from std::exception + assert(false); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_ses.connection_failed(m_socket, remote(), "connection failed for unkown reason"); + } + #ifndef NDEBUG void peer_connection::check_invariant() const { - assert((can_write() == m_selector.is_writability_monitored(m_socket)) - || m_connecting); + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; - assert(m_writability_monitored == m_selector.is_writability_monitored(m_socket)); - assert(m_readability_monitored == m_selector.is_readability_monitored(m_socket)); + if (!m_in_constructor && t->connection_for(remote()) != this) + { + assert(false); + } -/* - assert(m_num_pieces == std::count( - m_have_piece.begin() - , m_have_piece.end() - , true)); -*/ } + if (t->valid_metadata()) + { + int piece_count = std::count(m_have_piece.begin() + , m_have_piece.end(), true); + if (m_num_pieces != piece_count) + { + assert(false); + } + } + } #endif bool peer_connection::has_timed_out() const { + // TODO: the timeout should be set by an event rather + INVARIANT_CHECK; + using namespace boost::posix_time; ptime now(second_clock::universal_time()); @@ -2679,6 +1960,7 @@ namespace libtorrent time_duration d2; d1 = now - m_became_uninterested; d2 = now - m_became_uninteresting; + // TODO: these timeouts should be user settable if (!m_interesting && !m_peer_interested && d1 > minutes(10) @@ -2699,60 +1981,17 @@ namespace libtorrent d = second_clock::universal_time() - m_last_sent; if (d.total_seconds() < m_timeout / 2) return; - // we must either send a keep-alive - // message or something else. - if (m_announce_queue.empty()) - { - char noop[] = {0,0,0,0}; - m_send_buffer.insert(noop, noop + 4); - m_last_sent = second_clock::universal_time(); -#ifdef TORRENT_VERBOSE_LOGGING - using namespace boost::posix_time; - (*m_logger) << to_simple_string(second_clock::universal_time()) - << " ==> NOP\n"; -#endif - } - else - { - for (std::vector::iterator i = m_announce_queue.begin(); - i != m_announce_queue.end(); ++i) - { - send_have(*i); - } - m_announce_queue.clear(); - } - send_buffer_updated(); + if (m_connecting) return; + + write_keepalive(); } bool peer_connection::is_seed() const { + INVARIANT_CHECK; // if m_num_pieces == 0, we probably doesn't have the // metadata yet. return m_num_pieces == (int)m_have_piece.size() && m_num_pieces > 0; } - - void peer_connection::send_buffer_updated() - { - if (!can_write()) - { - if (m_writability_monitored) - { - m_selector.remove_writable(m_socket); - m_writability_monitored = false; - } - assert(!m_selector.is_writability_monitored(m_socket)); - return; - } - - assert(m_ul_bandwidth_quota.left() > 0); - assert(can_write()); - if (!m_writability_monitored) - { - m_selector.monitor_writability(m_socket); - m_writability_monitored = true; - } - assert(m_writability_monitored); - assert(m_selector.is_writability_monitored(m_socket)); - } } diff --git a/src/piece_picker.cpp b/src/piece_picker.cpp index e25b44a50..50c75121d 100755 --- a/src/piece_picker.cpp +++ b/src/piece_picker.cpp @@ -45,12 +45,14 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks) + piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks + , int sequenced_download_threshold) : m_piece_info(2) , m_downloading_piece_info(2) , m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece) , m_num_filtered(0) , m_num_have_filtered(0) + , m_sequenced_download_threshold(sequenced_download_threshold) { assert(blocks_per_piece > 0); assert(total_num_blocks >= 0); @@ -104,12 +106,9 @@ namespace libtorrent } } - // random shuffle the list - std::random_shuffle(piece_list.begin(), piece_list.end()); - // add the pieces to the piece_picker - for (std::vector::iterator i = piece_list.begin(); - i != piece_list.end(); ++i) + for (std::vector::reverse_iterator i = piece_list.rbegin(); + i != piece_list.rend(); ++i) { int index = *i; assert(index >= 0); @@ -133,7 +132,7 @@ namespace libtorrent for (std::vector::const_iterator i = unfinished.begin(); i != unfinished.end(); ++i) { - address peer; + tcp::endpoint peer; for (int j = 0; j < m_blocks_per_piece; ++j) { if (i->finished_blocks[j]) @@ -146,6 +145,34 @@ namespace libtorrent #endif } + void piece_picker::set_sequenced_download_threshold( + int sequenced_download_threshold) + { +#ifndef NDEBUG + integrity_check(); +#endif + + if (sequenced_download_threshold == m_sequenced_download_threshold) + return; + + int old_limit = m_sequenced_download_threshold; + m_sequenced_download_threshold = sequenced_download_threshold; + + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + if (i->priority(old_limit) != i->priority(m_sequenced_download_threshold)) + { + piece_pos& p = m_piece_map[i->index]; + int prev_priority = i->priority(old_limit); + move(p.downloading, p.filtered, prev_priority, i->index); + } + } +#ifndef NDEBUG + integrity_check(); +#endif + } + #ifndef NDEBUG void piece_picker::integrity_check(const torrent* t) const @@ -236,8 +263,8 @@ namespace libtorrent assert(!t->have_piece(index)); const std::vector >& c_vec = pick_piece_info_vector(i->downloading, i->filtered); - assert(i->peer_count < c_vec.size()); - const std::vector& vec = c_vec[i->peer_count]; + assert(i->priority(m_sequenced_download_threshold) < (int)c_vec.size()); + const std::vector& vec = c_vec[i->priority(m_sequenced_download_threshold)]; assert(i->index < vec.size()); assert(vec[i->index] == index); } @@ -302,80 +329,159 @@ namespace libtorrent std::vector >& dst_vec = pick_piece_info_vector( p.downloading, p.filtered); - if (dst_vec.size() <= p.peer_count) - dst_vec.resize(p.peer_count + 1); + int priority = p.priority(m_sequenced_download_threshold); + if ((int)dst_vec.size() <= priority) + dst_vec.resize(priority + 1); - assert(dst_vec.size() > p.peer_count); - p.index = (int)dst_vec[p.peer_count].size(); - dst_vec[p.peer_count].push_back(index); + assert((int)dst_vec.size() > priority); + + if (p.ordered(m_sequenced_download_threshold)) + { + // the piece should be inserted ordered, not randomly + std::vector& v = dst_vec[priority]; + std::vector::iterator i = std::lower_bound(v.begin(), v.end() + , index, std::greater()); + p.index = i - v.begin(); + v.insert(i, index); + i = v.begin() + p.index + 1; + for (;i != v.end(); ++i) + { + ++m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } + } + else if (dst_vec[priority].size() < 2) + { + p.index = dst_vec[priority].size(); + dst_vec[priority].push_back(index); + } + else + { + // find a random position in the destination vector where we will place + // this entry. + int dst_index = rand() % dst_vec[priority].size(); + + // copy the entry at that position to the back + m_piece_map[dst_vec[priority][dst_index]].index + = dst_vec[priority].size(); + dst_vec[priority].push_back(dst_vec[priority][dst_index]); + + // and then replace the one at dst_index with the one we're moving. + // this procedure is to make sure there's no ordering when pieces + // are moved in sequenced order. + p.index = dst_index; + dst_vec[priority][p.index] = index; + } } - // will update the piece with the given properties (downloading, filtered, peer_count, elem_index) - // to place it at the correct position in the vectors. - void piece_picker::move(bool downloading, bool filtered, int peer_count, int elem_index) + // will update the piece with the given properties (downloading, filtered, + // priority, elem_index) to place it at the correct position in the + // vectors. + void piece_picker::move(bool downloading, bool filtered, int priority + , int elem_index) { assert(!filtered); - assert(peer_count >= 0); + assert(priority >= 0); assert(elem_index >= 0); - std::vector >& src_vec(pick_piece_info_vector(downloading, filtered)); + std::vector >& src_vec(pick_piece_info_vector( + downloading, filtered)); - assert((int)src_vec.size() > peer_count); - assert((int)src_vec[peer_count].size() > elem_index); + assert((int)src_vec.size() > priority); + assert((int)src_vec[priority].size() > elem_index); - int index = src_vec[peer_count][elem_index]; + int index = src_vec[priority][elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; + int new_priority = p.priority(m_sequenced_download_threshold); assert(p.downloading != downloading || p.filtered != filtered - || (int)p.peer_count != peer_count); + || (int)new_priority != priority); - std::vector >& dst_vec(pick_piece_info_vector(p.downloading, p.filtered)); + std::vector >& dst_vec(pick_piece_info_vector( + p.downloading, p.filtered)); - if (dst_vec.size() <= p.peer_count) + if ((int)dst_vec.size() <= new_priority) { - dst_vec.resize(p.peer_count+1); - assert(dst_vec.size() > p.peer_count); + dst_vec.resize(new_priority + 1); + assert((int)dst_vec.size() > new_priority); } - p.index = (int)dst_vec[p.peer_count].size(); - dst_vec[p.peer_count].push_back(index); - assert(p.index < dst_vec[p.peer_count].size()); - assert(dst_vec[p.peer_count][p.index] == index); + if (p.ordered(m_sequenced_download_threshold)) + { + // the piece should be inserted ordered, not randomly + std::vector& v = dst_vec[new_priority]; + + std::vector::iterator i = std::lower_bound(v.begin(), v.end() + , index, std::greater()); + p.index = i - v.begin(); + v.insert(i, index); + i = v.begin() + p.index + 1; + for (;i != v.end(); ++i) + { + ++m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } + } + else if (dst_vec[new_priority].size() < 2) + { + p.index = dst_vec[new_priority].size(); + dst_vec[new_priority].push_back(index); + } + else + { + // find a random position in the destination vector where we will place + // this entry. + int dst_index = rand() % dst_vec[new_priority].size(); + + // copy the entry at that position to the back + m_piece_map[dst_vec[new_priority][dst_index]].index + = dst_vec[new_priority].size(); + dst_vec[new_priority].push_back(dst_vec[new_priority][dst_index]); + + // and then replace the one at dst_index with the one we're moving. + // this procedure is to make sure there's no ordering when pieces + // are moved in sequenced order. + p.index = dst_index; + dst_vec[new_priority][p.index] = index; + } + assert(p.index < dst_vec[p.priority(m_sequenced_download_threshold)].size()); + assert(dst_vec[p.priority(m_sequenced_download_threshold)][p.index] == index); // this will remove elem from the source vector without - // preserving order, but the order is random any way - int replace_index = src_vec[peer_count][elem_index] = src_vec[peer_count].back(); + // preserving order, but the order is random anyway + int replace_index = src_vec[priority][elem_index] = src_vec[priority].back(); if (index != replace_index) { // update the entry we moved from the back m_piece_map[replace_index].index = elem_index; - assert((int)src_vec[peer_count].size() > elem_index); - assert((int)m_piece_map[replace_index].peer_count == peer_count); + assert((int)src_vec[priority].size() > elem_index); + assert((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); assert((int)m_piece_map[replace_index].index == elem_index); - assert(src_vec[peer_count][elem_index] == replace_index); + assert(src_vec[priority][elem_index] == replace_index); } else { - assert((int)src_vec[peer_count].size() == elem_index+1); + assert((int)src_vec[priority].size() == elem_index+1); } - src_vec[peer_count].pop_back(); + src_vec[priority].pop_back(); } - void piece_picker::remove(bool downloading, bool filtered, int peer_count, int elem_index) + void piece_picker::remove(bool downloading, bool filtered, int priority + , int elem_index) { assert(!filtered); - assert(peer_count >= 0); + assert(priority >= 0); assert(elem_index >= 0); std::vector >& src_vec(pick_piece_info_vector(downloading, filtered)); - assert((int)src_vec.size() > peer_count); - assert((int)src_vec[peer_count].size() > elem_index); + assert((int)src_vec.size() > priority); + assert((int)src_vec[priority].size() > elem_index); - int index = src_vec[peer_count][elem_index]; + int index = src_vec[priority][elem_index]; if (downloading) { @@ -386,16 +492,30 @@ namespace libtorrent assert(i != m_downloads.end()); m_downloads.erase(i); } - m_piece_map[index].downloading = 0; - - // this will remove elem from the vector without - // preserving order - index = src_vec[peer_count][elem_index] = src_vec[peer_count].back(); - // update the entry we moved from the back - if ((int)src_vec[peer_count].size() > elem_index+1) - m_piece_map[index].index = elem_index; - src_vec[peer_count].pop_back(); - + piece_pos& p = m_piece_map[index]; + p.downloading = 0; + if (p.ordered(m_sequenced_download_threshold)) + { + std::vector& v = src_vec[priority]; + std::vector::iterator i = v.begin() + elem_index; + v.erase(i); + i = v.begin() + elem_index; + for (; i != v.end(); ++i) + { + --m_piece_map[*i].index; + assert(v[m_piece_map[*i].index] == *i); + } + } + else + { + // this will remove elem from the vector without + // preserving order + index = src_vec[priority][elem_index] = src_vec[priority].back(); + // update the entry we moved from the back + if ((int)src_vec[priority].size() > elem_index+1) + m_piece_map[index].index = elem_index; + src_vec[priority].pop_back(); + } } void piece_picker::restore_piece(int index) @@ -415,7 +535,7 @@ namespace libtorrent m_piece_map[index].downloading = 0; piece_pos& p = m_piece_map[index]; if (p.filtered) return; - move(true, p.filtered, p.peer_count, p.index); + move(true, p.filtered, p.priority(m_sequenced_download_threshold), p.index); #ifndef NDEBUG // integrity_check(); @@ -427,10 +547,10 @@ namespace libtorrent assert(i >= 0); assert(i < (int)m_piece_map.size()); - int peer_count = m_piece_map[i].peer_count; int index = m_piece_map[i].index; + int prev_priority = m_piece_map[i].priority(m_sequenced_download_threshold); - assert(peer_count < 2048); + assert(m_piece_map[i].peer_count < 2048); m_piece_map[i].peer_count++; assert(m_piece_map[i].peer_count != 0); @@ -438,9 +558,10 @@ namespace libtorrent // if we have the piece or if it's filtered // we don't have to move any entries in the piece_info vector - if (index == piece_pos::we_have_index || p.filtered) return; + if (index == piece_pos::we_have_index || p.filtered + || p.priority(m_sequenced_download_threshold) == prev_priority) return; - move(p.downloading, p.filtered, peer_count, index); + move(p.downloading, p.filtered, prev_priority, index); #ifndef NDEBUG // integrity_check(); @@ -457,18 +578,19 @@ namespace libtorrent assert(i >= 0); assert(i < (int)m_piece_map.size()); - int peer_count = m_piece_map[i].peer_count; + int prev_priority = m_piece_map[i].priority(m_sequenced_download_threshold); int index = m_piece_map[i].index; - assert(peer_count > 0); + assert(m_piece_map[i].peer_count > 0); if (m_piece_map[i].peer_count > 0) m_piece_map[i].peer_count--; piece_pos& p = m_piece_map[i]; - if (index == piece_pos::we_have_index || p.filtered) return; + if (index == piece_pos::we_have_index || p.filtered + || p.priority(m_sequenced_download_threshold) == prev_priority) return; - move(p.downloading, p.filtered, peer_count, index); + move(p.downloading, p.filtered, prev_priority, index); } // this is used to indicate that we succesfully have @@ -481,7 +603,7 @@ namespace libtorrent assert(index < (int)m_piece_map.size()); int info_index = m_piece_map[index].index; - int peer_count = m_piece_map[index].peer_count; + int priority = m_piece_map[index].priority(m_sequenced_download_threshold); assert(m_piece_map[index].downloading == 1); @@ -494,7 +616,7 @@ namespace libtorrent return; } if (info_index == piece_pos::we_have_index) return; - remove(p.downloading, p.filtered, peer_count, info_index); + remove(p.downloading, p.filtered, priority, info_index); p.index = piece_pos::we_have_index; #ifndef NDEBUG integrity_check(); @@ -517,7 +639,7 @@ namespace libtorrent if (p.index != piece_pos::we_have_index) { ++m_num_filtered; - remove(p.downloading, false, p.peer_count, p.index); + remove(p.downloading, false, p.priority(m_sequenced_download_threshold), p.index); assert(p.filtered == 1); } else @@ -582,7 +704,7 @@ namespace libtorrent void piece_picker::pick_pieces(const std::vector& pieces , std::vector& interesting_blocks , int num_blocks, bool prefer_whole_pieces - , address peer) const + , tcp::endpoint peer) const { assert(num_blocks > 0); assert(pieces.size() == m_piece_map.size()); @@ -599,9 +721,13 @@ namespace libtorrent assert(m_piece_info.begin() != m_piece_info.end()); // +1 is to ignore pieces that no peer has. The bucket with index 0 contains // pieces that 0 other peers has. - std::vector >::const_iterator free = m_piece_info.begin()+1; - assert(m_downloading_piece_info.begin() != m_downloading_piece_info.end()); - std::vector >::const_iterator partial = m_downloading_piece_info.begin()+1; + std::vector >::const_iterator free + = m_piece_info.begin() + 1; + assert(m_downloading_piece_info.begin() + != m_downloading_piece_info.end()); + + std::vector >::const_iterator partial + = m_downloading_piece_info.begin() + 1; std::vector backup_blocks; @@ -610,15 +736,18 @@ namespace libtorrent // has filled the interesting_blocks with num_blocks // blocks. - // it iterates over two ranges simultaneously. The pieces that are partially downloaded - // or partially requested, and the pieces that hasn't been requested at all. - // The default is to prioritize pieces that are partially requested/downloaded, so the - // loop will first look for blocks among those pieces. And it will also take two steps - // in that range when iterating. This has the effect that partial pieces doesn't have to - // be as rare as non-requested pieces in order to be prefered. - - // When prefer_whole_pieces is set (usually set when downloading from fast peers) the - // partial pieces will not be prioritized, but actually ignored as long as possible. + // it iterates over two ranges simultaneously. The pieces that are + // partially downloaded or partially requested, and the pieces that + // hasn't been requested at all. The default is to prioritize pieces + // that are partially requested/downloaded, so the loop will first + // look for blocks among those pieces. And it will also take two steps + // in that range when iterating. This has the effect that partial pieces + // doesn't have to be as rare as non-requested pieces in order to be + // prefered. + + // When prefer_whole_pieces is set (usually set when downloading from + // fast peers) the partial pieces will not be prioritized, but actually + // ignored as long as possible. while((free != m_piece_info.end()) || (partial != m_downloading_piece_info.end())) @@ -664,14 +793,14 @@ namespace libtorrent namespace { bool exclusively_requested_from(piece_picker::downloading_piece const& p - , int num_blocks_in_piece, address peer) + , int num_blocks_in_piece, tcp::endpoint peer) { for (int j = 0; j < num_blocks_in_piece; ++j) { if ((p.finished_blocks[j] == 1 || p.requested_blocks[j] == 1) && p.info[j].peer != peer - && p.info[j].peer != address()) + && p.info[j].peer != tcp::endpoint()) { return false; } @@ -715,7 +844,7 @@ namespace libtorrent , std::vector& interesting_blocks , std::vector& backup_blocks , int num_blocks, bool prefer_whole_pieces - , address peer) const + , tcp::endpoint peer) const { assert(num_blocks > 0); @@ -730,7 +859,7 @@ namespace libtorrent assert(m_piece_map[*i].downloading == 1); - // calculate the number of blocks in this + // calculate the number of blocks in this // piece. It's always m_blocks_per_piece, except // in the last piece. int num_blocks_in_piece = blocks_in_piece(*i); @@ -842,7 +971,7 @@ namespace libtorrent } - void piece_picker::mark_as_downloading(piece_block block, const address& peer) + void piece_picker::mark_as_downloading(piece_block block, const tcp::endpoint& peer) { #ifndef NDEBUG // integrity_check(); @@ -856,7 +985,7 @@ namespace libtorrent if (p.downloading == 0) { p.downloading = 1; - move(false, p.filtered, p.peer_count, p.index); + move(false, p.filtered, p.priority(m_sequenced_download_threshold), p.index); downloading_piece dp; dp.index = block.piece_index; @@ -878,7 +1007,7 @@ namespace libtorrent #endif } - void piece_picker::mark_as_finished(piece_block block, const address& peer) + void piece_picker::mark_as_finished(piece_block block, const tcp::endpoint& peer) { #ifndef NDEBUG // integrity_check(); @@ -894,7 +1023,7 @@ namespace libtorrent if (p.downloading == 0) { p.downloading = 1; - move(false, p.filtered, p.peer_count, p.index); + move(false, p.filtered, p.priority(m_sequenced_download_threshold), p.index); downloading_piece dp; dp.index = block.piece_index; @@ -945,7 +1074,7 @@ namespace libtorrent #endif } */ - void piece_picker::get_downloaders(std::vector
    & d, int index) const + void piece_picker::get_downloaders(std::vector& d, int index) const { assert(index >= 0 && index <= (int)m_piece_map.size()); std::vector::const_iterator i @@ -959,7 +1088,7 @@ namespace libtorrent } } - boost::optional
    piece_picker::get_downloader(piece_block block) const + boost::optional piece_picker::get_downloader(piece_block block) const { std::vector::const_iterator i = std::find_if( m_downloads.begin() @@ -967,16 +1096,16 @@ namespace libtorrent , has_index(block.piece_index)); if (i == m_downloads.end()) - return boost::optional
    (); + return boost::optional(); assert(block.block_index < max_blocks_per_piece); assert(block.block_index >= 0); if (i->requested_blocks[block.block_index] == false || i->finished_blocks[block.block_index] == true) - return boost::optional
    (); + return boost::optional(); - return boost::optional
    (i->info[block.block_index].peer); + return boost::optional(i->info[block.block_index].peer); } void piece_picker::abort_download(piece_block block) @@ -1003,7 +1132,12 @@ namespace libtorrent if (i->finished_blocks[block.block_index]) return; assert(block.block_index < blocks_in_piece(block.piece_index)); - assert(i->requested_blocks[block.block_index] == 1); +#ifndef NDEBUG + if (i->requested_blocks[block.block_index] != 1) + { + assert(false); + } +#endif // clear this block as being downloaded i->requested_blocks[block.block_index] = 0; @@ -1015,7 +1149,7 @@ namespace libtorrent m_downloads.erase(i); m_piece_map[block.piece_index].downloading = 0; piece_pos& p = m_piece_map[block.piece_index]; - move(true, p.filtered, p.peer_count, p.index); + move(true, p.filtered, p.priority(m_sequenced_download_threshold), p.index); } #ifndef NDEBUG // integrity_check(); diff --git a/src/policy.cpp b/src/policy.cpp index a75e7bea4..3e7b10c82 100755 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif +#include "libtorrent/web_peer_connection.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/torrent.hpp" #include "libtorrent/socket.hpp" @@ -72,26 +73,8 @@ namespace , peer_connection& c , std::vector ignore = std::vector()) { - // this will make the number of requests linearly dependent - // on the rate in which we download from the peer. - // we want the queue to represent: - // TODO: make this constant user-settable - const int queue_time = 3; // seconds - // (if the latency is more than this, the download will stall) - // so, the queue size is 5 * down_rate / 16 kiB (16 kB is the size of each request) - // the minimum request size is 2 and the maximum is 48 - // the block size doesn't have to be 16. So we first query the torrent for it - const int block_size = t.block_size(); - assert(block_size > 0); - - int desired_queue_size = static_cast(queue_time - * c.statistics().download_rate() / block_size); - if (desired_queue_size > max_request_queue) desired_queue_size = max_request_queue; - if (desired_queue_size < min_request_queue) desired_queue_size = min_request_queue; - - assert(desired_queue_size >= min_request_queue); - - int num_requests = desired_queue_size - (int)c.download_queue().size() + int num_requests = c.desired_queue_size() + - (int)c.download_queue().size() - (int)c.request_queue().size(); // if our request queue is already full, we @@ -117,7 +100,9 @@ namespace // the number of blocks we want, but it will try to make the picked // blocks be from whole pieces, possibly by returning more blocks // than we requested. - assert(c.remote() == c.get_socket()->sender()); +#ifndef NDEBUG + assert(c.remote() == c.get_socket()->remote_endpoint()); +#endif p.pick_pieces(c.get_bitfield(), interesting_pieces , num_requests, prefer_whole_pieces, c.remote()); @@ -141,7 +126,7 @@ namespace // ok, we found a piece that's not being downloaded // by somebody else. request it from this peer // and return - c.send_request(*i); + c.add_request(*i); num_requests--; } @@ -217,8 +202,8 @@ namespace assert(common_block != peer->download_queue().rend()); piece_block block = *common_block; - peer->send_cancel(block); - c.send_request(block); + peer->cancel_request(block); + c.add_request(block); // the one we interrupted may need to request a new piece // make sure it doesn't take over a block from the peer @@ -308,14 +293,14 @@ namespace struct match_peer_ip { - match_peer_ip(const address& id) - : m_id(id) + match_peer_ip(const tcp::endpoint& ip) + : m_ip(ip) {} bool operator()(const policy::peer& p) const - { return p.id.ip() == m_id.ip(); } + { return p.ip.address() == m_ip.address(); } - address m_id; + tcp::endpoint m_ip; }; struct match_peer_connection @@ -837,10 +822,18 @@ namespace libtorrent , m_peers.end() , match_peer_connection(c)); - assert(i != m_peers.end()); + if (i == m_peers.end()) + { + // this is probably a http seed + if (web_peer_connection const* p = dynamic_cast(&c)) + { + m_torrent->remove_url_seed(p->url()); + } + return; + } i->type = peer::not_connectable; - i->id.port = address::any_port; + i->ip.port(0); i->banned = true; } @@ -850,7 +843,7 @@ namespace libtorrent /* #ifndef NDEBUG // avoid the invariant check to fail - peer p(address("0.0.0.0", 0), peer::not_connectable); + peer p(tcp::endpoint("0.0.0.0", 0), peer::not_connectable); p.connection = &c; m_peers.push_back(p); #endif @@ -868,15 +861,17 @@ namespace libtorrent // TODO: only allow _one_ connection to use this // override at a time - assert(c.remote() == c.get_socket()->sender()); +#ifndef NDEBUG + assert(c.remote() == c.get_socket()->remote_endpoint()); +#endif if (m_torrent->num_peers() >= m_torrent->m_connections_quota.given - && c.remote().ip() != m_torrent->current_tracker().ip()) + && c.remote().address() != m_torrent->current_tracker().address()) { throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (c.remote().ip() == m_torrent->current_tracker().ip()) + if (c.remote().address() == m_torrent->current_tracker().address()) { m_torrent->debug_log("overriding connection limit for tracker NAT-check"); } @@ -894,8 +889,9 @@ namespace libtorrent // we don't have ny info about this peer. // add a new entry - - assert(c.remote() == c.get_socket()->sender()); +#ifndef NDEBUG + assert(c.remote() == c.get_socket()->remote_endpoint()); +#endif peer p(c.remote(), peer::not_connectable); m_peers.push_back(p); i = m_peers.end()-1; @@ -905,7 +901,7 @@ namespace libtorrent if (i->connection != 0) throw protocol_error("duplicate connection, closing"); if (i->banned) - throw protocol_error("ip address banned, closing"); + throw protocol_error("ip tcp::endpoint banned, closing"); } assert(i->connection == 0); @@ -918,12 +914,12 @@ namespace libtorrent m_last_optimistic_disconnect = second_clock::universal_time(); } - void policy::peer_from_tracker(const address& remote, const peer_id& id) + void policy::peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid) { INVARIANT_CHECK; // just ignore the obviously invalid entries from the tracker - if(remote.ip() == 0 || remote.port == 0) + if(remote.address().to_ulong() == 0 || remote.port() == 0) return; try @@ -956,7 +952,7 @@ namespace libtorrent // in case we got the ip from a remote connection, port is // not known, so save it. Client may also have changed port // for some reason. - i->id = remote; + i->ip = remote; if (i->connection) { @@ -965,11 +961,11 @@ namespace libtorrent // it again. #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - m_torrent->debug_log("already connected to peer: " + remote.as_string() + ":" - + boost::lexical_cast(remote.port)); + m_torrent->debug_log("already connected to peer: " + remote.address().to_string() + ":" + + boost::lexical_cast(remote.port())); #endif - assert(i->connection->associated_torrent() == m_torrent); + assert(i->connection->associated_torrent().lock().get() == m_torrent); return; } } @@ -990,20 +986,12 @@ namespace libtorrent } return; } - catch(network_error& e) + catch(std::exception& e) { if (m_torrent->alerts().should_post(alert::debug)) { m_torrent->alerts().post_alert( - peer_error_alert(remote, id, e.what())); - } - } - catch(protocol_error& e) - { - if (m_torrent->alerts().should_post(alert::debug)) - { - m_torrent->alerts().post_alert( - peer_error_alert(remote, id, e.what())); + peer_error_alert(remote, pid, e.what())); } } } @@ -1174,7 +1162,7 @@ namespace libtorrent try { assert(!p->connection); - p->connection = &m_torrent->connect_to_peer(p->id); + p->connection = &m_torrent->connect_to_peer(p->ip); assert(p->connection); p->connection->add_stat(p->prev_amount_download, p->prev_amount_upload); p->prev_amount_download = 0; @@ -1184,14 +1172,8 @@ namespace libtorrent second_clock::universal_time(); return true; } - catch (network_error& e) - { - // TODO: This path needs testing! -#if defined(TORRENT_VERBOSE_LOGGING) - std::string msg = e.what(); - (*p->connection->m_logger) << "*** CONNECTION FAILED '" << msg << "'\n"; -#endif - } + catch (std::exception& e) + {} return false; } @@ -1209,11 +1191,11 @@ namespace libtorrent } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c) + void policy::connection_closed(const peer_connection& c) try { INVARIANT_CHECK; - assert(c.is_disconnecting()); +// assert(c.is_disconnecting()); bool unchoked = false; std::vector::iterator i = std::find_if( @@ -1234,7 +1216,7 @@ namespace libtorrent if (c.failed()) { i->type = peer::not_connectable; - i->id.port = address::any_port; + i->ip.port(0); } // if the share ratio is 0 (infinite), the @@ -1242,7 +1224,7 @@ namespace libtorrent // because it isn't necessary. if (m_torrent->ratio() != 0.f) { - assert(i->connection->associated_torrent() == m_torrent); + assert(i->connection->associated_torrent().lock().get() == m_torrent); assert(i->connection->share_diff() < std::numeric_limits::max()); m_available_free_upload += i->connection->share_diff(); } @@ -1260,6 +1242,13 @@ namespace libtorrent else unchoke_one_peer(); } } + catch (std::exception& e) + { +#ifndef NDEBUG + std::string err = e.what(); +#endif + assert(false); + } void policy::peer_is_interesting(peer_connection& c) { @@ -1274,7 +1263,9 @@ namespace libtorrent bool policy::has_connection(const peer_connection* c) { assert(c); - assert(c->remote() == c->get_socket()->sender()); +#ifndef NDEBUG + assert(c->remote() == c->get_socket()->remote_endpoint()); +#endif return std::find_if( m_peers.begin() , m_peers.end() @@ -1309,6 +1300,9 @@ namespace libtorrent i != m_torrent->end(); ++i) { if (i->second->is_disconnecting()) continue; + // ignore web_peer_connections since they are not managed + // by the policy class + if (dynamic_cast(i->second)) continue; ++num_torrent_peers; } @@ -1332,8 +1326,8 @@ namespace libtorrent } #endif - policy::peer::peer(const address& pid, peer::connection_type t) - : id(pid) + policy::peer::peer(const tcp::endpoint& ip_, peer::connection_type t) + : ip(ip_) , type(t) , last_optimistically_unchoked( boost::gregorian::date(1970,boost::gregorian::Jan,1)) diff --git a/src/session.cpp b/src/session.cpp index 75a1f1b50..8fbe1cda9 100755 --- a/src/session.cpp +++ b/src/session.cpp @@ -67,10 +67,16 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/file.hpp" #include "libtorrent/allocate_resources.hpp" -#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" using namespace boost::posix_time; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::bind; +using boost::mutex; +using libtorrent::detail::session_impl; namespace libtorrent { namespace detail { @@ -139,10 +145,16 @@ namespace libtorrent { namespace detail if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) { - boost::mutex::scoped_lock l2(m_ses.m_mutex); + session_impl::mutex_t::scoped_lock l2(m_ses.m_mutex); m_ses.m_alerts.post_alert(fastresume_rejected_alert( t->torrent_ptr->get_handle() , error_msg)); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + (*m_ses.m_logger) << "fastresume data for " + << t->torrent_ptr->torrent_file().name() << " rejected: " + << error_msg << "\n"; +#endif } // clear the resume data now that it has been used @@ -153,8 +165,8 @@ namespace libtorrent { namespace detail if (up_to_date) { // lock the session to add the new torrent - boost::mutex::scoped_lock l(m_ses.m_mutex); - boost::mutex::scoped_lock l2(m_mutex); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); assert(m_torrents.front() == t); @@ -170,8 +182,8 @@ namespace libtorrent { namespace detail peer_id id; std::fill(id.begin(), id.end(), 0); - for (std::vector
    ::const_iterator i = t->peers.begin(); - i != t->peers.end(); ++i) + for (std::vector::const_iterator i = t->peers.begin(); + i != t->peers.end(); ++i) { t->torrent_ptr->get_policy().peer_from_tracker(*i, id); } @@ -181,7 +193,7 @@ namespace libtorrent { namespace detail // lock the checker while we move the torrent from // m_torrents to m_processing { - boost::mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l(m_mutex); assert(m_torrents.front() == t); m_torrents.pop_front(); @@ -194,11 +206,11 @@ namespace libtorrent { namespace detail } } } - catch(const std::exception& e) + catch (const std::exception& e) { // This will happen if the storage fails to initialize - boost::mutex::scoped_lock l(m_ses.m_mutex); - boost::mutex::scoped_lock l2(m_mutex); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); if (m_ses.m_alerts.should_post(alert::fatal)) { @@ -222,7 +234,7 @@ namespace libtorrent { namespace detail #ifndef NDEBUG std::cerr << "error while checking resume data\n"; #endif - boost::mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l(m_mutex); assert(!m_torrents.empty()); m_torrents.pop_front(); assert(false); @@ -239,7 +251,7 @@ namespace libtorrent { namespace detail boost::tie(finished, progress) = processing->torrent_ptr->check_files(); { - boost::mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l(m_mutex); processing->progress = progress; if (processing->abort) { @@ -266,8 +278,8 @@ namespace libtorrent { namespace detail if (finished) { // lock the session to add the new torrent - boost::mutex::scoped_lock l(m_ses.m_mutex); - boost::mutex::scoped_lock l2(m_mutex); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); assert(!m_processing.empty()); assert(m_processing.front() == processing); @@ -285,7 +297,7 @@ namespace libtorrent { namespace detail peer_id id; std::fill(id.begin(), id.end(), 0); - for (std::vector
    ::const_iterator i = processing->peers.begin(); + for (std::vector::const_iterator i = processing->peers.begin(); i != processing->peers.end(); ++i) { processing->torrent_ptr->get_policy().peer_from_tracker(*i, id); @@ -302,8 +314,8 @@ namespace libtorrent { namespace detail catch(const std::exception& e) { // This will happen if the storage fails to initialize - boost::mutex::scoped_lock l(m_ses.m_mutex); - boost::mutex::scoped_lock l2(m_mutex); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + mutex::scoped_lock l2(m_mutex); if (m_ses.m_alerts.should_post(alert::fatal)) { @@ -334,7 +346,7 @@ namespace libtorrent { namespace detail #ifndef NDEBUG std::cerr << "error while checking files\n"; #endif - boost::mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l(m_mutex); assert(!m_processing.empty()); processing.reset(); @@ -385,9 +397,9 @@ namespace libtorrent { namespace detail std::pair listen_port_range , const fingerprint& cl_fprint , const char* listen_interface = 0) - : m_tracker_manager(m_settings) + : m_tracker_manager(m_http_settings) , m_listen_port_range(listen_port_range) - , m_listen_interface(listen_interface, listen_port_range.first) + , m_listen_interface(listen_port_range.first) , m_abort(false) , m_upload_rate(-1) , m_download_rate(-1) @@ -395,7 +407,12 @@ namespace libtorrent { namespace detail , m_max_connections(-1) , m_half_open_limit(-1) , m_incoming_connection(false) + , m_last_tick(second_clock::universal_time()) + , m_timer(m_selector) { + if (listen_interface != 0) m_listen_interface.address( + address(listen_interface)); + #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) m_logger = create_log("main_session", false); using boost::posix_time::second_clock; @@ -403,7 +420,7 @@ namespace libtorrent { namespace detail (*m_logger) << to_simple_string(second_clock::universal_time()) << "\n"; #endif std::fill(m_extension_enabled, m_extension_enabled - + peer_connection::num_supported_extensions, true); + + num_supported_extensions, true); // ---- generate a peer id ---- std::srand((unsigned int)std::time(0)); @@ -429,87 +446,56 @@ namespace libtorrent { namespace detail *i = printable[rand() % (sizeof(printable)-1)]; } // this says that we support the extensions - std::memcpy(&m_peer_id[17], "ext", 3); + + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(bind(&session_impl::second_tick, this, _1)); } bool session_impl::extensions_enabled() const { - const int n = peer_connection::num_supported_extensions; + const int n = num_supported_extensions; return std::find(m_extension_enabled , m_extension_enabled + n, true) != m_extension_enabled + n; } - void session_impl::purge_connections() - { - while (!m_disconnect_peer.empty()) - { - boost::shared_ptr& p = m_disconnect_peer.back(); - assert(p->is_disconnecting()); - if (p->is_connecting()) - { - // Since this peer is still connecting, will not be - // in the list of completed connections. - connection_map::iterator i = m_half_open.find(p->get_socket()); - if (i == m_half_open.end()) - { - // this connection is not in the half-open list, so it - // has to be in the queue, waiting to be connected. - connection_queue::iterator j = std::find( - m_connection_queue.begin(), m_connection_queue.end(), p); - - assert(j != m_connection_queue.end()); - if (j != m_connection_queue.end()) m_connection_queue.erase(j); - } - else - { - m_half_open.erase(i); - process_connection_queue(); - } - } - else - { - connection_map::iterator i = m_connections.find(p->get_socket()); - assert(i != m_connections.end()); - if (i != m_connections.end()) m_connections.erase(i); - } - m_disconnect_peer.pop_back(); - } - } - void session_impl::open_listen_port() { try { // create listener socket - m_selector.remove(m_listen_socket); - m_listen_socket = boost::shared_ptr(new socket(socket::tcp, false)); + m_listen_socket = boost::shared_ptr(new socket_acceptor(m_selector)); for(;;) { try { - m_listen_socket->listen(m_listen_interface, 10); + m_listen_socket->open(asio::ipv4::tcp()); + m_listen_socket->bind(m_listen_interface); + m_listen_socket->listen(); break; } - catch (network_error& e) + catch (asio::error& e) { - if (e.error_code() == socket::address_not_available) + // TODO: make sure this is correct + if (e.code() == asio::error::host_not_found) { if (m_alerts.should_post(alert::fatal)) { - std::string msg = "cannot listen on the given interface '" + m_listen_interface.as_string() + "'"; + std::string msg = "cannot listen on the given interface '" + + m_listen_interface.address().to_string() + "'"; m_alerts.post_alert(listen_failed_alert(msg)); } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - std::string msg = "cannot listen on the given interface '" + m_listen_interface.as_string() + "'"; + std::string msg = "cannot listen on the given interface '" + + m_listen_interface.address().to_string() + "'"; (*m_logger) << msg << "\n"; #endif assert(m_listen_socket.unique()); m_listen_socket.reset(); break; } - m_listen_interface.port++; - if (m_listen_interface.port > m_listen_port_range.second) + m_listen_interface.port(m_listen_interface.port() + 1); + if (m_listen_interface.port() > m_listen_port_range.second) { std::stringstream msg; msg << "none of the ports in the range [" @@ -526,22 +512,22 @@ namespace libtorrent { namespace detail } } } - catch (network_error& e) + catch (asio::error& e) { - m_alerts.post_alert(listen_failed_alert(e.what())); + if (m_alerts.should_post(alert::fatal)) + { + m_alerts.post_alert(listen_failed_alert( + std::string("failed to open listen port") + e.what())); + } } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (m_listen_socket) { - (*m_logger) << "listening on port: " << m_listen_interface.port << "\n"; + (*m_logger) << "listening on port: " << m_listen_interface.port() << "\n"; } #endif - if (m_listen_socket) - { - m_selector.monitor_readability(m_listen_socket); - m_selector.monitor_errors(m_listen_socket); - } + if (m_listen_socket) async_accept(); } void session_impl::process_connection_queue() @@ -552,17 +538,96 @@ namespace libtorrent { namespace detail && m_half_open_limit > 0) return; - connection_queue::value_type& c = m_connection_queue.front(); - m_half_open.insert(std::make_pair(c->get_socket(), c)); - assert(c->associated_torrent()); - c->connect(); - m_connection_queue.pop_front(); + connection_queue::value_type c = m_connection_queue.front(); + + try + { + m_connection_queue.pop_front(); + assert(c->associated_torrent().lock().get()); + c->connect(); + m_half_open.insert(std::make_pair(c->get_socket(), c)); + } + catch (std::exception& e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "connect failed [" << c->remote() << "]: " + << e.what() << "\n"; +#endif + } } } - void session_impl::connection_failed(boost::shared_ptr const& s - , address const& a, char const* message) + void session_impl::async_accept() { + shared_ptr c(new stream_socket(m_selector)); + m_listen_socket->async_accept(*c + , bind(&session_impl::on_incoming_connection, this, c + , weak_ptr(m_listen_socket), _1)); + } + + void session_impl::on_incoming_connection(shared_ptr const& s + , weak_ptr const& listen_socket, asio::error const& e) try + { + if (listen_socket.expired()) + return; + + if (e == asio::error::operation_aborted) + return; + + mutex_t::scoped_lock l(m_mutex); + assert(listen_socket.lock() == m_listen_socket); + + async_accept(); + if (e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string msg = "error accepting connection on '" + + m_listen_interface.address().to_string() + "'"; + (*m_logger) << msg << "\n"; +#endif + assert(m_listen_socket.unique()); + return; + } + + // we got a connection request! + m_incoming_connection = true; + tcp::endpoint endp = s->remote_endpoint(); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << endp << " <== INCOMING CONNECTION\n"; +#endif + if (m_ip_filter.access(endp.address()) & ip_filter::blocked) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "filtered blocked ip\n"; +#endif + // TODO: issue an info-alert when an ip is blocked!! + return; + } + + boost::intrusive_ptr c( + new bt_peer_connection(*this, s)); +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + + m_connections.insert(std::make_pair(s, c)); + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::string err = exc.what(); +#endif + } + + void session_impl::connection_failed(boost::shared_ptr const& s + , tcp::endpoint const& a, char const* message) +#ifndef NDEBUG + try +#endif + { + mutex_t::scoped_lock l(m_mutex); + connection_map::iterator p = m_connections.find(s); // the connection may have been disconnected in the receive or send phase @@ -573,29 +638,15 @@ namespace libtorrent { namespace detail m_alerts.post_alert( peer_error_alert( a - , p->second->id() + , p->second->pid() , message)); } #if defined(TORRENT_VERBOSE_LOGGING) - (*p->second->m_logger) << "*** CONNECTION EXCEPTION\n"; + (*p->second->m_logger) << "*** CONNECTION FAILED " << message << "\n"; #endif p->second->set_failed(); - m_connections.erase(p); - } - else if (s == m_listen_socket) - { - if (m_alerts.should_post(alert::fatal)) - { - std::string msg = "cannot listen on the given interface '" + m_listen_interface.as_string() + "'"; - m_alerts.post_alert(listen_failed_alert(msg)); - } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - std::string msg = "cannot listen on the given interface '" + m_listen_interface.as_string() + "'"; - (*m_logger) << msg << "\n"; -#endif - assert(m_listen_socket.unique()); - m_listen_socket.reset(); + p->second->disconnect(); } else { @@ -609,456 +660,269 @@ namespace libtorrent { namespace detail m_alerts.post_alert( peer_error_alert( a - , p->second->id() + , p->second->pid() , message)); } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << "FAILED: " << a.as_string() << "\n"; + (*m_logger) << "CLOSED: " << a.address().to_string() + << " " << message << "\n"; #endif p->second->set_failed(); - m_half_open.erase(p); - process_connection_queue(); + p->second->disconnect(); } } } +#ifndef NDEBUG + catch (...) + { + assert(false); + }; +#endif + + void session_impl::close_connection(boost::intrusive_ptr const& p) + { + mutex_t::scoped_lock l(m_mutex); + + assert(p->is_disconnecting()); + + if (p->is_connecting()) + { + assert(p->is_local()); + // Since this peer is still connecting, will not be + // in the list of completed connections. + connection_map::iterator i = m_half_open.find(p->get_socket()); + if (i == m_half_open.end()) + { + // this connection is not in the half-open list, so it + // has to be in the queue, waiting to be connected. + connection_queue::iterator j = std::find( + m_connection_queue.begin(), m_connection_queue.end(), p); + + if (j != m_connection_queue.end()) + m_connection_queue.erase(j); + } + else + { + m_half_open.erase(i); + process_connection_queue(); + } + } + else + { + connection_map::iterator i = m_connections.find(p->get_socket()); + if (i != m_connections.end()) + m_connections.erase(i); + } + } + + void session_impl::second_tick(asio::error const& e) try + { + if (e) + { +#if defined(TORRENT_LOGGING) + (*m_logger) << "*** SECOND TIMER FAILED " << e.what() << "\n"; +#endif + m_abort = true; + m_selector.interrupt(); + return; + } + + session_impl::mutex_t::scoped_lock l(m_mutex); + + if (m_abort) return; + float tick_interval = (second_clock::universal_time() + - m_last_tick).total_milliseconds() / 1000.f; + m_last_tick = second_clock::universal_time(); + + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(bind(&session_impl::second_tick, this, _1)); + + // do the second_tick() on each connection + // this will update their statistics (download and upload speeds) + // also purge sockets that have timed out + // and keep sockets open by keeping them alive. + for (connection_map::iterator i = m_connections.begin(); + i != m_connections.end();) + { + // we need to do like this because j->second->disconnect() will + // erase the connection from the map we're iterating + connection_map::iterator j = i; + ++i; + // if this socket has timed out + // close it. + if (j->second->has_timed_out()) + { + if (m_alerts.should_post(alert::debug)) + { + m_alerts.post_alert( + peer_error_alert( + j->second->remote() + , j->second->pid() + , "connection timed out")); + } +#if defined(TORRENT_VERBOSE_LOGGING) + (*j->second->m_logger) << "*** CONNECTION TIMED OUT\n"; +#endif + + j->second->set_failed(); + j->second->disconnect(); + continue; + } + + j->second->keep_alive(); + } + + // check each torrent for tracker updates + // TODO: do this in a timer-event in each torrent instead + for (std::map >::iterator i + = m_torrents.begin(); i != m_torrents.end();) + { + torrent& t = *i->second; + assert(!t.is_aborted()); + if (t.should_request()) + { + tracker_request req = t.generate_tracker_request(); + req.listen_port = m_listen_interface.port(); + req.key = m_key; + m_tracker_manager.queue_request(m_selector, req, t.tracker_login() + , i->second); + + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert( + tracker_announce_alert( + t.get_handle(), "tracker announce")); + } + } + + // tick() will set the used upload quota + t.second_tick(m_stat, tick_interval); + ++i; + } + + // don't pass in the tick_interval here, because + // the stats have already been adjusted in + // the peer's second tick. + m_stat.second_tick(1.f); + + // distribute the maximum upload rate among the torrents + + allocate_resources(m_upload_rate == -1 + ? std::numeric_limits::max() + : int(m_upload_rate * tick_interval) + , m_torrents + , &torrent::m_ul_bandwidth_quota); + + allocate_resources(m_download_rate == -1 + ? std::numeric_limits::max() + : int(m_download_rate * tick_interval) + , m_torrents + , &torrent::m_dl_bandwidth_quota); + + allocate_resources(m_max_uploads == -1 + ? std::numeric_limits::max() + : m_max_uploads + , m_torrents + , &torrent::m_uploads_quota); + + allocate_resources(m_max_connections == -1 + ? std::numeric_limits::max() + : m_max_connections + , m_torrents + , &torrent::m_connections_quota); + + for (std::map >::iterator i + = m_torrents.begin(); i != m_torrents.end(); ++i) + { + i->second->distribute_resources(); + } + } + catch (std::exception& exc) + { +#ifndef NDEBUG + std::string err = exc.what(); +#endif + }; // msvc 7.1 seems to require this + + void session_impl::connection_completed( + boost::intrusive_ptr const& p) +#ifndef NDEBUG + try +#endif + { + mutex_t::scoped_lock l(m_mutex); + + if (m_abort) return; + + connection_map::iterator i = m_half_open.find(p->get_socket()); + + m_connections.insert(std::make_pair(p->get_socket(), p)); + if (i != m_half_open.end()) m_half_open.erase(i); + process_connection_queue(); + } +#ifndef NDEBUG + catch (std::exception& e) + { + assert(false); + }; +#endif - void session_impl::operator()() { eh_initializer(); if (m_listen_port_range.first != 0 && m_listen_port_range.second != 0) { - boost::mutex::scoped_lock l(m_mutex); + session_impl::mutex_t::scoped_lock l(m_mutex); open_listen_port(); } - std::vector > readable_clients; - std::vector > writable_clients; - std::vector > error_clients; boost::posix_time::ptime timer = second_clock::universal_time(); - for(;;) - { + // for(;;) + // { try { - -#ifndef NDEBUG - { - boost::mutex::scoped_lock l(m_mutex); - check_invariant("before SELECT"); - } -#endif - - - // if nothing happens within 500000 microseconds (0.5 seconds) - // do the loop anyway to check if anything else has changed - m_selector.wait(500000, readable_clients, writable_clients, error_clients); - -#ifndef NDEBUG - { - boost::mutex::scoped_lock l(m_mutex); - check_invariant("after SELECT"); - } - - for (std::vector >::iterator i = - writable_clients.begin(); i != writable_clients.end(); ++i) - { - assert((*i)->is_writable()); - } - - for (std::vector >::iterator i = - readable_clients.begin(); i != readable_clients.end(); ++i) - { - assert((*i)->is_readable()); - } -#endif - boost::mutex::scoped_lock l(m_mutex); -#ifndef NDEBUG - check_invariant("before abort"); -#endif - purge_connections(); - - if (m_abort) - { - m_tracker_manager.abort_all_requests(); - for (std::map >::iterator i = - m_torrents.begin(); i != m_torrents.end(); ++i) - { - i->second->abort(); - tracker_request req = i->second->generate_tracker_request(); - req.listen_port = m_listen_interface.port; - req.key = m_key; - m_tracker_manager.queue_request(req, i->second->tracker_login()); - } - m_connections.clear(); - m_torrents.clear(); - break; - } - -#ifndef NDEBUG - check_invariant("before SEND SOCKETS"); -#endif - - // ************************ - // SEND SOCKETS - // ************************ - - // let the writable connections send data - for (std::vector >::iterator i - = writable_clients.begin(); i != writable_clients.end(); ++i) - { - assert((*i)->is_writable()); - connection_map::iterator p = m_connections.find(*i); - // the connection may have been disconnected in the receive phase - if (p == m_connections.end()) - { - // if we didn't find the socket among the - // connected connections, look among the - // half-open connections to see if some of - // them have finished. - p = m_half_open.find(*i); - - if (p == m_half_open.end()) - { - m_selector.remove(*i); - } - else - { - p->second->connection_complete(); - assert(!p->second->is_connecting()); - m_connections.insert(*p); - m_half_open.erase(p); - process_connection_queue(); - } - } - else - { - try - { - assert(m_selector.is_writability_monitored(p->first)); - assert(p->second->can_write()); - assert(p->second->get_socket()->is_writable()); - p->second->send_data(); - } - catch (file_error& e) - { - torrent* t = p->second->associated_torrent(); - assert(t != 0); - - if (m_alerts.should_post(alert::fatal)) - { - m_alerts.post_alert( - file_error_alert( - t->get_handle() - , e.what())); - } - - // pause the torrent - t->pause(); - } - catch (std::exception& e) - { - // the connection wants to disconnect for some reason, - // remove it from the connection-list - if (m_alerts.should_post(alert::debug)) - { - m_alerts.post_alert( - peer_error_alert( - p->first->sender() - , p->second->id() - , e.what())); - } - -#if defined(TORRENT_VERBOSE_LOGGING) - (*p->second->m_logger) << "*** CLOSING CONNECTION '" << e.what() << "'\n"; -#endif - - p->second->set_failed(); -// m_selector.remove(*i); - m_connections.erase(p); - } - } - } - purge_connections(); - -#ifndef NDEBUG - check_invariant("after SEND SOCKETS"); -#endif - // ************************ - // RECEIVE SOCKETS - // ************************ - - // let the readable clients receive data - for (std::vector >::iterator i = readable_clients.begin(); - i != readable_clients.end(); ++i) - { - // special case for m_listen_socket socket - if (*i == m_listen_socket) - { - assert(m_listen_socket); - boost::shared_ptr s; - try - { - s = (*i)->accept(); - } - catch(std::exception& e) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << "accept failed: " << e.what() << "\n"; -#endif - } - if (s) - { - s->set_blocking(false); - // we got a connection request! - m_incoming_connection = true; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << s->sender().as_string() << " <== INCOMING CONNECTION\n"; -#endif - if (m_ip_filter.access(s->sender()) & ip_filter::blocked) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << "filtered blocked ip\n"; -#endif - // TODO: issue an info-alert when an ip is blocked - continue; - } - - boost::shared_ptr c( - new peer_connection(*this, m_selector, s)); - - assert(!c->is_connecting()); - m_connections.insert(std::make_pair(s, c)); - m_selector.monitor_errors(s); - } - continue; - } - connection_map::iterator p = m_connections.find(*i); - if(p == m_connections.end()) - { - assert(m_half_open.find(*i) == m_half_open.end()); - m_selector.remove(*i); - } - else - { - try - { - p->second->receive_data(); - } - catch (file_error& e) - { - torrent* t = p->second->associated_torrent(); - assert(t != 0); - - if (m_alerts.should_post(alert::fatal)) - { - m_alerts.post_alert( - file_error_alert( - t->get_handle() - , e.what())); - } - - t->pause(); - } - catch (std::exception& e) - { - if (m_alerts.should_post(alert::debug)) - { - m_alerts.post_alert( - peer_error_alert( - p->first->sender() - , p->second->id() - , e.what())); - } - -#if defined(TORRENT_VERBOSE_LOGGING) - (*p->second->m_logger) << "*** CLOSING CONNECTION '" << e.what() << "'\n"; -#endif - - // the connection wants to disconnect for some reason, remove it - // from the connection-list - p->second->set_failed(); - m_connections.erase(p); - } - } - } - purge_connections(); -#ifndef NDEBUG - check_invariant("after RECEIVE SOCKETS"); -#endif - - // ************************ - // ERROR SOCKETS - // ************************ - - - // disconnect the one we couldn't connect to - for (std::vector >::iterator i = error_clients.begin(); - i != error_clients.end(); ++i) - { - connection_failed(*i, (*i)->sender(), "connection exception"); - } - -#ifndef NDEBUG - check_invariant("after ERROR SOCKETS"); -#endif - - boost::posix_time::time_duration d = second_clock::universal_time() - timer; - if (d.seconds() < 1) continue; - timer = second_clock::universal_time(); - - // ************************ - // THE SECTION BELOW IS EXECUTED ONCE EVERY SECOND - // ************************ - - // do the second_tick() on each connection - // this will update their statistics (download and upload speeds) - // also purge sockets that have timed out - // and keep sockets open by keeping them alive. - for (connection_map::iterator i = m_connections.begin(); - i != m_connections.end();) - { - connection_map::iterator j = i; - ++i; - // if this socket has timed out - // close it. - if (j->second->has_timed_out()) - { - if (m_alerts.should_post(alert::debug)) - { - m_alerts.post_alert( - peer_error_alert( - j->first->sender() - , j->second->id() - , "connection timed out")); - } -#if defined(TORRENT_VERBOSE_LOGGING) - (*j->second->m_logger) << "*** CONNECTION TIMED OUT\n"; -#endif - - j->second->set_failed(); - m_connections.erase(j); - continue; - } - - j->second->keep_alive(); - } - - // check each torrent for abortion or - // tracker updates - for (std::map >::iterator i - = m_torrents.begin(); i != m_torrents.end();) - { - torrent& t = *i->second; - if (t.is_aborted()) - { - tracker_request req = t.generate_tracker_request(); - assert(req.event == tracker_request::stopped); - req.listen_port = m_listen_interface.port; - req.key = m_key; - m_tracker_manager.queue_request(req, t.tracker_login()); - - if (m_alerts.should_post(alert::info)) - { - m_alerts.post_alert( - tracker_announce_alert( - t.get_handle(), "tracker announce, event=stopped")); - } - -#ifndef NDEBUG - sha1_hash i_hash = t.torrent_file().info_hash(); -#endif - m_torrents.erase(i++); - assert(m_torrents.find(i_hash) == m_torrents.end()); - continue; - } - else if (t.should_request()) - { - tracker_request req = t.generate_tracker_request(); - req.listen_port = m_listen_interface.port; - req.key = m_key; - m_tracker_manager.queue_request(req, t.tracker_login(), i->second); - - if (m_alerts.should_post(alert::info)) - { - m_alerts.post_alert( - tracker_announce_alert( - t.get_handle(), "tracker announce")); - } - } - - // tick() will set the used upload quota - t.second_tick(m_stat); - ++i; - } - purge_connections(); - - m_stat.second_tick(); - - // distribute the maximum upload rate among the torrents - - allocate_resources(m_upload_rate == -1 - ? std::numeric_limits::max() - : m_upload_rate - , m_torrents - , &torrent::m_ul_bandwidth_quota); - - allocate_resources(m_download_rate == -1 - ? std::numeric_limits::max() - : m_download_rate - , m_torrents - , &torrent::m_dl_bandwidth_quota); - - allocate_resources(m_max_uploads == -1 - ? std::numeric_limits::max() - : m_max_uploads - , m_torrents - , &torrent::m_uploads_quota); - - allocate_resources(m_max_connections == -1 - ? std::numeric_limits::max() - : m_max_connections - , m_torrents - , &torrent::m_connections_quota); - - for (std::map >::iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) - { - i->second->distribute_resources(); - } - - m_tracker_manager.tick(); - - } - catch (std::bad_cast& e) - { - std::cerr << e.what() << "\n"; - assert(false); - } - catch (std::exception& e) - { - std::cerr << e.what() << "\n"; - assert(false); - } - catch (...) - { - std::cerr << "error!\n"; - assert(false); - } + m_selector.run(); + assert(m_abort == true); } - assert(m_torrents.empty()); - assert(m_connections.empty()); - - while (!m_tracker_manager.send_finished()) + catch (std::exception& e) { - m_tracker_manager.tick(); - boost::xtime t; - boost::xtime_get(&t, boost::TIME_UTC); - t.nsec += 100000000; - boost::thread::sleep(t); + std::cerr << e.what() << "\n"; + #ifndef NDEBUG + std::string err = e.what(); + #endif + assert(false); } + { + session_impl::mutex_t::scoped_lock l(m_mutex); + + m_tracker_manager.abort_all_requests(); + for (std::map >::iterator i = + m_torrents.begin(); i != m_torrents.end(); ++i) + { + i->second->abort(); + if (!i->second->is_paused() || i->second->should_request()) + { + tracker_request req = i->second->generate_tracker_request(); + req.listen_port = m_listen_interface.port(); + req.key = m_key; + std::string login = i->second->tracker_login(); + m_tracker_manager.queue_request(m_selector, req, login); + } + } + m_timer.expires_from_now(boost::posix_time::seconds( + m_http_settings.stop_tracker_timeout)); + m_timer.async_wait(bind(&demuxer::interrupt, &m_selector)); + } + + m_selector.reset(); + m_selector.run(); + + m_torrents.clear(); + m_connections.clear(); + assert(m_torrents.empty()); assert(m_connections.empty()); } @@ -1066,7 +930,7 @@ namespace libtorrent { namespace detail // the return value from this function is valid only as long as the // session is locked! - torrent* session_impl::find_torrent(const sha1_hash& info_hash) + boost::weak_ptr session_impl::find_torrent(sha1_hash const& info_hash) { std::map >::iterator i = m_torrents.find(info_hash); @@ -1078,8 +942,8 @@ namespace libtorrent { namespace detail assert(p); } #endif - if (i != m_torrents.end()) return boost::get_pointer(i->second); - return 0; + if (i != m_torrents.end()) return i->second; + return boost::weak_ptr(); } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1099,7 +963,7 @@ namespace libtorrent { namespace detail i != m_half_open.end(); ++i) { assert(i->second->is_connecting()); - assert(m_selector.is_writability_monitored(i->second->get_socket())); +// assert(m_selector.is_writability_monitored(i->second->get_socket())); } for (connection_map::iterator i = m_connections.begin(); @@ -1108,28 +972,30 @@ namespace libtorrent { namespace detail assert(i->second); assert(!i->second->is_connecting()); if (i->second->is_connecting() - || i->second->can_write() != m_selector.is_writability_monitored(i->first) - || i->second->can_read() != m_selector.is_readability_monitored(i->first)) +/* || i->second->can_write() != m_selector.is_writability_monitored(i->first) + || i->second->can_read() != m_selector.is_readability_monitored(i->first)*/) { std::ofstream error_log("error.log", std::ios_base::app); - boost::shared_ptr p = i->second; - error_log << "selector::is_writability_monitored() " << m_selector.is_writability_monitored(i->first) << "\n"; - error_log << "selector::is_readability_monitored() " << m_selector.is_readability_monitored(i->first) << "\n"; + boost::intrusive_ptr p = i->second; +// error_log << "selector::is_writability_monitored() " << m_selector.is_writability_monitored(i->first) << "\n"; +// error_log << "selector::is_readability_monitored() " << m_selector.is_readability_monitored(i->first) << "\n"; error_log << "peer_connection::is_connecting() " << p->is_connecting() << "\n"; error_log << "peer_connection::can_write() " << p->can_write() << "\n"; error_log << "peer_connection::can_read() " << p->can_read() << "\n"; error_log << "peer_connection::ul_quota_left " << p->m_ul_bandwidth_quota.left() << "\n"; error_log << "peer_connection::dl_quota_left " << p->m_dl_bandwidth_quota.left() << "\n"; error_log << "peer_connection::m_ul_bandwidth_quota.given " << p->m_ul_bandwidth_quota.given << "\n"; - error_log << "peer_connection::get_peer_id " << p->get_peer_id() << "\n"; + error_log << "peer_connection::get_peer_id " << p->pid() << "\n"; error_log << "place: " << place << "\n"; error_log.flush(); assert(false); } - if (i->second->associated_torrent()) + + boost::shared_ptr t = i->second->associated_torrent().lock(); + + if (t) { - assert(i->second->associated_torrent() - ->get_policy().has_connection(boost::get_pointer(i->second))); + assert(t->get_policy().has_connection(boost::get_pointer(i->second))); } } } @@ -1151,7 +1017,8 @@ namespace libtorrent { // turn off the filename checking in boost.filesystem using namespace boost::filesystem; - path::default_name_check(native); + if (path::default_name_check_writable()) + path::default_name_check(no_check); assert(listen_port_range.first > 0); assert(listen_port_range.first < listen_port_range.second); #ifndef NDEBUG @@ -1177,9 +1044,9 @@ namespace libtorrent void session::disable_extensions() { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); std::fill(m_impl.m_extension_enabled, m_impl.m_extension_enabled - + peer_connection::num_supported_extensions, false); + + num_supported_extensions, false); static char const printable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*'()"; @@ -1194,7 +1061,7 @@ namespace libtorrent void session::set_ip_filter(ip_filter const& f) { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_ip_filter = f; // Close connections whose endpoint is filtered @@ -1202,13 +1069,16 @@ namespace libtorrent for (detail::session_impl::connection_map::iterator i = m_impl.m_connections.begin(); i != m_impl.m_connections.end();) { - if (m_impl.m_ip_filter.access(i->first->sender()) + tcp::endpoint sender = i->first->remote_endpoint(); + if (m_impl.m_ip_filter.access(sender.address()) & ip_filter::blocked) { #if defined(TORRENT_VERBOSE_LOGGING) (*i->second->m_logger) << "*** CONNECTION FILTERED'\n"; #endif - m_impl.m_connections.erase(i++); + detail::session_impl::connection_map::iterator j = i; + ++i; + j->second->disconnect(); } else ++i; } @@ -1216,31 +1086,31 @@ namespace libtorrent void session::set_peer_id(peer_id const& id) { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_peer_id = id; } void session::set_key(int key) { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_key = key; } - void session::enable_extension(peer_connection::extension_index i) + void session::enable_extension(extension_index i) { assert(i >= 0); - assert(i < peer_connection::num_supported_extensions); - boost::mutex::scoped_lock l(m_impl.m_mutex); + assert(i < num_supported_extensions); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_extension_enabled[i] = true; - // this says that we support the extensions - std::memcpy(&m_impl.m_peer_id[17], "ext", 3); +// // this says that we support the extensions +// std::memcpy(&m_impl.m_peer_id[17], "ext", 3); } std::vector session::get_torrents() { - boost::mutex::scoped_lock l(m_impl.m_mutex); - boost::mutex::scoped_lock l2(m_checker_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); + mutex::scoped_lock l2(m_checker_impl.m_mutex); std::vector ret; for (std::deque >::iterator i = m_checker_impl.m_torrents.begin() @@ -1262,8 +1132,6 @@ namespace libtorrent return ret; } - // TODO: add a check to see if filenames are accepted on the - // current platform. // if the torrent already exists, this will throw duplicate_torrent torrent_handle session::add_torrent( torrent_info const& ti @@ -1284,21 +1152,20 @@ namespace libtorrent } #endif - assert(!save_path.empty()); if (ti.begin_files() == ti.end_files()) throw std::runtime_error("no files in torrent"); // lock the session and the checker thread (the order is important!) - boost::mutex::scoped_lock l(m_impl.m_mutex); - boost::mutex::scoped_lock l2(m_checker_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); + mutex::scoped_lock l2(m_checker_impl.m_mutex); if (m_impl.m_abort) throw std::runtime_error("session is closing"); // is the torrent already active? - if (m_impl.find_torrent(ti.info_hash())) + if (!m_impl.find_torrent(ti.info_hash()).expired()) throw duplicate_torrent(); // is the torrent currently being checked? @@ -1310,7 +1177,8 @@ namespace libtorrent // the thread boost::shared_ptr torrent_ptr( new torrent(m_impl, m_checker_impl, ti, save_path - , m_impl.m_listen_interface, compact_mode, block_size)); + , m_impl.m_listen_interface, compact_mode, block_size + , m_impl.m_settings)); boost::shared_ptr d( new detail::piece_checker_data); @@ -1352,7 +1220,7 @@ namespace libtorrent assert(!save_path.empty()); { // lock the checker_thread - boost::mutex::scoped_lock l(m_checker_impl.m_mutex); + mutex::scoped_lock l(m_checker_impl.m_mutex); // is the torrent currently being checked? if (m_checker_impl.find_torrent(info_hash)) @@ -1360,14 +1228,14 @@ namespace libtorrent } // lock the session - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); // the metadata extension has to be enabled for this to work assert(m_impl.m_extension_enabled - [peer_connection::extended_metadata_message]); + [extended_metadata_message]); // is the torrent already active? - if (m_impl.find_torrent(info_hash)) + if (!m_impl.find_torrent(info_hash).expired()) throw duplicate_torrent(); // create the torrent and the data associated with @@ -1375,7 +1243,8 @@ namespace libtorrent // the thread boost::shared_ptr torrent_ptr( new torrent(m_impl, m_checker_impl, tracker_url, info_hash, save_path - , m_impl.m_listen_interface, compact_mode, block_size)); + , m_impl.m_listen_interface, compact_mode, block_size + , m_impl.m_settings)); m_impl.m_torrents.insert( std::make_pair(info_hash, torrent_ptr)).first; @@ -1390,18 +1259,42 @@ namespace libtorrent assert(h.m_ses != 0); { - boost::mutex::scoped_lock l(m_impl.m_mutex); - torrent* t = m_impl.find_torrent(h.m_info_hash); - if (t != 0) + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); + detail::session_impl::torrent_map::iterator i = + m_impl.m_torrents.find(h.m_info_hash); + if (i != m_impl.m_torrents.end()) { - t->abort(); + torrent& t = *i->second; + t.abort(); + + if (!t.is_paused() || t.should_request()) + { + tracker_request req = t.generate_tracker_request(); + assert(req.event == tracker_request::stopped); + req.listen_port = m_impl.m_listen_interface.port(); + req.key = m_impl.m_key; + m_impl.m_tracker_manager.queue_request(m_impl.m_selector, req + , t.tracker_login()); + + if (m_impl.m_alerts.should_post(alert::info)) + { + m_impl.m_alerts.post_alert( + tracker_announce_alert( + t.get_handle(), "tracker announce, event=stopped")); + } + } +#ifndef NDEBUG + sha1_hash i_hash = t.torrent_file().info_hash(); +#endif + m_impl.m_torrents.erase(i); + assert(m_impl.m_torrents.find(i_hash) == m_impl.m_torrents.end()); return; } } if (h.m_chk) { - boost::mutex::scoped_lock l(m_checker_impl.m_mutex); + mutex::scoped_lock l(m_checker_impl.m_mutex); detail::piece_checker_data* d = m_checker_impl.find_torrent(h.m_info_hash); if (d != 0) @@ -1417,26 +1310,27 @@ namespace libtorrent std::pair const& port_range , const char* net_interface) { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); if (m_impl.m_listen_socket) - { - m_impl.m_selector.remove(m_impl.m_listen_socket); m_impl.m_listen_socket.reset(); - } m_impl.m_incoming_connection = false; m_impl.m_listen_port_range = port_range; - m_impl.m_listen_interface = address(net_interface, port_range.first); + if (net_interface && std::strlen(net_interface) > 0) + m_impl.m_listen_interface = tcp::endpoint(port_range.first, net_interface); + else + m_impl.m_listen_interface = tcp::endpoint(port_range.first); + m_impl.open_listen_port(); return m_impl.m_listen_socket; } unsigned short session::listen_port() const { - boost::mutex::scoped_lock l(m_impl.m_mutex); - return m_impl.m_listen_interface.port; + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); + return m_impl.m_listen_interface.port(); } session_status session::status() const @@ -1465,26 +1359,27 @@ namespace libtorrent bool session::is_listening() const { - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); return m_impl.m_listen_socket; } void session::set_http_settings(const http_settings& s) { - boost::mutex::scoped_lock l(m_impl.m_mutex); - m_impl.m_settings = s; + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); + m_impl.m_http_settings = s; } session::~session() { { // lock the main thread and abort it - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_abort = true; + m_impl.m_selector.interrupt(); } { - boost::mutex::scoped_lock l(m_checker_impl.m_mutex); + mutex::scoped_lock l(m_checker_impl.m_mutex); // abort the checker thread m_checker_impl.m_abort = true; @@ -1503,35 +1398,35 @@ namespace libtorrent void session::set_max_uploads(int limit) { assert(limit > 0 || limit == -1); - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_max_uploads = limit; } void session::set_max_connections(int limit) { assert(limit > 0 || limit == -1); - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_max_connections = limit; } void session::set_max_half_open_connections(int limit) { assert(limit > 0 || limit == -1); - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_half_open_limit = limit; } void session::set_upload_rate_limit(int bytes_per_second) { assert(bytes_per_second > 0 || bytes_per_second == -1); - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_upload_rate = bytes_per_second; } void session::set_download_rate_limit(int bytes_per_second) { assert(bytes_per_second > 0 || bytes_per_second == -1); - boost::mutex::scoped_lock l(m_impl.m_mutex); + session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); m_impl.m_download_rate = bytes_per_second; } @@ -1588,14 +1483,14 @@ namespace libtorrent { entry::list_type& peer_list = rd["peers"].list(); - std::vector
    tmp_peers; + std::vector tmp_peers; tmp_peers.reserve(peer_list.size()); for (entry::list_type::iterator i = peer_list.begin(); i != peer_list.end(); ++i) { - address a( - (*i)["ip"].string().c_str() - , (unsigned short)(*i)["port"].integer()); + tcp::endpoint a( + (unsigned short)(*i)["port"].integer() + , (*i)["ip"].string().c_str()); tmp_peers.push_back(a); } diff --git a/src/stat.cpp b/src/stat.cpp index e1c28d419..c36bd3725 100755 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -47,7 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -void libtorrent::stat::second_tick() +void libtorrent::stat::second_tick(float tick_interval) { INVARIANT_CHECK; @@ -59,10 +59,12 @@ void libtorrent::stat::second_tick() m_upload_payload_rate_history[i + 1] = m_upload_payload_rate_history[i]; } - m_download_rate_history[0] = m_downloaded_payload + m_downloaded_protocol; - m_upload_rate_history[0] = m_uploaded_payload + m_uploaded_protocol; - m_download_payload_rate_history[0] = m_downloaded_payload; - m_upload_payload_rate_history[0] = m_uploaded_payload; + m_download_rate_history[0] = (m_downloaded_payload + m_downloaded_protocol) + / tick_interval; + m_upload_rate_history[0] = (m_uploaded_payload + m_uploaded_protocol) + / tick_interval; + m_download_payload_rate_history[0] = m_downloaded_payload / tick_interval; + m_upload_payload_rate_history[0] = m_uploaded_payload / tick_interval; m_downloaded_payload = 0; m_uploaded_payload = 0; diff --git a/src/storage.cpp b/src/storage.cpp index 7f71a261f..574c18bc2 100755 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -232,9 +232,9 @@ namespace } }; */ - struct file_entry + struct lru_file_entry { - file_entry(boost::shared_ptr const& f) + lru_file_entry(boost::shared_ptr const& f) : file_ptr(f) , last_use(pt::second_clock::universal_time()) {} mutable boost::shared_ptr file_ptr; @@ -257,7 +257,7 @@ namespace path_view::iterator i = pt.find(p); if (i != pt.end()) { - file_entry e = *i; + lru_file_entry e = *i; e.last_use = pt::second_clock::universal_time(); // if you hit this assert, you probably have more than one @@ -294,7 +294,7 @@ namespace */ assert(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); lt.erase(i); } - file_entry e(boost::shared_ptr(new file(p, m))); + lru_file_entry e(boost::shared_ptr(new file(p, m))); e.mode = m; e.key = st; e.file_path = p; @@ -334,13 +334,13 @@ namespace int m_size; typedef multi_index_container< - file_entry, indexed_by< - ordered_unique > - , ordered_non_unique > - , ordered_non_unique > + lru_file_entry, indexed_by< + ordered_unique > + , ordered_non_unique > + , ordered_non_unique > > > file_set; @@ -620,6 +620,12 @@ namespace libtorrent slot_lock lock(*m_pimpl, slot); +#ifndef NDEBUG + std::vector slices + = m_pimpl->info.map_block(slot, offset, size); + assert(!slices.empty()); +#endif + size_type start = slot * (size_type)m_pimpl->info.piece_length() + offset; // find the file iterator and file offset @@ -642,8 +648,10 @@ namespace libtorrent assert(file_offset < file_iter->size); - in->seek(file_offset); - if (in->tell() != file_offset) + assert(slices[0].offset == file_offset); + + size_type new_pos = in->seek(file_offset); + if (new_pos != file_offset) { // the file was not big enough throw file_error("slot has no storage"); @@ -665,12 +673,24 @@ namespace libtorrent size_type result = left_to_read; int buf_pos = 0; +#ifndef NDEBUG + int counter = 0; +#endif + while (left_to_read > 0) { int read_bytes = left_to_read; if (file_offset + read_bytes > file_iter->size) read_bytes = static_cast(file_iter->size - file_offset); +#ifndef NDEBUG + assert(int(slices.size()) > counter); + size_type slice_size = slices[counter].size; + assert(slice_size == read_bytes); + assert(m_pimpl->info.file_at(slices[counter].file_index).path + == file_iter->path); +#endif + size_type actual_read = in->read(buf + buf_pos, read_bytes); if (read_bytes != actual_read) @@ -687,6 +707,9 @@ namespace libtorrent if (left_to_read > 0) { ++file_iter; +#ifndef NDEBUG + ++counter; +#endif path path = m_pimpl->save_path / file_iter->path; file_offset = 0; @@ -714,6 +737,12 @@ namespace libtorrent assert(size > 0); slot_lock lock(*m_pimpl, slot); + +#ifndef NDEBUG + std::vector slices + = m_pimpl->info.map_block(slot, offset, size); + assert(!slices.empty()); +#endif size_type start = slot * (size_type)m_pimpl->info.piece_length() + offset; @@ -737,9 +766,9 @@ namespace libtorrent , p, file::out | file::in); assert(file_offset < file_iter->size); + assert(slices[0].offset == file_offset); - out->seek(file_offset); - size_type pos = out->tell(); + size_type pos = out->seek(file_offset); if (pos != file_offset) { @@ -757,7 +786,9 @@ namespace libtorrent assert(left_to_write >= 0); int buf_pos = 0; - +#ifndef NDEBUG + int counter = 0; +#endif while (left_to_write > 0) { int write_bytes = left_to_write; @@ -767,6 +798,11 @@ namespace libtorrent write_bytes = static_cast(file_iter->size - file_offset); } + assert(int(slices.size()) > counter); + assert(slices[counter].size == write_bytes); + assert(m_pimpl->info.file_at(slices[counter].file_index).path + == file_iter->path); + assert(buf_pos >= 0); assert(write_bytes >= 0); size_type written = out->write(buf + buf_pos, write_bytes); @@ -786,6 +822,9 @@ namespace libtorrent if (left_to_write > 0) { + #ifndef NDEBUG + ++counter; + #endif ++file_iter; assert(file_iter != m_pimpl->info.end_files()); @@ -818,10 +857,12 @@ namespace libtorrent bool check_fastresume( detail::piece_checker_data& d , std::vector& pieces + , int& num_pieces , bool compact_mode); std::pair check_files( - std::vector& pieces); + std::vector& pieces + , int& num_pieces); void release_files(); @@ -868,6 +909,7 @@ namespace libtorrent const std::vector& piece_data , int current_slot , std::vector& have_pieces + , int& num_pieces , const std::multimap& hash_to_piece); int allocate_slot_for_piece(int piece_index); @@ -949,6 +991,12 @@ namespace libtorrent // build the first time it is used (to save time if it // isn't needed) std::multimap m_hash_to_piece; + + // used as temporary piece data storage in allocate_slots + // it is a member in order to avoid allocating it on + // the heap every time a new slot is allocated. (This is quite + // frequent with high download speeds) + std::vector m_scratch_buffer; }; piece_manager::impl::impl( @@ -1156,6 +1204,7 @@ namespace libtorrent const std::vector& piece_data , int current_slot , std::vector& have_pieces + , int& num_pieces , const std::multimap& hash_to_piece) { INVARIANT_CHECK; @@ -1245,7 +1294,7 @@ namespace libtorrent { // this index is the only piece with this // hash. The previous slot we found with - // this hash must be tha same piece. Mark + // this hash must be the same piece. Mark // that piece as unassigned, since this slot // is the correct place for the piece. m_slot_to_piece[other_slot] = unassigned; @@ -1254,12 +1303,19 @@ namespace libtorrent assert(m_piece_to_slot[piece_index] != current_slot); assert(m_piece_to_slot[piece_index] >= 0); m_piece_to_slot[piece_index] = has_no_slot; +#ifndef NDEBUG have_pieces[piece_index] = false; +#endif + } + else + { + ++num_pieces; } assert(have_pieces[piece_index] == false); assert(m_piece_to_slot[piece_index] == has_no_slot); have_pieces[piece_index] = true; + return piece_index; } @@ -1279,6 +1335,7 @@ namespace libtorrent assert(have_pieces[free_piece] == false); assert(m_piece_to_slot[free_piece] == has_no_slot); have_pieces[free_piece] = true; + ++num_pieces; return free_piece; } @@ -1296,7 +1353,7 @@ namespace libtorrent bool piece_manager::impl::check_fastresume( detail::piece_checker_data& data , std::vector& pieces - , bool compact_mode) + , int& num_pieces, bool compact_mode) { assert(m_info.piece_length() > 0); // synchronization ------------------------------------------------------ @@ -1320,6 +1377,7 @@ namespace libtorrent pieces.clear(); pieces.resize(m_info.num_pieces(), false); + num_pieces = 0; // if we have fast-resume info // use it instead of doing the actual checking @@ -1342,6 +1400,7 @@ namespace libtorrent , piece_picker::has_index(found_piece)) == data.unfinished_pieces.end()) { + ++num_pieces; pieces[found_piece] = true; } } @@ -1385,8 +1444,10 @@ namespace libtorrent // file check is at. 0 is nothing done, and 1 // is finished std::pair piece_manager::impl::check_files( - std::vector& pieces) + std::vector& pieces, int& num_pieces) { + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + if (m_state == state_allocating) { if (m_compact_mode) @@ -1395,17 +1456,18 @@ namespace libtorrent return std::make_pair(true, 1.f); } - // if we're not in compact mode, make sure the - // pieces are spread out and placed at their - // final position. - assert(!m_unallocated_slots.empty()); - allocate_slots(1); if (m_unallocated_slots.empty()) { m_state = state_finished; return std::make_pair(true, 1.f); } + // if we're not in compact mode, make sure the + // pieces are spread out and placed at their + // final position. + assert(!m_unallocated_slots.empty()); + allocate_slots(1); + return std::make_pair(false, 1.f - (float)m_unallocated_slots.size() / (float)m_slot_to_piece.size()); } @@ -1462,8 +1524,10 @@ namespace libtorrent m_piece_data , m_current_slot , pieces + , num_pieces , m_hash_to_piece); + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); assert(piece_index == unassigned || piece_index >= 0); const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; @@ -1712,23 +1776,27 @@ namespace libtorrent std::vector().swap(m_piece_data); std::multimap().swap(m_hash_to_piece); m_state = state_allocating; + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); return std::make_pair(false, 1.f); } + assert(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + return std::make_pair(false, (float)m_current_slot / m_info.num_pieces()); } bool piece_manager::check_fastresume( detail::piece_checker_data& d, std::vector& pieces - , bool compact_mode) + , int& num_pieces, bool compact_mode) { - return m_pimpl->check_fastresume(d, pieces, compact_mode); + return m_pimpl->check_fastresume(d, pieces, num_pieces, compact_mode); } std::pair piece_manager::check_files( - std::vector& pieces) + std::vector& pieces + , int& num_pieces) { - return m_pimpl->check_files(pieces); + return m_pimpl->check_files(pieces, num_pieces); } int piece_manager::impl::allocate_slot_for_piece(int piece_index) @@ -1888,9 +1956,9 @@ namespace libtorrent // this object will syncronize the allocation with // potential other threads allocation_syncronization sync_obj( - m_allocating - , m_allocating_condition - , m_allocating_monitor); + m_allocating + , m_allocating_condition + , m_allocating_monitor); // synchronization ------------------------------------------------------ boost::recursive_mutex::scoped_lock lock(m_mutex); @@ -1902,7 +1970,8 @@ namespace libtorrent const int piece_size = static_cast(m_info.piece_length()); - std::vector buffer(piece_size, 0); + std::vector& buffer = m_scratch_buffer; + buffer.resize(piece_size); for (int i = 0; i < num_slots && !m_unallocated_slots.empty(); ++i) { diff --git a/src/torrent.cpp b/src/torrent.cpp index e0a7e4ad2..21a53a157 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -59,6 +60,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/hasher.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/peer.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" #include "libtorrent/peer_id.hpp" #include "libtorrent/alert.hpp" #include "libtorrent/identify_client.hpp" @@ -71,6 +74,8 @@ using boost::tuples::get; using boost::tuples::make_tuple; using boost::filesystem::complete; using boost::bind; +using boost::mutex; +using libtorrent::detail::session_impl; // PROFILING CODE @@ -148,36 +153,37 @@ namespace struct find_peer_by_ip { - find_peer_by_ip(const address& a, const torrent* t) + find_peer_by_ip(tcp::endpoint const& a, const torrent* t) : ip(a) , tor(t) { assert(t != 0); } bool operator()(const detail::session_impl::connection_map::value_type& c) const { - if (c.first->sender().ip() != ip.ip()) return false; - if (tor != c.second->associated_torrent()) return false; + tcp::endpoint sender = c.first->remote_endpoint(); + if (sender.address() != ip.address()) return false; + if (tor != c.second->associated_torrent().lock().get()) return false; return true; } - const address& ip; - const torrent* tor; + tcp::endpoint const& ip; + torrent const* tor; }; struct peer_by_id { - peer_by_id(const peer_id& i): id(i) {} + peer_by_id(const peer_id& i): pid(i) {} - bool operator()(const std::pair& p) const + bool operator()(const std::pair& p) const { - if (p.second->get_peer_id() != id) return false; + if (p.second->pid() != pid) return false; // have a special case for all zeros. We can have any number - // of peers with that id, since it's used to indicate no id. - if (std::count(id.begin(), id.end(), 0) == 20) return false; + // of peers with that pid, since it's used to indicate no pid. + if (std::count(pid.begin(), pid.end(), 0) == 20) return false; return true; } - const peer_id& id; + peer_id const& pid; }; } @@ -189,9 +195,10 @@ namespace libtorrent , detail::checker_impl& checker , torrent_info const& tf , boost::filesystem::path const& save_path - , address const& net_interface + , tcp::endpoint const& net_interface , bool compact_mode - , int block_size) + , int block_size + , session_settings const& s) : m_torrent_file(tf) , m_abort(false) , m_paused(false) @@ -203,6 +210,7 @@ namespace libtorrent , m_duration(1800) , m_complete(-1) , m_incomplete(-1) + , m_host_resolver(ses.m_selector) , m_policy() , m_ses(ses) , m_checker(checker) @@ -217,7 +225,8 @@ namespace libtorrent , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) - , m_net_interface(net_interface.ip(), address::any_port) + , m_total_redundant_bytes(0) + , m_net_interface(0, net_interface.address()) , m_upload_bandwidth_limit(std::numeric_limits::max()) , m_download_bandwidth_limit(std::numeric_limits::max()) , m_save_path(complete(save_path)) @@ -226,7 +235,13 @@ namespace libtorrent , m_metadata_size(0) , m_default_block_size(block_size) , m_connections_initialized(true) + , m_settings(s) { +#ifndef NDEBUG + m_initial_done = 0; +#endif + INVARIANT_CHECK; + m_uploads_quota.min = 2; m_connections_quota.min = 2; // this will be corrected the next time the main session @@ -269,9 +284,10 @@ namespace libtorrent , char const* tracker_url , sha1_hash const& info_hash , boost::filesystem::path const& save_path - , address const& net_interface + , tcp::endpoint const& net_interface , bool compact_mode - , int block_size) + , int block_size + , session_settings const& s) : m_torrent_file(info_hash) , m_abort(false) , m_paused(false) @@ -283,6 +299,7 @@ namespace libtorrent , m_duration(1800) , m_complete(-1) , m_incomplete(-1) + , m_host_resolver(ses.m_selector) , m_policy() , m_ses(ses) , m_checker(checker) @@ -296,7 +313,8 @@ namespace libtorrent , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) - , m_net_interface(net_interface.ip(), address::any_port) + , m_total_redundant_bytes(0) + , m_net_interface(0, net_interface.address()) , m_upload_bandwidth_limit(std::numeric_limits::max()) , m_download_bandwidth_limit(std::numeric_limits::max()) , m_save_path(complete(save_path)) @@ -305,7 +323,13 @@ namespace libtorrent , m_metadata_size(0) , m_default_block_size(block_size) , m_connections_initialized(false) + , m_settings(s) { +#ifndef NDEBUG + m_initial_done = 0; +#endif + INVARIANT_CHECK; + m_uploads_quota.min = 2; m_connections_quota.min = 2; // this will be corrected the next time the main session @@ -348,18 +372,18 @@ namespace libtorrent torrent::~torrent() { - assert(m_connections.empty()); + INVARIANT_CHECK; + + if (m_ses.m_abort) + m_abort = true; if (!m_connections.empty()) - { - boost::mutex::scoped_lock l(m_ses.m_mutex); disconnect_all(); - m_ses.purge_connections(); - } - if (m_ses.m_abort) m_abort = true; } void torrent::init() { + INVARIANT_CHECK; + assert(m_torrent_file.is_valid()); assert(m_torrent_file.num_files() > 0); assert(m_torrent_file.total_size() >= 0); @@ -368,19 +392,28 @@ namespace libtorrent m_storage.reset(new piece_manager(m_torrent_file, m_save_path)); m_block_size = calculate_block_size(m_torrent_file, m_default_block_size); m_picker.reset(new piece_picker( - static_cast(m_torrent_file.piece_length() / m_block_size) - , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size))); + static_cast(m_torrent_file.piece_length() / m_block_size) + , static_cast((m_torrent_file.total_size()+m_block_size-1)/m_block_size) + , m_settings.sequenced_download_threshold)); + + std::vector const& url_seeds = m_torrent_file.url_seeds(); + std::copy(url_seeds.begin(), url_seeds.end(), std::inserter(m_web_seeds + , m_web_seeds.begin())); } void torrent::use_interface(const char* net_interface) { - m_net_interface = address(net_interface, address::any_port); + INVARIANT_CHECK; + + m_net_interface = tcp::endpoint(0, net_interface); } // returns true if it is time for this torrent to make another // tracker request bool torrent::should_request() { + INVARIANT_CHECK; + if (m_just_paused) { m_just_paused = false; @@ -392,6 +425,8 @@ namespace libtorrent void torrent::tracker_warning(std::string const& msg) { + INVARIANT_CHECK; + if (m_ses.m_alerts.should_post(alert::warning)) { m_ses.m_alerts.post_alert(tracker_warning_alert(get_handle(), msg)); @@ -405,6 +440,10 @@ namespace libtorrent , int complete , int incomplete) { + INVARIANT_CHECK; + + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + m_failed_trackers = 0; // less than 5 minutes announce intervals // are insane. @@ -419,7 +458,7 @@ namespace libtorrent if (complete >= 0) m_complete = complete; if (incomplete >= 0) m_incomplete = incomplete; - + // connect to random peers from the list std::random_shuffle(peer_list.begin(), peer_list.end()); @@ -433,7 +472,7 @@ namespace libtorrent { s << " " << std::setfill(' ') << std::setw(16) << i->ip << " " << std::setw(5) << std::dec << i->port << " "; - if (!i->id.is_all_zeros()) s << " " << i->id << " " << identify_client(i->id); + if (!i->pid.is_all_zeros()) s << " " << i->pid << " " << identify_client(i->pid); s << "\n"; } debug_log(s.str()); @@ -443,12 +482,12 @@ namespace libtorrent i != peer_list.end(); ++i) { // don't make connections to ourself - if (i->id == m_ses.get_peer_id()) + if (i->pid == m_ses.get_peer_id()) continue; - address a(i->ip.c_str(), i->port); + tcp::endpoint a(i->port, i->ip.c_str()); - if (m_ses.m_ip_filter.access(a) == ip_filter::blocked) + if (m_ses.m_ip_filter.access(a.address()) == ip_filter::blocked) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) debug_log("blocked ip from tracker: " + i->ip); @@ -456,7 +495,7 @@ namespace libtorrent continue; } - m_policy->peer_from_tracker(a, i->id); + m_policy->peer_from_tracker(a, i->pid); } if (m_ses.m_alerts.should_post(alert::info)) @@ -543,8 +582,9 @@ namespace libtorrent std::map downloading_piece; for (const_peer_iterator i = begin(); i != end(); ++i) { + peer_connection* pc = i->second; boost::optional p - = i->second->downloading_piece(); + = pc->downloading_piece_progress(); if (p) { if (m_have_pieces[p->piece_index]) @@ -580,6 +620,8 @@ namespace libtorrent void torrent::piece_failed(int index) { + INVARIANT_CHECK; + assert(m_storage.get()); assert(m_picker.get()); assert(index >= 0); @@ -594,16 +636,16 @@ namespace libtorrent // increase the total amount of failed bytes m_total_failed_bytes += m_torrent_file.piece_size(index); - std::vector
    downloaders; + std::vector downloaders; m_picker->get_downloaders(downloaders, index); // decrease the trust point of all peers that sent // parts of this piece. // first, build a set of all peers that participated - std::set
    peers; + std::set peers; std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); - for (std::set
    ::iterator i = peers.begin() + for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) { peer_iterator p = m_connections.find(*i); @@ -612,6 +654,7 @@ namespace libtorrent // either, we have received too many failed hashes // or this was the only peer that sent us this piece. + // TODO: make this a changable setting if (p->second->trust_points() <= -7 || peers.size() == 1) { // we don't trust this peer anymore @@ -649,8 +692,13 @@ namespace libtorrent void torrent::abort() { + INVARIANT_CHECK; + m_abort = true; - m_event = tracker_request::stopped; + // if the torrent is paused, it doesn't need + // to announce with even=stopped again. + if (!m_paused) + m_event = tracker_request::stopped; // disconnect all peers and close all // files belonging to the torrents disconnect_all(); @@ -659,19 +707,21 @@ namespace libtorrent void torrent::announce_piece(int index) { + INVARIANT_CHECK; + assert(m_picker.get()); assert(index >= 0); assert(index < m_torrent_file.num_pieces()); - std::vector
    downloaders; + std::vector downloaders; m_picker->get_downloaders(downloaders, index); // increase the trust point of all peers that sent // parts of this piece. - std::set
    peers; + std::set peers; std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); - for (std::set
    ::iterator i = peers.begin() + for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) { peer_iterator p = m_connections.find(*i); @@ -692,6 +742,8 @@ namespace libtorrent void torrent::filter_piece(int index, bool filter) { + INVARIANT_CHECK; + // this call is only valid on torrents with metadata assert(m_picker.get()); assert(index >= 0); @@ -705,6 +757,8 @@ namespace libtorrent void torrent::filter_pieces(std::vector const& bitmask) { + INVARIANT_CHECK; + // this call is only valid on torrents with metadata assert(m_picker.get()); @@ -722,16 +776,14 @@ namespace libtorrent else state.push_back(index); } - std::random_shuffle(state.begin(), state.end()); - for (std::vector::iterator i = state.begin(); - i != state.end(); ++i) + + for (std::vector::reverse_iterator i = state.rbegin(); + i != state.rend(); ++i) { m_picker->mark_as_unfiltered(*i); } } - // TODO: add a function to set the filter with one call - bool torrent::is_piece_filtered(int index) const { // this call is only valid on torrents with metadata @@ -749,35 +801,6 @@ namespace libtorrent m_picker->filtered_pieces(bitmask); } - - - //idea from Arvid and MooPolice - //todo refactoring and improving the function body - void torrent::filter_file(int index, bool filter) - { - // this call is only valid on torrents with metadata - if (!valid_metadata()) return; - - assert(index < m_torrent_file.num_files()); - assert(index >= 0); - - size_type start_position = 0; - int start_piece_index = 0; - int end_piece_index = 0; - int piece_length = m_torrent_file.piece_length(); - - for (int i = 0; i < index; ++i) - start_position += m_torrent_file.file_at(i).size; - - start_piece_index = start_position / piece_length; - // make the end piece index be rounded upwards - end_piece_index = (start_position + m_torrent_file.file_at(index).size - + piece_length - 1) / piece_length; - - for(int i = start_piece_index; i <= end_piece_index; ++i) - filter_piece(i, filter); - } - void torrent::filter_files(std::vector const& bitmask) { // this call is only valid on torrents with metadata @@ -826,19 +849,23 @@ namespace libtorrent tracker_request torrent::generate_tracker_request() { + INVARIANT_CHECK; + m_next_request = second_clock::universal_time() + boost::posix_time::seconds(tracker_retry_delay_max); tracker_request req; req.info_hash = m_torrent_file.info_hash(); - req.id = m_ses.get_peer_id(); + req.pid = m_ses.get_peer_id(); req.downloaded = m_stat.total_payload_download(); req.uploaded = m_stat.total_payload_upload(); req.left = bytes_left(); if (req.left == -1) req.left = 1000; req.event = m_event; - m_event = tracker_request::none; + + if (m_event != tracker_request::stopped) + m_event = tracker_request::none; req.url = m_trackers[m_currently_trying_tracker].url; assert(m_connections_quota.given > 0); req.num_want = std::max( @@ -856,8 +883,10 @@ namespace libtorrent return req; } - void torrent::remove_peer(peer_connection* p) + void torrent::remove_peer(peer_connection* p) try { + INVARIANT_CHECK; + assert(p != 0); peer_iterator i = m_connections.find(p->remote()); @@ -865,18 +894,7 @@ namespace libtorrent if (ready_for_connections()) { - // if the peer_connection was downloading any pieces - // abort them - for (std::deque::const_iterator i = p->download_queue().begin(); - i != p->download_queue().end(); ++i) - { - m_picker->abort_download(*i); - } - for (std::deque::const_iterator i = p->request_queue().begin(); - i != p->request_queue().end(); ++i) - { - m_picker->abort_download(*i); - } + assert(p->associated_torrent().lock().get() == this); std::vector piece_list; const std::vector& pieces = p->get_bitfield(); @@ -887,10 +905,8 @@ namespace libtorrent if (*i) piece_list.push_back(static_cast(i - pieces.begin())); } - std::random_shuffle(piece_list.begin(), piece_list.end()); - - for (std::vector::iterator i = piece_list.begin(); - i != piece_list.end(); ++i) + for (std::vector::reverse_iterator i = piece_list.rbegin(); + i != piece_list.rend(); ++i) { peer_lost(*i); } @@ -902,23 +918,78 @@ namespace libtorrent m_policy->check_invariant(); #endif } - - peer_connection& torrent::connect_to_peer(const address& a) + catch (std::exception& e) { - boost::shared_ptr s(new socket(socket::tcp, false)); - boost::shared_ptr c(new peer_connection( - m_ses, m_ses.m_selector, this, s, a)); - - m_ses.m_connection_queue.push_back(c); - - assert(m_connections.find(a) == m_connections.end()); - #ifndef NDEBUG - m_policy->check_invariant(); + std::string err = e.what(); #endif - + assert(false); + }; + + void torrent::connect_to_url_seed(std::string const& url) + { + INVARIANT_CHECK; + + std::string protocol; + std::string hostname; + int port; + std::string path; + boost::tie(protocol, hostname, port, path) + = parse_url_components(url); + + typedef std::map resolving; + + resolving::iterator i = m_resolving_web_seeds.insert(std::pair(url, host())).first; + m_host_resolver.async_by_name(i->second, hostname + , bind(&torrent::on_name_lookup, shared_from_this(), _1, port + , boost::ref(i->first), boost::ref(i->second))); + } + + void torrent::on_name_lookup(asio::error const& e, int port, std::string url + , host h) try + { + detail::session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + typedef std::map resolving; + resolving::iterator i = m_resolving_web_seeds.find(url); + if (i != m_resolving_web_seeds.end()) m_resolving_web_seeds.erase(i); + + if (e) + { + if (m_ses.m_alerts.should_post(alert::warning)) + { + std::stringstream msg; + msg << "HTTP seed hostname lookup failed: " << e.what(); + m_ses.m_alerts.post_alert( + url_seed_alert(url, msg.str())); + } + + // the name lookup failed for the http host. Don't try + // this host again + remove_url_seed(url); + return; + } + + tcp::endpoint a(port, h.address(0)); + + boost::shared_ptr s(new stream_socket(m_ses.m_selector)); + boost::intrusive_ptr c(new web_peer_connection( + m_ses, shared_from_this(), s, a, url)); +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + try { + m_ses.m_connection_queue.push_back(c); + + assert(m_connections.find(a) == m_connections.end()); + +#ifndef NDEBUG + m_policy->check_invariant(); +#endif // add the newly connected peer to this torrent's peer list m_connections.insert( std::make_pair(a, boost::get_pointer(c))); @@ -927,15 +998,62 @@ namespace libtorrent m_policy->check_invariant(); #endif - m_ses.m_selector.monitor_errors(s); m_ses.process_connection_queue(); } catch (std::exception& e) { // TODO: post an error alert! - std::map::iterator i = m_connections.find(a); + std::map::iterator i = m_connections.find(a); if (i != m_connections.end()) m_connections.erase(i); m_ses.connection_failed(s, a, e.what()); + c->disconnect(); + } + } + catch (std::exception& exc) + { + assert(false); + }; + + peer_connection& torrent::connect_to_peer(const tcp::endpoint& a) + { + INVARIANT_CHECK; + + if (m_connections.find(a) != m_connections.end()) + throw protocol_error("already connected to peer"); + + boost::shared_ptr s(new stream_socket(m_ses.m_selector)); + boost::intrusive_ptr c(new bt_peer_connection( + m_ses, shared_from_this(), s, a)); +#ifndef NDEBUG + c->m_in_constructor = false; +#endif + + try + { + m_ses.m_connection_queue.push_back(c); + + assert(m_connections.find(a) == m_connections.end()); + +#ifndef NDEBUG + m_policy->check_invariant(); +#endif + // add the newly connected peer to this torrent's peer list + m_connections.insert( + std::make_pair(a, boost::get_pointer(c))); + +#ifndef NDEBUG + m_policy->check_invariant(); +#endif + + m_ses.process_connection_queue(); + } + catch (std::exception& e) + { + // TODO: post an error alert! + std::map::iterator i = m_connections.find(a); + if (i != m_connections.end()) m_connections.erase(i); + m_ses.connection_failed(s, a, e.what()); + c->disconnect(); throw; } return *c; @@ -943,24 +1061,39 @@ namespace libtorrent void torrent::attach_peer(peer_connection* p) { + INVARIANT_CHECK; + assert(p != 0); - assert(m_connections.find(p->remote()) == m_connections.end()); assert(!p->is_local()); - detail::session_impl::connection_map::iterator i - = m_ses.m_connections.find(p->get_socket()); - assert(i != m_ses.m_connections.end()); + if (m_connections.find(p->remote()) != m_connections.end()) + throw protocol_error("already connected to peer"); - // it's important that we call new_connection before - // the connection is added to the torrent's list. - // because if this fails, it will throw, and if this throws - // m_attatched_to_torrent won't be set in the peer_connections - // and the destructor won't remove the entry from the torrent's - // connection list. - m_policy->new_connection(*i->second); + if (m_ses.m_connections.find(p->get_socket()) + == m_ses.m_connections.end()) + { + throw protocol_error("peer is not properly constructed"); + } - assert(p->remote() == p->get_socket()->sender()); - m_connections.insert(std::make_pair(p->remote(), p)); + + peer_iterator i = m_connections.insert( + std::make_pair(p->remote(), p)).first; + + try + { + // if new_connection throws, we have to remove the + // it from the list. + + m_policy->new_connection(*i->second); + } + catch (std::exception& e) + { + m_connections.erase(i); + throw; + } +#ifndef NDEBUG + assert(p->remote() == p->get_socket()->remote_endpoint()); +#endif #ifndef NDEBUG m_policy->check_invariant(); @@ -970,24 +1103,34 @@ namespace libtorrent void torrent::disconnect_all() { - for (peer_iterator i = m_connections.begin(); - i != m_connections.end(); ++i) + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + while (!m_connections.empty()) { - assert(i->second->associated_torrent() == this); + peer_connection& p = *m_connections.begin()->second; + assert(p.associated_torrent().lock().get() == this); #if defined(TORRENT_VERBOSE_LOGGING) - if (m_abort) - (*i->second->m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; - else - (*i->second->m_logger) << "*** CLOSING CONNECTION 'pausing'\n"; + if (m_abort) + (*p.m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; + else + (*p.m_logger) << "*** CLOSING CONNECTION 'pausing'\n"; #endif - i->second->disconnect(); +#ifndef NDEBUG + std::size_t size = m_connections.size(); +#endif + p.disconnect(); + assert(m_connections.size() <= size); } } // called when torrent is finished (all interested pieces downloaded) void torrent::finished() { + INVARIANT_CHECK; + if (alerts().should_post(alert::info)) { alerts().post_alert(torrent_finished_alert( @@ -996,18 +1139,21 @@ namespace libtorrent } // disconnect all seeds + std::vector seeds; for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { - assert(i->second->associated_torrent() == this); + assert(i->second->associated_torrent().lock().get() == this); if (i->second->is_seed()) { #if defined(TORRENT_VERBOSE_LOGGING) (*i->second->m_logger) << "*** SEED, CLOSING CONNECTION\n"; #endif - i->second->disconnect(); + seeds.push_back(i->second); } } + std::for_each(seeds.begin(), seeds.end() + , bind(&peer_connection::disconnect, _1)); m_storage->release_files(); } @@ -1047,6 +1193,8 @@ namespace libtorrent void torrent::try_next_tracker() { + INVARIANT_CHECK; + using namespace boost::posix_time; ++m_currently_trying_tracker; @@ -1072,29 +1220,43 @@ namespace libtorrent bool torrent::check_fastresume(detail::piece_checker_data& data) { + INVARIANT_CHECK; + if (!m_storage.get()) { // this means we have received the metadata through the // metadata extension, and we have to initialize init(); } + assert(m_storage.get()); - return m_storage->check_fastresume(data, m_have_pieces, m_compact_mode); + bool done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces + , m_compact_mode); +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif + return done; } std::pair torrent::check_files() { + INVARIANT_CHECK; + assert(m_storage.get()); - return m_storage->check_files(m_have_pieces); + std::pair progress = m_storage->check_files(m_have_pieces, m_num_pieces); + +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif + return progress; } void torrent::files_checked(std::vector const& unfinished_pieces) { - m_num_pieces = std::count( - m_have_pieces.begin() - , m_have_pieces.end() - , true); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; m_picker->files_checked(m_have_pieces, unfinished_pieces); if (!m_connections_initialized) @@ -1102,15 +1264,25 @@ namespace libtorrent m_connections_initialized = true; // all peer connections have to initialize themselves now that the metadata // is available - typedef std::map conn_map; + typedef std::map conn_map; for (conn_map::iterator i = m_connections.begin() - , end(m_connections.end()); i != end; ++i) + , end(m_connections.end()); i != end;) { - try { i->second->init(); } - catch (std::exception&e) {} - // TODO: in case of an exception, close the connection + try { i->second->init(); ++i;} + catch (std::exception& e) + { + // the connection failed, close it + conn_map::iterator j = i; + ++j; + m_ses.connection_failed(i->second->get_socket() + , i->first, e.what()); + i = j; + } } } +#ifndef NDEBUG + m_initial_done = boost::get<0>(bytes_done()); +#endif } alert_manager& torrent::alerts() const @@ -1155,8 +1327,15 @@ namespace libtorrent #ifndef NDEBUG void torrent::check_invariant() const { - assert(m_num_pieces - == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); +// size_type download = m_stat.total_payload_download(); +// size_type done = boost::get<0>(bytes_done()); +// assert(download >= done - m_initial_done); + for (const_peer_iterator i = begin(); i != end(); ++i) + assert(i->second->associated_torrent().lock().get() == this); + +// This check is very expensive. +// assert(m_num_pieces +// == std::count(m_have_pieces.begin(), m_have_pieces.end(), true)); assert(m_priority >= 0.f && m_priority < 1.f); assert(!valid_metadata() || m_block_size > 0); assert(!valid_metadata() || (m_torrent_file.piece_length() % m_block_size) == 0); @@ -1177,6 +1356,20 @@ namespace libtorrent m_connections_quota.max = std::max(m_connections_quota.min, limit); } + void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) + { + peer_connection* p = connection_for(ip); + if (p == 0) return; + p->set_upload_limit(limit); + } + + void torrent::set_peer_download_limit(tcp::endpoint ip, int limit) + { + peer_connection* p = connection_for(ip); + if (p == 0) return; + p->set_download_limit(limit); + } + void torrent::set_upload_limit(int limit) { assert(limit >= -1); @@ -1195,6 +1388,8 @@ namespace libtorrent void torrent::pause() { + INVARIANT_CHECK; + if (m_paused) return; disconnect_all(); m_paused = true; @@ -1208,6 +1403,8 @@ namespace libtorrent void torrent::resume() { + INVARIANT_CHECK; + if (!m_paused) return; m_paused = false; @@ -1219,8 +1416,10 @@ namespace libtorrent m_time_scaler = 0; } - void torrent::second_tick(stat& accumulator) + void torrent::second_tick(stat& accumulator, float tick_interval) { + INVARIANT_CHECK; + m_connections_quota.used = (int)m_connections.size(); m_uploads_quota.used = m_policy->num_uploads(); @@ -1235,10 +1434,43 @@ namespace libtorrent if (m_paused) { // let the stats fade out to 0 - m_stat.second_tick(); + m_stat.second_tick(tick_interval); return; } + // ---- WEB SEEDS ---- + + // if we're a seed, we don't need to connect to any web-seed + if (!is_seed() && !m_web_seeds.empty()) + { + // keep trying web-seeds if there are any + // first find out which web seeds we are connected to + std::set web_seeds; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + web_peer_connection* p + = dynamic_cast(i->second); + if (!p) continue; + web_seeds.insert(p->url()); + } + + for (std::map::iterator i + = m_resolving_web_seeds.begin(), end(m_resolving_web_seeds.end()); + i != end; ++i) + web_seeds.insert(web_seeds.begin(), i->first); + + // from the list of available web seeds, subtract the ones we are + // already connected to. + std::vector not_connected_web_seeds; + std::set_difference(m_web_seeds.begin(), m_web_seeds.end(), web_seeds.begin() + , web_seeds.end(), std::back_inserter(not_connected_web_seeds)); + + // connect to all of those that we aren't connected to + std::for_each(not_connected_web_seeds.begin(), not_connected_web_seeds.end() + , bind(&torrent::connect_to_url_seed, this, _1)); + } + for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { @@ -1246,7 +1478,7 @@ namespace libtorrent m_stat += p->statistics(); // updates the peer connection's ul/dl bandwidth // resource requests - p->second_tick(); + p->second_tick(tick_interval); m_ul_bandwidth_quota.used += p->m_ul_bandwidth_quota.used; m_ul_bandwidth_quota.min += p->m_ul_bandwidth_quota.min; @@ -1275,11 +1507,16 @@ namespace libtorrent m_dl_bandwidth_quota.max = resource_request::inf; accumulator += m_stat; - m_stat.second_tick(); + // don't pass the tick interval in here, because + // the stats have already been adjusted in the + // peer's stats. + m_stat.second_tick(1.f); } void torrent::distribute_resources() { + INVARIANT_CHECK; + m_time_scaler--; if (m_time_scaler <= 0) { @@ -1302,15 +1539,19 @@ namespace libtorrent // tell all peers to reset their used quota. This is // a new second and they can again use up their quota - for (std::map::iterator i + for (std::map::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { i->second->reset_upload_quota(); + assert(i->second->m_dl_bandwidth_quota.used + <= i->second->m_dl_bandwidth_quota.given); } } bool torrent::verify_piece(int piece_index) { + INVARIANT_CHECK; + assert(m_storage.get()); assert(piece_index >= 0); assert(piece_index < m_torrent_file.num_pieces()); @@ -1337,7 +1578,7 @@ namespace libtorrent return true; } - const address& torrent::current_tracker() const + const tcp::endpoint& torrent::current_tracker() const { return m_tracker_address; } @@ -1347,6 +1588,8 @@ namespace libtorrent std::vector const& torrent::metadata() const { + INVARIANT_CHECK; + if (m_metadata.empty()) { bencode(std::back_inserter(m_metadata) @@ -1360,6 +1603,8 @@ namespace libtorrent torrent_status torrent::status() const { + INVARIANT_CHECK; + assert(std::accumulate( m_have_pieces.begin() , m_have_pieces.end() @@ -1367,14 +1612,9 @@ namespace libtorrent torrent_status st; - st.block_size = block_size(); - - st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end(), - boost::bind(std::logical_not(), boost::bind( - &peer_connection::is_connecting - , boost::bind(&std::map
    ::value_type::second, _1)))); + boost::bind(std::logical_not(), boost::bind(&peer_connection::is_connecting, + boost::bind(&std::map::value_type::second, _1)))); st.num_complete = m_complete; st.num_incomplete = m_incomplete; @@ -1393,6 +1633,7 @@ namespace libtorrent // failed bytes st.total_failed_bytes = m_total_failed_bytes; + st.total_redundant_bytes = m_total_redundant_bytes; // transfer rate st.download_rate = m_stat.download_rate(); @@ -1424,9 +1665,13 @@ namespace libtorrent if (m_metadata_size == 0) st.progress = 0.f; else st.progress = std::min(1.f, m_metadata_progress / (float)m_metadata_size); + st.block_size = 0; + return st; } + st.block_size = block_size(); + // fill in status that depends on metadata st.total_wanted = m_torrent_file.total_size(); @@ -1450,9 +1695,10 @@ namespace libtorrent if (st.total_wanted == 0) st.progress = 1.f; else st.progress = st.total_wanted_done - / static_cast(st.total_wanted); + / static_cast(st.total_wanted); st.pieces = &m_have_pieces; + st.num_pieces = m_num_pieces; if (m_got_tracker_response == false) st.state = torrent_status::connecting_to_tracker; @@ -1470,9 +1716,11 @@ namespace libtorrent int torrent::num_seeds() const { + INVARIANT_CHECK; + return (int)std::count_if(m_connections.begin(), m_connections.end(), boost::bind(&peer_connection::is_seed, - boost::bind(&std::map::value_type::second, _1))); + boost::bind(&std::map::value_type::second, _1))); } int div_round_up(int numerator, int denominator) @@ -1607,14 +1855,16 @@ namespace libtorrent // count the number of peers that supports the // extension and that has metadata int peers = 0; - typedef std::map conn_map; + typedef std::map conn_map; for (conn_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - if (!i->second->supports_extension( - peer_connection::extended_metadata_message)) + bt_peer_connection* c = dynamic_cast(i->second); + if (c == 0) continue; + if (!c->supports_extension( + extended_metadata_message)) continue; - if (!i->second->has_metadata()) + if (!c->has_metadata()) continue; ++peers; } @@ -1665,6 +1915,7 @@ namespace libtorrent void torrent::tracker_request_timed_out( tracker_request const&) { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) debug_log("*** tracker timed out"); #endif @@ -1686,6 +1937,7 @@ namespace libtorrent void torrent::tracker_request_error(tracker_request const& , int response_code, const std::string& str) { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) debug_log(std::string("*** tracker error: ") + str); #endif diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 89d864db3..73fba2fb1 100755 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -54,6 +54,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "libtorrent/peer_id.hpp" +#include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/torrent_info.hpp" #include "libtorrent/tracker_manager.hpp" #include "libtorrent/bencode.hpp" @@ -71,6 +72,8 @@ namespace std #endif using boost::bind; +using boost::mutex; +using libtorrent::detail::session_impl; namespace libtorrent { @@ -92,15 +95,15 @@ namespace libtorrent if (chk) { - boost::mutex::scoped_lock l(chk->m_mutex); + mutex::scoped_lock l(chk->m_mutex); detail::piece_checker_data* d = chk->find_torrent(hash); if (d != 0) return f(*d->torrent_ptr); } { - boost::mutex::scoped_lock l(ses->m_mutex); - torrent* t = ses->find_torrent(hash); - if (t != 0) return f(*t); + session_impl::mutex_t::scoped_lock l(ses->m_mutex); + boost::shared_ptr t = ses->find_torrent(hash).lock(); + if (t) return f(*t); } throw invalid_handle(); @@ -144,6 +147,24 @@ namespace libtorrent , bind(&torrent::set_max_connections, _1, max_connections)); } + void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const + { + INVARIANT_CHECK; + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_upload_limit, _1, ip, limit)); + } + + void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const + { + INVARIANT_CHECK; + assert(limit >= -1); + + call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::set_peer_download_limit, _1, ip, limit)); + } + void torrent_handle::set_upload_limit(int limit) const { INVARIANT_CHECK; @@ -231,7 +252,7 @@ namespace libtorrent if (m_chk) { - boost::mutex::scoped_lock l(m_chk->m_mutex); + mutex::scoped_lock l(m_chk->m_mutex); detail::piece_checker_data* d = m_chk->find_torrent(m_info_hash); if (d != 0) @@ -254,9 +275,9 @@ namespace libtorrent } { - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t != 0) return t->status(); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (t) return t->status(); } throw_invalid_handle(); @@ -293,13 +314,6 @@ namespace libtorrent return ret; } - void torrent_handle::filter_file(int index, bool filter) const - { - INVARIANT_CHECK; - call_member(m_ses, m_chk, m_info_hash - , bind(&torrent::filter_file, _1, index, filter)); - } - void torrent_handle::filter_files(std::vector const& files) const { INVARIANT_CHECK; @@ -315,6 +329,14 @@ namespace libtorrent , m_chk, m_info_hash, bind(&torrent::trackers, _1)); } + void torrent_handle::add_url_seed(std::string const& url) + { + INVARIANT_CHECK; + + return call_member(m_ses, m_chk, m_info_hash + , bind(&torrent::add_url_seed, _1, url)); + } + void torrent_handle::replace_trackers( std::vector const& urls) const { @@ -341,15 +363,15 @@ namespace libtorrent if (m_chk) { - boost::mutex::scoped_lock l(m_chk->m_mutex); + mutex::scoped_lock l(m_chk->m_mutex); detail::piece_checker_data* d = m_chk->find_torrent(m_info_hash); if (d != 0) return true; } { - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t != 0) return true; + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::weak_ptr t = m_ses->find_torrent(m_info_hash); + if (!t.expired()) return true; } return false; @@ -362,9 +384,9 @@ namespace libtorrent std::vector piece_index; if (m_ses == 0) return entry(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) return entry(); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) return entry(); if (!t->valid_metadata()) return entry(); @@ -398,9 +420,7 @@ namespace libtorrent // info for each unfinished piece for (std::vector::const_iterator i - = q.begin(); - i != q.end(); - ++i) + = q.begin(); i != q.end(); ++i) { if (i->finished_blocks.count() == 0) continue; @@ -450,10 +470,10 @@ namespace libtorrent // but still connectable if (!i->second->is_local()) continue; - address ip = i->second->remote(); + tcp::endpoint ip = i->second->remote(); entry peer(entry::dictionary_t); - peer["ip"] = ip.as_string(); - peer["port"] = ip.port; + peer["ip"] = ip.address().to_string(); + peer["port"] = ip.port(); peer_list.push_back(peer); } @@ -491,15 +511,15 @@ namespace libtorrent , bind(&torrent::metadata, _1)); } - void torrent_handle::connect_peer(address const& adr) const + void torrent_handle::connect_peer(tcp::endpoint const& adr) const { INVARIANT_CHECK; if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) throw_invalid_handle(); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); peer_id id; std::fill(id.begin(), id.end(), 0); @@ -513,9 +533,9 @@ namespace libtorrent if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) throw_invalid_handle(); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); using boost::posix_time::second_clock; t->force_tracker_request(second_clock::universal_time() @@ -528,9 +548,9 @@ namespace libtorrent if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) throw_invalid_handle(); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) throw_invalid_handle(); t->force_tracker_request(); } @@ -555,10 +575,10 @@ namespace libtorrent v.clear(); if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - const torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) return; + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) return; for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) @@ -567,75 +587,22 @@ namespace libtorrent // peers that haven't finished the handshake should // not be included in this list - if (peer->associated_torrent() == 0) continue; + if (peer->associated_torrent().expired()) continue; v.push_back(peer_info()); peer_info& p = v.back(); - - const stat& statistics = peer->statistics(); - 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.id = peer->get_peer_id(); - p.ip = peer->remote(); - - p.total_download = statistics.total_payload_download(); - p.total_upload = statistics.total_payload_upload(); - - if (peer->m_ul_bandwidth_quota.given == std::numeric_limits::max()) - p.upload_limit = -1; - else - p.upload_limit = peer->m_ul_bandwidth_quota.given; - - if (peer->m_ul_bandwidth_quota.max == std::numeric_limits::max()) - p.upload_ceiling = -1; - else - p.upload_ceiling = peer->m_ul_bandwidth_quota.given; - - p.load_balancing = peer->total_free_upload(); - - p.download_queue_length = (int)peer->download_queue().size(); - p.upload_queue_length = (int)peer->upload_queue().size(); - - boost::optional ret = peer->downloading_piece(); - if (ret) - { - 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 (peer->is_interesting()) p.flags |= peer_info::interesting; - if (peer->is_choked()) p.flags |= peer_info::choked; - if (peer->is_peer_interested()) p.flags |= peer_info::remote_interested; - if (peer->has_peer_choked()) p.flags |= peer_info::remote_choked; - if (peer->support_extensions()) p.flags |= peer_info::supports_extensions; - if (peer->is_local()) p.flags |= peer_info::local_connection; - if (peer->is_connecting() && !peer->is_queued()) p.flags |= peer_info::connecting; - if (peer->is_queued()) p.flags |= peer_info::queued; - p.pieces = peer->get_bitfield(); - p.seed = peer->is_seed(); + peer->get_peer_info(p); } } - bool torrent_handle::send_chat_message(address ip, std::string message) const + bool torrent_handle::send_chat_message(tcp::endpoint ip, std::string message) const { if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - const torrent* t = m_ses->find_torrent(m_info_hash); - if (t == 0) return false; + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + if (!t) return false; for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) @@ -644,21 +611,23 @@ namespace libtorrent // peers that haven't finished the handshake should // not be included in this list - if (peer->associated_torrent() == 0) continue; + if (peer->associated_torrent().expired()) continue; + + tcp::endpoint sender = peer->get_socket()->remote_endpoint(); + // loop until we find the required ip tcp::endpoint + if (ip != sender) continue; + + bt_peer_connection* p = dynamic_cast(peer); + if (!p) return false; // peers that don's support chat message extension // should not be included either - if (!peer->supports_extension( - peer_connection::extended_chat_message)) - continue; + if (!p->supports_extension(extended_chat_message)) + return false; - // loop until we find the required ip address - if (ip == peer->get_socket()->sender()) - { - // send the message - peer->send_chat_message(message); - return true; - } + // send the message + p->write_chat_message(message); + return true; } return false; } @@ -669,11 +638,11 @@ namespace libtorrent if (m_ses == 0) throw_invalid_handle(); - boost::mutex::scoped_lock l(m_ses->m_mutex); - torrent* t = m_ses->find_torrent(m_info_hash); + session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); + boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); queue.clear(); - if (t == 0) return; + if (!t) return; if (!t->valid_metadata()) return; const piece_picker& p = t->picker(); diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index c1ac17c31..33a5562c0 100755 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -279,8 +279,7 @@ namespace libtorrent void torrent_info::read_torrent_info(const entry& torrent_file) { // extract the url of the tracker - entry const* i = torrent_file.find_key("announce-list"); - if (i) + if (entry const* i = torrent_file.find_key("announce-list")) { const entry::list_type& l = i->list(); for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j) @@ -302,19 +301,19 @@ namespace libtorrent torrent_file["announce"].string())); } // shuffle each tier - std::vector::iterator i = m_urls.begin(); - std::vector::iterator j; + std::vector::iterator start = m_urls.begin(); + std::vector::iterator stop; int current_tier = m_urls.front().tier; - for (j = m_urls.begin(); j != m_urls.end(); ++j) + for (stop = m_urls.begin(); stop != m_urls.end(); ++stop) { - if (j->tier != current_tier) + if (stop->tier != current_tier) { - std::random_shuffle(i, j); - i = j; - current_tier = j->tier; + std::random_shuffle(start, stop); + start = stop; + current_tier = stop->tier; } } - std::random_shuffle(i, j); + std::random_shuffle(start, stop); } else { @@ -330,6 +329,26 @@ namespace libtorrent } catch (type_error) {} + // if there are any url-seeds, extract them + try + { + entry const& url_seeds = torrent_file["url-list"]; + if (url_seeds.type() == entry::string_t) + { + m_url_seeds.push_back(url_seeds.string()); + } + else if (url_seeds.type() == entry::list_t) + { + entry::list_type const& l = url_seeds.list(); + for (entry::list_type::const_iterator i = l.begin(); + i != l.end(); ++i) + { + m_url_seeds.push_back(i->string()); + } + } + } + catch (type_error&) {} + // extract comment if (entry const* e = torrent_file.find_key("comment.utf-8")) { m_comment = e->string(); } @@ -409,6 +428,11 @@ namespace libtorrent } + void torrent_info::add_url_seed(std::string const& url) + { + m_url_seeds.push_back(url); + } + void torrent_info::set_comment(char const* str) { m_comment = str; @@ -458,7 +482,7 @@ namespace libtorrent for (fs::path::iterator j = boost::next(file_path.begin()); j != file_path.end(); ++j) { - path_e.list().push_back(*j); + path_e.list().push_back(entry(*j)); } } } @@ -513,7 +537,7 @@ namespace libtorrent trackers.list().push_back(tier); tier.list().clear(); } - tier.list().push_back(i->url); + tier.list().push_back(entry(i->url)); } trackers.list().push_back(tier); dict["announce-list"] = trackers; @@ -527,6 +551,24 @@ namespace libtorrent if (!m_created_by.empty()) dict["created by"] = m_created_by; + + if (!m_url_seeds.empty()) + { + if (m_url_seeds.size() == 1) + { + dict["url-list"] = m_url_seeds.front(); + } + else + { + entry& list = dict["url-list"]; + list = entry(entry::list_t); + for (std::vector::const_iterator i + = m_url_seeds.begin(); i != m_url_seeds.end(); ++i) + { + list.list().push_back(entry(*i)); + } + } + } dict["info"] = create_info_metadata(); @@ -584,4 +626,55 @@ namespace libtorrent else return piece_length(); } + + std::vector torrent_info::map_block(int piece, size_type offset + , int size) const + { + assert(num_files() > 0); + std::vector ret; + + size_type start = piece * (size_type)m_piece_length + offset; + + // find the file iterator and file offset + // TODO: make a vector that can map piece -> file index in O(1) + size_type file_offset = start; + std::vector::const_iterator file_iter; + + int counter = 0; + for (file_iter = begin_files();; ++counter, ++file_iter) + { + assert(file_iter != end_files()); + if (file_offset < file_iter->size) + { + file_slice f; + f.file_index = counter; + f.offset = file_offset; + f.size = (std::min)(file_iter->size - file_offset, (size_type)size); + size -= f.size; + file_offset += f.size; + ret.push_back(f); + } + + assert(size >= 0); + if (size <= 0) break; + + file_offset -= file_iter->size; + } + return ret; + } + + peer_request torrent_info::map_file(int file_index, size_type file_offset + , int size) const + { + size_type offset = file_offset; + for (int i = 0; i < file_index; ++i) + offset += file_at(i).size; + + peer_request ret; + ret.piece = offset / piece_length(); + ret.start = offset - ret.piece * piece_length(); + ret.length = size; + return ret; + } + } diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp index f83e2517d..34db162af 100755 --- a/src/tracker_manager.cpp +++ b/src/tracker_manager.cpp @@ -35,10 +35,11 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include "zlib.h" +#include + #include "libtorrent/tracker_manager.hpp" #include "libtorrent/http_tracker_connection.hpp" #include "libtorrent/udp_tracker_connection.hpp" @@ -49,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; using boost::tuples::make_tuple; using boost::tuples::tuple; +using boost::bind; namespace { @@ -76,54 +78,11 @@ namespace namespace libtorrent { -/* - address parse_url(std::string const& url) - { - std::string hostname; // hostname only - int port = 80; + using boost::posix_time::second_clock; + using boost::posix_time::seconds; + using boost::posix_time::ptime; + using boost::posix_time::time_duration; - // PARSE URL - std::string::const_iterator start = url.begin(); - std::string::const_iterator end - = std::find(url.begin(), url.end(), ':'); - - while ((*start == ' ' || *start == '\t') && start != end) ++start; - - if (end == url.end()) throw std::runtime_error("invalid url: \"" + url + "\""); - ++end; - if (end == url.end()) throw std::runtime_error("invalid url: \"" + url + "\""); - if (*end != '/') throw std::runtime_error("invalid url: \"" + url + "\""); - ++end; - if (end == url.end()) throw std::runtime_error("invalid url: \"" + url + "\""); - if (*end != '/') throw std::runtime_error("invalid url: \"" + url + "\""); - ++end; - start = end; - - end = std::find(start, url.end(), '/'); - std::string::const_iterator port_pos - = std::find(start, url.end(), ':'); - - if (port_pos < end) - { - hostname.assign(start, port_pos); - ++port_pos; - try - { - port = boost::lexical_cast(std::string(port_pos, end)); - } - catch(boost::bad_lexical_cast&) - { - throw std::runtime_error("invalid url: \"" + url + "\""); - } - } - else - { - hostname.assign(start, end); - } - - return address(hostname.c_str(), port); - } -*/ // returns -1 if gzip header is invalid or the header size in bytes int gzip_header(const char* buf, int size) { @@ -332,6 +291,101 @@ namespace libtorrent return ret; } + void intrusive_ptr_add_ref(timeout_handler const* c) + { + assert(c != 0); + assert(c->m_refs >= 0); + timeout_handler::mutex_t::scoped_lock l(c->m_mutex); + ++c->m_refs; + } + + void intrusive_ptr_release(timeout_handler const* c) + { + assert(c != 0); + assert(c->m_refs > 0); + timeout_handler::mutex_t::scoped_lock l(c->m_mutex); + --c->m_refs; + if (c->m_refs == 0) + { + l.unlock(); + delete c; + } + } + + + timeout_handler::timeout_handler(demuxer& d) + : m_demuxer(d) + , m_start_time(second_clock::universal_time()) + , m_read_time(second_clock::universal_time()) + , m_timeout(d) + , m_completion_timeout(0) + , m_read_timeout(0) + , m_refs(0) + {} + + void timeout_handler::set_timeout(int completion_timeout, int read_timeout) + { + m_completion_timeout = completion_timeout; + m_read_timeout = read_timeout; + m_start_time = second_clock::universal_time(); + m_read_time = second_clock::universal_time(); + + m_timeout.expires_at(std::min( + m_read_time + seconds(m_read_timeout) + , m_start_time + seconds(m_completion_timeout))); + m_timeout.async_wait(bind(&timeout_handler::timeout_callback, self(), _1)); + } + + void timeout_handler::restart_read_timeout() + { + m_read_time = second_clock::universal_time(); + } + + void timeout_handler::cancel() + { + m_timeout.cancel(); + m_completion_timeout = 0; + } + + void timeout_handler::timeout_callback(asio::error const& error) try + { + if (error) return; + if (m_completion_timeout == 0) return; + + ptime now(second_clock::universal_time()); + time_duration receive_timeout = now - m_read_time; + time_duration completion_timeout = now - m_start_time; + + if (m_read_timeout + < receive_timeout.total_seconds() + || m_completion_timeout + < completion_timeout.total_seconds()) + { + on_timeout(); + return; + } + + m_timeout.expires_at(std::min( + m_read_time + seconds(m_read_timeout) + , m_start_time + seconds(m_completion_timeout))); + m_timeout.async_wait(bind(&timeout_handler::timeout_callback, self(), _1)); + } + catch (std::exception& e) + { + assert(false); + } + + tracker_connection::tracker_connection( + tracker_manager& man + , tracker_request req + , demuxer& d + , boost::weak_ptr r) + : timeout_handler(d) + , m_requester(r) + , m_man(man) + , m_req(req) + {} + request_callback& tracker_connection::requester() { boost::shared_ptr r = m_requester.lock(); @@ -339,92 +393,97 @@ namespace libtorrent return *r; } - - void tracker_manager::tick() + void tracker_connection::fail(int code, char const* msg) { - tracker_connections_t::iterator i; - for (i = m_connections.begin(); i != m_connections.end();) - { - boost::shared_ptr& c = *i; - try - { - if (!c->tick()) - { - ++i; - continue; - } - } - catch (const std::exception& e) - { - if (c->has_requester()) - c->requester().tracker_request_error(c->tracker_req() - , -1, e.what()); - } - if (c->has_requester()) c->requester().m_manager = 0; - i = m_connections.erase(i); - } + if (has_requester()) requester().tracker_request_error( + m_req, code, msg); + close(); } - namespace + void tracker_connection::fail_timeout() { + if (has_requester()) requester().tracker_request_timed_out(m_req); + close(); + } + + void tracker_connection::close() + { + m_man.remove_request(this); + cancel(); + } - tuple - parse_url_components(std::string url) + void tracker_manager::remove_request(tracker_connection const* c) + { + mutex_t::scoped_lock l(m_mutex); + + tracker_connections_t::iterator i = std::find(m_connections.begin() + , m_connections.end(), boost::intrusive_ptr(c)); + if (i == m_connections.end()) return; + + m_connections.erase(i); + } + + tuple + parse_url_components(std::string url) + { + std::string hostname; // hostname only + std::string protocol; // should be http + int port = 80; + + // PARSE URL + std::string::iterator start = url.begin(); + // remove white spaces in front of the url + while (start != url.end() && (*start == ' ' || *start == '\t')) + ++start; + std::string::iterator end + = std::find(url.begin(), url.end(), ':'); + protocol = std::string(start, end); + + if (end == url.end()) throw std::runtime_error("invalid url"); + ++end; + if (end == url.end()) throw std::runtime_error("invalid url"); + if (*end != '/') throw std::runtime_error("invalid url"); + ++end; + if (end == url.end()) throw std::runtime_error("invalid url"); + if (*end != '/') throw std::runtime_error("invalid url"); + ++end; + start = end; + + end = std::find(start, url.end(), '/'); + std::string::iterator port_pos + = std::find(start, url.end(), ':'); + + if (port_pos < end) { - std::string hostname; // hostname only - std::string protocol; // should be http - int port = 80; - - // PARSE URL - std::string::iterator start = url.begin(); - std::string::iterator end - = std::find(url.begin(), url.end(), ':'); - protocol = std::string(start, end); - - if (end == url.end()) throw std::runtime_error("invalid url"); - ++end; - if (end == url.end()) throw std::runtime_error("invalid url"); - if (*end != '/') throw std::runtime_error("invalid url"); - ++end; - if (end == url.end()) throw std::runtime_error("invalid url"); - if (*end != '/') throw std::runtime_error("invalid url"); - ++end; - start = end; - - end = std::find(start, url.end(), '/'); - std::string::iterator port_pos - = std::find(start, url.end(), ':'); - - if (port_pos < end) + hostname.assign(start, port_pos); + ++port_pos; + try { - hostname.assign(start, port_pos); - ++port_pos; - try - { - port = boost::lexical_cast(std::string(port_pos, end)); - } - catch(boost::bad_lexical_cast&) - { - throw std::runtime_error("invalid url: \"" + url - + "\", port number expected"); - } + port = boost::lexical_cast(std::string(port_pos, end)); } - else + catch(boost::bad_lexical_cast&) { - hostname.assign(start, end); + throw std::runtime_error("invalid url: \"" + url + + "\", port number expected"); } - - start = end; - return make_tuple(protocol, hostname, port - , std::string(start, url.end())); } + else + { + hostname.assign(start, end); + } + + start = end; + return make_tuple(protocol, hostname, port + , std::string(start, url.end())); } void tracker_manager::queue_request( - tracker_request req + demuxer& d + , tracker_request req , std::string const& auth , boost::weak_ptr c) { + mutex_t::scoped_lock l(m_mutex); assert(req.num_want >= 0); if (req.event == tracker_request::stopped) req.num_want = 0; @@ -439,28 +498,31 @@ namespace libtorrent boost::tie(protocol, hostname, port, request_string) = parse_url_components(req.url); - boost::shared_ptr con; + boost::intrusive_ptr con; if (protocol == "http") { - con.reset(new http_tracker_connection( - *this + con = new http_tracker_connection( + d + , *this , req , hostname , port , request_string , c , m_settings - , auth)); + , auth); } else if (protocol == "udp") { - con.reset(new udp_tracker_connection( - req + con = new udp_tracker_connection( + d + , *this + , req , hostname , port , c - , m_settings)); + , m_settings); } else { @@ -473,54 +535,28 @@ namespace libtorrent } catch (std::exception& e) { - if (!c.expired()) - { - boost::shared_ptr r = c.lock(); + if (boost::shared_ptr r = c.lock()) r->tracker_request_error(req, -1, e.what()); - } } } -/* - void tracker_manager::abort_request(request_callback* c) - { - assert(c != 0); - tracker_connections_t::iterator i; - for (i = m_connections.begin(); i != m_connections.end(); ++i) - { - if ((*i)->requester() == c) - { - m_connections.erase(i); - break; - } - } - } -*/ void tracker_manager::abort_all_requests() { // removes all connections from m_connections // except those with a requester == 0 (since those are // 'event=stopped'-requests) + mutex_t::scoped_lock l(m_mutex); tracker_connections_t keep_connections; for (tracker_connections_t::const_iterator i = - m_connections.begin(); i != m_connections.end(); ++i) + m_connections.begin(); i != m_connections.end(); ++i) { - if (!(*i)->has_requester()) keep_connections.push_back(*i); + tracker_request const& req = (*i)->tracker_req(); + if (req.event == tracker_request::stopped) + keep_connections.push_back(*i); } std::swap(m_connections, keep_connections); } - - bool tracker_manager::send_finished() const - { - for (tracker_connections_t::const_iterator i = - m_connections.begin(); i != m_connections.end(); ++i) - { - if (!(*i)->send_finished()) return false; - } - return true; - } - } diff --git a/src/udp_tracker_connection.cpp b/src/udp_tracker_connection.cpp index 396711197..cff4ae1a4 100755 --- a/src/udp_tracker_connection.cpp +++ b/src/udp_tracker_connection.cpp @@ -38,6 +38,17 @@ POSSIBILITY OF SUCH DAMAGE. #include "zlib.h" +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "libtorrent/tracker_manager.hpp" #include "libtorrent/udp_tracker_connection.hpp" #include "libtorrent/io.hpp" @@ -55,135 +66,183 @@ namespace } using namespace boost::posix_time; +using boost::bind; +using boost::lexical_cast; namespace libtorrent { udp_tracker_connection::udp_tracker_connection( - tracker_request const& req + demuxer& d + , tracker_manager& man + , tracker_request const& req , std::string const& hostname , unsigned short port , boost::weak_ptr c , const http_settings& stn) - : tracker_connection(c) - , m_request_time(second_clock::universal_time()) - , m_request(req) + : tracker_connection(man, req, d, c) + , m_man(man) + , m_name_lookup(d) + , m_port(port) , m_transaction_id(0) , m_connection_id(0) , m_settings(stn) , m_attempts(0) { - m_name_lookup = dns_lookup(hostname.c_str(), port); - m_socket.reset(new socket(socket::udp, false)); - + m_socket.reset(new datagram_socket(d)); + m_name_lookup.async_by_name(m_host, hostname.c_str() + , bind(&udp_tracker_connection::name_lookup, self(), _1)); + set_timeout(m_settings.tracker_completion_timeout + , m_settings.tracker_receive_timeout); } - bool udp_tracker_connection::send_finished() const + void udp_tracker_connection::name_lookup(asio::error const& error) try { - using namespace boost::posix_time; - - time_duration d = second_clock::universal_time() - m_request_time; - return (m_transaction_id != 0 - && m_connection_id != 0) - || d > seconds(m_settings.tracker_timeout); - } - - bool udp_tracker_connection::tick() - { - using namespace boost::posix_time; - - if (m_name_lookup.running()) + if (error == asio::error::operation_aborted) return; + if (error) { - if (!m_name_lookup.finished()) return false; - - if (m_name_lookup.failed()) - { - if (has_requester()) requester().tracker_request_error( - m_request, -1, m_name_lookup.error()); - return true; - } - address a(m_name_lookup.ip()); - if (has_requester()) requester().m_tracker_address = a; + fail(-1, error.what()); + return; + } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) requester().debug_log("name lookup successful"); + if (has_requester()) requester().debug_log("udp tracker name lookup successful"); #endif - - m_socket->connect(a); - send_udp_connect(); - - // clear the lookup entry so it will not be - // marked as running anymore - m_name_lookup = dns_lookup(); - } - - time_duration d = second_clock::universal_time() - m_request_time; - if (m_connection_id == 0 - && d > seconds(udp_connect_timeout)) - { - if (m_attempts >= udp_connection_retries) - { - if (has_requester()) - requester().tracker_request_timed_out(m_request); - return true; - } - send_udp_connect(); - return false; - } - - if (m_connection_id != 0 - && d > seconds(udp_announce_timeout)) - { - if (m_attempts >= udp_announce_retries) - { - if (has_requester()) - requester().tracker_request_timed_out(m_request); - return true; - } - - if (m_request.kind == tracker_request::announce_request) - send_udp_announce(); - else if (m_request.kind == tracker_request::scrape_request) - send_udp_scrape(); - return false; - } - - char buf[udp_buffer_size]; - int ret = m_socket->receive(buf, udp_buffer_size); - - // if there was nothing to receive, return - if (ret == 0) return false; - if (ret < 0) - { - socket::error_code err = m_socket->last_error(); - if (err == socket::would_block) return false; - throw network_error(m_socket->last_error()); - } - if (ret == udp_buffer_size) - { - if (has_requester()) - requester().tracker_request_error( - m_request, -1, "tracker reply too big"); - return true; - } - - if (m_connection_id == 0) - { - return parse_connect_response(buf, ret); - } - else if (m_request.kind == tracker_request::announce_request) - { - return parse_announce_response(buf, ret); - } - else if (m_request.kind == tracker_request::scrape_request) - { - return parse_scrape_response(buf, ret); - } - + restart_read_timeout(); + m_target = udp::endpoint(m_port, m_host.address(0)); + if (has_requester()) requester().m_tracker_address + = tcp::endpoint(m_port, m_host.address(0)); + m_socket->connect(m_target); + send_udp_connect(); + } + catch (std::exception& e) + { + fail(-1, e.what()); assert(false); - return false; } + void udp_tracker_connection::on_timeout() + { + m_socket.reset(); + m_name_lookup.cancel(); + fail_timeout(); + } + + void udp_tracker_connection::send_udp_connect() + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("==> UDP_TRACKER_CONNECT [" + + lexical_cast(tracker_req().info_hash) + "]"); + } +#endif + + char send_buf[16]; + char* ptr = send_buf; + + if (m_transaction_id == 0) + m_transaction_id = rand() ^ (rand() << 16); + + // connection_id + detail::write_uint32(0x417, ptr); + detail::write_uint32(0x27101980, ptr); + // action (connect) + detail::write_int32(action_connect, ptr); + // transaction_id + detail::write_int32(m_transaction_id, ptr); + + m_socket->send(asio::buffer((void*)send_buf, 16), 0); + ++m_attempts; + m_buffer.resize(udp_buffer_size); + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + } + + void udp_tracker_connection::connect_response(asio::error const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (error) + { + fail(-1, error.what()); + return; + } + + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + + const char* ptr = &m_buffer[0]; + int action = detail::read_int32(ptr); + int transaction = detail::read_int32(ptr); + + if (action == action_error) + { + fail(-1, std::string(ptr, bytes_transferred - 8).c_str()); + return; + } + + if (action != action_connect) + { + fail(-1, "invalid action in connect reply"); + return; + } + + if (m_transaction_id != transaction) + { + fail(-1, "incorrect transaction id"); + return; + } + + if (bytes_transferred < 16) + { + fail(-1, "udp_tracker_connection: " + "got a message with size < 16"); + return; + } + // reset transaction + m_transaction_id = 0; + m_attempts = 0; + m_connection_id = detail::read_int64(ptr); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("<== UDP_TRACKER_CONNECT_RESPONSE [" + + lexical_cast(m_connection_id) + "]"); + } +#endif + + if (tracker_req().kind == tracker_request::announce_request) + send_udp_announce(); + else if (tracker_req().kind == tracker_request::scrape_request) + send_udp_scrape(); + } + catch (std::exception& e) + { + fail(-1, e.what()); + assert(false); + } + void udp_tracker_connection::send_udp_announce() { if (m_transaction_id == 0) @@ -191,39 +250,51 @@ namespace libtorrent std::vector buf; std::back_insert_iterator > out(buf); + + tracker_request const& req = tracker_req(); // connection_id detail::write_int64(m_connection_id, out); // action (announce) - detail::write_int32(announce, out); + detail::write_int32(action_announce, out); // transaction_id detail::write_int32(m_transaction_id, out); // info_hash - std::copy(m_request.info_hash.begin(), m_request.info_hash.end(), out); + std::copy(req.info_hash.begin(), req.info_hash.end(), out); // peer_id - std::copy(m_request.id.begin(), m_request.id.end(), out); + std::copy(req.pid.begin(), req.pid.end(), out); // downloaded - detail::write_int64(m_request.downloaded, out); + detail::write_int64(req.downloaded, out); // left - detail::write_int64(m_request.left, out); + detail::write_int64(req.left, out); // uploaded - detail::write_int64(m_request.uploaded, out); + detail::write_int64(req.uploaded, out); // event - detail::write_int32(m_request.event, out); + detail::write_int32(req.event, out); // ip address detail::write_int32(0, out); // key - detail::write_int32(m_request.key, out); + detail::write_int32(req.key, out); // num_want - detail::write_int32(m_request.num_want, out); + detail::write_int32(req.num_want, out); // port - detail::write_uint16(m_request.listen_port, out); + detail::write_uint16(req.listen_port, out); // extensions detail::write_uint16(0, out); - m_socket->send(&buf[0], buf.size()); - m_request_time = second_clock::universal_time(); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("==> UDP_TRACKER_ANNOUNCE [" + + lexical_cast(req.info_hash) + "]"); + } +#endif + + m_socket->send(asio::buffer(buf), 0); ++m_attempts; + + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , bind(&udp_tracker_connection::announce_response, self(), _1, _2)); } void udp_tracker_connection::send_udp_scrape() @@ -237,91 +308,100 @@ namespace libtorrent // connection_id detail::write_int64(m_connection_id, out); // action (scrape) - detail::write_int32(scrape, out); + detail::write_int32(action_scrape, out); // transaction_id detail::write_int32(m_transaction_id, out); // info_hash - std::copy(m_request.info_hash.begin(), m_request.info_hash.end(), out); + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end(), out); - m_socket->send(&buf[0], buf.size()); - m_request_time = second_clock::universal_time(); + m_socket->send(asio::buffer(&buf[0], buf.size()), 0); ++m_attempts; + + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , bind(&udp_tracker_connection::scrape_response, self(), _1, _2)); } - void udp_tracker_connection::send_udp_connect() + void udp_tracker_connection::announce_response(asio::error const& error + , std::size_t bytes_transferred) try { - char send_buf[16]; - char* ptr = send_buf; - - if (m_transaction_id == 0) - m_transaction_id = rand() ^ (rand() << 16); - - // connection_id - detail::write_uint32(0x417, ptr); - detail::write_uint32(0x27101980, ptr); - // action (connect) - detail::write_int32(connect, ptr); - // transaction_id - detail::write_int32(m_transaction_id, ptr); - - m_socket->send(send_buf, 16); - m_request_time = second_clock::universal_time(); - ++m_attempts; - } - - bool udp_tracker_connection::parse_announce_response(const char* buf, int len) - { - assert(buf != 0); - assert(len > 0); - - if (len < 8) + if (error == asio::error::operation_aborted) return; + if (error) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with size < 8, ignoring"); -#endif - return false; + fail(-1, error.what()); + return; } + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + char* buf = &m_buffer[0]; int action = detail::read_int32(buf); int transaction = detail::read_int32(buf); + if (transaction != m_transaction_id) { - return false; + fail(-1, "incorrect transaction id"); + return; } - if (action == error) + if (action == action_error) { - if (has_requester()) - requester().tracker_request_error( - m_request, -1, std::string(buf, buf + len - 8)); - return true; + fail(-1, std::string(buf, bytes_transferred - 8).c_str()); + return; } - if (action != announce) return false; - if (len < 20) + if (action != action_announce) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with size < 20, ignoring"); -#endif - return false; + fail(-1, "invalid action in announce response"); + return; } + + if (bytes_transferred < 20) + { + fail(-1, "got a message with size < 20"); + return; + } + int interval = detail::read_int32(buf); int incomplete = detail::read_int32(buf); int complete = detail::read_int32(buf); - int num_peers = (len - 20) / 6; - if ((len - 20) % 6 != 0) + int num_peers = (bytes_transferred - 20) / 6; + if ((bytes_transferred - 20) % 6 != 0) { - if (has_requester()) - requester().tracker_request_error( - m_request, -1, "invalid tracker response"); - return true; + fail(-1, "invalid udp tracker response length"); + return; } - if (!has_requester()) return true; +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (has_requester()) + { + requester().debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); + } +#endif + + if (!has_requester()) + { + m_man.remove_request(this); + return; + } std::vector peer_list; for (int i = 0; i < num_peers; ++i) @@ -334,126 +414,102 @@ namespace libtorrent s << (int)detail::read_uint8(buf); e.ip = s.str(); e.port = detail::read_uint16(buf); - e.id.clear(); + e.pid.clear(); peer_list.push_back(e); } - requester().tracker_response(m_request, peer_list, interval + requester().tracker_response(tracker_req(), peer_list, interval , complete, incomplete); - return true; + + m_man.remove_request(this); + return; } - - bool udp_tracker_connection::parse_scrape_response(const char* buf, int len) + catch (std::exception& e) { - assert(buf != 0); - assert(len > 0); + fail(-1, e.what()); + assert(false); + }; // msvc 7.1 seems to require this - if (len < 8) + void udp_tracker_connection::scrape_response(asio::error const& error + , std::size_t bytes_transferred) try + { + if (error == asio::error::operation_aborted) return; + if (error) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with size < 8, ignoring"); -#endif - return false; + fail(-1, error.what()); + return; } + if (m_target != m_sender) + { + // this packet was not received from the tracker + m_socket->async_receive_from(asio::buffer(m_buffer), 0, m_sender + , bind(&udp_tracker_connection::connect_response, self(), _1, _2)); + return; + } + + if (bytes_transferred >= udp_buffer_size) + { + fail(-1, "udp response too big"); + return; + } + + if (bytes_transferred < 8) + { + fail(-1, "got a message with size < 8"); + return; + } + + restart_read_timeout(); + char* buf = &m_buffer[0]; int action = detail::read_int32(buf); int transaction = detail::read_int32(buf); + if (transaction != m_transaction_id) { - return false; + fail(-1, "incorrect transaction id"); + return; } - if (action == error) + if (action == action_error) { - if (has_requester()) - requester().tracker_request_error( - m_request, -1, std::string(buf, buf + len - 8)); - return true; + fail(-1, std::string(buf, bytes_transferred - 8).c_str()); + return; } - if (action != scrape) return false; - if (len < 20) + if (action != action_scrape) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with size < 20, ignoring"); -#endif - return false; + fail(-1, "invalid action in announce response"); + return; } + + if (bytes_transferred < 20) + { + fail(-1, "got a message with size < 20"); + return; + } + int complete = detail::read_int32(buf); /*int downloaded = */detail::read_int32(buf); int incomplete = detail::read_int32(buf); - if (!has_requester()) return true; - + if (!has_requester()) + { + m_man.remove_request(this); + return; + } + std::vector peer_list; - requester().tracker_response(m_request, peer_list, 0 + requester().tracker_response(tracker_req(), peer_list, 0 , complete, incomplete); - return true; + + m_man.remove_request(this); } - - - bool udp_tracker_connection::parse_connect_response(const char* buf, int len) + catch (std::exception& e) { - assert(buf != 0); - assert(len > 0); - - if (len < 8) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with size < 8, ignoring"); -#endif - return false; - } - const char* ptr = buf; - int action = detail::read_int32(ptr); - int transaction = detail::read_int32(ptr); - - if (action == error) - { - if (has_requester()) - requester().tracker_request_error( - m_request, -1, std::string(ptr, buf + len)); - return true; - } - if (action != connect) return false; - if (m_transaction_id != transaction) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a message with incorrect transaction id, ignoring"); -#endif - return false; - } - - if (len < 16) - { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (has_requester()) - requester().debug_log("udp_tracker_connection: " - "got a connection message size < 16, ignoring"); -#endif - return false; - } - // reset transaction - m_transaction_id = 0; - m_attempts = 0; - m_connection_id = detail::read_int64(ptr); - - if (m_request.kind == tracker_request::announce_request) - send_udp_announce(); - else if (m_request.kind == tracker_request::scrape_request) - send_udp_scrape(); - - return false; + fail(-1, e.what()); + assert(false); } - } diff --git a/src/web_peer_connection.cpp b/src/web_peer_connection.cpp new file mode 100755 index 000000000..25989fe07 --- /dev/null +++ b/src/web_peer_connection.cpp @@ -0,0 +1,417 @@ +/* + +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 +#include +#include +#include +#include +#include + +#include "libtorrent/web_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 +{ + web_peer_connection::web_peer_connection( + detail::session_impl& ses + , boost::weak_ptr t + , boost::shared_ptr s + , tcp::endpoint const& remote + , std::string const& url) + : peer_connection(ses, t, s, remote) + , m_url(url) + , m_first_request(true) + { + INVARIANT_CHECK; + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << "*** web_peer_connection\n"; +#endif + + std::string protocol; + boost::tie(protocol, m_host, m_port, m_path) + = parse_url_components(url); + + m_server_string = "URL seed @ "; + m_server_string += m_host; + } + + web_peer_connection::~web_peer_connection() + {} + + boost::optional + web_peer_connection::downloading_piece_progress() const + { + if (!m_parser.header_finished() || m_requests.empty()) + return boost::optional(); + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + int body_start = m_parser.body_start(); + buffer::const_interval recv_buffer = receive_buffer(); + assert(body_start <= recv_buffer.left()); + piece_block_progress ret; + + ret.piece_index = m_requests.front().piece; + ret.block_index = m_requests.front().start / t->block_size(); + ret.bytes_downloaded = recv_buffer.left() - body_start; + ret.full_block_bytes = m_requests.front().length; + return ret; + } + + void web_peer_connection::on_connected() + { + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + // this is always a seed + incoming_bitfield(std::vector( + t->torrent_file().num_pieces(), true)); + // it is always possible to request pieces + incoming_unchoke(); + + reset_recv_buffer(512*1024+1024); + } + + void web_peer_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + assert(t->valid_metadata()); + + bool single_file_request = false; + if (!m_path.empty() && m_path[m_path.size() - 1] != '/') + single_file_request = true; + + torrent_info const& info = t->torrent_file(); + + std::string request; + + m_requests.push_back(r); + + if (single_file_request) + { + request += "GET "; + request += escape_path(m_path.c_str(), m_path.length()); + request += " HTTP/1.1\r\n"; + request += "Host: "; + request += m_host; + if (m_first_request) + { + request += "\r\nUser-Agent: "; + request += escape_string(m_ses.m_http_settings.user_agent.c_str() + , m_ses.m_http_settings.user_agent.size()); + } + request += "\r\nRange: bytes="; + request += boost::lexical_cast(r.piece + * info.piece_length() + r.start); + request += "-"; + request += boost::lexical_cast(r.piece + * info.piece_length() + r.start + r.length - 1); + if (m_first_request) + request += "\r\nConnection: keep-alive"; + request += "\r\n\r\n"; + m_first_request = false; + m_file_requests.push_back(0); + } + else + { + std::vector files = info.map_block(r.piece, r.start + , r.length); + + for (std::vector::iterator i = files.begin(); + i != files.end(); ++i) + { + file_slice const& f = *i; + + std::string path = m_path; + path += info.file_at(f.file_index).path.string(); + request += "GET "; + request += escape_path(path.c_str(), path.length()); + request += " HTTP/1.1\r\n"; + request += "Host: "; + request += m_host; + if (m_first_request) + { + request += "\r\nUser-Agent: "; + request += escape_string(m_ses.m_http_settings.user_agent.c_str() + , m_ses.m_http_settings.user_agent.size()); + } + request += "\r\nRange: bytes="; + request += boost::lexical_cast(f.offset); + request += "-"; + request += boost::lexical_cast(f.offset + f.size - 1); + if (m_first_request) + request += "\r\nConnection: keep-alive"; + request += "\r\n\r\n"; + m_first_request = false; + m_file_requests.push_back(f.file_index); + } + } + + send_buffer(request.c_str(), request.c_str() + request.size()); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + // throws exception when the client should be disconnected + void web_peer_connection::on_receive(const asio::error& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + return; + } + + boost::shared_ptr t = associated_torrent().lock(); + assert(t); + + incoming_piece_fragment(); + + for (;;) + { + buffer::const_interval recv_buffer = receive_buffer(); + int payload; + int protocol; + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer); + m_statistics.received_bytes(payload, protocol); + + if (m_parser.status_code() != 206 && m_parser.status_code() != -1) + { + // we should not try this server again. + t->remove_url_seed(m_url); + if (m_parser.status_code() == 404) + throw std::runtime_error("File not found on server"); + throw std::runtime_error("HTTP server does not support byte range requests"); + } + + if (!m_parser.finished()) break; + + std::string server_version = m_parser.header("Server"); + if (!server_version.empty()) + { + m_server_string = "URL seed @ "; + m_server_string += m_host; + m_server_string += " ("; + m_server_string += server_version; + m_server_string += ")"; + } + + std::stringstream range_str(m_parser.header("Content-Range")); + size_type range_start; + size_type range_end; + char dummy; + std::string bytes; + range_str >> bytes >> range_start >> dummy >> range_end; + if (!range_str) + { + // we should not try this server again. + t->remove_url_seed(m_url); + throw std::runtime_error("invalid range in HTTP response: " + range_str.str()); + } + // the http range is inclusive + range_end++; + + torrent_info const& info = t->torrent_file(); + + if (m_requests.empty() || m_file_requests.empty()) + throw std::runtime_error("unexpected HTTP response"); + + int file_index = m_file_requests.front(); + m_file_requests.pop_front(); + + peer_request r = info.map_file(file_index, range_start + , range_end - range_start); + + buffer::const_interval http_body = m_parser.get_body(); + + if (r == m_requests.front()) + { + m_requests.pop_front(); + incoming_piece(r, http_body.begin); + cut_receive_buffer(http_body.end - recv_buffer.begin, 512*1024+1024); + return; + } + + if (!m_piece.empty()) + { + // this is not the first partial request we get + if (m_intermediate_piece.start + m_intermediate_piece.length != r.start + || m_intermediate_piece.piece != r.piece) + { + throw std::runtime_error("invalid range in HTTP response"); + } + } + else + { + // this is the first part of a partial request + if (r.start != m_requests.front().start + || r.piece != m_requests.front().piece) + { + throw std::runtime_error("invalid range in HTTP response"); + } + m_intermediate_piece.piece = r.piece; + m_intermediate_piece.start = r.start; + m_intermediate_piece.length = 0; + } + + m_piece.reserve(info.piece_length()); + std::copy(http_body.begin, http_body.end, back_inserter(m_piece)); + m_intermediate_piece.length += r.length; + if (m_intermediate_piece.length == m_requests.front().length) + { + assert(m_requests.front() == m_intermediate_piece); + assert(int(m_piece.size()) == m_intermediate_piece.length); + m_requests.pop_front(); + incoming_piece(m_intermediate_piece, &m_piece[0]); + m_piece.clear(); + } + else if (m_intermediate_piece.length > m_requests.front().length) + { + throw std::runtime_error("too large HTTP response body"); + } + + cut_receive_buffer(http_body.end - recv_buffer.begin, 512*1024+1024); + } + } + + // -------------------------- + // SEND DATA + // -------------------------- + + void web_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::max()) + p.upload_limit = -1; + else + p.upload_limit = m_ul_bandwidth_quota.given; + + if (m_dl_bandwidth_quota.given == std::numeric_limits::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 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 (is_local()) p.flags |= peer_info::local_connection; + if (!is_connecting() && m_server_string.empty()) + 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_server_string; + p.connection_type = peer_info::web_seed; + } + + // throws exception when the client should be disconnected + void web_peer_connection::on_sent(asio::error const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + m_statistics.sent_bytes(0, bytes_transferred); + } + + +#ifndef NDEBUG + void web_peer_connection::check_invariant() const + { +/* + assert(m_num_pieces == std::count( + m_have_piece.begin() + , m_have_piece.end() + , true)); +*/ } +#endif + +} + diff --git a/test/Jamfile b/test/Jamfile index 4138e9d1d..e6ecbf5db 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -15,5 +15,6 @@ test-suite libtorrent : [ run test_bencoding.cpp ] [ run test_ip_filter.cpp ] [ run test_hasher.cpp ] + [ run test_metadata_extension.cpp ] ; diff --git a/test/Makefile.am b/test/Makefile.am index b1fb13a62..dd9d1ac4d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = test_hasher test_bencoding test_ip_filter test_piece_picker test_storage +bin_PROGRAMS = test_hasher test_bencoding test_ip_filter test_piece_picker test_storage test_metadata_extension test_buffer EXTRA_DIST = Jamfile test_hasher_SOURCES = main.cpp test_hasher.cpp @@ -16,7 +16,13 @@ test_piece_picker_LDADD = $(top_builddir)/src/libtorrent.la test_storage_SOURCES = main.cpp test_storage.cpp test_storage_LDADD = $(top_builddir)/src/libtorrent.la +test_buffer_SOURCES = main.cpp test_buffer.cpp +test_buffer_LDADD = $(top_builddir)/src/libtorrent.la + +test_metadata_extension_SOURCES = main.cpp test_metadata_extension.cpp +test_metadata_extension_LDADD = $(top_builddir)/src/libtorrent.la + noinst_HEADERS = test.hpp -AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/asio/include @DEBUGFLAGS@ @PTHREAD_CFLAGS@ AM_LDFLAGS= -L./ -l@BOOST_DATE_TIME_LIB@ -l@BOOST_FILESYSTEM_LIB@ -l@BOOST_THREAD_LIB@ @PTHREAD_LIBS@ diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp index 592a1751e..4c232be9d 100644 --- a/test/test_bencoding.cpp +++ b/test/test_bencoding.cpp @@ -63,8 +63,8 @@ int test_main() // ** dictionaries ** { entry e(entry::dictionary_t); - e["cow"] = entry("moo"); e["spam"] = entry("eggs"); + e["cow"] = entry("moo"); TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); TEST_CHECK(decode(encode(e)) == e); } diff --git a/test/test_ip_filter.cpp b/test/test_ip_filter.cpp index d7afd2a10..0a1bc56fb 100644 --- a/test/test_ip_filter.cpp +++ b/test/test_ip_filter.cpp @@ -19,8 +19,8 @@ void test_rules_invariant(std::vector const& r, ip_filter c TEST_CHECK(!r.empty()); if (r.empty()) return; - TEST_CHECK(r.front().first == address(0,0,0,0,0)); - TEST_CHECK(r.back().last == address(255,255,255,255,0)); + TEST_CHECK(r.front().first == address("0.0.0.0")); + TEST_CHECK(r.back().last == address("255.255.255.255")); iterator i = r.begin(); iterator j = boost::next(i); @@ -29,7 +29,7 @@ void test_rules_invariant(std::vector const& r, ip_filter c { TEST_CHECK(f.access(i->last) == i->flags); TEST_CHECK(f.access(j->first) == j->flags); - TEST_CHECK(i->last.ip() + 1 == j->first.ip()); + TEST_CHECK(i->last.to_ulong() + 1 == j->first.to_ulong()); } } @@ -41,15 +41,15 @@ int test_main() // **** test joining of ranges at the end **** ip_filter::ip_range expected1[] = { - {address(0,0,0,0,0), address(0,255,255,255,0), 0} - , {address(1,0,0,0,0), address(3,0,0,0,0), ip_filter::blocked} - , {address(3,0,0,1,0), address(255,255,255,255,0), 0} + {address("0.0.0.0"), address("0.255.255.255"), 0} + , {address("1.0.0.0"), address("3.0.0.0"), ip_filter::blocked} + , {address("3.0.0.1"), address("255.255.255.255"), 0} }; { ip_filter f; - f.add_rule(address(1,0,0,0,0), address(2,0,0,0,0), ip_filter::blocked); - f.add_rule(address(2,0,0,1,0), address(3,0,0,0,0), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.0.0.0"), ip_filter::blocked); + f.add_rule(address("2.0.0.1"), address("3.0.0.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -62,8 +62,8 @@ int test_main() { ip_filter f; - f.add_rule(address(2,0,0,1,0), address(3,0,0,0,0), ip_filter::blocked); - f.add_rule(address(1,0,0,0,0), address(2,0,0,0,0), ip_filter::blocked); + f.add_rule(address("2.0.0.1"), address("3.0.0.0"), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.0.0.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -77,8 +77,8 @@ int test_main() { ip_filter f; - f.add_rule(address(2,0,0,1,0), address(3,0,0,0,0), ip_filter::blocked); - f.add_rule(address(1,0,0,0,0), address(2,4,0,0,0), ip_filter::blocked); + f.add_rule(address("2.0.0.1"), address("3.0.0.0"), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.4.0.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -92,8 +92,8 @@ int test_main() { ip_filter f; - f.add_rule(address(1,0,0,0,0), address(2,4,0,0,0), ip_filter::blocked); - f.add_rule(address(2,0,0,1,0), address(3,0,0,0,0), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.4.0.0"), ip_filter::blocked); + f.add_rule(address("2.0.0.1"), address("3.0.0.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -107,12 +107,12 @@ int test_main() { ip_filter f; - f.add_rule(address(1,0,0,0,0), address(2,0,0,0,0), ip_filter::blocked); - f.add_rule(address(3,0,0,0,0), address(4,0,0,0,0), ip_filter::blocked); - f.add_rule(address(5,0,0,0,0), address(6,0,0,0,0), ip_filter::blocked); - f.add_rule(address(7,0,0,0,0), address(8,0,0,0,0), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.0.0.0"), ip_filter::blocked); + f.add_rule(address("3.0.0.0"), address("4.0.0.0"), ip_filter::blocked); + f.add_rule(address("5.0.0.0"), address("6.0.0.0"), ip_filter::blocked); + f.add_rule(address("7.0.0.0"), address("8.0.0.0"), ip_filter::blocked); - f.add_rule(address(1,0,1,0,0), address(9,0,0,0,0), ip_filter::blocked); + f.add_rule(address("1.0.1.0"), address("9.0.0.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -120,9 +120,9 @@ int test_main() TEST_CHECK(range.size() == 3); ip_filter::ip_range expected[] = { - {address(0,0,0,0,0), address(0,255,255,255,0), 0} - , {address(1,0,0,0,0), address(9,0,0,0,0), ip_filter::blocked} - , {address(9,0,0,1,0), address(255,255,255,255,0), 0} + {address("0.0.0.0"), address("0.255.255.255"), 0} + , {address("1.0.0.0"), address("9.0.0.0"), ip_filter::blocked} + , {address("9.0.0.1"), address("255.255.255.255"), 0} }; TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); @@ -132,12 +132,12 @@ int test_main() { ip_filter f; - f.add_rule(address(1,0,0,0,0), address(2,0,0,0,0), ip_filter::blocked); - f.add_rule(address(3,0,0,0,0), address(4,0,0,0,0), ip_filter::blocked); - f.add_rule(address(5,0,0,0,0), address(6,0,0,0,0), ip_filter::blocked); - f.add_rule(address(7,0,0,0,0), address(8,0,0,0,0), ip_filter::blocked); + f.add_rule(address("1.0.0.0"), address("2.0.0.0"), ip_filter::blocked); + f.add_rule(address("3.0.0.0"), address("4.0.0.0"), ip_filter::blocked); + f.add_rule(address("5.0.0.0"), address("6.0.0.0"), ip_filter::blocked); + f.add_rule(address("7.0.0.0"), address("8.0.0.0"), ip_filter::blocked); - f.add_rule(address(0,0,1,0,0), address(7,0,4,0,0), ip_filter::blocked); + f.add_rule(address("0.0.1.0"), address("7.0.4.0"), ip_filter::blocked); range = f.export_filter(); test_rules_invariant(range, f); @@ -145,9 +145,9 @@ int test_main() TEST_CHECK(range.size() == 3); ip_filter::ip_range expected[] = { - {address(0,0,0,0,0), address(0,0,0,255,0), 0} - , {address(0,0,1,0,0), address(8,0,0,0,0), ip_filter::blocked} - , {address(8,0,0,1,0), address(255,255,255,255,0), 0} + {address("0.0.0.0"), address("0.0.0.255"), 0} + , {address("0.0.1.0"), address("8.0.0.0"), ip_filter::blocked} + , {address("8.0.0.1"), address("255.255.255.255"), 0} }; TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); diff --git a/test/test_metadata_extension.cpp b/test/test_metadata_extension.cpp new file mode 100644 index 000000000..4d1d33fa1 --- /dev/null +++ b/test/test_metadata_extension.cpp @@ -0,0 +1,106 @@ +#include "libtorrent/session.hpp" +#include "libtorrent/hasher.hpp" +#include + +#include "test.hpp" + +void sleep(int msec) +{ + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.nsec += msec * 1000000; + boost::thread::sleep(xt); +} + +void test_transfer(char const* tracker_url, libtorrent::torrent_info const& t) +{ + using namespace libtorrent; + + session ses1; + session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49000, 50000)); + + // they should not use the same save dir, because the + // file pool will complain if two torrents are trying to + // use the same files + torrent_handle tor1 = ses1.add_torrent(t, "./tmp1"); + torrent_handle tor2 = ses2.add_torrent(tracker_url + , t.info_hash(), "./tmp2"); + + std::cerr << "waiting for file check to complete\n"; + + // wait for 5 seconds or until the torrent is in a state + // were it can accept connections + for (int i = 0; i < 50; ++i) + { + torrent_status st = tor1.status(); + if (st.state != torrent_status::queued_for_checking + &&st.state != torrent_status::checking_files) + break; + sleep(100); + } + + std::cerr << "connecting peer\n"; + tor1.connect_peer(tcp::endpoint(ses2.listen_port(), "127.0.0.1")); + + for (int i = 0; i < 50; ++i) + { + // make sure this function can be called on + // torrents without metadata + tor2.status(); + if (tor2.has_metadata()) break; + sleep(100); + } + + std::cerr << "metadata received. waiting for transfer to complete\n"; + TEST_CHECK(tor2.has_metadata()); + + for (int i = 0; i < 50; ++i) + { + tor2.status(); + if (tor2.is_seed()) break; + sleep(100); + } + + std::cerr << "done\n"; + TEST_CHECK(tor2.is_seed()); +} + +int test_main() +{ + using namespace libtorrent; + using namespace boost::filesystem; + + char const* tracker_url = "http://non-existant-name.com/announce"; + + torrent_info t; + t.add_file(path("temporary"), 42); + t.set_piece_size(256 * 1024); + t.add_tracker(tracker_url); + + std::vector piece(42); + std::fill(piece.begin(), piece.end(), 0xfe); + + // calculate the hash for all pieces + int num = t.num_pieces(); + for (int i = 0; i < num; ++i) + { + t.set_hash(i, hasher(&piece[0], piece.size()).final()); + } + + create_directory("./tmp1"); + std::ofstream file("./tmp1/temporary"); + file.write(&piece[0], piece.size()); + file.close(); + remove_all("./tmp2/temporary"); + + t.create_torrent(); + + // test where one has data and one doesn't + test_transfer(tracker_url, t); + + // test where both have data (to trigger the file check) + test_transfer(tracker_url, t); + + return 0; +} + diff --git a/test/test_piece_picker.cpp b/test/test_piece_picker.cpp index 31c13fe5a..5c10fa8ad 100644 --- a/test/test_piece_picker.cpp +++ b/test/test_piece_picker.cpp @@ -12,7 +12,7 @@ int test_main() const int num_pieces = 6; // 4 blocks per piece - piece_picker p(4, num_pieces * 4); + piece_picker p(4, num_pieces * 4, 7); // we have the first piece std::vector have(num_pieces, false); @@ -86,21 +86,21 @@ int test_main() std::vector picked; picked.clear(); - p.pick_pieces(peer1, picked, 1, false, address()); + p.pick_pieces(peer1, picked, 1, false, tcp::endpoint()); TEST_CHECK(picked.size() == 1); TEST_CHECK(picked.front().piece_index == 2); // now pick a piece from peer2. The block is supposed to be // from piece 3, since it is the rarest piece that peer has. picked.clear(); - p.pick_pieces(peer2, picked, 1, false, address()); + p.pick_pieces(peer2, picked, 1, false, tcp::endpoint()); TEST_CHECK(picked.size() == 1); TEST_CHECK(picked.front().piece_index == 3); // same thing for peer3. picked.clear(); - p.pick_pieces(peer3, picked, 1, false, address()); + p.pick_pieces(peer3, picked, 1, false, tcp::endpoint()); TEST_CHECK(picked.size() == 1); TEST_CHECK(picked.front().piece_index == 5); @@ -114,7 +114,7 @@ int test_main() p.inc_refcount(1); picked.clear(); - p.pick_pieces(peer3, picked, 1, false, address()); + p.pick_pieces(peer3, picked, 1, false, tcp::endpoint()); TEST_CHECK(picked.size() == 1); TEST_CHECK(picked.front().piece_index == 1); // and the block picked should not be 0 or 2 @@ -138,9 +138,9 @@ int test_main() // we have block 0 and 2 already, so we can't mark // them as begin downloaded. - p.mark_as_downloading(piece_block(1, 1), address(1,1,1,1,0)); - p.mark_as_downloading(piece_block(1, 3), address(1,1,1,1,0)); - p.mark_as_downloading(piece_block(2, 0), address(1,1,1,1,0)); + p.mark_as_downloading(piece_block(1, 1), tcp::endpoint(0, address("1.1.1.1"))); + p.mark_as_downloading(piece_block(1, 3), tcp::endpoint(0, address("1.1.1.1"))); + p.mark_as_downloading(piece_block(2, 0), tcp::endpoint(0, address("1.1.1.1"))); std::vector const& downloads = p.get_download_queue(); TEST_CHECK(downloads.size() == 2); @@ -168,7 +168,7 @@ int test_main() TEST_CHECK(!p.is_downloading(piece_block(2, 1))); picked.clear(); - p.pick_pieces(peer1, picked, 1, false, address()); + p.pick_pieces(peer1, picked, 1, false, tcp::endpoint()); TEST_CHECK(picked.size() == 2); piece_block expected3[] = { piece_block(2, 0), piece_block(2, 1) }; @@ -181,7 +181,7 @@ int test_main() // partially selected) picked.clear(); - p.pick_pieces(peer1, picked, 1, true, address()); + p.pick_pieces(peer1, picked, 1, true, tcp::endpoint()); // it will pick 4 blocks, since we said we // wanted whole pieces. @@ -199,7 +199,7 @@ int test_main() // to make sure it can still fall back on partial pieces picked.clear(); - p.pick_pieces(peer1, picked, 100, true, address()); + p.pick_pieces(peer1, picked, 100, true, tcp::endpoint()); TEST_CHECK(picked.size() == 14); @@ -221,13 +221,8 @@ int test_main() // to make sure it can still fall back on partial pieces picked.clear(); - p.pick_pieces(peer1, picked, 100, true, address(1,1,1,1,0)); + p.pick_pieces(peer1, picked, 100, true, tcp::endpoint(0, address("1.1.1.1"))); - for (std::vector::iterator i = picked.begin(); i != picked.end(); ++i) - { - std::cerr << "(" << i->piece_index << "," << i->block_index << ")\n"; - } - TEST_CHECK(picked.size() == 11); piece_block expected6[] = @@ -245,7 +240,7 @@ int test_main() // make sure the piece picker allows filtered pieces // to become available - p.mark_as_finished(piece_block(4, 2), address()); + p.mark_as_finished(piece_block(4, 2), tcp::endpoint()); } return 0; diff --git a/test/test_storage.cpp b/test/test_storage.cpp index 89cf28ead..f43563039 100644 --- a/test/test_storage.cpp +++ b/test/test_storage.cpp @@ -79,12 +79,17 @@ int test_main() libtorrent::detail::piece_checker_data d; std::vector pieces; - TEST_CHECK(pm.check_fastresume(d, pieces, true) == false); + num_pieces = 0; + TEST_CHECK(pm.check_fastresume(d, pieces, num_pieces, true) == false); bool finished = false; float progress; + num_pieces = 0; while (!finished) - boost::tie(finished, progress) = pm.check_files(pieces); + boost::tie(finished, progress) = pm.check_files(pieces, num_pieces); + TEST_CHECK(num_pieces == std::count(pieces.begin(), pieces.end() + , true)); + pm.read(piece, 0, 0, piece_size); TEST_CHECK(std::equal(piece, piece + piece_size, piece0));