diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index f26f255ea..d3ab3067d 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -160,16 +160,19 @@ class Torrent: """Updates the state based on what libtorrent's state for the torrent is""" # Set the initial state based on the lt state LTSTATE = deluge.common.LT_TORRENT_STATE - ltstate = self.handle.status().state + ltstate = int(self.handle.status().state) + + log.debug("set_state_based_on_ltstate: %s", ltstate) + if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]: - self.set_state("Checking") + self.state = "Checking" elif ltstate == LTSTATE["Connecting"] or ltstate == LTSTATE["Downloading"] or\ ltstate == LTSTATE["Downloading Metadata"]: - self.set_state("Downloading") + self.state = "Downloading" elif ltstate == LTSTATE["Finished"] or ltstate == LTSTATE["Seeding"]: - self.set_state("Seeding") + self.state = "Seeding" elif ltstate == LTSTATE["Allocating"]: - self.set_state("Allocating") + self.state = "Allocating" def set_state(self, state): """Accepts state strings, ie, "Paused", "Seeding", etc.""" @@ -183,6 +186,7 @@ class Torrent: component.get("TorrentManager").append_not_state_paused(self.torrent_id) self.handle.pause() + log.debug("Setting %s's state to %s", self.torrent_id, state) self.state = state # Update the torrentqueue on any state changes @@ -276,7 +280,7 @@ class Torrent: country += " " else: country += c - + ret.append({ "ip": "%s:%s" % (peer.ip[0], peer.ip[1]), "up_speed": peer.up_speed, @@ -428,11 +432,11 @@ class Torrent: pass if self.handle.is_finished(): - self.state = "Seeding" + self.set_state("Seeding") else: # Only delete the .fastresume file if we're still downloading stuff self.delete_fastresume() - self.state = "Downloading" + self.set_state("Downloading") return True diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index cdcb38b00..aa3880881 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -305,15 +305,15 @@ class TorrentManager(component.Component): # Resume the torrent if needed if state == "Queued": - torrent.set_state("Queued") + torrent.state = "Queued" elif state == "Paused" or state == "Error": - torrent.set_state("Paused") + torrent.state = "Paused" if state == None and not options["add_paused"]: torrent.handle.resume() # We set the state based on libtorrent's state torrent.set_state_based_on_ltstate() if state == None and options["add_paused"]: - torrent.set_state("Paused") + torrent.set_state = "Paused" # Emit the torrent_added signal self.signals.emit("torrent_added", torrent.torrent_id) diff --git a/deluge/core/torrentqueue.py b/deluge/core/torrentqueue.py index dc0a13a2d..bec1815cb 100644 --- a/deluge/core/torrentqueue.py +++ b/deluge/core/torrentqueue.py @@ -65,7 +65,7 @@ class TorrentQueue(component.Component): stop_ratio = self.config["stop_seed_ratio"] for torrent_id in self.torrents.get_torrent_list(): - if self.torrents[torrent_id].handle.is_seed(): + if self.torrents[torrent_id].handle.is_finished(): if self.torrents[torrent_id].get_ratio() >= stop_ratio: # This torrent is at or exceeding the stop ratio so we need to # pause or remove it from the session. @@ -94,7 +94,7 @@ class TorrentQueue(component.Component): elif self.torrents[torrent_id].state == "Downloading": self.downloading.append((self.queue.index(torrent_id), torrent_id)) elif self.torrents[torrent_id].state == "Queued": - if self.torrents[torrent_id].handle.is_seed(): + if self.torrents[torrent_id].handle.is_finished(): self.queued_seeding.append((self.queue.index(torrent_id), torrent_id)) else: self.queued_downloading.append((self.queue.index(torrent_id), torrent_id)) @@ -105,10 +105,10 @@ class TorrentQueue(component.Component): self.queued_downloading.sort() self.queued_seeding.sort() -# log.debug("total seeding: %s", len(self.seeding)) -# log.debug("total downloading: %s", len(self.downloading)) -# log.debug("queued seeding: %s", len(self.queued_seeding)) -# log.debug("queued downloading: %s", len(self.queued_downloading)) + #log.debug("total seeding: %s", len(self.seeding)) + #log.debug("total downloading: %s", len(self.downloading)) + #log.debug("queued seeding: %s", len(self.queued_seeding)) + #log.debug("queued downloading: %s", len(self.queued_downloading)) def update_order(self): # This will queue/resume torrents if the queueing order changes @@ -118,6 +118,9 @@ class TorrentQueue(component.Component): # log.debug("min(queued_seeding): %s", min(self.queued_seeding)[0]) #except: # pass + + #log.debug("queued seeding: %s", self.queued_seeding) + #log.debug("queued downloading: %s", self.queued_downloading) if self.seeding != [] and self.queued_seeding != []: if min(self.queued_seeding)[0] < max(self.seeding)[0]: @@ -137,6 +140,7 @@ class TorrentQueue(component.Component): def update_max_active(self): if self.config["max_active_seeding"] > -1: + log.debug("max_active_seeding: %s", self.config["max_active_seeding"]) if len(self.seeding) > self.config["max_active_seeding"]: # We need to queue some more torrents because we're over the active limit num_to_queue = len(self.seeding) - self.config["max_active_seeding"] diff --git a/libtorrent/bindings/python/src/alert.cpp b/libtorrent/bindings/python/src/alert.cpp index aec3884e0..9a72b9942 100755 --- a/libtorrent/bindings/python/src/alert.cpp +++ b/libtorrent/bindings/python/src/alert.cpp @@ -27,6 +27,7 @@ extern char const* piece_finished_alert_doc; extern char const* block_finished_alert_doc; extern char const* block_downloading_alert_doc; extern char const* storage_moved_alert_doc; +extern char const* torrent_deleted_alert_doc; extern char const* torrent_paused_alert_doc; extern char const* torrent_checked_alert_doc; extern char const* url_seed_alert_doc; @@ -159,6 +160,10 @@ void bind_alert() "storage_moved_alert", storage_moved_alert_doc, no_init ); + class_, noncopyable>( + "torrent_deleted_alert", torrent_deleted_alert_doc, no_init + ); + class_, noncopyable>( "torrent_paused_alert", torrent_paused_alert_doc, no_init ); diff --git a/libtorrent/bindings/python/src/docstrings.cpp b/libtorrent/bindings/python/src/docstrings.cpp index 87d7e89c7..9ad19203f 100755 --- a/libtorrent/bindings/python/src/docstrings.cpp +++ b/libtorrent/bindings/python/src/docstrings.cpp @@ -270,6 +270,9 @@ char const* storage_moved_alert_doc = "It contains a `torrent_handle` to the torrent in question. This alert\n" "is generated as severity level `alert.severity_levels.warning`."; +char const* torrent_deleted_alert_doc = + ""; + char const* torrent_paused_alert_doc = "This alert is generated when a torrent switches from being a\n" "active to paused.\n" diff --git a/libtorrent/bindings/python/src/peer_info.cpp b/libtorrent/bindings/python/src/peer_info.cpp index f11096677..3c22820fe 100755 --- a/libtorrent/bindings/python/src/peer_info.cpp +++ b/libtorrent/bindings/python/src/peer_info.cpp @@ -48,6 +48,8 @@ void bind_peer_info() scope pi = class_("peer_info") .def_readonly("flags", &peer_info::flags) .def_readonly("source", &peer_info::source) + .def_readonly("read_state", &peer_info::read_state) + .def_readonly("write_state", &peer_info::write_state) .add_property("ip", get_ip) .def_readonly("up_speed", &peer_info::up_speed) .def_readonly("down_speed", &peer_info::down_speed) @@ -66,6 +68,10 @@ void bind_peer_info() .def_readonly("num_hashfails", &peer_info::num_hashfails) #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES .add_property("country", get_country) +#endif +#ifndef TORRENT_DISABLE_GEO_IP + .def_readonly("inet_as_name", &peer_info::inet_as_name) + .def_readonly("inet_as", &peer_info::inet_as) #endif .def_readonly("load_balancing", &peer_info::load_balancing) .def_readonly("download_queue_length", &peer_info::download_queue_length) @@ -79,6 +85,9 @@ void bind_peer_info() .def_readonly("connection_type", &peer_info::connection_type) .def_readonly("remote_dl_rate", &peer_info::remote_dl_rate) .def_readonly("pending_disk_bytes", &peer_info::pending_disk_bytes) + .def_readonly("send_quota", &peer_info::send_quota) + .def_readonly("receive_quota", &peer_info::receive_quota) + .def_readonly("rtt", &peer_info::rtt) ; // flags @@ -108,5 +117,11 @@ void bind_peer_info() pi.attr("pex") = (int)peer_info::pex; pi.attr("lsd") = (int)peer_info::lsd; pi.attr("resume_data") = (int)peer_info::resume_data; + + // read/write state + pi.attr("bw_idle") = (int)peer_info::bw_idle; + pi.attr("bw_torrent") = (int)peer_info::bw_torrent; + pi.attr("bw_global") = (int)peer_info::bw_global; + pi.attr("bw_network") = (int)peer_info::bw_network; } diff --git a/libtorrent/bindings/python/src/peer_plugin.cpp b/libtorrent/bindings/python/src/peer_plugin.cpp index 94b5fde66..2689728b3 100755 --- a/libtorrent/bindings/python/src/peer_plugin.cpp +++ b/libtorrent/bindings/python/src/peer_plugin.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace boost::python; @@ -144,7 +145,7 @@ namespace return this->peer_plugin::on_request(req); } - bool on_piece(peer_request const& piece, char const* data) + bool on_piece(peer_request const& piece, disk_buffer_holder& data) { if (override f = this->get_override("on_piece")) return f(piece, data); @@ -152,7 +153,7 @@ namespace return peer_plugin::on_piece(piece, data); } - bool default_on_piece(peer_request const& piece, char const* data) + bool default_on_piece(peer_request const& piece, disk_buffer_holder& data) { return this->peer_plugin::on_piece(piece, data); } diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 3b4a891c0..0c049a12c 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -106,6 +106,32 @@ namespace return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor); } + void start_natpmp(session& s) + { + allow_threading_guard guard; + s.start_natpmp(); + return; + } + + void start_upnp(session& s) + { + allow_threading_guard guard; + s.start_upnp(); + return; + } +#ifndef TORRENT_DISABLE_GEO_IP + bool load_asnum_db(session& s, std::string file) + { + allow_threading_guard guard; + return s.load_asnum_db(file.c_str()); + } + + bool load_country_db(session& s, std::string file) + { + allow_threading_guard guard; + return s.load_country_db(file.c_str()); + } +#endif } // namespace unnamed void bind_session() @@ -250,6 +276,12 @@ void bind_session() .def("set_pe_settings", allow_threads(&session::set_pe_settings), session_set_pe_settings_doc) .def("get_pe_settings", allow_threads(&session::get_pe_settings), return_value_policy()) #endif +#ifndef TORRENT_DISABLE_GEO_IP + .def("load_asnum_db", &load_asnum_db) + .def("load_country_db", &load_country_db) +#endif + .def("load_state", allow_threads(&session::load_state)) + .def("state", allow_threads(&session::state)) .def( "set_severity_level", allow_threads(&session::set_severity_level) , session_set_severity_level_doc @@ -259,11 +291,11 @@ void bind_session() .def("set_peer_proxy", allow_threads(&session::set_peer_proxy)) .def("set_tracker_proxy", allow_threads(&session::set_tracker_proxy)) .def("set_web_seed_proxy", allow_threads(&session::set_web_seed_proxy)) - .def("start_upnp", allow_threads(&session::start_upnp), session_start_upnp_doc) + .def("start_upnp", &start_upnp, session_start_upnp_doc) .def("stop_upnp", allow_threads(&session::stop_upnp), session_stop_upnp_doc) .def("start_lsd", allow_threads(&session::start_lsd), session_start_lsd_doc) .def("stop_lsd", allow_threads(&session::stop_lsd), session_stop_lsd_doc) - .def("start_natpmp", allow_threads(&session::start_natpmp), session_start_natpmp_doc) + .def("start_natpmp", &start_natpmp, session_start_natpmp_doc) .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) .def("set_ip_filter", allow_threads(&session::set_ip_filter), session_set_ip_filter_doc) ; diff --git a/libtorrent/bindings/python/src/torrent_handle.cpp b/libtorrent/bindings/python/src/torrent_handle.cpp index 1d86145fb..5733e9f2d 100755 --- a/libtorrent/bindings/python/src/torrent_handle.cpp +++ b/libtorrent/bindings/python/src/torrent_handle.cpp @@ -291,7 +291,7 @@ void bind_torrent_handle() .def("upload_limit", _(&torrent_handle::upload_limit)) .def("set_download_limit", _(&torrent_handle::set_download_limit)) .def("download_limit", _(&torrent_handle::download_limit)) - .def("set_sequenced_download_threshold", _(&torrent_handle::set_sequenced_download_threshold)) + .def("set_sequential_download", _(&torrent_handle::set_sequential_download)) .def("set_peer_upload_limit", set_peer_upload_limit) .def("set_peer_download_limit", set_peer_download_limit) .def("connect_peer", connect_peer) @@ -301,7 +301,7 @@ void bind_torrent_handle() .def("set_max_connections", _(&torrent_handle::set_max_connections)) .def("set_tracker_login", _(&torrent_handle::set_tracker_login)) .def("move_storage", _(&torrent_handle::move_storage)) - .def("info_hash", _(&torrent_handle::info_hash), copy) + .def("info_hash", _(&torrent_handle::info_hash)) ; } diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index a1dcf4364..1dc843ede 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -291,6 +291,20 @@ namespace libtorrent { return std::auto_ptr(new torrent_deleted_alert(*this)); } }; + struct TORRENT_EXPORT save_resume_data_alert: torrent_alert + { + save_resume_data_alert(boost::shared_ptr const& rd + , torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::warning, msg) + , resume_data(rd) + {} + + boost::shared_ptr resume_data; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new save_resume_data_alert(*this)); } + }; + struct TORRENT_EXPORT torrent_paused_alert: torrent_alert { torrent_paused_alert(torrent_handle const& h, std::string const& msg) @@ -331,11 +345,15 @@ namespace libtorrent struct TORRENT_EXPORT file_error_alert: torrent_alert { file_error_alert( - const torrent_handle& h + std::string const& f + , const torrent_handle& h , const std::string& msg) : torrent_alert(h, alert::fatal, msg) + , file(f) {} + std::string file; + virtual std::auto_ptr clone() const { return std::auto_ptr(new file_error_alert(*this)); } }; @@ -364,6 +382,36 @@ namespace libtorrent { return std::auto_ptr(new metadata_received_alert(*this)); } }; + struct TORRENT_EXPORT udp_error_alert: alert + { + udp_error_alert( + udp::endpoint const& ep + , std::string const& msg) + : alert(alert::info, msg) + , endpoint(ep) + {} + + udp::endpoint endpoint; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new udp_error_alert(*this)); } + }; + + struct TORRENT_EXPORT external_ip_alert: alert + { + external_ip_alert( + address const& ip + , std::string const& msg) + : alert(alert::info, msg) + , external_address(ip) + {} + + address external_address; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new external_ip_alert(*this)); } + }; + struct TORRENT_EXPORT listen_failed_alert: alert { listen_failed_alert( @@ -396,20 +444,27 @@ namespace libtorrent struct TORRENT_EXPORT portmap_error_alert: alert { - portmap_error_alert(const std::string& msg) - : alert(alert::warning, msg) + portmap_error_alert(int i, int t, const std::string& msg) + : alert(alert::warning, msg), mapping(i), type(t) {} + int mapping; + int type; + virtual std::auto_ptr clone() const { return std::auto_ptr(new portmap_error_alert(*this)); } }; struct TORRENT_EXPORT portmap_alert: alert { - portmap_alert(const std::string& msg) - : alert(alert::info, msg) + portmap_alert(int i, int port, int t, const std::string& msg) + : alert(alert::info, msg), mapping(i), external_port(port), type(t) {} + int mapping; + int external_port; + int type; + virtual std::auto_ptr clone() const { return std::auto_ptr(new portmap_alert(*this)); } }; diff --git a/libtorrent/include/libtorrent/assert.hpp b/libtorrent/include/libtorrent/assert.hpp index dd3c6b737..6943fadc2 100644 --- a/libtorrent/include/libtorrent/assert.hpp +++ b/libtorrent/include/libtorrent/assert.hpp @@ -33,7 +33,11 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_ASSERT #include "libtorrent/config.hpp" -#include +#include + +#ifdef __GNUC__ +std::string demangle(char const* name); +#endif #if (defined __linux__ || defined __MACH__) && defined __GNUC__ && !defined(NDEBUG) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index 03e9cb619..dd7f882c4 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -40,6 +40,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#ifndef TORRENT_DISABLE_GEO_IP +#include "libtorrent/GeoIP.h" +#endif + #ifdef _MSC_VER #pragma warning(push, 1) #endif @@ -94,74 +98,7 @@ namespace libtorrent { struct session_impl; - // this data is shared between the main thread and the - // thread that initialize pieces - struct piece_checker_data - { - piece_checker_data() - : processing(false), progress(0.f), abort(false) {} - - boost::shared_ptr torrent_ptr; - fs::path save_path; - - sha1_hash info_hash; - - void parse_resume_data( - const entry& rd - , const torrent_info& info - , std::string& error); - - std::vector piece_map; - std::vector unfinished_pieces; - std::vector block_info; - std::vector peers; - std::vector banned_peers; - entry resume_data; - - // this is true if this torrent is being processed (checked) - // if it is not being processed, then it can be removed from - // the queue without problems, otherwise the abort flag has - // to be set. - bool processing; - - // is filled in by storage::initialize_pieces() - // and represents the progress. It should be a - // value in the range [0, 1] - float progress; - - // abort defaults to false and is typically - // filled in by torrent_handle when the user - // aborts the torrent - bool abort; - }; - - struct checker_impl: boost::noncopyable - { - checker_impl(session_impl& s): m_ses(s), m_abort(false) {} - void operator()(); - piece_checker_data* find_torrent(const sha1_hash& info_hash); - void remove_torrent(sha1_hash const& info_hash, int options); - -#ifndef NDEBUG - void check_invariant() const; -#endif - - // when the files has been checked - // the torrent is added to the session - session_impl& m_ses; - - mutable boost::mutex m_mutex; - boost::condition m_cond; - - // a list of all torrents that are currently in queue - // or checking their files - std::deque > m_torrents; - std::deque > m_processing; - - bool m_abort; - }; - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING struct tracker_logger; #endif @@ -185,7 +122,7 @@ namespace libtorrent std::pair listen_port_range , fingerprint const& cl_fprint , char const* listen_interface -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path const& logpath #endif ); @@ -194,10 +131,18 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::function( torrent*, void*)> ext); +#endif +#ifndef NDEBUG + bool has_peer(peer_connection const* p) const + { + return std::find_if(m_connections.begin(), m_connections.end() + , boost::bind(&boost::intrusive_ptr::get, _1) == p) + != m_connections.end(); + } #endif void operator()(); - void open_listen_port() throw(); + void open_listen_port(); // if we are listening on an IPv6 interface // this will return one of the IPv6 addresses on this @@ -216,9 +161,8 @@ namespace libtorrent boost::weak_ptr find_torrent(const sha1_hash& info_hash); peer_id const& get_peer_id() const { return m_peer_id; } - void close_connection(boost::intrusive_ptr const& p); - void connection_failed(boost::intrusive_ptr const& p - , tcp::endpoint const& a, char const* message); + void close_connection(peer_connection const* p + , char const* message); void set_settings(session_settings const& s); session_settings const& settings() const { return m_settings; } @@ -241,7 +185,8 @@ namespace libtorrent // called when a port mapping is successful, or a router returns // a failure to map a port - void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); + void on_port_mapping(int mapping, int port, std::string const& errmsg + , int nat_transport); bool is_aborted() const { return m_abort; } @@ -277,6 +222,9 @@ namespace libtorrent std::vector get_torrents(); + void check_torrent(boost::shared_ptr const& t); + void done_checking(boost::shared_ptr const& t); + void set_severity_level(alert::severity_t s); std::auto_ptr pop_alert(); @@ -334,11 +282,29 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT void set_dht_proxy(proxy_settings const& s) - { m_dht_proxy = s; } + { + m_dht_proxy = s; + m_dht_socket.set_proxy_settings(s); + } proxy_settings const& dht_proxy() const { return m_dht_proxy; } #endif +#ifndef TORRENT_DISABLE_GEO_IP + std::string as_name_for_ip(address const& a); + int as_for_ip(address const& a); + std::pair* lookup_as(int as); + bool load_asnum_db(char const* file); + bool has_asnum_db() const { return m_asnum_db; } + + bool load_country_db(char const* file); + bool has_country_db() const { return m_country_db; } + char const* country_for_ip(address const& a); +#endif + + void load_state(entry const& ses_state); + entry state() const; + #ifdef TORRENT_STATS void log_buffer_usage() { @@ -358,29 +324,36 @@ namespace libtorrent } #endif void start_lsd(); - void start_natpmp(); - void start_upnp(); + natpmp* start_natpmp(); + upnp* start_upnp(); void stop_lsd(); void stop_natpmp(); void stop_upnp(); + int next_port(); + // handles delayed alerts alert_manager m_alerts; std::pair allocate_buffer(int size); void free_buffer(char* buf, int size); + + char* allocate_disk_buffer(); void free_disk_buffer(char* buf); - - address m_external_address; + + void set_external_address(address const& ip); + address const& external_address() const { return m_external_address; } // private: void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR // this pool is used to allocate and recycle send // buffers from. boost::pool<> m_send_buffers; +#endif boost::mutex m_send_buffer_mutex; // the file pool that all storages in this session's @@ -391,20 +364,21 @@ namespace libtorrent // when they are destructed. file_pool m_files; + // this is where all active sockets are stored. + // the selector can sleep while there's no activity on + // them + io_service m_io_service; + // handles disk io requests asynchronously // peers have pointers into the disk buffer // pool, and must be destructed before this // object. The disk thread relies on the file // pool object, and must be destructed before - // m_files. + // m_files. The disk io thread posts completion + // events to the io service, and needs to be + // constructed after it. disk_io_thread m_disk_thread; - // this is where all active sockets are stored. - // the selector can sleep while there's no activity on - // them - io_service m_io_service; - asio::strand m_strand; - // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last @@ -422,6 +396,7 @@ namespace libtorrent tracker_manager m_tracker_manager; torrent_map m_torrents; + std::list > m_queued_for_checking; // this maps sockets to their peer_connection // object. It is the complete list of all connected @@ -496,7 +471,14 @@ namespace libtorrent // should exit volatile bool m_abort; + // the max number of unchoked peers as set by the user int m_max_uploads; + + // the number of unchoked peers as set by the auto-unchoker + // this should always be >= m_max_uploads + int m_allowed_upload_slots; + + // the max number of connections, as set by the user int m_max_connections; // the number of unchoked peers @@ -533,6 +515,10 @@ namespace libtorrent void second_tick(asio::error_code const& e); ptime m_last_tick; + // when outgoing_ports is configured, this is the + // port we'll bind the next outgoing socket to + int m_next_port; + #ifndef TORRENT_DISABLE_DHT boost::intrusive_ptr m_dht; dht_settings m_dht_settings; @@ -545,6 +531,11 @@ namespace libtorrent // see m_external_listen_port. This is the same // but for the udp port used by the DHT. int m_external_udp_port; + + udp_socket m_dht_socket; + + void on_receive_udp(asio::error_code const& e + , udp::endpoint const& ep, char const* buf, int len); #endif #ifndef TORRENT_DISABLE_ENCRYPTION @@ -555,6 +546,10 @@ namespace libtorrent boost::intrusive_ptr m_upnp; boost::intrusive_ptr m_lsd; + // 0 is natpmp 1 is upnp + int m_tcp_mapping[2]; + int m_udp_mapping[2]; + // the timer used to fire the second_tick deadline_timer m_timer; @@ -575,7 +570,7 @@ namespace libtorrent // the number of send buffers that are allocated int m_buffer_allocations; #endif -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING boost::shared_ptr create_log(std::string const& name , int instance, bool append = true); @@ -588,7 +583,9 @@ namespace libtorrent public: boost::shared_ptr m_logger; private: + #endif + address m_external_address; #ifndef TORRENT_DISABLE_EXTENSIONS typedef std::list m_as_peak; +#endif // the main working thread boost::scoped_ptr m_thread; - - // the thread that calls initialize_pieces() - // on all torrents before they start downloading - boost::scoped_ptr m_checker_thread; }; -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING struct tracker_logger : request_callback { tracker_logger(session_impl& ses): m_ses(ses) {} @@ -622,7 +622,8 @@ namespace libtorrent , std::vector& peers , int interval , int complete - , int incomplete) + , int incomplete + , address const& external_ip) { std::stringstream s; s << "TRACKER RESPONSE:\n" @@ -636,6 +637,7 @@ namespace libtorrent if (!i->pid.is_all_zeros()) s << " " << i->pid; s << "\n"; } + s << "external ip: " << external_ip << "\n"; debug_log(s.str()); } diff --git a/libtorrent/include/libtorrent/bandwidth_manager.hpp b/libtorrent/include/libtorrent/bandwidth_manager.hpp index 0953cc320..b34f4b993 100644 --- a/libtorrent/include/libtorrent/bandwidth_manager.hpp +++ b/libtorrent/include/libtorrent/bandwidth_manager.hpp @@ -41,6 +41,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT +#include +#endif + #include "libtorrent/socket.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/assert.hpp" @@ -52,7 +56,6 @@ using boost::shared_ptr; using boost::intrusive_ptr; using boost::bind; -//#define TORRENT_VERBOSE_BANDWIDTH_LIMIT namespace libtorrent { @@ -80,7 +83,7 @@ struct history_entry }; template -T clamp(T val, T ceiling, T floor) throw() +T clamp(T val, T ceiling, T floor) { TORRENT_ASSERT(ceiling >= floor); if (val >= ceiling) return ceiling; @@ -88,10 +91,23 @@ T clamp(T val, T ceiling, T floor) throw() return val; } +template +struct assign_at_exit +{ + assign_at_exit(T& var, T val): var_(var), val_(val) {} + ~assign_at_exit() { var_ = val_; } + T& var_; + T val_; +}; + template struct bandwidth_manager { - bandwidth_manager(io_service& ios, int channel) throw() + bandwidth_manager(io_service& ios, int channel +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + , bool log = false +#endif + ) : m_ios(ios) , m_history_timer(m_ios) , m_limit(bandwidth_limit::inf) @@ -99,16 +115,22 @@ struct bandwidth_manager , m_channel(channel) , m_in_hand_out_bandwidth(false) , m_abort(false) - {} + { +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + if (log) + m_log.open("bandwidth_limiter.log", std::ios::trunc); + m_start = time_now(); +#endif + } - void throttle(int limit) throw() + void throttle(int limit) { mutex_t::scoped_lock l(m_mutex); TORRENT_ASSERT(limit >= 0); m_limit = limit; } - int throttle() const throw() + int throttle() const { mutex_t::scoped_lock l(m_mutex); return m_limit; @@ -124,6 +146,23 @@ struct bandwidth_manager } #ifndef NDEBUG + bool is_queued(PeerConnection const* peer) const + { + mutex_t::scoped_lock l(m_mutex); + return is_queued(peer); + } + + bool is_queued(PeerConnection const* peer, boost::mutex::scoped_lock& l) const + { + TORRENT_ASSERT(l.locked()); + for (typename queue_t::const_iterator i = m_queue.begin() + , end(m_queue.end()); i != end; ++i) + { + if (i->peer.get() == peer) return true; + } + return false; + } + bool is_in_history(PeerConnection const* peer) const { mutex_t::scoped_lock l(m_mutex); @@ -142,41 +181,35 @@ struct bandwidth_manager } #endif - int queue_size() const - { - mutex_t::scoped_lock l(m_mutex); - return m_queue.size(); - } - + int queue_size() const + { + mutex_t::scoped_lock l(m_mutex); + return m_queue.size(); + } + // non prioritized means that, if there's a line for bandwidth, // others will cut in front of the non-prioritized peers. // this is used by web seeds - void request_bandwidth(intrusive_ptr peer - , int blk, int priority) + void request_bandwidth(intrusive_ptr const& peer + , int blk, int priority) { mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; if (m_abort) return; TORRENT_ASSERT(blk > 0); + TORRENT_ASSERT(!is_queued(peer.get(), l)); // make sure this peer isn't already in line // waiting for bandwidth -#ifndef NDEBUG - for (typename queue_t::iterator i = m_queue.begin() - , end(m_queue.end()); i != end; ++i) - { - TORRENT_ASSERT(i->peer < peer || peer < i->peer); - } -#endif - TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); + TORRENT_ASSERT(peer->max_assignable_bandwidth(m_channel) > 0); - typename queue_t::reverse_iterator i(m_queue.rbegin()); - while (i != m_queue.rend() && priority > i->priority) - { - ++i->priority; - ++i; - } - m_queue.insert(i.base(), bw_queue_entry(peer, blk, priority)); + typename queue_t::reverse_iterator i(m_queue.rbegin()); + while (i != m_queue.rend() && priority > i->priority) + { + ++i->priority; + ++i; + } + m_queue.insert(i.base(), bw_queue_entry(peer, blk, priority)); if (!m_queue.empty()) hand_out_bandwidth(l); } @@ -206,25 +239,32 @@ private: void add_history_entry(history_entry const& e) { - try { INVARIANT_CHECK; + m_history.push_front(e); m_current_quota += e.amount; // in case the size > 1 there is already a timer // active that will be invoked, no need to set one up + +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + m_log << std::setw(7) << total_milliseconds(time_now() - m_start) << " + " + " queue: " << std::setw(3) << m_queue.size() + << " used: " << std::setw(7) << m_current_quota + << " limit: " << std::setw(7) << m_limit + << " history: " << std::setw(3) << m_history.size() + << std::endl; +#endif if (m_history.size() > 1) return; if (m_abort) return; - m_history_timer.expires_at(e.expires_at); + asio::error_code ec; + m_history_timer.expires_at(e.expires_at, ec); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); - } - catch (std::exception&) {} } void on_history_expire(asio::error_code const& e) { - try { if (e) return; mutex_t::scoped_lock l(m_mutex); @@ -240,6 +280,15 @@ private: m_history.pop_back(); m_current_quota -= e.amount; TORRENT_ASSERT(m_current_quota >= 0); + +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + m_log << std::setw(7) << total_milliseconds(time_now() - m_start) << " - " + " queue: " << std::setw(3) << m_queue.size() + << " used: " << std::setw(7) << m_current_quota + << " limit: " << std::setw(7) << m_limit + << " history: " << std::setw(3) << m_history.size() + << std::endl; +#endif intrusive_ptr c = e.peer; shared_ptr t = e.tor.lock(); l.unlock(); @@ -251,7 +300,8 @@ private: // now, wait for the next chunk to expire if (!m_history.empty() && !m_abort) { - m_history_timer.expires_at(m_history.back().expires_at); + asio::error_code ec; + m_history_timer.expires_at(m_history.back().expires_at, ec); m_history_timer.async_wait(bind(&bandwidth_manager::on_history_expire, this, _1)); } @@ -259,8 +309,6 @@ private: // means we can hand out more (in case there // are still consumers in line) if (!m_queue.empty()) hand_out_bandwidth(l); - } - catch (std::exception&) {} } void hand_out_bandwidth(boost::mutex::scoped_lock& l) @@ -270,8 +318,9 @@ private: // to the loop further down on the callstack if (m_in_hand_out_bandwidth) return; m_in_hand_out_bandwidth = true; + // set it to false when exiting function + assign_at_exit sg(m_in_hand_out_bandwidth, false); - try { INVARIANT_CHECK; ptime now(time_now()); @@ -281,18 +330,7 @@ private: // available bandwidth to hand out int amount = limit - m_current_quota; -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " hand_out_bandwidht. m_queue.size() = " << m_queue.size() - << " amount = " << amount - << " limit = " << limit - << " m_current_quota = " << m_current_quota << std::endl; -#endif - - if (amount <= 0) - { - m_in_hand_out_bandwidth = false; - return; - } + if (amount <= 0) return; queue_t tmp; while (!m_queue.empty() && amount > 0) @@ -358,9 +396,12 @@ private: } if (block_size > qe.max_block_size) block_size = qe.max_block_size; -#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT - std::cerr << " block_size = " << block_size << " amount = " << amount << std::endl; -#endif + if (amount < block_size / 4) + { + tmp.push_back(qe); +// m_queue.push_front(qe); + break; + } // so, hand out max_assignable, but no more than // the available bandwidth (amount) and no more @@ -377,14 +418,7 @@ private: add_history_entry(history_entry( qe.peer, t, hand_out_amount, now + bw_window_size)); } - if (!tmp.empty()) m_queue.insert(m_queue.begin(), tmp.begin(), tmp.end()); - } - catch (std::exception&) - { - m_in_hand_out_bandwidth = false; - throw; - } - m_in_hand_out_bandwidth = false; + if (!tmp.empty()) m_queue.insert(m_queue.begin(), tmp.begin(), tmp.end()); } @@ -423,6 +457,11 @@ private: bool m_in_hand_out_bandwidth; bool m_abort; + +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + std::ofstream m_log; + ptime m_start; +#endif }; } diff --git a/libtorrent/include/libtorrent/bencode.hpp b/libtorrent/include/libtorrent/bencode.hpp index 9bea1d846..bd77aa104 100755 --- a/libtorrent/include/libtorrent/bencode.hpp +++ b/libtorrent/include/libtorrent/bencode.hpp @@ -103,28 +103,35 @@ namespace libtorrent namespace detail { template - void write_string(OutIt& out, const std::string& val) + int write_string(OutIt& out, const std::string& val) { - std::string::const_iterator end = val.begin() + val.length(); - std::copy(val.begin(), end, out); + int ret = val.length(); + std::string::const_iterator end = val.begin() + ret; + for (std::string::const_iterator i = val.begin() + , end(val.begin() + ret); i != end; ++i) + *out++ = *i; + return ret; } TORRENT_EXPORT char const* integer_to_str(char* buf, int size, entry::integer_type val); template - void write_integer(OutIt& out, entry::integer_type val) + int write_integer(OutIt& out, entry::integer_type val) { // the stack allocated buffer for keeping the // decimal representation of the number can // not hold number bigger than this: BOOST_STATIC_ASSERT(sizeof(entry::integer_type) <= 8); char buf[21]; + int ret = 0; for (char const* str = integer_to_str(buf, 21, val); *str != 0; ++str) { *out = *str; ++out; + ++ret; } + return ret; } template @@ -172,26 +179,31 @@ namespace libtorrent } } + // returns the number of bytes written template - void bencode_recursive(OutIt& out, const entry& e) + int bencode_recursive(OutIt& out, const entry& e) { + int ret = 0; switch(e.type()) { case entry::int_t: write_char(out, 'i'); - write_integer(out, e.integer()); + ret += write_integer(out, e.integer()); write_char(out, 'e'); + ret += 2; break; case entry::string_t: - write_integer(out, e.string().length()); + ret += write_integer(out, e.string().length()); write_char(out, ':'); - write_string(out, e.string()); + ret += write_string(out, e.string()); + ret += 1; break; case entry::list_t: write_char(out, 'l'); for (entry::list_type::const_iterator i = e.list().begin(); i != e.list().end(); ++i) - bencode_recursive(out, *i); + ret += bencode_recursive(out, *i); write_char(out, 'e'); + ret += 2; break; case entry::dictionary_t: write_char(out, 'd'); @@ -199,18 +211,21 @@ namespace libtorrent i != e.dict().end(); ++i) { // write key - write_integer(out, i->first.length()); + ret += write_integer(out, i->first.length()); write_char(out, ':'); - write_string(out, i->first); + ret += write_string(out, i->first); // write value - bencode_recursive(out, i->second); + ret += bencode_recursive(out, i->second); + ret += 1; } write_char(out, 'e'); + ret += 2; break; default: // do nothing break; } + return ret; } template @@ -225,6 +240,9 @@ namespace libtorrent if (in == end) { err = true; +#ifndef NDEBUG + ret.m_type_queried = false; +#endif return; } switch (*in) @@ -241,6 +259,9 @@ namespace libtorrent ++in; // 'e' ret = entry(entry::int_t); ret.integer() = boost::lexical_cast(val); +#ifndef NDEBUG + ret.m_type_queried = false; +#endif } break; // ---------------------------------------------- @@ -254,13 +275,25 @@ namespace libtorrent ret.list().push_back(entry()); entry& e = ret.list().back(); bdecode_recursive(in, end, e, err, depth + 1); - if (err) return; + if (err) + { +#ifndef NDEBUG + ret.m_type_queried = false; +#endif + return; + } if (in == end) { err = true; +#ifndef NDEBUG + ret.m_type_queried = false; +#endif return; } } +#ifndef NDEBUG + ret.m_type_queried = false; +#endif TORRENT_ASSERT(*in == 'e'); ++in; // 'e' } break; @@ -275,16 +308,34 @@ namespace libtorrent { entry key; bdecode_recursive(in, end, key, err, depth + 1); - if (err) return; + if (err || key.type() != entry::string_t) + { +#ifndef NDEBUG + ret.m_type_queried = false; +#endif + return; + } entry& e = ret[key.string()]; bdecode_recursive(in, end, e, err, depth + 1); - if (err) return; + if (err) + { +#ifndef NDEBUG + ret.m_type_queried = false; +#endif + return; + } if (in == end) { err = true; +#ifndef NDEBUG + ret.m_type_queried = false; +#endif return; } } +#ifndef NDEBUG + ret.m_type_queried = false; +#endif TORRENT_ASSERT(*in == 'e'); ++in; // 'e' } break; @@ -295,27 +346,45 @@ namespace libtorrent if (isdigit((unsigned char)*in)) { std::string len_s = read_until(in, end, ':', err); - if (err) return; + if (err) + { +#ifndef NDEBUG + ret.m_type_queried = false; +#endif + return; + } TORRENT_ASSERT(*in == ':'); ++in; // ':' int len = std::atoi(len_s.c_str()); ret = entry(entry::string_t); read_string(in, end, len, ret.string(), err); - if (err) return; + if (err) + { +#ifndef NDEBUG + ret.m_type_queried = false; +#endif + return; + } } else { err = true; +#ifndef NDEBUG + ret.m_type_queried = false; +#endif return; } +#ifndef NDEBUG + ret.m_type_queried = false; +#endif } } } template - void bencode(OutIt out, const entry& e) + int bencode(OutIt out, const entry& e) { - detail::bencode_recursive(out, e); + return detail::bencode_recursive(out, e); } template @@ -324,6 +393,7 @@ namespace libtorrent entry e; bool err = false; detail::bdecode_recursive(start, end, e, err, 0); + TORRENT_ASSERT(e.m_type_queried == false); if (err) { #ifdef BOOST_NO_EXCEPTIONS @@ -335,7 +405,25 @@ namespace libtorrent return e; } + template + entry bdecode(InIt start, InIt end, int& len) + { + entry e; + bool err = false; + InIt s = start; + detail::bdecode_recursive(start, end, e, err, 0); + len = std::distance(s, start); + TORRENT_ASSERT(len >= 0); + if (err) + { +#ifdef BOOST_NO_EXCEPTIONS + return entry(); +#else + throw invalid_encoding(); +#endif + } + return e; + } } #endif // TORRENT_BENCODE_HPP_INCLUDED - diff --git a/libtorrent/include/libtorrent/broadcast_socket.hpp b/libtorrent/include/libtorrent/broadcast_socket.hpp index f7aae342b..3afc23fa9 100644 --- a/libtorrent/include/libtorrent/broadcast_socket.hpp +++ b/libtorrent/include/libtorrent/broadcast_socket.hpp @@ -74,12 +74,29 @@ namespace libtorrent boost::shared_ptr socket; char buffer[1024]; udp::endpoint remote; + void close() + { + if (!socket) return; + asio::error_code ec; + socket->close(ec); + } }; void on_receive(socket_entry* s, asio::error_code const& ec , std::size_t bytes_transferred); + void open_unicast_socket(io_service& ios, address const& addr); + void open_multicast_socket(io_service& ios, address const& addr + , bool loopback); + // these sockets are used to + // join the multicast group (on each interface) + // and receive multicast messages std::list m_sockets; + // these sockets are not bound to any + // specific port and are used to + // send messages to the multicast group + // and receive unicast responses + std::list m_unicast_sockets; udp::endpoint m_multicast_endpoint; receive_handler_t m_on_receive; diff --git a/libtorrent/include/libtorrent/bt_peer_connection.hpp b/libtorrent/include/libtorrent/bt_peer_connection.hpp index dc5237a7d..4ef15631a 100755 --- a/libtorrent/include/libtorrent/bt_peer_connection.hpp +++ b/libtorrent/include/libtorrent/bt_peer_connection.hpp @@ -102,6 +102,8 @@ namespace libtorrent , boost::shared_ptr s , policy::peer* peerinfo); + void start(); + ~bt_peer_connection(); #ifndef TORRENT_DISABLE_ENCRYPTION @@ -208,7 +210,7 @@ namespace libtorrent 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, char* buffer); + void write_piece(peer_request const& r, disk_buffer_holder& buffer); void write_handshake(); #ifndef TORRENT_DISABLE_EXTENSIONS void write_extensions(); @@ -267,6 +269,8 @@ namespace libtorrent // initializes m_RC4_handler void init_pe_RC4_handler(char const* secret, sha1_hash const& stream_key); +public: + // these functions encrypt the send buffer if m_rc4_encrypted // is true, otherwise it passes the call to the // peer_connection functions of the same names @@ -283,6 +287,8 @@ namespace libtorrent } void setup_send(); +private: + // Returns offset at which bytestream (src, src + src_size) // matches bytestream(target, target + target_size). // If no sync found, return -1 diff --git a/libtorrent/include/libtorrent/buffer.hpp b/libtorrent/include/libtorrent/buffer.hpp index a823a7677..425a6a634 100644 --- a/libtorrent/include/libtorrent/buffer.hpp +++ b/libtorrent/include/libtorrent/buffer.hpp @@ -44,6 +44,11 @@ class buffer public: struct interval { + interval() + : begin(0) + , end(0) + {} + interval(char* begin, char* end) : begin(begin) , end(end) diff --git a/libtorrent/include/libtorrent/connection_queue.hpp b/libtorrent/include/libtorrent/connection_queue.hpp index c229ec217..c0e229a40 100644 --- a/libtorrent/include/libtorrent/connection_queue.hpp +++ b/libtorrent/include/libtorrent/connection_queue.hpp @@ -40,6 +40,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/time.hpp" +#ifdef TORRENT_CONNECTION_LOGGING +#include +#endif + namespace libtorrent { @@ -48,20 +52,20 @@ class connection_queue : public boost::noncopyable public: connection_queue(io_service& ios); - bool free_slots() const; + // if there are no free slots, returns the negative + // number of queued up connections + int free_slots() const; void enqueue(boost::function const& on_connect , boost::function const& on_timeout - , time_duration timeout); + , time_duration timeout, int priority = 0); void done(int ticket); void limit(int limit); int limit() const; void close(); #ifndef NDEBUG - void check_invariant() const; - #endif private: @@ -71,7 +75,7 @@ private: struct entry { - entry(): connecting(false), ticket(0), expires(max_time()) {} + entry(): connecting(false), ticket(0), expires(max_time()), priority(0) {} // called when the connection is initiated boost::function on_connect; // called if done hasn't been called within the timeout @@ -80,6 +84,7 @@ private: int ticket; ptime expires; time_duration timeout; + int priority; }; std::list m_queue; @@ -97,6 +102,9 @@ private: #ifndef NDEBUG bool m_in_timeout_function; #endif +#ifdef TORRENT_CONNECTION_LOGGING + std::ofstream m_log; +#endif }; } diff --git a/libtorrent/include/libtorrent/debug.hpp b/libtorrent/include/libtorrent/debug.hpp index 96c2c9dc7..0f2dad20b 100755 --- a/libtorrent/include/libtorrent/debug.hpp +++ b/libtorrent/include/libtorrent/debug.hpp @@ -60,17 +60,21 @@ namespace libtorrent { logger(fs::path const& logpath, fs::path const& filename, int instance, bool append = true) { +#ifndef BOOST_NO_EXCEPTIONS try { +#endif fs::path dir(fs::complete(logpath / ("libtorrent_logs" + boost::lexical_cast(instance)))); if (!fs::exists(dir)) fs::create_directories(dir); m_file.open((dir / filename).string().c_str(), std::ios_base::out | (append ? std::ios_base::app : std::ios_base::out)); *this << "\n\n\n*** starting log ***\n"; +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { std::cerr << "failed to create log '" << filename.string() << "': " << e.what() << std::endl; } +#endif } template diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index 4e0e95adf..c01393d7e 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -42,14 +42,24 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include +#include #include #include "libtorrent/config.hpp" +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR +#include +#endif namespace libtorrent { + struct cached_piece_info + { + int piece; + std::vector blocks; + ptime last_use; + }; + struct disk_io_job { disk_io_job() @@ -69,6 +79,9 @@ namespace libtorrent , move_storage , release_files , delete_files + , check_fastresume + , check_files + , save_resume_data }; action_t action; @@ -82,21 +95,61 @@ namespace libtorrent // to the error message std::string str; + // on error, this is set to the path of the + // file the disk operation failed on + std::string error_file; + // priority decides whether or not this // job will skip entries in the queue or // not. It always skips in front of entries // with lower priority int priority; + boost::shared_ptr resume_data; + // this is called when operation completes boost::function callback; }; + struct cache_status + { + cache_status() + : blocks_written(0) + , writes(0) + , blocks_read(0) + , blocks_read_hit(0) + , reads(0) + , cache_size(0) + , read_cache_size(0) + {} + + // the number of 16kB blocks written + size_type blocks_written; + // the number of write operations used + size_type writes; + // (blocks_written - writes) / blocks_written represents the + // "cache hit" ratio in the write cache + // the number of blocks read + + // the number of blocks passed back to the bittorrent engine + size_type blocks_read; + // the number of blocks that was just copied from the read cache + size_type blocks_read_hit; + // the number of read operations used + size_type reads; + + // the number of blocks in the cache (both read and write) + int cache_size; + + // the number of blocks in the cache used for read cache + int read_cache_size; + }; + // this is a singleton consisting of the thread and a queue // of disk io jobs struct disk_io_thread : boost::noncopyable { - disk_io_thread(int block_size = 16 * 1024); + disk_io_thread(asio::io_service& ios, int block_size = 16 * 1024); ~disk_io_thread(); #ifdef TORRENT_STATS @@ -112,10 +165,6 @@ namespace libtorrent , boost::function const& f = boost::function()); -#ifndef NDEBUG - disk_io_job find_job(boost::intrusive_ptr s - , int action, int piece) const; -#endif // keep track of the number of bytes in the job queue // at any given time. i.e. the sum of all buffer_size. // this is used to slow down the download global download @@ -123,28 +172,116 @@ namespace libtorrent size_type queue_buffer_size() const { return m_queue_buffer_size; } + void get_cache_info(sha1_hash const& ih + , std::vector& ret) const; + + cache_status status() const; + void set_cache_size(int s); + void set_cache_expiry(int ex); + void operator()(); +#ifndef NDEBUG + bool is_disk_buffer(char* buffer) const; +#endif + char* allocate_buffer(); void free_buffer(char* buf); +#ifndef NDEBUG + void check_invariant() const; +#endif + private: + struct cached_piece_entry + { + int piece; + // storage this piece belongs to + boost::intrusive_ptr storage; + // the last time a block was writting to this piece + ptime last_use; + // the number of blocks in the cache for this piece + int num_blocks; + // the pointers to the block data + boost::shared_array blocks; + }; + typedef boost::recursive_mutex mutex_t; + typedef std::list cache_t; + + char* allocate_buffer(mutex_t::scoped_lock& l); + void free_buffer(char* buf, mutex_t::scoped_lock& l); + + // cache operations + cache_t::iterator find_cached_piece( + cache_t& cache, disk_io_job const& j + , mutex_t::scoped_lock& l); + + // write cache operations + void flush_oldest_piece(mutex_t::scoped_lock& l); + void flush_expired_pieces(mutex_t::scoped_lock& l); + void flush_and_remove(cache_t::iterator i, mutex_t::scoped_lock& l); + void flush(cache_t::iterator i, mutex_t::scoped_lock& l); + void cache_block(disk_io_job& j, mutex_t::scoped_lock& l); + + // read cache operations + bool clear_oldest_read_piece(cache_t::iterator ignore + , mutex_t::scoped_lock& l); + int read_into_piece(cached_piece_entry& p, int start_block, mutex_t::scoped_lock& l); + int cache_read_block(disk_io_job const& j, mutex_t::scoped_lock& l); + void free_piece(cached_piece_entry& p, mutex_t::scoped_lock& l); + bool make_room(int num_blocks + , cache_t::iterator ignore + , mutex_t::scoped_lock& l); + int try_read_from_cache(disk_io_job const& j, mutex_t::scoped_lock& l); + mutable mutex_t m_mutex; boost::condition m_signal; bool m_abort; std::list m_jobs; size_type m_queue_buffer_size; - // memory pool for read and write operations - boost::pool<> m_pool; + // write cache + cache_t m_pieces; + + // read cache + cache_t m_read_pieces; -#ifndef NDEBUG - int m_block_size; - disk_io_job m_current; + // total number of blocks in use by both the read + // and the write cache. This is not supposed to + // exceed m_cache_size + cache_status m_cache_stats; + int m_num_cached_blocks; + + // in (16kB) blocks + int m_cache_size; + + // expiration time of cache entries in seconds + int m_cache_expiry; + + // if set to true, each piece flush will allocate + // one piece worth of temporary memory on the heap + // and copy the block data into it, and then perform + // a single write operation from that buffer. + // if memory is constrained, that temporary buffer + // might is avoided by setting this to false. + // in case the allocation fails, the piece flush + // falls back to writing each block separately. + bool m_coalesce_writes; + bool m_coalesce_reads; + bool m_use_read_cache; + +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + // memory pool for read and write operations + // and disk cache + boost::pool<> m_pool; #endif + // number of bytes per block. The BitTorrent + // protocol defines the block size to 16 KiB. + int m_block_size; + #ifdef TORRENT_DISK_STATS std::ofstream m_log; #endif @@ -152,6 +289,11 @@ namespace libtorrent int m_allocations; #endif + size_type m_writes; + size_type m_blocks_written; + + asio::io_service& m_ios; + // thread for performing blocking disk io operations boost::thread m_disk_io_thread; }; diff --git a/libtorrent/include/libtorrent/entry.hpp b/libtorrent/include/libtorrent/entry.hpp index 7238af37e..dfd256d18 100755 --- a/libtorrent/include/libtorrent/entry.hpp +++ b/libtorrent/include/libtorrent/entry.hpp @@ -204,6 +204,16 @@ namespace libtorrent }; #endif +#ifndef NDEBUG + public: + // in debug mode this is set to false by bdecode + // to indicate that the program has not yet queried + // the type of this entry, and sould not assume + // that it has a certain type. This is asserted in + // the accessor functions. This does not apply if + // exceptions are used. + mutable bool m_type_queried; +#endif }; inline std::ostream& operator<<(std::ostream& os, const entry& e) @@ -212,11 +222,14 @@ namespace libtorrent return os; } - inline entry::data_type entry::type() const { return m_type; } + inline entry::data_type entry::type() const + { +#ifndef NDEBUG + m_type_queried = true; +#endif + return m_type; + } - inline entry::entry(): m_type(undefined_t) {} - inline entry::entry(data_type t): m_type(t) { construct(t); } - inline entry::entry(const entry& e) { copy(e); } inline entry::~entry() { destruct(); } inline void entry::operator=(const entry& e) @@ -225,12 +238,13 @@ namespace libtorrent copy(e); } - inline entry::integer_type& entry::integer() { if (m_type == undefined_t) construct(int_t); #ifndef BOOST_NO_EXCEPTIONS if (m_type != int_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == int_t); return *reinterpret_cast(data); @@ -240,6 +254,8 @@ namespace libtorrent { #ifndef BOOST_NO_EXCEPTIONS if (m_type != int_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == int_t); return *reinterpret_cast(data); @@ -250,6 +266,8 @@ namespace libtorrent if (m_type == undefined_t) construct(string_t); #ifndef BOOST_NO_EXCEPTIONS if (m_type != string_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == string_t); return *reinterpret_cast(data); @@ -259,6 +277,8 @@ namespace libtorrent { #ifndef BOOST_NO_EXCEPTIONS if (m_type != string_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == string_t); return *reinterpret_cast(data); @@ -269,6 +289,8 @@ namespace libtorrent if (m_type == undefined_t) construct(list_t); #ifndef BOOST_NO_EXCEPTIONS if (m_type != list_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == list_t); return *reinterpret_cast(data); @@ -278,6 +300,8 @@ namespace libtorrent { #ifndef BOOST_NO_EXCEPTIONS if (m_type != list_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == list_t); return *reinterpret_cast(data); @@ -288,6 +312,8 @@ namespace libtorrent if (m_type == undefined_t) construct(dictionary_t); #ifndef BOOST_NO_EXCEPTIONS if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == dictionary_t); return *reinterpret_cast(data); @@ -297,6 +323,8 @@ namespace libtorrent { #ifndef BOOST_NO_EXCEPTIONS if (m_type != dictionary_t) throw type_error("invalid type requested from entry"); +#elif !defined NDEBUG + TORRENT_ASSERT(m_type_queried); #endif TORRENT_ASSERT(m_type == dictionary_t); return *reinterpret_cast(data); diff --git a/libtorrent/include/libtorrent/enum_net.hpp b/libtorrent/include/libtorrent/enum_net.hpp index 56d89d472..5a0d4b92a 100644 --- a/libtorrent/include/libtorrent/enum_net.hpp +++ b/libtorrent/include/libtorrent/enum_net.hpp @@ -56,9 +56,11 @@ namespace libtorrent // returns true if the specified address is on the same // local network as us - TORRENT_EXPORT bool in_local_network(asio::io_service& ios, address const& addr, asio::error_code& ec); + TORRENT_EXPORT bool in_local_network(asio::io_service& ios, address const& addr + , asio::error_code& ec); - TORRENT_EXPORT address router_for_interface(address const interface, asio::error_code& ec); + TORRENT_EXPORT address get_default_gateway(asio::io_service& ios, address const& addr + , asio::error_code& ec); } #endif diff --git a/libtorrent/include/libtorrent/escape_string.hpp b/libtorrent/include/libtorrent/escape_string.hpp index e0e743e1e..b663d094d 100755 --- a/libtorrent/include/libtorrent/escape_string.hpp +++ b/libtorrent/include/libtorrent/escape_string.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_ESCAPE_STRING_HPP_INCLUDED #include +#include #include "libtorrent/config.hpp" namespace libtorrent @@ -41,6 +42,15 @@ 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); + + // encodes a string using the base64 scheme + TORRENT_EXPORT std::string base64encode(std::string const& s); + // encodes a string using the base32 scheme + TORRENT_EXPORT std::string base32encode(std::string const& s); + TORRENT_EXPORT std::string base32decode(std::string const& s); + + TORRENT_EXPORT boost::optional url_has_argument( + std::string const& url, std::string argument); } #endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/extensions.hpp b/libtorrent/include/libtorrent/extensions.hpp index fd48588e1..8046d307d 100644 --- a/libtorrent/include/libtorrent/extensions.hpp +++ b/libtorrent/include/libtorrent/extensions.hpp @@ -56,6 +56,7 @@ namespace libtorrent struct peer_request; class peer_connection; class entry; + struct disk_buffer_holder; struct TORRENT_EXPORT torrent_plugin { @@ -143,7 +144,7 @@ namespace libtorrent virtual bool on_request(peer_request const& req) { return false; } - virtual bool on_piece(peer_request const& piece, char const* data) + virtual bool on_piece(peer_request const& piece, disk_buffer_holder& data) { return false; } virtual bool on_cancel(peer_request const& req) diff --git a/libtorrent/include/libtorrent/file.hpp b/libtorrent/include/libtorrent/file.hpp index bd0d03539..8a03c5294 100755 --- a/libtorrent/include/libtorrent/file.hpp +++ b/libtorrent/include/libtorrent/file.hpp @@ -109,9 +109,9 @@ namespace libtorrent file(fs::path const& p, open_mode m); ~file(); - void open(fs::path const& p, open_mode m); + bool open(fs::path const& p, open_mode m); void close(); - void set_size(size_type size); + bool set_size(size_type size); size_type write(const char*, size_type num_bytes); size_type read(char*, size_type num_bytes); @@ -119,6 +119,8 @@ namespace libtorrent size_type seek(size_type pos, seek_mode m = begin); size_type tell(); + std::string const& error() const; + private: struct impl; diff --git a/libtorrent/include/libtorrent/file_pool.hpp b/libtorrent/include/libtorrent/file_pool.hpp index a22c26538..d57f6def5 100644 --- a/libtorrent/include/libtorrent/file_pool.hpp +++ b/libtorrent/include/libtorrent/file_pool.hpp @@ -65,7 +65,8 @@ namespace libtorrent { file_pool(int size = 40): m_size(size) {} - boost::shared_ptr open_file(void* st, fs::path const& p, file::open_mode m); + boost::shared_ptr open_file(void* st, fs::path const& p + , file::open_mode m, std::string& error); void release(void* st); void resize(int size); @@ -74,9 +75,7 @@ namespace libtorrent struct lru_file_entry { - lru_file_entry(boost::shared_ptr const& f) - : file_ptr(f) - , last_use(time_now()) {} + lru_file_entry(): last_use(time_now()) {} mutable boost::shared_ptr file_ptr; fs::path file_path; void* key; diff --git a/libtorrent/include/libtorrent/http_connection.hpp b/libtorrent/include/libtorrent/http_connection.hpp index b65b303ae..f9b521862 100644 --- a/libtorrent/include/libtorrent/http_connection.hpp +++ b/libtorrent/include/libtorrent/http_connection.hpp @@ -42,14 +42,22 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/socket.hpp" -#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/http_parser.hpp" #include "libtorrent/time.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/session_settings.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/variant_stream.hpp" +#endif namespace libtorrent { struct http_connection; +class connection_queue; typedef boost::function http_handler; @@ -81,6 +89,8 @@ struct http_connection : boost::enable_shared_from_this, boost: , m_redirects(5) , m_connection_ticket(-1) , m_cc(cc) + , m_ssl(false) + , m_priority(0) { TORRENT_ASSERT(!m_handler.empty()); } @@ -93,14 +103,22 @@ struct http_connection : boost::enable_shared_from_this, boost: std::string sendbuffer; void get(std::string const& url, time_duration timeout = seconds(30) - , int handle_redirects = 5); + , int prio = 0, proxy_settings const* ps = 0, int handle_redirects = 5 + , std::string const& user_agent = "", address const& bind_addr = address_v4::any()); void start(std::string const& hostname, std::string const& port - , time_duration timeout, int handle_redirect = 5); + , time_duration timeout, int prio = 0, proxy_settings const* ps = 0 + , bool ssl = false, int handle_redirect = 5 + , address const& bind_addr = address_v4::any()); + void close(); - tcp::socket const& socket() const { return m_sock; } - +#ifdef TORRENT_USE_OPENSSL + variant_stream > const& socket() const { return m_sock; } +#else + socket_type const& socket() const { return m_sock; } +#endif + private: void on_resolve(asio::error_code const& e @@ -118,7 +136,11 @@ private: void callback(asio::error_code const& e, char const* data = 0, int size = 0); std::vector m_recvbuffer; - tcp::socket m_sock; +#ifdef TORRENT_USE_OPENSSL + variant_stream > m_sock; +#else + socket_type m_sock; +#endif int m_read_pos; tcp::resolver m_resolver; http_parser m_parser; @@ -158,6 +180,21 @@ private: int m_connection_ticket; connection_queue& m_cc; + + // specifies whether or not the connection is + // configured to use a proxy + proxy_settings m_proxy; + + // true if the connection is using ssl + bool m_ssl; + + // the address to bind to. address_v4::any() + // means do not bind + address m_bind_addr; + + // the priority we have in the connection queue. + // 0 is normal, 1 is high + int m_priority; }; } diff --git a/libtorrent/include/libtorrent/http_tracker_connection.hpp b/libtorrent/include/libtorrent/http_tracker_connection.hpp index 41df4c953..2a61fef83 100755 --- a/libtorrent/include/libtorrent/http_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/http_tracker_connection.hpp @@ -33,85 +33,30 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED #define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED -#include #include -#include -#include #ifdef _MSC_VER #pragma warning(push, 1) #endif #include -#include -#include -#include #ifdef _MSC_VER #pragma warning(pop) #endif -#include "libtorrent/socket.hpp" -#include "libtorrent/entry.hpp" -#include "libtorrent/session_settings.hpp" #include "libtorrent/peer_id.hpp" -#include "libtorrent/peer.hpp" #include "libtorrent/tracker_manager.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/buffer.hpp" -#include "libtorrent/socket_type.hpp" -#include "libtorrent/connection_queue.hpp" namespace libtorrent { - class http_parser - { - public: - http_parser(); - std::string const& header(char const* key) const - { - static std::string empty; - std::map::const_iterator i - = m_header.find(key); - if (i == m_header.end()) return empty; - return i->second; - } - - std::string const& protocol() const { return m_protocol; } - int status_code() const { return m_status_code; } - std::string const& method() const { return m_method; } - std::string const& path() const { return m_path; } - std::string const& message() const { return m_server_message; } - buffer::const_interval get_body() const; - 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; } - int content_length() const { return m_content_length; } - - void reset(); - - std::map const& headers() const { return m_header; } - - private: - int m_recv_pos; - int m_status_code; - std::string m_method; - std::string m_path; - std::string m_protocol; - std::string m_server_message; - - int m_content_length; - - 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; - }; + struct http_connection; + class entry; + class http_parser; + class connection_queue; + struct session_settings; class TORRENT_EXPORT http_tracker_connection : public tracker_connection @@ -120,13 +65,10 @@ namespace libtorrent public: http_tracker_connection( - asio::strand& str + io_service& ios , connection_queue& cc , tracker_manager& man , tracker_request const& req - , std::string const& hostname - , unsigned short port - , std::string request , address bind_infc , boost::weak_ptr c , session_settings const& stn @@ -140,43 +82,16 @@ namespace libtorrent 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 on_response(asio::error_code const& ec, http_parser const& parser + , char const* data, int size); - void name_lookup(asio::error_code const& error, tcp::resolver::iterator i); - void connect(int ticket, tcp::endpoint target_address); - void connected(asio::error_code const& error); - void sent(asio::error_code const& error); - void receive(asio::error_code const& error - , std::size_t bytes_transferred); + virtual void on_timeout() {} - virtual void on_timeout(); - - void parse(const entry& e); - peer_entry extract_peer_info(const entry& e); + void parse(int status_code, const entry& e); + bool extract_peer_info(const entry& e, peer_entry& ret); tracker_manager& m_man; - http_parser m_parser; - - asio::strand& m_strand; - tcp::resolver m_name_lookup; - int m_port; - socket_type m_socket; - int m_recv_pos; - std::vector m_buffer; - std::string m_send_buffer; - - session_settings const& m_settings; - proxy_settings const& m_proxy; - std::string m_password; - - bool m_timed_out; - - int m_connection_ticket; - connection_queue& m_cc; + boost::shared_ptr m_tracker_connection; }; } diff --git a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp index 5cccdf827..c2f4e5b60 100644 --- a/libtorrent/include/libtorrent/intrusive_ptr_base.hpp +++ b/libtorrent/include/libtorrent/intrusive_ptr_base.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" diff --git a/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp index 6882d34bb..0d0064af5 100644 --- a/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp +++ b/libtorrent/include/libtorrent/kademlia/dht_tracker.hpp @@ -50,9 +50,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/node.hpp" #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/traversal_algorithm.hpp" -#include "libtorrent/kademlia/packet_iterator.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/session_status.hpp" +#include "libtorrent/udp_socket.hpp" namespace libtorrent { namespace dht { @@ -70,16 +70,14 @@ namespace libtorrent { namespace dht { friend void intrusive_ptr_add_ref(dht_tracker const*); friend void intrusive_ptr_release(dht_tracker const*); - dht_tracker(asio::io_service& ios, dht_settings const& settings - , asio::ip::address listen_interface, entry const& bootstrap); + dht_tracker(udp_socket& sock, dht_settings const& settings + , entry const& bootstrap); void stop(); void add_node(udp::endpoint node); void add_node(std::pair const& node); void add_router_node(std::pair const& node); - void rebind(asio::ip::address listen_interface, int listen_port); - entry state() const; void announce(sha1_hash const& ih, int listen_port @@ -88,6 +86,10 @@ namespace libtorrent { namespace dht void dht_status(session_status& s); + // translate bittorrent kademlia message into the generic kademlia message + // used by the library + void on_receive(udp::endpoint const& ep, char const* pkt, int size); + private: boost::intrusive_ptr self() @@ -101,22 +103,12 @@ namespace libtorrent { namespace dht void refresh_timeout(asio::error_code const& e); void tick(asio::error_code const& e); - // translate bittorrent kademlia message into the generic kademlia message - // used by the library - void on_receive(asio::error_code const& error, size_t bytes_transferred); void on_bootstrap(); void send_packet(msg const& m); - asio::strand m_strand; - asio::ip::udp::socket m_socket; - node_impl m_dht; + udp_socket& m_sock; - // this is the index of the receive buffer we are currently receiving to - // the other buffer is the one containing the last message - int m_buffer; - std::vector m_in_buf[2]; - udp::endpoint m_remote_endpoint[2]; std::vector m_send_buf; ptime m_last_new_key; diff --git a/libtorrent/include/libtorrent/kademlia/find_data.hpp b/libtorrent/include/libtorrent/kademlia/find_data.hpp index 17d77c9d8..39a945296 100644 --- a/libtorrent/include/libtorrent/kademlia/find_data.hpp +++ b/libtorrent/include/libtorrent/kademlia/find_data.hpp @@ -39,7 +39,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include diff --git a/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp b/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp index a7c47f29a..ffcc90b5b 100644 --- a/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp +++ b/libtorrent/include/libtorrent/kademlia/rpc_manager.hpp @@ -44,7 +44,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include #include @@ -94,6 +93,7 @@ public: void reply_with_ping(msg& m); #ifndef NDEBUG + size_t allocation_size() const; void check_invariant() const; #endif @@ -114,7 +114,7 @@ private: typedef boost::array transactions_t; transactions_t m_transactions; - std::vector m_aborted_transactions; + std::vector m_aborted_transactions; // this is the next transaction id to be used int m_next_transaction_id; diff --git a/libtorrent/include/libtorrent/natpmp.hpp b/libtorrent/include/libtorrent/natpmp.hpp index 3b9923972..2a52d4e8a 100644 --- a/libtorrent/include/libtorrent/natpmp.hpp +++ b/libtorrent/include/libtorrent/natpmp.hpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/intrusive_ptr_base.hpp" #include +#include #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) #include @@ -45,8 +46,8 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -// int: external tcp port -// int: external udp port +// int: port mapping index +// int: external port // std::string: error message typedef boost::function portmap_callback_t; @@ -59,34 +60,38 @@ public: // maps the ports, if a port is set to 0 // it will not be mapped - void set_mappings(int tcp, int udp); + enum protocol_type { none = 0, udp = 1, tcp = 2 }; + int add_mapping(protocol_type p, int external_port, int local_port); + void delete_mapping(int mapping_index); void close(); private: - void update_mapping(int i, int port); + void update_mapping(int i); void send_map_request(int i); void resend_request(int i, asio::error_code const& e); void on_reply(asio::error_code const& e , std::size_t bytes_transferred); void try_next_mapping(int i); void update_expiration_timer(); - void refresh_mapping(int i); void mapping_expired(asio::error_code const& e, int i); - struct mapping + void disable(char const* message); + + struct mapping_t { - mapping() - : need_update(false) + enum action_t { action_none, action_add, action_delete }; + mapping_t() + : action(action_none) , local_port(0) , external_port(0) - , protocol(1) + , protocol(none) {} // indicates that the mapping has changed // and needs an update - bool need_update; + int action; // the time the port mapping will expire ptime expires; @@ -100,14 +105,12 @@ private: // should announce to others int external_port; - // 1 = udp, 2 = tcp int protocol; }; portmap_callback_t m_callback; - // 0 is tcp and 1 is udp - mapping m_mappings[2]; + std::vector m_mappings; // the endpoint to the nat router udp::endpoint m_nat_endpoint; @@ -136,9 +139,17 @@ private: // timer used to refresh mappings deadline_timer m_refresh_timer; + + // the mapping index that will expire next + int m_next_refresh; bool m_disabled; + bool m_abort; + + typedef boost::mutex mutex_t; + mutex_t m_mutex; + #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) std::ofstream m_log; #endif diff --git a/libtorrent/include/libtorrent/pch.hpp b/libtorrent/include/libtorrent/pch.hpp index 735999826..16fb27f3c 100644 --- a/libtorrent/include/libtorrent/pch.hpp +++ b/libtorrent/include/libtorrent/pch.hpp @@ -86,7 +86,6 @@ #include #include #include -#include #ifdef __OBJC__ #undef Protocol diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 97d76a3d1..07fe96d4c 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -75,6 +75,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" namespace libtorrent { @@ -122,6 +123,12 @@ namespace libtorrent , boost::shared_ptr s , policy::peer* peerinfo); + // this function is called after it has been constructed and properly + // reference counted. It is safe to call self() in this function + // and schedule events with references to itself (that is not safe to + // do in the constructor). + virtual void start(); + virtual ~peer_connection(); void set_peer_info(policy::peer* pi) @@ -235,7 +242,7 @@ namespace libtorrent void timed_out(); // this will cause this peer_connection to be disconnected. - void disconnect(); + void disconnect(char const* message); bool is_disconnecting() const { return m_disconnecting; } // this is called when the connection attempt has succeeded @@ -286,7 +293,16 @@ namespace libtorrent int desired_queue_size() const { return m_desired_queue_size; } -#ifdef TORRENT_VERBOSE_LOGGING + // compares this connection against the given connection + // for which one is more eligible for an unchoke. + // returns true if this is more eligible + bool unchoke_compare(boost::intrusive_ptr const& p) const; + + // resets the byte counters that are used to measure + // the number of bytes transferred within unchoke cycles + void reset_choke_counters(); + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING boost::shared_ptr m_logger; #endif @@ -306,6 +322,7 @@ namespace libtorrent 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, disk_buffer_holder& data); void incoming_piece(peer_request const& p, char const* data); void incoming_piece_fragment(); void incoming_cancel(peer_request const& r); @@ -399,6 +416,19 @@ namespace libtorrent int send_buffer_capacity() const { return m_send_buffer.capacity(); } + int packet_size() const { return m_packet_size; } + + bool packet_finished() const + { return m_packet_size <= m_recv_pos; } + +#ifndef NDEBUG + bool piece_failed; +#endif + + // upload and download channel state + // enum from peer_info::bw_state + char m_channel_state[2]; + protected: virtual void get_specific_peer_info(peer_info& p) const = 0; @@ -411,7 +441,7 @@ namespace libtorrent 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, char* buffer) = 0; + virtual void write_piece(peer_request const& r, disk_buffer_holder& buffer) = 0; virtual void write_reject_request(peer_request const& r) = 0; virtual void write_allow_fast(int piece) = 0; @@ -427,10 +457,14 @@ namespace libtorrent #ifndef TORRENT_DISABLE_ENCRYPTION buffer::interval wr_recv_buffer() { + TORRENT_ASSERT(m_disk_recv_buffer == 0); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); if (m_recv_buffer.empty()) return buffer::interval(0,0); return buffer::interval(&m_recv_buffer[0] , &m_recv_buffer[0] + m_recv_pos); } + + std::pair wr_recv_buffers(int bytes); #endif buffer::const_interval receive_buffer() const @@ -440,15 +474,11 @@ namespace libtorrent , &m_recv_buffer[0] + m_recv_pos); } + bool allocate_disk_receive_buffer(int disk_buffer_size); + char* release_disk_receive_buffer(); + bool has_disk_receive_buffer() const { return m_disk_recv_buffer; } void cut_receive_buffer(int size, int packet_size); - void reset_recv_buffer(int packet_size); - int packet_size() const { return m_packet_size; } - - bool packet_finished() const - { - return m_packet_size <= m_recv_pos; - } void setup_receive(); @@ -499,6 +529,14 @@ namespace libtorrent char m_country[2]; #endif +#ifndef NDEBUG + boost::intrusive_ptr self() + { + TORRENT_ASSERT(!m_in_constructor); + return intrusive_ptr_base::self(); + } +#endif + private: void fill_send_buffer(); @@ -525,6 +563,13 @@ namespace libtorrent int m_recv_pos; buffer m_recv_buffer; + // if this peer is receiving a piece, this + // points to a disk buffer that the data is + // read into. This eliminates a memcopy from + // the receive buffer into the disk buffer + int m_disk_recv_buffer_size; + char* m_disk_recv_buffer; + chained_buffer m_send_buffer; // the number of bytes we are currently reading @@ -661,12 +706,6 @@ namespace libtorrent // connections. bool m_queued; - // these are true when there's a asynchronous write - // or read operation in progress. Or an asyncronous bandwidth - // request is in progress. - bool m_writing; - bool m_reading; - // if set to non-zero, this peer will always prefer // to request entire n pieces, rather than blocks. // where n is the value of this variable. @@ -741,6 +780,30 @@ namespace libtorrent // immediate. bool m_fast_reconnect; + // the time when async_connect was called + ptime m_connect; + + // estimated round trip time to this peer + // based on the time from when async_connect + // was called to when on_connection_complete + // was called. The rtt is specified in milliseconds + int m_rtt; + + // the total payload download bytes + // at the last unchoke cycle. This is used to + // measure the number of bytes transferred during + // an unchoke cycle, to unchoke peers the more bytes + // they sent us + size_type m_downloaded_at_last_unchoke; + +#ifndef TORRENT_DISABLE_GEO_IP + std::string m_inet_as_name; +#endif + + // max transfer rates seen on this peer + int m_download_rate_peak; + int m_upload_rate_peak; + #ifndef NDEBUG public: bool m_in_constructor; diff --git a/libtorrent/include/libtorrent/peer_id.hpp b/libtorrent/include/libtorrent/peer_id.hpp index a546c6e08..b91717c0a 100755 --- a/libtorrent/include/libtorrent/peer_id.hpp +++ b/libtorrent/include/libtorrent/peer_id.hpp @@ -48,8 +48,6 @@ namespace libtorrent class TORRENT_EXPORT big_number { - // private type - struct private_pointer {}; // the number of bytes of the number enum { number_size = 20 }; public: @@ -57,16 +55,19 @@ namespace libtorrent big_number() {} - big_number(std::string const& s) + explicit big_number(char const* s) + { + if (s == 0) clear(); + else std::memcpy(m_number, s, size); + } + + explicit big_number(std::string const& s) { TORRENT_ASSERT(s.size() >= 20); int sl = int(s.size()) < size ? int(s.size()) : size; std::memcpy(m_number, &s[0], sl); } - // when initialized with 0 - big_number(private_pointer*) { clear(); } - void clear() { std::fill(m_number,m_number+number_size,0); diff --git a/libtorrent/include/libtorrent/peer_info.hpp b/libtorrent/include/libtorrent/peer_info.hpp index e65f33a18..b45b5dcbd 100755 --- a/libtorrent/include/libtorrent/peer_info.hpp +++ b/libtorrent/include/libtorrent/peer_info.hpp @@ -78,6 +78,16 @@ namespace libtorrent int source; + // bw_idle: the channel is not used + // bw_torrent: the channel is waiting for torrent quota + // bw_global: the channel is waiting for global quota + // bw_network: the channel is waiting for an async write + // for read operation to complete + enum bw_state { bw_idle, bw_torrent, bw_global, bw_network }; + + char read_state; + char write_state; + tcp::endpoint ip; float up_speed; float down_speed; @@ -101,6 +111,9 @@ namespace libtorrent // the number bytes that's actually used of the send buffer int used_send_buffer; + int receive_buffer_size; + int used_receive_buffer; + // the number of failed hashes for this peer int num_hashfails; @@ -112,6 +125,12 @@ namespace libtorrent char country[2]; #endif +#ifndef TORRENT_DISABLE_GEO_IP + // atonomous system this peer belongs to + std::string inet_as_name; + int inet_as; +#endif + size_type load_balancing; // this is the number of requests @@ -157,8 +176,31 @@ namespace libtorrent // number of bytes this peer has in // the disk write queue int pending_disk_bytes; + + // numbers used for bandwidth limiting + int send_quota; + int receive_quota; + + // estimated rtt to peer, in milliseconds + int rtt; + + // the highest transfer rates seen for this peer + int download_rate_peak; + int upload_rate_peak; }; + struct TORRENT_EXPORT peer_list_entry + { + enum flags_t + { + banned = 1, + }; + + tcp::endpoint ip; + int flags; + boost::uint8_t failcount; + boost::uint8_t source; + }; } #endif // TORRENT_PEER_INFO_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/piece_picker.hpp b/libtorrent/include/libtorrent/piece_picker.hpp index ef69c3334..581975af9 100755 --- a/libtorrent/include/libtorrent/piece_picker.hpp +++ b/libtorrent/include/libtorrent/piece_picker.hpp @@ -133,22 +133,24 @@ namespace libtorrent void get_availability(std::vector& avail) const; - void set_sequenced_download_threshold(int sequenced_download_threshold); + void sequential_download(bool sd); + bool sequential_download() const { return m_sequential_download >= 0; } // the vector tells which pieces we already have // and which we don't have. - void files_checked( - std::vector const& pieces - , std::vector const& unfinished - , std::vector& verify_pieces); + void init(std::vector const& pieces); // increases the peer count for the given piece - // (is used when a HAVE or BITFIELD message is received) + // (is used when a HAVE message is received) void inc_refcount(int index); + void dec_refcount(int index); + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(std::vector const& bitmask); // decreases the peer count for the given piece // (used when a peer disconnects) - void dec_refcount(int index); + void dec_refcount(std::vector const& bitmask); // these will increase and decrease the peer count // of all pieces. They are used when seeds join @@ -237,6 +239,7 @@ namespace libtorrent , piece_state_t s); void mark_as_writing(piece_block block, void* peer); void mark_as_finished(piece_block block, void* peer); + void write_failed(piece_block block); int num_peers(piece_block block) const; // returns information about the given piece @@ -274,9 +277,11 @@ namespace libtorrent #ifndef NDEBUG // used in debug mode + void verify_priority(int start, int end, int prio) const; void check_invariant(const torrent* t = 0) const; void verify_pick(std::vector const& picked , std::vector const& bitfield) const; + void print_pieces() const; #endif // functor that compares indices on downloading_pieces @@ -295,6 +300,8 @@ namespace libtorrent private: + friend struct piece_pos; + bool can_pick(int piece, std::vector const& bitmask) const; std::pair expand_piece(int piece, int whole_pieces , std::vector const& have) const; @@ -346,26 +353,20 @@ namespace libtorrent bool filtered() const { return piece_priority == filter_priority; } void filtered(bool f) { piece_priority = f ? filter_priority : 0; } - int priority(int limit) const + int priority(piece_picker const* picker) const { - if (downloading || filtered() || have()) return 0; + if (downloading || filtered() + || have() || peer_count + picker->m_seeds == 0) + return -1; + + // priority 5, 6 and 7 disregards availability of the piece + if (piece_priority > 4) return 7 - piece_priority; + // pieces we are currently downloading have high priority - int prio = peer_count * 2; - // if the peer_count is 0 or 1, the priority cannot be higher - if (prio <= 1) return prio; - if (prio >= limit * 2) prio = limit * 2; - // the different priority levels - switch (piece_priority) - { - case 1: return prio; - case 2: return prio - 1; - case 3: return (std::max)(prio / 2, 1); - case 4: return (std::max)(prio / 2 - 1, 1); - case 5: return (std::max)(prio / 3, 1); - case 6: return (std::max)(prio / 3 - 1, 1); - case 7: return 1; - } - return prio; + int prio = peer_count * 4; +// if (prio >= picker->m_prio_limit * 6) prio = picker->m_prio_limit * 6; + + return prio + (4 - piece_priority); } bool operator!=(piece_pos p) const @@ -378,27 +379,44 @@ namespace libtorrent BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); - bool is_ordered(int priority) const - { - return priority >= m_sequenced_download_threshold * 2; - } + void update_pieces() const; + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + void priority_range(int prio, int* start, int* end); + + // adds the piece 'index' to m_pieces void add(int index); - void move(int vec_index, int elem_index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, int elem_index); + // updates the position of the piece with the given + // priority and the elem_index in the m_pieces vector + void update(int priority, int elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, int elem_index); + void sort_piece(std::vector::iterator dp); downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); - // this vector contains all pieces we don't have. - // in the first entry (index 0) is a vector of all pieces - // that no peer have, the vector at index 1 contains - // all pieces that exactly one peer have, index 2 contains - // all pieces exactly two peers have and so on. - // this is not entirely true. The availibility of a piece - // is adjusted depending on its priority. But the principle - // is that the higher index, the lower priority a piece has. - std::vector > m_piece_info; + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds; + + // the following vectors are mutable because they sometimes may + // be updated lazily, triggered by const functions + + // this vector contains all piece indices that are pickable + // sorted by priority. Pieces are in random random order + // among pieces with the same priority + mutable std::vector m_pieces; + + // these are indices to the priority boundries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundries[0] etc. + mutable std::vector m_priority_boundries; // this maps indices to number of peers that has this piece and // index into the m_piece_info vectors. @@ -406,7 +424,7 @@ namespace libtorrent // doesn't exist in the piece_info buckets // pieces with the filtered flag set doesn't have entries in // the m_piece_info buckets either - std::vector m_piece_map; + mutable std::vector m_piece_map; // each piece that's currently being downloaded // has an entry in this list with block allocations. @@ -438,9 +456,16 @@ namespace libtorrent // the number of pieces we have int m_num_have; - // the required popularity of a piece in order to download - // it in sequence instead of random order. - int m_sequenced_download_threshold; + // -1 means sequential download is not active. + // >= 0 means that pieces are requested in sequential order + // and this variable is the next piece to request. + // in that case m_pieces is cleared and not used. + int m_sequential_download; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty; + #ifndef NDEBUG bool m_files_checked_called; #endif diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 95397b49e..29753a11e 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -85,13 +85,16 @@ namespace libtorrent // the tracker, pex, lsd or dht. policy::peer* peer_from_tracker(const tcp::endpoint& remote, const peer_id& pid , int source, char flags); - void update_peer_port(int port, policy::peer* p, int src); + + // false means duplicate connection + bool update_peer_port(int port, policy::peer* p, int src); // called when an incoming connection is accepted - void new_connection(peer_connection& c); + // false means the connection was refused or failed + bool new_connection(peer_connection& c); // the given connection was just closed - void connection_closed(const peer_connection& c) throw(); + void connection_closed(const peer_connection& c); // the peer has got at least one interesting piece void peer_is_interesting(peer_connection& c); @@ -123,56 +126,27 @@ namespace libtorrent struct peer { enum connection_type { not_connectable, connectable }; - peer(tcp::endpoint const& ip, connection_type t, int src); size_type total_download() const; size_type total_upload() const; // the ip/port pair this peer is or was connected on - // 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. tcp::endpoint ip; - connection_type type; -#ifndef TORRENT_DISABLE_ENCRYPTION - // Hints encryption support of peer. Only effective for - // and when the outgoing encryption policy allows both - // encrypted and non encrypted connections - // (pe_settings::out_enc_policy == enabled). The initial - // state of this flag determines the initial connection - // attempt type (true = encrypted, false = standard). - // This will be toggled everytime either an encrypted or - // non-encrypted handshake fails. - bool pe_support; +#ifndef TORRENT_DISABLE_GEO_IP +#ifndef NDEBUG + // only used in debug mode to assert that + // the first entry in the AS pair keeps the same + boost::uint16_t inet_as_num; +#endif + // The AS this peer belongs to + std::pair* inet_as; #endif - // the number of failed connection attempts this peer has - int failcount; - // the number of times this peer has been - // part of a piece that failed the hash check - int hashfails; - - // this is true if the peer is a seed - bool seed; - - int fast_reconnects; - - // true if this peer currently is unchoked - // because of an optimistic unchoke. - // when the optimistic unchoke is moved to - // another peer, this peer will be choked - // if this is true - bool optimistically_unchoked; - - // the time when this peer was optimistically unchoked - // the last time. - libtorrent::ptime last_optimistically_unchoked; - - // the time when the peer connected to us - // or disconnected if it isn't connected right now - libtorrent::ptime connected; + // the number of failed connection attempts + // this peer has + boost::uint8_t failcount; // for every valid piece we receive where this // peer was one of the participants, we increase @@ -180,15 +154,68 @@ namespace libtorrent // where this peer was a participant, we decrease // this value. If it sinks below a threshold, its // considered a bad peer and will be banned. - int trust_points; + boost::int8_t trust_points; - // if this is true, the peer has previously participated - // in a piece that failed the piece hash check. This will - // put the peer on parole and only request entire pieces. - // if a piece pass that was partially requested from this - // peer it will leave parole mode and continue download + // a bitmap combining the peer_source flags + // from peer_info. + boost::uint8_t source; + + // the number of times this peer has been + // part of a piece that failed the hash check + boost::uint8_t hashfails; + + // type specifies if the connection was incoming + // or outgoing. If we ever saw this peer as connectable + // it will remain as connectable + unsigned type:4; + + // the number of times we have allowed a fast + // reconnect for this peer. + unsigned fast_reconnects:4; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of peer. Only effective + // for and when the outgoing encryption policy + // allows both encrypted and non encrypted + // connections (pe_settings::out_enc_policy + // == enabled). The initial state of this flag + // determines the initial connection attempt + // type (true = encrypted, false = standard). + // This will be toggled everytime either an + // encrypted or non-encrypted handshake fails. + bool pe_support:1; +#endif + // true if this peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another peer, this peer will be choked + // if this is true + bool optimistically_unchoked:1; + + // this is true if the peer is a seed + bool seed:1; + + // if this is true, the peer has previously + // participated in a piece that failed the piece + // hash check. This will put the peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this peer it + // will leave parole mode and continue download // pieces as normal peers. - bool on_parole; + bool on_parole:1; + + // is set to true if this peer has been banned + bool banned:1; + +#ifndef TORRENT_DISABLE_DHT + // this is set to true when this peer as been + // pinged by the DHT + bool added_to_dht:1; +#endif + + // if the peer is connected now, this + // will refer to a valid peer_connection + peer_connection* connection; // this is the accumulated amount of // uploaded and downloaded data to this @@ -202,16 +229,13 @@ namespace libtorrent size_type prev_amount_upload; size_type prev_amount_download; - // is set to true if this peer has been banned - bool banned; + // the time when this peer was optimistically unchoked + // the last time. + libtorrent::ptime last_optimistically_unchoked; - // a bitmap combining the peer_source flags - // from peer_info. - int source; - - // if the peer is connected now, this - // will refer to a valid peer_connection - peer_connection* connection; + // the time when the peer connected to us + // or disconnected if it isn't connected right now + libtorrent::ptime connected; }; int num_peers() const { return m_peers.size(); } @@ -226,23 +250,23 @@ namespace libtorrent bool connect_one_peer(); bool disconnect_one_peer(); - private: -/* - bool unchoke_one_peer(); - void choke_one_peer(); - iterator find_choke_candidate(); - iterator find_unchoke_candidate(); + bool has_peer(policy::peer const* p) const; + + int num_seeds() const { return m_num_seeds; } + int num_connect_candidates() const { return m_num_connect_candidates; } + void recalculate_connect_candidates() + { + if (m_num_connect_candidates == 0) + m_num_connect_candidates = 1; + } + + private: - // the seed prefix means that the - // function is used while seeding. - bool seed_unchoke_one_peer(); - void seed_choke_one_peer(); - iterator find_seed_choke_candidate(); - iterator find_seed_unchoke_candidate(); -*/ iterator find_disconnect_candidate(); iterator find_connect_candidate(); + bool is_connect_candidate(peer const& p, bool finished); + std::multimap m_peers; torrent* m_torrent; @@ -251,10 +275,16 @@ namespace libtorrent // been distributed yet. size_type m_available_free_upload; - // if there is a connection limit, - // we disconnect one peer every minute in hope of - // establishing a connection with a better peer -// ptime m_last_optimistic_disconnect; + // The number of peers in our peer list + // that are connect candidates. i.e. they're + // not already connected and they have not + // yet reached their max try count and they + // have the connectable state (we have a listen + // port for them). + int m_num_connect_candidates; + + // the number of seeds in the peer list + int m_num_seeds; }; } diff --git a/libtorrent/include/libtorrent/proxy_base.hpp b/libtorrent/include/libtorrent/proxy_base.hpp index 037a1c2d4..cdf5ff216 100644 --- a/libtorrent/include/libtorrent/proxy_base.hpp +++ b/libtorrent/include/libtorrent/proxy_base.hpp @@ -75,6 +75,7 @@ public: return m_sock.read_some(buffers, ec); } +#ifndef BOOST_NO_EXCEPTIONS template std::size_t read_some(Mutable_Buffers const& buffers) { @@ -86,6 +87,7 @@ public: { m_sock.io_control(ioc); } +#endif template void io_control(IO_Control_Command& ioc, asio::error_code& ec) @@ -99,32 +101,52 @@ public: m_sock.async_write_some(buffers, handler); } +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.set_option(opt); + } +#endif + + template + asio::error_code set_option(SettableSocketOption const& opt, asio::error_code& ec) + { + return m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS void bind(endpoint_type const& endpoint) { m_sock.bind(endpoint); } +#endif void bind(endpoint_type const& endpoint, asio::error_code& ec) { m_sock.bind(endpoint, ec); } +#ifndef BOOST_NO_EXCEPTIONS void open(protocol_type const& p) { m_sock.open(p); } +#endif void open(protocol_type const& p, asio::error_code& ec) { m_sock.open(p, ec); } +#ifndef BOOST_NO_EXCEPTIONS void close() { m_remote_endpoint = endpoint_type(); m_sock.close(); m_resolver.cancel(); } +#endif void close(asio::error_code& ec) { @@ -132,22 +154,26 @@ public: m_resolver.cancel(); } - endpoint_type remote_endpoint() +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return m_remote_endpoint; + } +#endif + + endpoint_type remote_endpoint(asio::error_code& ec) const { return m_remote_endpoint; } - endpoint_type remote_endpoint(asio::error_code& ec) - { - return m_remote_endpoint; - } - - endpoint_type local_endpoint() +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const { return m_sock.local_endpoint(); } +#endif - endpoint_type local_endpoint(asio::error_code& ec) + endpoint_type local_endpoint(asio::error_code& ec) const { return m_sock.local_endpoint(ec); } @@ -161,6 +187,8 @@ public: { return m_sock.lowest_layer(); } + + bool is_open() const { return m_sock.is_open(); } protected: diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index d2ab6ab2e..63dae16e7 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/fingerprint.hpp" #include "libtorrent/time.hpp" +#include "libtorrent/disk_io_thread.hpp" #include "libtorrent/storage.hpp" @@ -75,6 +76,8 @@ namespace libtorrent class ip_filter; class port_filter; class connection_queue; + class natpmp; + class upnp; namespace fs = boost::filesystem; @@ -123,7 +126,7 @@ namespace libtorrent session(fingerprint const& print = fingerprint("LT" , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path logpath = "." #endif ); @@ -131,7 +134,7 @@ namespace libtorrent fingerprint const& print , std::pair listen_port_range , char const* listen_interface = "0.0.0.0" -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path logpath = "." #endif ); @@ -176,6 +179,10 @@ namespace libtorrent session_proxy abort() { return session_proxy(m_impl); } session_status status() const; + cache_status get_cache_status() const; + + void get_cache_info(sha1_hash const& ih + , std::vector& ret) const; #ifndef TORRENT_DISABLE_DHT void start_dht(entry const& startup_state = entry()); @@ -195,6 +202,14 @@ namespace libtorrent void add_extension(boost::function(torrent*, void*)> ext); #endif +#ifndef TORRENT_DISABLE_GEO_IP + bool load_asnum_db(char const* file); + bool load_country_db(char const* file); +#endif + + void load_state(entry const& ses_state); + entry state() const; + void set_ip_filter(ip_filter const& f); void set_port_filter(port_filter const& f); void set_peer_id(peer_id const& pid); @@ -272,8 +287,8 @@ namespace libtorrent // starts/stops UPnP, NATPMP or LSD port mappers // they are stopped by default void start_lsd(); - void start_natpmp(); - void start_upnp(); + natpmp* start_natpmp(); + upnp* start_upnp(); void stop_lsd(); void stop_natpmp(); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index 2817d27d2..eb6fa35ca 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -102,7 +102,11 @@ namespace libtorrent , min_reconnect_time(60) , peer_connect_timeout(7) , ignore_limits_on_local_network(true) +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS + , connection_speed(2) +#else , connection_speed(20) +#endif , send_redundant_have(false) , lazy_bitfields(true) , inactivity_timeout(600) @@ -118,6 +122,12 @@ namespace libtorrent #endif , free_torrent_hashes(true) , upnp_ignore_nonrouters(true) + , send_buffer_watermark(80 * 1024) + , auto_upload_slots(true) + , cache_size(512) + , cache_expiry(60) + , outgoing_ports(0,0) + , peer_tos(0) {} // this is the user agent that will be sent to the tracker @@ -298,6 +308,45 @@ namespace libtorrent // any upnp devices that don't have an address that matches // our currently configured router. bool upnp_ignore_nonrouters; + + // if the send buffer has fewer bytes than this, we'll + // read another 16kB block onto it. If set too small, + // upload rate capacity will suffer. If set too high, + // memory will be wasted. + // The actual watermark may be lower than this in case + // the upload rate is low, this is the upper limit. + int send_buffer_watermark; + + // if auto_upload_slots is true, and a global upload + // limit is set and the upload rate is less than 90% + // of the upload limit, on new slot is opened up. If + // the upload rate is >= upload limit for an extended + // period of time, one upload slot is closed. The + // upload slots are never automatically decreased below + // the manual settings, through max_uploads. + bool auto_upload_slots; + + // the disk write cache, specified in 16 KiB blocks. + // default is 512 (= 8 MB) + int cache_size; + + // the number of seconds a write cache entry sits + // idle in the cache before it's forcefully flushed + // to disk. Default is 60 seconds. + int cache_expiry; + + // if != (0, 0), this is the range of ports that + // outgoing connections will be bound to. This + // is useful for users that have routers that + // allow QoS settings based on local port. + std::pair outgoing_ports; + + // the TOS byte of all peer traffic (including + // web seeds) is set to this value. The default + // is the QBSS scavenger service + // http://qbone.internet2.edu/qbss/ + // For unmarked packets, set to 0 + char peer_tos; }; #ifndef TORRENT_DISABLE_DHT diff --git a/libtorrent/include/libtorrent/session_status.hpp b/libtorrent/include/libtorrent/session_status.hpp index e0a9b88a7..4e42dba5f 100644 --- a/libtorrent/include/libtorrent/session_status.hpp +++ b/libtorrent/include/libtorrent/session_status.hpp @@ -54,6 +54,8 @@ namespace libtorrent size_type total_payload_upload; int num_peers; + int num_unchoked; + int allowed_upload_slots; int up_bandwidth_queue; int down_bandwidth_queue; diff --git a/libtorrent/include/libtorrent/socket.hpp b/libtorrent/include/libtorrent/socket.hpp index 499842dd7..31c8525a0 100755 --- a/libtorrent/include/libtorrent/socket.hpp +++ b/libtorrent/include/libtorrent/socket.hpp @@ -50,7 +50,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include @@ -98,6 +97,15 @@ namespace libtorrent typedef asio::basic_deadline_timer deadline_timer; + inline std::ostream& print_address(std::ostream& os, address const& addr) + { + asio::error_code ec; + std::string a = addr.to_string(ec); + if (ec) return os; + os << a; + return os; + } + inline std::ostream& print_endpoint(std::ostream& os, tcp::endpoint const& ep) { address const& addr = ep.address(); @@ -186,6 +194,19 @@ namespace libtorrent int m_value; }; + struct type_of_service + { + type_of_service(char val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_TOS; } + template + char const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + char m_value; + }; } #endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index a7b5e37a4..51c7b3c01 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -71,6 +71,7 @@ namespace libtorrent class session; struct file_pool; struct disk_io_job; + struct disk_buffer_holder; enum storage_mode_t { @@ -119,31 +120,33 @@ namespace libtorrent // if allocate_files is true. // allocate_files is true if allocation mode // is set to full and sparse files are supported - virtual void initialize(bool allocate_files) = 0; + // false return value indicates an error + virtual bool initialize(bool allocate_files) = 0; - // may throw file_error if storage for slot does not exist - virtual size_type read(char* buf, int slot, int offset, int size) = 0; + // negative return value indicates an error + virtual int read(char* buf, int slot, int offset, int size) = 0; - // may throw file_error if storage for slot hasn't been allocated - virtual void write(const char* buf, int slot, int offset, int size) = 0; + // negative return value indicates an error + virtual int write(const char* buf, int slot, int offset, int size) = 0; + // non-zero return value indicates an error virtual bool move_storage(fs::path save_path) = 0; // verify storage dependent fast resume entries - virtual bool verify_resume_data(entry& rd, std::string& error) = 0; + virtual bool verify_resume_data(entry const& rd, std::string& error) = 0; // write storage dependent fast resume entries - virtual void write_resume_data(entry& rd) const = 0; + virtual bool write_resume_data(entry& rd) const = 0; // moves (or copies) the content in src_slot to dst_slot - virtual void move_slot(int src_slot, int dst_slot) = 0; + virtual bool move_slot(int src_slot, int dst_slot) = 0; // swaps the data in slot1 and slot2 - virtual void swap_slots(int slot1, int slot2) = 0; + virtual bool swap_slots(int slot1, int slot2) = 0; // swaps the puts the data in slot1 in slot2, the data in slot2 // in slot3 and the data in slot3 in slot1 - virtual void swap_slots3(int slot1, int slot2, int slot3) = 0; + virtual bool swap_slots3(int slot1, int slot2, int slot3) = 0; // returns the sha1-hash for the data at the given slot virtual sha1_hash hash_for_slot(int slot, partial_hash& h, int piece_size) = 0; @@ -151,10 +154,25 @@ namespace libtorrent // this will close all open files that are opened for // writing. This is called when a torrent has finished // downloading. - virtual void release_files() = 0; + // non-zero return value indicates an error + virtual bool release_files() = 0; // this will close all open files and delete them - virtual void delete_files() = 0; + // non-zero return value indicates an error + virtual bool delete_files() = 0; + + void set_error(std::string const& file, std::string const& msg) const + { + m_error_file = file; + m_error = msg; + } + + std::string const& error() const { return m_error; } + std::string const& error_file() const { return m_error_file; } + void clear_error() { m_error.clear(); m_error_file.clear(); } + + mutable std::string m_error; + mutable std::string m_error_file; virtual ~storage_interface() {} }; @@ -167,6 +185,10 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& path, file_pool& fp); + TORRENT_EXPORT storage_interface* mapped_storage_constructor( + boost::intrusive_ptr ti + , fs::path const& path, file_pool& fp); + struct disk_io_thread; class TORRENT_EXPORT piece_manager @@ -183,50 +205,32 @@ namespace libtorrent , fs::path const& path , file_pool& fp , disk_io_thread& io - , storage_constructor_type sc); + , storage_constructor_type sc + , storage_mode_t sm); ~piece_manager(); - bool check_fastresume(aux::piece_checker_data& d - , std::vector& pieces, int& num_pieces, storage_mode_t storage_mode - , std::string& error_msg); - std::pair check_files(std::vector& pieces - , int& num_pieces, boost::recursive_mutex& mutex); - - // frees a buffer that was returned from a read operation - void free_buffer(char* buf); + torrent_info const* info() const { return m_info.get(); } void write_resume_data(entry& rd) const; - bool verify_resume_data(entry& rd, std::string& error); - bool is_allocating() const - { return m_state == state_expand_pieces; } - - void mark_failed(int index); - - unsigned long piece_crc( - int slot_index - , int block_size - , piece_picker::block_info const* bi); - - int slot_for(int piece) const; - int piece_for(int slot) const; + void async_check_fastresume(entry const* resume_data + , boost::function const& handler); + void async_check_files(boost::function const& handler); + void async_read( peer_request const& r , boost::function const& handler - , char* buffer = 0 , int priority = 0); void async_write( peer_request const& r - , char const* buffer + , disk_buffer_holder& buffer , boost::function const& f); void async_hash(int piece, boost::function const& f); - fs::path save_path() const; - void async_release_files( boost::function const& handler = boost::function()); @@ -238,12 +242,48 @@ namespace libtorrent void async_move_storage(fs::path const& p , boost::function const& handler); - // fills the vector that maps all allocated - // slots to the piece that is stored (or - // partially stored) there. -2 is the index - // of unassigned pieces and -1 is unallocated - void export_piece_map(std::vector& pieces - , std::vector const& have) const; + void async_save_resume_data( + boost::function const& handler); + + enum return_t + { + // return values from check_fastresume and check_files + no_error = 0, + need_full_check = -1, + fatal_disk_error = -2, + }; + + private: + + fs::path save_path() const; + + bool verify_resume_data(entry const& rd, std::string& error) + { return m_storage->verify_resume_data(rd, error); } + + bool is_allocating() const + { return m_state == state_expand_pieces; } + + void mark_failed(int index); + + std::string const& error() const { return m_storage->error(); } + std::string const& error_file() const { return m_storage->error_file(); } + void clear_error() { m_storage->clear_error(); } + + int slot_for(int piece) const; + int piece_for(int slot) const; + + // helper functions for check_dastresume + int check_no_fastresume(std::string& error); + int check_init_storage(std::string& error); + + // if error is set and return value is 'no_error' or 'need_full_check' + // the error message indicates that the fast resume data was rejected + // if 'fatal_disk_error' is returned, the error message indicates what + // when wrong in the disk access + int check_fastresume(entry const& rd, std::string& error); + + // this function returns true if the checking is complete + int check_files(int& current_slot, int& have_piece, std::string& error); bool compact_allocation() const { return m_storage_mode == storage_mode_compact; } @@ -251,36 +291,31 @@ namespace libtorrent #ifndef NDEBUG std::string name() const { return m_info->name(); } #endif - - private: bool allocate_slots(int num_slots, bool abort_on_disk = false); - int identify_data( - const std::vector& piece_data - , int current_slot - , std::vector& have_pieces - , int& num_pieces - , const std::multimap& hash_to_piece - , boost::recursive_mutex& mutex); - - size_type read_impl( + int read_impl( char* buf , int piece_index , int offset , int size); - void write_impl( + int write_impl( const char* buf , int piece_index , int offset , int size); + bool check_one_piece(int& have_piece); + int identify_data( + const std::vector& piece_data + , int current_slot); + void switch_to_full_mode(); sha1_hash hash_for_piece_impl(int piece); - void release_files_impl() { m_storage->release_files(); } - void delete_files_impl() { m_storage->delete_files(); } + int release_files_impl() { return m_storage->release_files(); } + int delete_files_impl() { return m_storage->delete_files(); } bool move_storage_impl(fs::path const& save_path); @@ -330,8 +365,6 @@ namespace libtorrent state_none, // the file checking is complete state_finished, - // creating the directories - state_create_files, // checking the files state_full_check, // move pieces to their final position @@ -376,9 +409,6 @@ namespace libtorrent // the piece_manager destructs. This is because // the torrent_info object is owned by the torrent. boost::shared_ptr m_torrent; -#ifndef NDEBUG - bool m_resume_data_verified; -#endif }; } diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 92ea37a40..bfec8bb53 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -98,20 +98,19 @@ namespace libtorrent torrent( aux::session_impl& ses - , aux::checker_impl& checker , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc - , bool paused); + , bool paused + , entry const& resume_data); // used with metadata-less torrents // (the metadata is downloaded from the peers) torrent( aux::session_impl& ses - , aux::checker_impl& checker , char const* tracker_url , sha1_hash const& info_hash , char const* name @@ -120,7 +119,8 @@ namespace libtorrent , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc - , bool paused); + , bool paused + , entry const& resume_data); ~torrent(); @@ -133,10 +133,21 @@ namespace libtorrent , void* userdata); #endif +#ifndef NDEBUG + bool has_peer(peer_connection* p) const + { return m_connections.find(p) != m_connections.end(); } +#endif + // this is called when the torrent has metadata. // it will initialize the storage and the piece-picker void init(); + void on_resume_data_checked(int ret, disk_io_job const& j); + void on_piece_checked(int ret, disk_io_job const& j); + void files_checked(); + void start_checking(); + + storage_mode_t storage_mode() const { return m_storage_mode; } // this will flag the torrent as aborted. The main // loop in session_impl will check for this state // on all torrents once every second, and take @@ -144,19 +155,12 @@ namespace libtorrent void abort(); bool is_aborted() const { return m_abort; } - // returns true if this torrent is being allocated - // by the checker thread. - bool is_allocating() const; - session_settings const& settings() const; aux::session_impl& session() { return m_ses; } - void set_sequenced_download_threshold(int threshold); + void set_sequential_download(bool sd); - bool verify_resume_data(entry& rd, std::string& error) - { TORRENT_ASSERT(m_storage); return m_storage->verify_resume_data(rd, error); } - void second_tick(stat& accumulator, float tick_interval); // debug purpose only @@ -164,11 +168,6 @@ namespace libtorrent std::string name() const; - bool check_fastresume(aux::piece_checker_data&); - std::pair check_files(); - void files_checked(std::vector const& - unfinished_pieces); - stat statistics() const { return m_stat; } size_type bytes_left() const; boost::tuples::tuple bytes_done() const; @@ -179,6 +178,7 @@ namespace libtorrent void pause(); void resume(); bool is_paused() const { return m_paused; } + void save_resume_data(); void delete_files(); @@ -229,12 +229,12 @@ namespace libtorrent void request_bandwidth(int channel , boost::intrusive_ptr const& p - , int priority); + , int max_block_size, int priority); void perform_bandwidth_request(int channel , boost::intrusive_ptr const& p , int block_size, int priority); - + void expire_bandwidth(int channel, int amount); void assign_bandwidth(int channel, int amount, int blk); @@ -243,6 +243,8 @@ namespace libtorrent int max_assignable_bandwidth(int channel) const { return m_bandwidth_limit[channel].max_assignable(); } + int bandwidth_queue_size(int channel) const; + // -------------------------------------------- // PEER MANAGEMENT @@ -268,7 +270,8 @@ namespace libtorrent // used by peer_connection to attach itself to a torrent // since incoming connections don't know what torrent // they're a part of until they have received an info_hash. - void attach_peer(peer_connection* p); + // false means attach failed + bool attach_peer(peer_connection* p); // this will remove the peer and make sure all // the pieces it had have their reference counter @@ -279,6 +282,7 @@ namespace libtorrent bool want_more_peers() const; bool try_connect_peer(); + void give_connect_points(int points); // the number of peers that belong to this torrent int num_peers() const { return (int)m_connections.size(); } @@ -295,6 +299,7 @@ namespace libtorrent void resolve_peer_country(boost::intrusive_ptr const& p) const; + void get_full_peer_list(std::vector& v) const; void get_peer_info(std::vector& v); void get_download_queue(std::vector& queue); @@ -308,7 +313,7 @@ namespace libtorrent virtual void tracker_response( tracker_request const& r , std::vector& e, int interval - , int complete, int incomplete); + , int complete, int incomplete, address const& external_ip); virtual void tracker_request_timed_out( tracker_request const& r); virtual void tracker_request_error(tracker_request const& r @@ -364,8 +369,7 @@ namespace libtorrent int num_pieces() const { return m_num_pieces; } - // when we get a have- or bitfield- messages, this is called for every - // piece a peer has gained. + // when we get a have message, this is called for that piece void peer_has(int index) { if (m_picker.get()) @@ -382,6 +386,22 @@ namespace libtorrent #endif } + // when we get a bitfield message, this is called for that piece + void peer_has(std::vector const& bitfield) + { + if (m_picker.get()) + { + TORRENT_ASSERT(!is_seed()); + m_picker->inc_refcount(bitfield); + } +#ifndef NDEBUG + else + { + TORRENT_ASSERT(is_seed()); + } +#endif + } + void peer_has_all() { if (m_picker.get()) @@ -397,7 +417,6 @@ namespace libtorrent #endif } - // when peer disconnects, this is called for every piece it had void peer_lost(int index) { if (m_picker.get()) @@ -450,12 +469,12 @@ namespace libtorrent // completed() is called immediately after it. void finished(); - void async_verify_piece(int piece_index, boost::function const&); + void async_verify_piece(int piece_index, boost::function const&); // this is called from the peer_connection // each time a piece has failed the hash // test - void piece_finished(int index, bool passed_hash_check); + void piece_finished(int index, int passed_hash_check); void piece_failed(int index); void received_redundant_data(int num_bytes) { TORRENT_ASSERT(num_bytes > 0); m_total_redundant_bytes += num_bytes; } @@ -496,10 +515,12 @@ namespace libtorrent void replace_trackers(std::vector const& urls); - torrent_handle get_handle() const; + torrent_handle get_handle(); + + void write_resume_data(entry& rd) const; // LOGGING -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING virtual void debug_log(const std::string& line); #endif @@ -536,7 +557,8 @@ namespace libtorrent // bencoded tree and moves the torrent // to the checker thread for initial checking // of the storage. - void set_metadata(entry const&); + // a return value of false indicates an error + bool set_metadata(entry const& metadata, std::string& error); private: @@ -544,9 +566,10 @@ namespace libtorrent void on_files_released(int ret, disk_io_job const& j); void on_torrent_paused(int ret, disk_io_job const& j); void on_storage_moved(int ret, disk_io_job const& j); + void on_save_resume_data(int ret, disk_io_job const& j); void on_piece_verified(int ret, disk_io_job const& j - , boost::function f); + , boost::function f); void try_next_tracker(); int prioritize_tracker(int tracker_index); @@ -554,7 +577,7 @@ namespace libtorrent , boost::intrusive_ptr p) const; bool request_bandwidth_from_session(int channel) const; - void update_peer_interest(); + void update_peer_interest(bool was_finished); boost::intrusive_ptr m_torrent_file; @@ -683,7 +706,6 @@ namespace libtorrent // a back reference to the session // this torrent belongs to. aux::session_impl& m_ses; - aux::checker_impl& m_checker; boost::scoped_ptr m_picker; @@ -717,7 +739,7 @@ namespace libtorrent // in case the piece picker hasn't been constructed // when this settings is set, this variable will keep // its value until the piece picker is created - int m_sequenced_download_threshold; + bool m_sequential_download; // is false by default and set to // true when the first tracker reponse @@ -746,6 +768,12 @@ namespace libtorrent // determines the storage state for this torrent. storage_mode_t m_storage_mode; + // the state of this torrent (queued, checking, downloading) + torrent_status::state_t m_state; + float m_progress; + + entry m_resume_data; + // defaults to 16 kiB, but can be set by the user // when creating the torrent const int m_default_block_size; @@ -795,6 +823,19 @@ namespace libtorrent // total_done - m_initial_done <= total_payload_download size_type m_initial_done; #endif + // this is the deficit counter in the Deficit Round Robin + // used to determine which torrent gets the next + // connection attempt. See: + // http://www.ecs.umass.edu/ece/wolf/courses/ECE697J/papers/DRR.pdf + // The quanta assigned to each torrent depends on the torrents + // priority, whether it's seed and the number of connected + // peers it has. This has the effect that some torrents + // will have more connection attempts than other. Each + // connection attempt costs 100 points from the deficit + // counter. points are deducted in try_connect_peer and + // increased in give_connect_points. Outside of the + // torrent object, these points are called connect_points. + int m_deficit_counter; policy m_policy; }; diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index 24afbe165..e389fe8d0 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -187,6 +187,10 @@ namespace libtorrent // (including seeds), but are not necessarily // connected to int list_peers; + + // the number of peers in our peerlist that + // we potentially could connect to + int connect_candidates; const std::vector* pieces; @@ -278,8 +282,9 @@ namespace libtorrent friend struct aux::session_impl; friend class torrent; - torrent_handle(): m_ses(0), m_chk(0), m_info_hash(0) {} + torrent_handle() {} + void get_full_peer_list(std::vector& v) const; void get_peer_info(std::vector& v) const; torrent_status status() const; void get_download_queue(std::vector& queue) const; @@ -310,6 +315,7 @@ namespace libtorrent bool is_paused() const; void pause() const; void resume() const; + void save_resume_data() const; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void resolve_countries(bool r); @@ -349,7 +355,9 @@ namespace libtorrent // to. void use_interface(const char* net_interface) const; - entry write_resume_data() const; + // use save_resume_data() instead. It is async. and + // will return the resume data in an alert + entry write_resume_data() const TORRENT_DEPRECATED; // forces this torrent to reannounce // (make a rerequest from the tracker) @@ -378,7 +386,7 @@ namespace libtorrent void set_download_limit(int limit) const; int download_limit() const; - void set_sequenced_download_threshold(int threshold) const; + void set_sequential_download(bool sd) const; void set_peer_upload_limit(tcp::endpoint ip, int limit) const; void set_peer_download_limit(tcp::endpoint ip, int limit) const; @@ -404,38 +412,28 @@ namespace libtorrent // post condition: save_path() == save_path if true is returned void move_storage(fs::path const& save_path) const; - const sha1_hash& info_hash() const - { return m_info_hash; } + sha1_hash info_hash() const; bool operator==(const torrent_handle& h) const - { return m_info_hash == h.m_info_hash; } + { return m_torrent.lock() == h.m_torrent.lock(); } bool operator!=(const torrent_handle& h) const - { return m_info_hash != h.m_info_hash; } + { return m_torrent.lock() != h.m_torrent.lock(); } bool operator<(const torrent_handle& h) const - { return m_info_hash < h.m_info_hash; } + { return m_torrent.lock() < h.m_torrent.lock(); } private: - torrent_handle(aux::session_impl* s, - aux::checker_impl* c, - const sha1_hash& h) - : m_ses(s) - , m_chk(c) - , m_info_hash(h) - { - TORRENT_ASSERT(m_ses != 0); - TORRENT_ASSERT(m_chk != 0); - } + torrent_handle(boost::weak_ptr const& t) + : m_torrent(t) + {} #ifndef NDEBUG void check_invariant() const; #endif - aux::session_impl* m_ses; - aux::checker_impl* m_chk; - sha1_hash m_info_hash; + boost::weak_ptr m_torrent; }; diff --git a/libtorrent/include/libtorrent/torrent_info.hpp b/libtorrent/include/libtorrent/torrent_info.hpp index 1eab60902..4a520300b 100755 --- a/libtorrent/include/libtorrent/torrent_info.hpp +++ b/libtorrent/include/libtorrent/torrent_info.hpp @@ -244,7 +244,7 @@ namespace libtorrent void add_node(std::pair const& node); - void parse_info_section(entry const& e); + bool parse_info_section(entry const& e, std::string& error); entry const* extra(char const* key) const { return m_extra_info.find_key(key); } @@ -257,7 +257,7 @@ namespace libtorrent private: - void read_torrent_info(const entry& libtorrent); + bool read_torrent_info(const entry& libtorrent, std::string& error); // the urls to the trackers std::vector m_urls; diff --git a/libtorrent/include/libtorrent/tracker_manager.hpp b/libtorrent/include/libtorrent/tracker_manager.hpp index 8fec9563c..580e6d5eb 100755 --- a/libtorrent/include/libtorrent/tracker_manager.hpp +++ b/libtorrent/include/libtorrent/tracker_manager.hpp @@ -71,9 +71,6 @@ namespace libtorrent struct timeout_handler; struct tracker_connection; - // encodes a string using the base64 scheme - TORRENT_EXPORT std::string base64encode(const std::string& s); - // returns -1 if gzip header is invalid or the header size in bytes TORRENT_EXPORT int gzip_header(const char* buf, int size); @@ -129,7 +126,8 @@ namespace libtorrent , std::vector& peers , int interval , int complete - , int incomplete) = 0; + , int incomplete + , address const& external_ip) = 0; virtual void tracker_request_timed_out( tracker_request const&) = 0; virtual void tracker_request_error( @@ -139,24 +137,18 @@ namespace libtorrent tcp::endpoint m_tracker_address; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING virtual void debug_log(const std::string& line) = 0; #endif private: tracker_manager* m_manager; }; - TORRENT_EXPORT bool inflate_gzip( - std::vector& buffer - , tracker_request const& req - , request_callback* requester - , int maximum_tracker_response_length); - struct TORRENT_EXPORT timeout_handler : intrusive_ptr_base , boost::noncopyable { - timeout_handler(asio::strand& str); + timeout_handler(io_service& str); void set_timeout(int completion_timeout, int read_timeout); void restart_read_timeout(); @@ -172,7 +164,6 @@ namespace libtorrent boost::intrusive_ptr self() { return boost::intrusive_ptr(this); } - asio::strand& m_strand; // used for timeouts // this is set when the request has been sent ptime m_start_time; @@ -194,7 +185,7 @@ namespace libtorrent { tracker_connection(tracker_manager& man , tracker_request const& req - , asio::strand& str + , io_service& ios , address bind_interface , boost::weak_ptr r); @@ -226,7 +217,7 @@ namespace libtorrent , m_abort(false) {} void queue_request( - asio::strand& str + io_service& ios , connection_queue& cc , tracker_request r , std::string const& auth diff --git a/libtorrent/include/libtorrent/udp_tracker_connection.hpp b/libtorrent/include/libtorrent/udp_tracker_connection.hpp index 4fba505a4..b245f932d 100755 --- a/libtorrent/include/libtorrent/udp_tracker_connection.hpp +++ b/libtorrent/include/libtorrent/udp_tracker_connection.hpp @@ -49,7 +49,7 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif -#include "libtorrent/socket.hpp" +#include "libtorrent/udp_socket.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/peer_id.hpp" @@ -65,14 +65,14 @@ namespace libtorrent public: udp_tracker_connection( - asio::strand& str + io_service& ios + , connection_queue& cc , tracker_manager& man , tracker_request const& req - , std::string const& hostname - , unsigned short port , address bind_infc , boost::weak_ptr c - , session_settings const& stn); + , session_settings const& stn + , proxy_settings const& ps); void close(); @@ -92,30 +92,30 @@ namespace libtorrent void name_lookup(asio::error_code const& error, udp::resolver::iterator i); void timeout(asio::error_code const& error); + void on_receive(asio::error_code const& e, udp::endpoint const& ep + , char const* buf, int size); + void on_connect_response(char const* buf, int size); + void on_announce_response(char const* buf, int size); + void on_scrape_response(char const* buf, int size); + void send_udp_connect(); - void connect_response(asio::error_code const& error, std::size_t bytes_transferred); - void send_udp_announce(); - void announce_response(asio::error_code const& error, std::size_t bytes_transferred); - void send_udp_scrape(); - void scrape_response(asio::error_code const& error, std::size_t bytes_transferred); virtual void on_timeout(); tracker_manager& m_man; - asio::strand& m_strand; udp::resolver m_name_lookup; - datagram_socket m_socket; + udp_socket m_socket; udp::endpoint m_target; - udp::endpoint m_sender; int m_transaction_id; boost::int64_t m_connection_id; session_settings const& m_settings; int m_attempts; - std::vector m_buffer; + + action_t m_state; }; } diff --git a/libtorrent/include/libtorrent/upnp.hpp b/libtorrent/include/libtorrent/upnp.hpp index 1d0f8d425..7455fbf72 100644 --- a/libtorrent/include/libtorrent/upnp.hpp +++ b/libtorrent/include/libtorrent/upnp.hpp @@ -58,9 +58,10 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -// int: external tcp port -// int: external udp port +// int: port-mapping index +// int: external port // std::string: error message +// an empty string as error means success typedef boost::function portmap_callback_t; class upnp : public intrusive_ptr_base @@ -71,27 +72,35 @@ public: , portmap_callback_t const& cb, bool ignore_nonrouters); ~upnp(); - // maps the ports, if a port is set to 0 - // it will not be mapped - void set_mappings(int tcp, int udp); + enum protocol_type { none = 0, udp = 1, tcp = 2 }; + int add_mapping(protocol_type p, int external_port, int local_port); + void delete_mapping(int mapping_index); void discover_device(); void close(); + std::string router_model() + { + mutex_t::scoped_lock l(m_mutex); + return m_model; + } + private: + void discover_device_impl(); static address_v4 upnp_multicast_address; static udp::endpoint upnp_multicast_endpoint; - enum { num_mappings = 2 }; enum { default_lease_time = 3600 }; - void update_mapping(int i, int port); void resend_request(asio::error_code const& e); void on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); struct rootdevice; + void next(rootdevice& d, int i); + void update_map(rootdevice& d, int i); + void on_upnp_xml(asio::error_code const& e , libtorrent::http_parser const& p, rootdevice& d); @@ -103,28 +112,43 @@ private: , int mapping); void on_expire(asio::error_code const& e); - void map_port(rootdevice& d, int i); - void unmap_port(rootdevice& d, int i); - void disable(); + void disable(char const* msg); + void return_error(int mapping, int code); void delete_port_mapping(rootdevice& d, int i); void create_port_mapping(http_connection& c, rootdevice& d, int i); void post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action); + int num_mappings() const { return int(m_mappings.size()); } + + struct global_mapping_t + { + global_mapping_t() + : protocol(none) + , external_port(0) + , local_port(0) + {} + int protocol; + int external_port; + int local_port; + }; + struct mapping_t { + enum action_t { action_none, action_add, action_delete }; mapping_t() - : need_update(false) + : action(action_none) , local_port(0) , external_port(0) - , protocol(1) + , protocol(none) + , failcount(0) {} // the time the port mapping will expire ptime expires; - bool need_update; + int action; // the local port for this mapping. If this is set // to 0, the mapping is not in use @@ -135,8 +159,11 @@ private: // should announce to others int external_port; - // 1 = udp, 0 = tcp + // 2 = udp, 1 = tcp int protocol; + + // the number of times this mapping has failed + int failcount; }; struct rootdevice @@ -146,8 +173,6 @@ private: , supports_specific_external(true) , disabled(false) { - mapping[0].protocol = 0; - mapping[1].protocol = 1; #ifndef NDEBUG magic = 1337; #endif @@ -160,7 +185,7 @@ private: magic = 0; } #endif - + // the interface url, through which the list of // supported interfaces are fetched std::string url; @@ -170,7 +195,7 @@ private: // either the WANIP namespace or the WANPPP namespace char const* service_namespace; - mapping_t mapping[num_mappings]; + std::vector mapping; std::string hostname; int port; @@ -200,8 +225,7 @@ private: { return url < rhs.url; } }; - int m_udp_local_port; - int m_tcp_local_port; + std::vector m_mappings; std::string const& m_user_agent; @@ -215,8 +239,6 @@ private: asio::io_service& m_io_service; - asio::strand m_strand; - // the udp socket used to send and receive // multicast messages on the network broadcast_socket m_socket; @@ -234,6 +256,11 @@ private: connection_queue& m_cc; + typedef boost::mutex mutex_t; + mutex_t m_mutex; + + std::string m_model; + #ifdef TORRENT_UPNP_LOGGING std::ofstream m_log; #endif diff --git a/libtorrent/include/libtorrent/variant_stream.hpp b/libtorrent/include/libtorrent/variant_stream.hpp index bbe3d964d..30d972e31 100644 --- a/libtorrent/include/libtorrent/variant_stream.hpp +++ b/libtorrent/include/libtorrent/variant_stream.hpp @@ -182,6 +182,20 @@ namespace aux Protocol const& proto; }; +// -------------- is_open ----------- + + struct is_open_visitor + : boost::static_visitor + { + is_open_visitor() {} + + template + bool operator()(T const* p) const + { return p->is_open(); } + + bool operator()(boost::blank) const { return false; } + }; + // -------------- close ----------- struct close_visitor_ec @@ -221,7 +235,7 @@ namespace aux {} template - EndpointType operator()(T* p) const + EndpointType operator()(T const* p) const { return p->remote_endpoint(error_code); } EndpointType operator()(boost::blank) const @@ -235,13 +249,52 @@ namespace aux : boost::static_visitor { template - EndpointType operator()(T* p) const + EndpointType operator()(T const* p) const { return p->remote_endpoint(); } EndpointType operator()(boost::blank) const { return EndpointType(); } }; +// -------------- set_option ----------- + + template + struct set_option_visitor + : boost::static_visitor<> + { + set_option_visitor(SettableSocketOption const& opt) + : opt_(opt) + {} + + template + void operator()(T* p) const + { p->set_option(opt_); } + + void operator()(boost::blank) const {} + + SettableSocketOption const& opt_; + }; + + template + struct set_option_visitor_ec + : boost::static_visitor + { + set_option_visitor_ec(SettableSocketOption const& opt, asio::error_code& ec) + : opt_(opt) + , ec_(ec) + {} + + template + asio::error_code operator()(T* p) const + { return p->set_option(opt_, ec_); } + + asio::error_code operator()(boost::blank) const + { return ec_; } + + SettableSocketOption const& opt_; + asio::error_code& ec_; + }; + // -------------- local_endpoint ----------- template @@ -253,7 +306,7 @@ namespace aux {} template - EndpointType operator()(T* p) const + EndpointType operator()(T const* p) const { return p->local_endpoint(error_code); } @@ -271,7 +324,7 @@ namespace aux : boost::static_visitor { template - EndpointType operator()(T* p) const + EndpointType operator()(T const* p) const { return p->local_endpoint(); } @@ -379,7 +432,7 @@ namespace aux {} template - std::size_t operator()(T* p) const + std::size_t operator()(T const* p) const { return p->in_avail(ec); } @@ -396,7 +449,7 @@ namespace aux : boost::static_visitor { template - std::size_t operator()(T* p) const + std::size_t operator()(T const* p) const { return p->in_avail(); } @@ -414,7 +467,7 @@ namespace aux template IOService& operator()(T* p) const { - return p->io_service(); + return p->get_io_service(); } IOService& operator()(boost::blank) const @@ -471,11 +524,13 @@ public: typedef typename S0::endpoint_type endpoint_type; typedef typename S0::protocol_type protocol_type; - explicit variant_stream() : m_variant(boost::blank()) {} + explicit variant_stream(asio::io_service& ios) + : m_io_service(ios), m_variant(boost::blank()) {} template void instantiate(asio::io_service& ios) { + TORRENT_ASSERT(&ios == &m_io_service); std::auto_ptr owned(new S(ios)); boost::apply_visitor(aux::delete_visitor(), m_variant); m_variant = owned.get(); @@ -594,6 +649,11 @@ public: ); } + bool is_open() const + { + return boost::apply_visitor(aux::is_open_visitor(), m_variant); + } + void close() { if (!instantiated()) return; @@ -608,13 +668,13 @@ public: ); } - std::size_t in_avail() + std::size_t in_avail() const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::in_avail_visitor(), m_variant); } - std::size_t in_avail(asio::error_code& ec) + std::size_t in_avail(asio::error_code& ec) const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor( @@ -622,13 +682,13 @@ public: ); } - endpoint_type remote_endpoint() + endpoint_type remote_endpoint() const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::remote_endpoint_visitor(), m_variant); } - endpoint_type remote_endpoint(asio::error_code& ec) + endpoint_type remote_endpoint(asio::error_code& ec) const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor( @@ -636,13 +696,29 @@ public: ); } - endpoint_type local_endpoint() + template + void set_option(SettableSocketOption const& opt) + { + TORRENT_ASSERT(instantiated()); + boost::apply_visitor(aux::set_option_visitor(opt) + , m_variant); + } + + template + asio::error_code set_option(SettableSocketOption const& opt, asio::error_code& ec) + { + TORRENT_ASSERT(instantiated()); + return boost::apply_visitor(aux::set_option_visitor_ec(opt, ec) + , m_variant); + } + + endpoint_type local_endpoint() const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor(aux::local_endpoint_visitor(), m_variant); } - endpoint_type local_endpoint(asio::error_code& ec) + endpoint_type local_endpoint(asio::error_code& ec) const { TORRENT_ASSERT(instantiated()); return boost::apply_visitor( @@ -650,12 +726,9 @@ public: ); } - asio::io_service& io_service() + asio::io_service& get_io_service() { - TORRENT_ASSERT(instantiated()); - return boost::apply_visitor( - aux::io_service_visitor(), m_variant - ); + return m_io_service; } lowest_layer_type& lowest_layer() @@ -667,6 +740,7 @@ public: } private: + asio::io_service& m_io_service; variant_type m_variant; }; diff --git a/libtorrent/include/libtorrent/web_peer_connection.hpp b/libtorrent/include/libtorrent/web_peer_connection.hpp index 742d823f0..f9d235bbc 100755 --- a/libtorrent/include/libtorrent/web_peer_connection.hpp +++ b/libtorrent/include/libtorrent/web_peer_connection.hpp @@ -70,8 +70,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" // parse_url #include "libtorrent/tracker_manager.hpp" -// http_parser -#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/http_parser.hpp" namespace libtorrent { @@ -123,7 +122,7 @@ namespace libtorrent void write_cancel(peer_request const& r) { incoming_reject_request(r); } void write_have(int index) {} - void write_piece(peer_request const& r, char* buffer) { TORRENT_ASSERT(false); } + void write_piece(peer_request const& r, disk_buffer_holder& buffer) { TORRENT_ASSERT(false); } void write_keepalive() {} void on_connected(); void write_reject_request(peer_request const&) {} diff --git a/libtorrent/src/GeoIP.c b/libtorrent/src/GeoIP.c new file mode 100644 index 000000000..2df4b5562 --- /dev/null +++ b/libtorrent/src/GeoIP.c @@ -0,0 +1,1038 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */ +/* GeoIP.c + * + * Copyright (C) 2006 MaxMind LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "GeoIP.h" + +#ifndef WIN32 +#include +#include +#include /* For ntohl */ +#include + +#include + +#else +#include +#include +#define snprintf _snprintf +#endif +#include +#include +#include +#include +#include +#include /* for fstat */ +#include /* for fstat */ + +#ifdef HAVE_STDINT_H +#include /* For uint32_t */ +#endif + +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + +#define COUNTRY_BEGIN 16776960 +#define STATE_BEGIN_REV0 16700000 +#define STATE_BEGIN_REV1 16000000 +#define STRUCTURE_INFO_MAX_SIZE 20 +#define DATABASE_INFO_MAX_SIZE 100 +#define MAX_ORG_RECORD_LENGTH 300 +#define US_OFFSET 1 +#define CANADA_OFFSET 677 +#define WORLD_OFFSET 1353 +#define FIPS_RANGE 360 + +#define CHECK_ERR(err, msg) { \ + if (err != Z_OK) { \ + fprintf(stderr, "%s error: %d\n", msg, err); \ + exit(1); \ + } \ +} + +const char GeoIP_country_code[253][3] = { "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN", + "AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB", + "BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO", + "BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD", + "CF","CG","CH","CI","CK","CL","CM","CN","CO","CR", + "CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO", + "DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ", + "FK","FM","FO","FR","FX","GA","GB","GD","GE","GF", + "GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT", + "GU","GW","GY","HK","HM","HN","HR","HT","HU","ID", + "IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO", + "JP","KE","KG","KH","KI","KM","KN","KP","KR","KW", + "KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT", + "LU","LV","LY","MA","MC","MD","MG","MH","MK","ML", + "MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV", + "MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI", + "NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF", + "PG","PH","PK","PL","PM","PN","PR","PS","PT","PW", + "PY","QA","RE","RO","RU","RW","SA","SB","SC","SD", + "SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO", + "SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH", + "TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW", + "TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE", + "VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA", + "ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE", + "BL","MF"}; + +const char GeoIP_country_code3[253][4] = { "--","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT", + "AGO","AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB", + "BGD","BEL","BFA","BGR","BHR","BDI","BEN","BMU","BRN","BOL", + "BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC","COD", + "CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI", + "CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM", + "DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI", + "FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF", + "GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM", + "GUM","GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN", + "IRL","ISR","IND","IO","IRQ","IRN","ISL","ITA","JAM","JOR", + "JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT", + "CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU", + "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI", + "MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV", + "MWI","MEX","MYS","MOZ","NAM","NCL","NER","NFK","NGA","NIC", + "NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER","PYF", + "PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW", + "PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN", + "SWE","SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM", + "SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF","TGO","THA", + "TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN", + "TZA","UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN", + "VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","SRB","ZAF", + "ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY", + "BLM","MAF"}; + +const char * GeoIP_country_name[253] = {"N/A","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles", + "Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados", + "Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia", + "Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the", + "Central African Republic","Congo","Switzerland","Cote D'Ivoire","Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica", + "Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic", + "Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji", + "Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana", + "Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala", + "Guam","Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia", + "Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan", + "Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis","Korea, Democratic People's Republic of","Korea, Republic of","Kuwait", + "Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania", + "Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia","Mali", + "Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives", + "Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua", + "Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia", + "Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau", + "Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles","Sudan", + "Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname", + "Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo","Thailand", + "Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan", + "Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines","Venezuela", + "Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa", + "Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey", + "Saint Barthelemy","Saint Martin"}; + +/* Possible continent codes are AF, AS, EU, NA, OC, SA for Africa, Asia, Europe, North America, Oceania +and South America. */ + +const char GeoIP_country_continent[253][3] = {"--","AS","EU","EU","AS","AS","SA","SA","EU","AS","SA", + "AF","AN","SA","OC","EU","OC","SA","AS","EU","SA", + "AS","EU","AF","EU","AS","AF","AF","SA","AS","SA", + "SA","SA","AS","AF","AF","EU","SA","NA","AS","AF", + "AF","AF","EU","AF","OC","SA","AF","AS","SA","SA", + "SA","AF","AS","AS","EU","EU","AF","EU","SA","SA", + "AF","SA","EU","AF","AF","AF","EU","AF","EU","OC", + "SA","OC","EU","EU","EU","AF","EU","SA","AS","SA", + "AF","EU","SA","AF","AF","SA","AF","EU","SA","SA", + "OC","AF","SA","AS","AF","SA","EU","SA","EU","AS", + "EU","AS","AS","AS","AS","AS","EU","EU","SA","AS", + "AS","AF","AS","AS","OC","AF","SA","AS","AS","AS", + "SA","AS","AS","AS","SA","EU","AS","AF","AF","EU", + "EU","EU","AF","AF","EU","EU","AF","OC","EU","AF", + "AS","AS","AS","OC","SA","AF","SA","EU","AF","AS", + "AF","NA","AS","AF","AF","OC","AF","OC","AF","SA", + "EU","EU","AS","OC","OC","OC","AS","SA","SA","OC", + "OC","AS","AS","EU","SA","OC","SA","AS","EU","OC", + "SA","AS","AF","EU","AS","AF","AS","OC","AF","AF", + "EU","AS","AF","EU","EU","EU","AF","EU","AF","AF", + "SA","AF","SA","AS","AF","SA","AF","AF","AF","AS", + "AS","OC","AS","AF","OC","AS","AS","SA","OC","AS", + "AF","EU","AF","OC","NA","SA","AS","EU","SA","SA", + "SA","SA","AS","OC","OC","OC","AS","AF","EU","AF", + "AF","EU","AF","--","--","--","EU","EU","EU","EU", + "SA","SA"}; + +const char * GeoIPDBDescription[NUM_DB_TYPES] = {NULL, "GeoIP Country Edition", "GeoIP City Edition, Rev 1", "GeoIP Region Edition, Rev 1", "GeoIP ISP Edition", "GeoIP Organization Edition", "GeoIP City Edition, Rev 0", "GeoIP Region Edition, Rev 0","GeoIP Proxy Edition","GeoIP ASNum Edition","GeoIP Netspeed Edition","GeoIP Domain Name Edition"}; + +char * custom_directory = NULL; + +void GeoIP_setup_custom_directory (char * dir) { + custom_directory = dir; +} +/* +char *_GeoIP_full_path_to(const char *file_name) { + int len; + char *path = malloc(sizeof(char) * 1024); + + if (custom_directory == NULL){ +#ifndef WIN32 + memset(path, 0, sizeof(char) * 1024); + snprintf(path, sizeof(char) * 1024 - 1, "%s/%s", GEOIPDATADIR, file_name); +#else + char buf[MAX_PATH], *p, *q = NULL; + memset(buf, 0, sizeof(buf)); + len = GetModuleFileName(GetModuleHandle(NULL), buf, sizeof(buf) - 1); + for (p = buf + len; p > buf; p--) + if (*p == '\\') + { + if (!q) + q = p; + else + *p = '/'; + } + *q = 0; + memset(path, 0, sizeof(char) * 1024); + snprintf(path, sizeof(char) * 1024 - 1, "%s/%s", buf, file_name); +#endif + } else { + len = strlen(custom_directory); + if (custom_directory[len-1] != '/') { + snprintf(path, sizeof(char) * 1024 - 1, "%s/%s",custom_directory, file_name); + } else { + snprintf(path, sizeof(char) * 1024 - 1, "%s%s", custom_directory, file_name); + } + } + return path; +} + +char ** GeoIPDBFileName = NULL; + +void _GeoIP_setup_dbfilename() { + if (NULL == GeoIPDBFileName) { + GeoIPDBFileName = malloc(sizeof(char *) * NUM_DB_TYPES); + memset(GeoIPDBFileName, 0, sizeof(char *) * NUM_DB_TYPES); + + GeoIPDBFileName[GEOIP_COUNTRY_EDITION] = _GeoIP_full_path_to("GeoIP.dat"); + GeoIPDBFileName[GEOIP_REGION_EDITION_REV0] = _GeoIP_full_path_to("GeoIPRegion.dat"); + GeoIPDBFileName[GEOIP_REGION_EDITION_REV1] = _GeoIP_full_path_to("GeoIPRegion.dat"); + GeoIPDBFileName[GEOIP_CITY_EDITION_REV0] = _GeoIP_full_path_to("GeoIPCity.dat"); + GeoIPDBFileName[GEOIP_CITY_EDITION_REV1] = _GeoIP_full_path_to("GeoIPCity.dat"); + GeoIPDBFileName[GEOIP_ISP_EDITION] = _GeoIP_full_path_to("GeoIPISP.dat"); + GeoIPDBFileName[GEOIP_ORG_EDITION] = _GeoIP_full_path_to("GeoIPOrg.dat"); + GeoIPDBFileName[GEOIP_PROXY_EDITION] = _GeoIP_full_path_to("GeoIPProxy.dat"); + GeoIPDBFileName[GEOIP_ASNUM_EDITION] = _GeoIP_full_path_to("GeoIPASNum.dat"); + GeoIPDBFileName[GEOIP_NETSPEED_EDITION] = _GeoIP_full_path_to("GeoIPNetSpeed.dat"); + GeoIPDBFileName[GEOIP_DOMAIN_EDITION] = _GeoIP_full_path_to("GeoIPDomain.dat"); + } +} +*/ + +static +int _file_exists(const char *file_name) { + struct stat file_stat; + return( (stat(file_name, &file_stat) == 0) ? 1:0); +} +/* +int GeoIP_db_avail(int type) { + const char * filePath; + if (type < 0 || type >= NUM_DB_TYPES) { + return 0; + } + _GeoIP_setup_dbfilename(); + filePath = GeoIPDBFileName[type]; + if (NULL == filePath) { + return 0; + } + return _file_exists(filePath); +} +*/ +static +void _setup_segments(GeoIP * gi) { + int i, j; + unsigned char delim[3]; + unsigned char buf[SEGMENT_RECORD_LENGTH]; + + gi->databaseSegments = NULL; + + /* default to GeoIP Country Edition */ + gi->databaseType = GEOIP_COUNTRY_EDITION; + gi->record_length = STANDARD_RECORD_LENGTH; + fseek(gi->GeoIPDatabase, -3l, SEEK_END); + for (i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) { + fread(delim, 1, 3, gi->GeoIPDatabase); + if (delim[0] == 255 && delim[1] == 255 && delim[2] == 255) { + fread(&gi->databaseType, 1, 1, gi->GeoIPDatabase); + if (gi->databaseType >= 106) { + /* backwards compatibility with databases from April 2003 and earlier */ + gi->databaseType -= 105; + } + + if (gi->databaseType == GEOIP_REGION_EDITION_REV0) { + /* Region Edition, pre June 2003 */ + gi->databaseSegments = malloc(sizeof(int)); + gi->databaseSegments[0] = STATE_BEGIN_REV0; + } else if (gi->databaseType == GEOIP_REGION_EDITION_REV1) { + /* Region Edition, post June 2003 */ + gi->databaseSegments = malloc(sizeof(int)); + gi->databaseSegments[0] = STATE_BEGIN_REV1; + } else if (gi->databaseType == GEOIP_CITY_EDITION_REV0 || + gi->databaseType == GEOIP_CITY_EDITION_REV1 || + gi->databaseType == GEOIP_ORG_EDITION || + gi->databaseType == GEOIP_ISP_EDITION || + gi->databaseType == GEOIP_ASNUM_EDITION) { + /* City/Org Editions have two segments, read offset of second segment */ + gi->databaseSegments = malloc(sizeof(int)); + gi->databaseSegments[0] = 0; + fread(buf, SEGMENT_RECORD_LENGTH, 1, gi->GeoIPDatabase); + for (j = 0; j < SEGMENT_RECORD_LENGTH; j++) { + gi->databaseSegments[0] += (buf[j] << (j * 8)); + } + if (gi->databaseType == GEOIP_ORG_EDITION || + gi->databaseType == GEOIP_ISP_EDITION) + gi->record_length = ORG_RECORD_LENGTH; + } + break; + } else { + fseek(gi->GeoIPDatabase, -4l, SEEK_CUR); + } + } + if (gi->databaseType == GEOIP_COUNTRY_EDITION || + gi->databaseType == GEOIP_PROXY_EDITION || + gi->databaseType == GEOIP_NETSPEED_EDITION) { + gi->databaseSegments = malloc(sizeof(int)); + gi->databaseSegments[0] = COUNTRY_BEGIN; + } +} + +static +int _check_mtime(GeoIP *gi) { + struct stat buf; + if (gi->flags & GEOIP_CHECK_CACHE) { + if (stat(gi->file_path, &buf) != -1) { + if (buf.st_mtime != gi->mtime) { + /* GeoIP Database file updated */ + if (gi->flags & (GEOIP_MEMORY_CACHE | GEOIP_MMAP_CACHE)) { +#ifndef WIN32 + if ( gi->flags & GEOIP_MMAP_CACHE) { + munmap(gi->cache, gi->size); + gi->cache = NULL; + } else +#endif + { + /* reload database into memory cache */ + if ((gi->cache = (unsigned char*) realloc(gi->cache, buf.st_size)) == NULL) { + fprintf(stderr,"Out of memory when reloading %s\n",gi->file_path); + return -1; + } + } + } + /* refresh filehandle */ + fclose(gi->GeoIPDatabase); + gi->GeoIPDatabase = fopen(gi->file_path,"rb"); + if (gi->GeoIPDatabase == NULL) { + fprintf(stderr,"Error Opening file %s when reloading\n",gi->file_path); + return -1; + } + gi->mtime = buf.st_mtime; + gi->size = buf.st_size; + +#ifndef WIN32 + if ( gi->flags & GEOIP_MMAP_CACHE) { + gi->cache = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fileno(gi->GeoIPDatabase), 0); + if ( gi->cache == MAP_FAILED ) { + + fprintf(stderr,"Error remapping file %s when reloading\n",gi->file_path); + gi->cache = 0; + return -1; + } + } else +#endif + if ( gi->flags & GEOIP_MEMORY_CACHE ) { + if (fread(gi->cache, sizeof(unsigned char), buf.st_size, gi->GeoIPDatabase) != (size_t) buf.st_size) { + fprintf(stderr,"Error reading file %s when reloading\n",gi->file_path); + return -1; + } + } + if (gi->databaseSegments != NULL) { + free(gi->databaseSegments); + gi->databaseSegments = NULL; + } + _setup_segments(gi); + if (gi->databaseSegments == NULL) { + fprintf(stderr, "Error reading file %s -- corrupt\n", gi->file_path); + return -1; + } + if (gi->flags & GEOIP_INDEX_CACHE) { + gi->index_cache = (unsigned char *) realloc(gi->index_cache, sizeof(unsigned char) * ((gi->databaseSegments[0] * (long)gi->record_length * 2))); + if (gi->index_cache != NULL) { + fseek(gi->GeoIPDatabase, 0, SEEK_SET); + if (fread(gi->index_cache, sizeof(unsigned char), gi->databaseSegments[0] * (long)gi->record_length * 2, gi->GeoIPDatabase) != (size_t) (gi->databaseSegments[0]*(long)gi->record_length * 2)) { + fprintf(stderr,"Error reading file %s where reloading\n",gi->file_path); + return -1; + } + } + } + } + } + } + return 0; +} + +unsigned int _GeoIP_seek_record (GeoIP *gi, unsigned long ipnum) { + int depth; + unsigned int x; + unsigned char stack_buffer[2 * MAX_RECORD_LENGTH]; + const unsigned char *buf = (gi->cache == NULL) ? stack_buffer : NULL; + unsigned int offset = 0; + + const unsigned char * p; + int j; + + _check_mtime(gi); + for (depth = 31; depth >= 0; depth--) { + if (gi->cache == NULL && gi->index_cache == NULL) { + /* read from disk */ + fseek(gi->GeoIPDatabase, (long)gi->record_length * 2 * offset, SEEK_SET); + fread(stack_buffer,gi->record_length,2,gi->GeoIPDatabase); + } else if (gi->index_cache == NULL) { + /* simply point to record in memory */ + buf = gi->cache + (long)gi->record_length * 2 *offset; + } else { + buf = gi->index_cache + (long)gi->record_length * 2 * offset; + } + + if (ipnum & (1 << depth)) { + /* Take the right-hand branch */ + if ( gi->record_length == 3 ) { + /* Most common case is completely unrolled and uses constants. */ + x = (buf[3*1 + 0] << (0*8)) + + (buf[3*1 + 1] << (1*8)) + + (buf[3*1 + 2] << (2*8)); + + } else { + /* General case */ + j = gi->record_length; + p = &buf[2*j]; + x = 0; + do { + x <<= 8; + x += *(--p); + } while ( --j ); + } + + } else { + /* Take the left-hand branch */ + if ( gi->record_length == 3 ) { + /* Most common case is completely unrolled and uses constants. */ + x = (buf[3*0 + 0] << (0*8)) + + (buf[3*0 + 1] << (1*8)) + + (buf[3*0 + 2] << (2*8)); + } else { + /* General case */ + j = gi->record_length; + p = &buf[1*j]; + x = 0; + do { + x <<= 8; + x += *(--p); + } while ( --j ); + } + } + + if (x >= gi->databaseSegments[0]) { + gi->netmask = 32 - depth; + return x; + } + offset = x; + } + + /* shouldn't reach here */ + fprintf(stderr,"Error Traversing Database for ipnum = %lu - Perhaps database is corrupt?\n",ipnum); + return 0; +} + +unsigned long +_GeoIP_addr_to_num(const char *addr) +{ + unsigned int c, octet, t; + unsigned long ipnum; + int i = 3; + + octet = ipnum = 0; + while ((c = *addr++)) { + if (c == '.') { + if (octet > 255) + return 0; + ipnum <<= 8; + ipnum += octet; + i--; + octet = 0; + } else { + t = octet; + octet <<= 3; + octet += t; + octet += t; + c -= '0'; + if (c > 9) + return 0; + octet += c; + } + } + if ((octet > 255) || (i != 0)) + return 0; + ipnum <<= 8; + return ipnum + octet; +} +/* +GeoIP* GeoIP_open_type (int type, int flags) { + GeoIP * gi; + const char * filePath; + if (type < 0 || type >= NUM_DB_TYPES) { + printf("Invalid database type %d\n", type); + return NULL; + } + _GeoIP_setup_dbfilename(); + filePath = GeoIPDBFileName[type]; + if (filePath == NULL) { + printf("Invalid database type %d\n", type); + return NULL; + } + gi = GeoIP_open (filePath, flags); + return gi; +} + +GeoIP* GeoIP_new (int flags) { + GeoIP * gi; + _GeoIP_setup_dbfilename(); + gi = GeoIP_open (GeoIPDBFileName[GEOIP_COUNTRY_EDITION], flags); + return gi; +} +*/ +GeoIP* GeoIP_open (const char * filename, int flags) { + struct stat buf; + GeoIP * gi; + size_t len; + + gi = (GeoIP *)malloc(sizeof(GeoIP)); + if (gi == NULL) + return NULL; + len = sizeof(char) * (strlen(filename)+1); + gi->file_path = malloc(len); + if (gi->file_path == NULL) { + free(gi); + return NULL; + } + strncpy(gi->file_path, filename, len); + gi->GeoIPDatabase = fopen(filename,"rb"); + if (gi->GeoIPDatabase == NULL) { + fprintf(stderr,"Error Opening file %s\n",filename); + free(gi->file_path); + free(gi); + return NULL; + } else { + if (flags & (GEOIP_MEMORY_CACHE | GEOIP_MMAP_CACHE) ) { + if (fstat(fileno(gi->GeoIPDatabase), &buf) == -1) { + fprintf(stderr,"Error stating file %s\n",filename); + free(gi->file_path); + free(gi); + return NULL; + } + gi->mtime = buf.st_mtime; + gi->size = buf.st_size; +#ifndef WIN32 + /* MMAP added my Peter Shipley */ + if ( flags & GEOIP_MMAP_CACHE) { + gi->cache = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fileno(gi->GeoIPDatabase), 0); + if ( gi->cache == MAP_FAILED ) { + fprintf(stderr,"Error mmaping file %s\n",filename); + free(gi->file_path); + free(gi); + return NULL; + } + } else +#endif + { + gi->cache = (unsigned char *) malloc(sizeof(unsigned char) * buf.st_size); + + if (gi->cache != NULL) { + if (fread(gi->cache, sizeof(unsigned char), buf.st_size, gi->GeoIPDatabase) != (size_t) buf.st_size) { + fprintf(stderr,"Error reading file %s\n",filename); + free(gi->cache); + free(gi->file_path); + free(gi); + return NULL; + } + } + } + } else { + if (flags & GEOIP_CHECK_CACHE) { + if (fstat(fileno(gi->GeoIPDatabase), &buf) == -1) { + fprintf(stderr,"Error stating file %s\n",filename); + free(gi->file_path); + free(gi); + return NULL; + } + gi->mtime = buf.st_mtime; + } + gi->cache = NULL; + } + gi->flags = flags; + gi->charset = GEOIP_CHARSET_ISO_8859_1; + + _setup_segments(gi); + if (flags & GEOIP_INDEX_CACHE) { + gi->index_cache = (unsigned char *) malloc(sizeof(unsigned char) * ((gi->databaseSegments[0] * (long)gi->record_length * 2))); + if (gi->index_cache != NULL) { + fseek(gi->GeoIPDatabase, 0, SEEK_SET); + if (fread(gi->index_cache, sizeof(unsigned char), gi->databaseSegments[0] * (long)gi->record_length * 2, gi->GeoIPDatabase) != (size_t) (gi->databaseSegments[0]*(long)gi->record_length * 2)) { + fprintf(stderr,"Error reading file %s\n",filename); + free(gi->databaseSegments); + free(gi->index_cache); + free(gi); + return NULL; + } + } + } else { + gi->index_cache = NULL; + } + return gi; + } +} + +void GeoIP_delete (GeoIP *gi) { + if (gi == NULL ) + return; + if (gi->GeoIPDatabase != NULL) + fclose(gi->GeoIPDatabase); + if (gi->cache != NULL) { +#ifndef WIN32 + if ( gi->flags & GEOIP_MMAP_CACHE) { + munmap(gi->cache, gi->size); + } else +#endif + { + free(gi->cache); + } + gi->cache = NULL; + } + if (gi->index_cache != NULL) + free(gi->index_cache); + if (gi->file_path != NULL) + free(gi->file_path); + if (gi->databaseSegments != NULL) + free(gi->databaseSegments); + free(gi); +} + +const char *GeoIP_country_code_by_name (GeoIP* gi, const char *name) { + int country_id; + country_id = GeoIP_id_by_name(gi, name); + return (country_id > 0) ? GeoIP_country_code[country_id] : NULL; +} + +const char *GeoIP_country_code3_by_name (GeoIP* gi, const char *name) { + int country_id; + country_id = GeoIP_id_by_name(gi, name); + return (country_id > 0) ? GeoIP_country_code3[country_id] : NULL; +} + +const char *GeoIP_country_name_by_name (GeoIP* gi, const char *name) { + int country_id; + country_id = GeoIP_id_by_name(gi, name); + return (country_id > 0) ? GeoIP_country_name[country_id] : NULL; +} + +unsigned long _GeoIP_lookupaddress (const char *host) { + unsigned long addr = inet_addr(host); + struct hostent phe2; + struct hostent * phe = &phe2; + char *buf = NULL; + int buflength = 16384; + int herr = 0; + int result = 0; +#ifdef HAVE_GETHOSTBYNAME_R + buf = malloc(buflength); +#endif + if (addr == INADDR_NONE) { +#ifdef HAVE_GETHOSTBYNAME_R + while (1) { + /* we use gethostbyname_r here because it is thread-safe and gethostbyname is not */ +#ifdef GETHOSTBYNAME_R_RETURNS_INT + result = gethostbyname_r(host,&phe2,buf,buflength,&phe,&herr); +#else + phe = gethostbyname_r(host,&phe2,buf,buflength,&herr); +#endif + if (herr != ERANGE) + break; + if (result == 0) + break; + /* double the buffer if the buffer is too small */ + buflength = buflength * 2; + buf = realloc(buf,buflength); + } +#endif +#ifndef HAVE_GETHOSTBYNAME_R + /* Some systems do not support gethostbyname_r, such as Mac OS X */ + phe = gethostbyname(host); +#endif + if (!phe || result != 0) { + free(buf); + return 0; + } + addr = *((unsigned long *) phe->h_addr_list[0]); + } +#ifdef HAVE_GETHOSTBYNAME_R + free(buf); +#endif + return ntohl(addr); +} + +int GeoIP_id_by_name (GeoIP* gi, const char *name) { + unsigned long ipnum; + int ret; + if (name == NULL) { + return 0; + } + if (gi->databaseType != GEOIP_COUNTRY_EDITION && gi->databaseType != GEOIP_PROXY_EDITION && gi->databaseType != GEOIP_NETSPEED_EDITION) { + printf("Invalid database type %s, expected %s\n", GeoIPDBDescription[(int)gi->databaseType], GeoIPDBDescription[GEOIP_COUNTRY_EDITION]); + return 0; + } + if (!(ipnum = _GeoIP_lookupaddress(name))) + return 0; + ret = _GeoIP_seek_record(gi, ipnum) - COUNTRY_BEGIN; + return ret; + +} + +const char *GeoIP_country_code_by_addr (GeoIP* gi, const char *addr) { + int country_id; + country_id = GeoIP_id_by_addr(gi, addr); + return (country_id > 0) ? GeoIP_country_code[country_id] : NULL; +} + +const char *GeoIP_country_code3_by_addr (GeoIP* gi, const char *addr) { + int country_id; + country_id = GeoIP_id_by_addr(gi, addr); + return (country_id > 0) ? GeoIP_country_code3[country_id] : NULL; + return GeoIP_country_code3[country_id]; +} + +const char *GeoIP_country_name_by_addr (GeoIP* gi, const char *addr) { + int country_id; + country_id = GeoIP_id_by_addr(gi, addr); + return (country_id > 0) ? GeoIP_country_name[country_id] : NULL; + return GeoIP_country_name[country_id]; +} + +const char *GeoIP_country_name_by_ipnum (GeoIP* gi, unsigned long ipnum) { + int country_id; + country_id = GeoIP_id_by_ipnum(gi, ipnum); + return (country_id > 0) ? GeoIP_country_name[country_id] : NULL; +} + +const char *GeoIP_country_code_by_ipnum (GeoIP* gi, unsigned long ipnum) { + int country_id; + country_id = GeoIP_id_by_ipnum(gi, ipnum); + return (country_id > 0) ? GeoIP_country_code[country_id] : NULL; +} + +const char *GeoIP_country_code3_by_ipnum (GeoIP* gi, unsigned long ipnum) { + int country_id; + country_id = GeoIP_id_by_ipnum(gi, ipnum); + return (country_id > 0) ? GeoIP_country_code3[country_id] : NULL; +} + +int GeoIP_country_id_by_addr (GeoIP* gi, const char *addr) { + return GeoIP_id_by_addr(gi, addr); +} + +int GeoIP_country_id_by_name (GeoIP* gi, const char *host) { + return GeoIP_id_by_name(gi, host); +} + +int GeoIP_id_by_addr (GeoIP* gi, const char *addr) { + unsigned long ipnum; + int ret; + if (addr == NULL) { + return 0; + } + if (gi->databaseType != GEOIP_COUNTRY_EDITION && + gi->databaseType != GEOIP_PROXY_EDITION && + gi->databaseType != GEOIP_NETSPEED_EDITION) { + printf("Invalid database type %s, expected %s\n", + GeoIPDBDescription[(int)gi->databaseType], + GeoIPDBDescription[GEOIP_COUNTRY_EDITION]); + return 0; + } + ipnum = _GeoIP_addr_to_num(addr); + ret = _GeoIP_seek_record(gi, ipnum) - COUNTRY_BEGIN; + return ret; +} + +int GeoIP_id_by_ipnum (GeoIP* gi, unsigned long ipnum) { + int ret; + if (ipnum == 0) { + return 0; + } + if (gi->databaseType != GEOIP_COUNTRY_EDITION && + gi->databaseType != GEOIP_PROXY_EDITION && + gi->databaseType != GEOIP_NETSPEED_EDITION) { + printf("Invalid database type %s, expected %s\n", + GeoIPDBDescription[(int)gi->databaseType], + GeoIPDBDescription[GEOIP_COUNTRY_EDITION]); + return 0; + } + ret = _GeoIP_seek_record(gi, ipnum) - COUNTRY_BEGIN; + return ret; +} + +char *GeoIP_database_info (GeoIP* gi) { + int i; + unsigned char buf[3]; + char *retval; + int hasStructureInfo = 0; + + if(gi == NULL) + return NULL; + + _check_mtime(gi); + fseek(gi->GeoIPDatabase, -3l, SEEK_END); + + /* first get past the database structure information */ + for (i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) { + fread(buf, 1, 3, gi->GeoIPDatabase); + if (buf[0] == 255 && buf[1] == 255 && buf[2] == 255) { + hasStructureInfo = 1; + break; + } + fseek(gi->GeoIPDatabase, -4l, SEEK_CUR); + } + if (hasStructureInfo == 1) { + fseek(gi->GeoIPDatabase, -6l, SEEK_CUR); + } else { + /* no structure info, must be pre Sep 2002 database, go back to end */ + fseek(gi->GeoIPDatabase, -3l, SEEK_END); + } + + for (i = 0; i < DATABASE_INFO_MAX_SIZE; i++) { + fread(buf, 1, 3, gi->GeoIPDatabase); + if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0) { + retval = malloc(sizeof(char) * (i+1)); + if (retval == NULL) { + return NULL; + } + fread(retval, 1, i, gi->GeoIPDatabase); + retval[i] = '\0'; + return retval; + } + fseek(gi->GeoIPDatabase, -4l, SEEK_CUR); + } + return NULL; +} + +/* GeoIP Region Edition functions */ + +void GeoIP_assign_region_by_inetaddr(GeoIP* gi, unsigned long inetaddr, GeoIPRegion *region) { + unsigned int seek_region; + + /* This also writes in the terminating NULs (if you decide to + * keep them) and clear any fields that are not set. */ + memset(region, 0, sizeof(GeoIPRegion)); + + seek_region = _GeoIP_seek_record(gi, ntohl(inetaddr)); + + if (gi->databaseType == GEOIP_REGION_EDITION_REV0) { + /* Region Edition, pre June 2003 */ + seek_region -= STATE_BEGIN_REV0; + if (seek_region >= 1000) { + region->country_code[0] = 'U'; + region->country_code[1] = 'S'; + region->region[0] = (char) ((seek_region - 1000)/26 + 65); + region->region[1] = (char) ((seek_region - 1000)%26 + 65); + } else { + memcpy(region->country_code, GeoIP_country_code[seek_region], 2); + } + } else if (gi->databaseType == GEOIP_REGION_EDITION_REV1) { + /* Region Edition, post June 2003 */ + seek_region -= STATE_BEGIN_REV1; + if (seek_region < US_OFFSET) { + /* Unknown */ + /* we don't need to do anything here b/c we memset region to 0 */ + } else if (seek_region < CANADA_OFFSET) { + /* USA State */ + region->country_code[0] = 'U'; + region->country_code[1] = 'S'; + region->region[0] = (char) ((seek_region - US_OFFSET)/26 + 65); + region->region[1] = (char) ((seek_region - US_OFFSET)%26 + 65); + } else if (seek_region < WORLD_OFFSET) { + /* Canada Province */ + region->country_code[0] = 'C'; + region->country_code[1] = 'A'; + region->region[0] = (char) ((seek_region - CANADA_OFFSET)/26 + 65); + region->region[1] = (char) ((seek_region - CANADA_OFFSET)%26 + 65); + } else { + /* Not US or Canada */ + memcpy(region->country_code, GeoIP_country_code[(seek_region - WORLD_OFFSET) / FIPS_RANGE], 2); + } + } +} + +static +GeoIPRegion * _get_region(GeoIP* gi, unsigned long ipnum) { + GeoIPRegion * region; + + region = malloc(sizeof(GeoIPRegion)); + if (region) { + GeoIP_assign_region_by_inetaddr(gi, htonl(ipnum), region); + } + return region; +} + +GeoIPRegion * GeoIP_region_by_addr (GeoIP* gi, const char *addr) { + unsigned long ipnum; + if (addr == NULL) { + return 0; + } + if (gi->databaseType != GEOIP_REGION_EDITION_REV0 && + gi->databaseType != GEOIP_REGION_EDITION_REV1) { + printf("Invalid database type %s, expected %s\n", GeoIPDBDescription[(int)gi->databaseType], GeoIPDBDescription[GEOIP_REGION_EDITION_REV1]); + return 0; + } + ipnum = _GeoIP_addr_to_num(addr); + return _get_region(gi, ipnum); +} + +GeoIPRegion * GeoIP_region_by_name (GeoIP* gi, const char *name) { + unsigned long ipnum; + if (name == NULL) { + return 0; + } + if (gi->databaseType != GEOIP_REGION_EDITION_REV0 && + gi->databaseType != GEOIP_REGION_EDITION_REV1) { + printf("Invalid database type %s, expected %s\n", GeoIPDBDescription[(int)gi->databaseType], GeoIPDBDescription[GEOIP_REGION_EDITION_REV1]); + return 0; + } + if (!(ipnum = _GeoIP_lookupaddress(name))) + return 0; + return _get_region(gi, ipnum); +} + +GeoIPRegion * GeoIP_region_by_ipnum (GeoIP* gi, unsigned long ipnum) { + if (gi->databaseType != GEOIP_REGION_EDITION_REV0 && + gi->databaseType != GEOIP_REGION_EDITION_REV1) { + printf("Invalid database type %s, expected %s\n", GeoIPDBDescription[(int)gi->databaseType], GeoIPDBDescription[GEOIP_REGION_EDITION_REV1]); + return 0; + } + return _get_region(gi, ipnum); +} + +void GeoIPRegion_delete (GeoIPRegion *gir) { + free(gir); +} + +/* GeoIP Organization, ISP and AS Number Edition private method */ +static +char *_get_name (GeoIP* gi, unsigned long ipnum) { + int seek_org; + char buf[MAX_ORG_RECORD_LENGTH]; + char * org_buf, * buf_pointer; + int record_pointer; + size_t len; + + if (gi->databaseType != GEOIP_ORG_EDITION && + gi->databaseType != GEOIP_ISP_EDITION && + gi->databaseType != GEOIP_ASNUM_EDITION) { + printf("Invalid database type %s, expected %s\n", GeoIPDBDescription[(int)gi->databaseType], GeoIPDBDescription[GEOIP_ORG_EDITION]); + return 0; + } + + seek_org = _GeoIP_seek_record(gi, ipnum); + if (seek_org == gi->databaseSegments[0]) + return NULL; + + record_pointer = seek_org + (2 * gi->record_length - 1) * gi->databaseSegments[0]; + + if (gi->cache == NULL) { + fseek(gi->GeoIPDatabase, record_pointer, SEEK_SET); + fread(buf, sizeof(char), MAX_ORG_RECORD_LENGTH, gi->GeoIPDatabase); + len = sizeof(char) * (strlen(buf)+1); + org_buf = malloc(len); + strncpy(org_buf, buf, len); + } else { + buf_pointer = gi->cache + (long)record_pointer; + len = sizeof(char) * (strlen(buf_pointer)+1); + org_buf = malloc(len); + strncpy(org_buf, buf_pointer, len); + } + return org_buf; +} + +char *GeoIP_name_by_ipnum (GeoIP* gi, unsigned long ipnum) { + return _get_name(gi,ipnum); +} + +char *GeoIP_name_by_addr (GeoIP* gi, const char *addr) { + unsigned long ipnum; + if (addr == NULL) { + return 0; + } + ipnum = _GeoIP_addr_to_num(addr); + return _get_name(gi, ipnum); +} + +char *GeoIP_name_by_name (GeoIP* gi, const char *name) { + unsigned long ipnum; + if (name == NULL) { + return 0; + } + if (!(ipnum = _GeoIP_lookupaddress(name))) + return 0; + return _get_name(gi, ipnum); +} + +char *GeoIP_org_by_ipnum (GeoIP* gi, unsigned long ipnum) { + return GeoIP_name_by_ipnum(gi, ipnum); +} + +char *GeoIP_org_by_addr (GeoIP* gi, const char *addr) { + return GeoIP_name_by_addr(gi, addr); +} + +char *GeoIP_org_by_name (GeoIP* gi, const char *name) { + return GeoIP_name_by_name(gi, name); +} + +unsigned char GeoIP_database_edition (GeoIP* gi) { + return gi->databaseType; +} + +int GeoIP_charset( GeoIP* gi){ + return gi->charset; +} + +int GeoIP_set_charset( GeoIP* gi, int charset ){ + int old_charset = gi->charset; + gi->charset = charset; + return old_charset; +} + +int GeoIP_last_netmask (GeoIP* gi) { + return gi->netmask; +} + diff --git a/libtorrent/src/assert.cpp b/libtorrent/src/assert.cpp index 1073e05a3..6991736f4 100644 --- a/libtorrent/src/assert.cpp +++ b/libtorrent/src/assert.cpp @@ -30,6 +30,35 @@ POSSIBILITY OF SUCH DAMAGE. */ +#ifdef __GNUC__ + +#include +#include +#include + +std::string demangle(char const* name) +{ +// in case this string comes + char const* start = strchr(name, '('); + if (start != 0) ++start; + else start = name; + char const* end = strchr(start, '+'); + + std::string in; + if (end == 0) in.assign(start); + else in.assign(start, end); + + size_t len; + int status; + char* unmangled = ::abi::__cxa_demangle(in.c_str(), 0, &len, &status); + if (unmangled == 0) return in; + std::string ret(unmangled); + free(unmangled); + return ret; +} + +#endif + #ifndef NDEBUG #include @@ -58,7 +87,7 @@ void assert_fail(char const* expr, int line, char const* file, char const* funct for (int i = 0; i < size; ++i) { - fprintf(stderr, "%d: %s\n", i, symbols[i]); + fprintf(stderr, "%d: %s\n", i, demangle(symbols[i]).c_str()); } free(symbols); diff --git a/libtorrent/src/broadcast_socket.cpp b/libtorrent/src/broadcast_socket.cpp index c7b7e71c8..64f41b7e2 100644 --- a/libtorrent/src/broadcast_socket.cpp +++ b/libtorrent/src/broadcast_socket.cpp @@ -151,6 +151,11 @@ namespace libtorrent asio::error_code ec; std::vector interfaces = enum_net_interfaces(ios, ec); + if (multicast_endpoint.address().is_v4()) + open_multicast_socket(ios, address_v4::any(), loopback); + else + open_multicast_socket(ios, address_v6::any(), loopback); + for (std::vector::const_iterator i = interfaces.begin() , end(interfaces.end()); i != end; ++i) { @@ -161,58 +166,70 @@ namespace libtorrent // ignore any loopback interface if (is_loopback(i->interface_address)) continue; - boost::shared_ptr s(new datagram_socket(ios)); - if (i->interface_address.is_v4()) - { - s->open(udp::v4(), ec); - if (ec) continue; - s->set_option(datagram_socket::reuse_address(true), ec); - if (ec) continue; - s->bind(udp::endpoint(address_v4::any(), multicast_endpoint.port()), ec); - if (ec) continue; - s->set_option(join_group(multicast_endpoint.address()), ec); - if (ec) continue; - s->set_option(outbound_interface(i->interface_address.to_v4()), ec); - if (ec) continue; - } - else - { - s->open(udp::v6(), ec); - if (ec) continue; - s->set_option(datagram_socket::reuse_address(true), ec); - if (ec) continue; - s->bind(udp::endpoint(address_v6::any(), multicast_endpoint.port()), ec); - if (ec) continue; - s->set_option(join_group(multicast_endpoint.address()), ec); - if (ec) continue; -// s->set_option(outbound_interface(i->interface_address.to_v6()), ec); -// if (ec) continue; - } - s->set_option(hops(255), ec); - if (ec) continue; - s->set_option(enable_loopback(loopback), ec); - if (ec) continue; - m_sockets.push_back(socket_entry(s)); - socket_entry& se = m_sockets.back(); - s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) - , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); #ifndef NDEBUG // std::cerr << "broadcast socket [ if: " << i->to_v4().to_string() // << " group: " << multicast_endpoint.address() << " ]" << std::endl; #endif + open_unicast_socket(ios, i->interface_address); } } + void broadcast_socket::open_multicast_socket(io_service& ios + , address const& addr, bool loopback) + { + using namespace asio::ip::multicast; + + asio::error_code ec; + boost::shared_ptr s(new datagram_socket(ios)); + if (addr.is_v4()) + s->open(udp::v4(), ec); + else + s->open(udp::v6(), ec); + if (ec) return; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) return; + s->bind(udp::endpoint(addr, m_multicast_endpoint.port()), ec); + if (ec) return; + s->set_option(join_group(m_multicast_endpoint.address()), ec); + if (ec) return; + s->set_option(hops(255), ec); + if (ec) return; + s->set_option(enable_loopback(loopback), ec); + if (ec) return; + m_sockets.push_back(socket_entry(s)); + socket_entry& se = m_sockets.back(); + s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + } + + void broadcast_socket::open_unicast_socket(io_service& ios, address const& addr) + { + using namespace asio::ip::multicast; + asio::error_code ec; + boost::shared_ptr s(new datagram_socket(ios)); + s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); + if (ec) return; + s->bind(udp::endpoint(addr, 0), ec); + if (ec) return; + if (addr.is_v4()) + s->set_option(outbound_interface(addr.to_v4()), ec); + if (ec) return; + m_unicast_sockets.push_back(socket_entry(s)); + socket_entry& se = m_unicast_sockets.back(); + s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + } + void broadcast_socket::send(char const* buffer, int size, asio::error_code& ec) { - for (std::list::iterator i = m_sockets.begin() - , end(m_sockets.end()); i != end; ++i) + for (std::list::iterator i = m_unicast_sockets.begin() + , end(m_unicast_sockets.end()); i != end; ++i) { if (!i->socket) continue; asio::error_code e; i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); #ifndef NDEBUG -// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; +// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << " to: " << m_multicast_endpoint << std::endl; #endif if (e) { @@ -234,14 +251,10 @@ namespace libtorrent void broadcast_socket::close() { - m_on_receive.clear(); + std::for_each(m_sockets.begin(), m_sockets.end(), bind(&socket_entry::close, _1)); + std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), bind(&socket_entry::close, _1)); - for (std::list::iterator i = m_sockets.begin() - , end(m_sockets.end()); i != end; ++i) - { - if (!i->socket) continue; - i->socket->close(); - } + m_on_receive.clear(); } } diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index d70e7af31..7394406dc 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -163,13 +163,19 @@ namespace libtorrent m_bandwidth_limit[upload_channel].assign(80); #endif +#ifndef NDEBUG + m_in_constructor = false; +#endif + } + + void bt_peer_connection::start() + { + peer_connection::start(); + // start in the state where we are trying to read the // handshake from the other side reset_recv_buffer(20); setup_receive(); -#ifndef NDEBUG - m_in_constructor = false; -#endif } bt_peer_connection::~bt_peer_connection() @@ -382,6 +388,7 @@ namespace libtorrent #endif buffer::interval send_buf = allocate_send_buffer(dh_key_len + pad_size); + if (send_buf.begin == 0) return; // out of memory std::copy(m_DH_key_exchange->get_local_key(), m_DH_key_exchange->get_local_key() + dh_key_len, @@ -416,6 +423,7 @@ namespace libtorrent // synchash,skeyhash,vc,crypto_provide,len(pad),pad,len(ia) buffer::interval send_buf = allocate_send_buffer(20 + 20 + 8 + 4 + 2 + pad_size + 2); + if (send_buf.begin == 0) return; // out of memory // sync hash (hash('req1',S)) h.reset(); @@ -490,6 +498,7 @@ namespace libtorrent const int buf_size = 8 + 4 + 2 + pad_size; buffer::interval send_buf = allocate_send_buffer(buf_size); + if (send_buf.begin == 0) return; // out of memory write_pe_vc_cryptofield(send_buf, crypto_select, pad_size); m_RC4_handler->encrypt(send_buf.end - buf_size, buf_size); @@ -682,6 +691,7 @@ namespace libtorrent const int string_len = sizeof(version_string)-1; buffer::interval i = allocate_send_buffer(1 + string_len + 8 + 20 + 20); + if (i.begin == 0) return; // out of memory // length of version string *i.begin = string_len; ++i.begin; @@ -789,7 +799,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 1) - throw protocol_error("'choke' message size != 1"); + { + disconnect("'choke' message size != 1"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -820,7 +833,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 1) - throw protocol_error("'unchoke' message size != 1"); + { + disconnect("'unchoke' message size != 1"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -837,7 +853,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 1) - throw protocol_error("'interested' message size != 1"); + { + disconnect("'interested' message size != 1"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -854,7 +873,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 1) - throw protocol_error("'not interested' message size != 1"); + { + disconnect("'not interested' message size != 1"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -871,7 +893,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 5) - throw protocol_error("'have' message size != 5"); + { + disconnect("'have' message size != 5"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -900,7 +925,10 @@ namespace libtorrent // verify the bitfield size if (t->valid_metadata() && packet_size() - 1 != ((int)get_bitfield().size() + 7) / 8) - throw protocol_error("bitfield with invalid size"); + { + disconnect("bitfield with invalid size"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -934,7 +962,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 13) - throw protocol_error("'request' message size != 13"); + { + disconnect("'request' message size != 13"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -962,6 +993,14 @@ namespace libtorrent buffer::const_interval recv_buffer = receive_buffer(); int recv_pos = recv_buffer.end - recv_buffer.begin; + if (recv_pos == 1) + { + TORRENT_ASSERT(!has_disk_receive_buffer()); + if (!allocate_disk_receive_buffer(packet_size() - 9)) + return; + } + TORRENT_ASSERT(has_disk_receive_buffer()); + // classify the received data as protocol chatter // or data payload for the statistics if (recv_pos <= 9) @@ -990,7 +1029,8 @@ namespace libtorrent p.start = detail::read_int32(ptr); p.length = packet_size() - 9; - incoming_piece(p, recv_buffer.begin + 9); + disk_buffer_holder holder(m_ses, release_disk_receive_buffer()); + incoming_piece(p, holder); } // ----------------------------- @@ -1003,7 +1043,10 @@ namespace libtorrent TORRENT_ASSERT(received > 0); if (packet_size() != 13) - throw protocol_error("'cancel' message size != 13"); + { + disconnect("'cancel' message size != 13"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -1027,11 +1070,17 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_dht_port) - throw protocol_error("got 'dht_port' message from peer that doesn't support it"); + { + disconnect("got 'dht_port' message from peer that doesn't support it"); + return; + } TORRENT_ASSERT(received > 0); if (packet_size() != 3) - throw protocol_error("'dht_port' message size != 3"); + { + disconnect("'dht_port' message size != 3"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -1048,7 +1097,10 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_fast) - throw protocol_error("got 'suggest_piece' without FAST extension support"); + { + disconnect("got 'suggest_piece' without FAST excension support"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -1065,7 +1117,10 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_fast) - throw protocol_error("got 'have_all' without FAST extension support"); + { + disconnect("got 'have_all' without FAST extension support"); + return; + } m_statistics.received_bytes(0, received); incoming_have_all(); } @@ -1075,7 +1130,10 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_fast) - throw protocol_error("got 'have_none' without FAST extension support"); + { + disconnect("got 'have_none' without FAST extension support"); + return; + } m_statistics.received_bytes(0, received); incoming_have_none(); } @@ -1085,7 +1143,10 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_fast) - throw protocol_error("got 'reject_request' without FAST extension support"); + { + disconnect("got 'reject_request' without FAST extension support"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -1106,7 +1167,10 @@ namespace libtorrent INVARIANT_CHECK; if (!m_supports_fast) - throw protocol_error("got 'allowed_fast' without FAST extension support"); + { + disconnect("got 'allowed_fast' without FAST extension support"); + return; + } m_statistics.received_bytes(0, received); if (!packet_finished()) return; @@ -1128,10 +1192,16 @@ namespace libtorrent TORRENT_ASSERT(received > 0); m_statistics.received_bytes(0, received); if (packet_size() < 2) - throw protocol_error("'extended' message smaller than 2 bytes"); + { + disconnect("'extended' message smaller than 2 bytes"); + return; + } if (associated_torrent().expired()) - throw protocol_error("'extended' message sent before proper handshake"); + { + disconnect("'extended' message sent before proper handshake"); + return; + } buffer::const_interval recv_buffer = receive_buffer(); if (recv_buffer.left() < 2) return; @@ -1157,8 +1227,10 @@ namespace libtorrent } #endif - throw protocol_error("unknown extended message id: " - + boost::lexical_cast(extended_id)); + std::stringstream msg; + msg << "unknown extended message id: " << extended_id; + disconnect(msg.str().c_str()); + return; } void bt_peer_connection::on_extended_handshake() @@ -1171,15 +1243,11 @@ namespace libtorrent buffer::const_interval recv_buffer = receive_buffer(); entry root; - try + root = bdecode(recv_buffer.begin + 2, recv_buffer.end); + if (root.type() == entry::undefined_t) { - root = bdecode(recv_buffer.begin + 2, recv_buffer.end); - } - catch (std::exception& exc) - { - (void)exc; #ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "invalid extended handshake: " << exc.what() << "\n"; + (*m_logger) << "invalid extended handshake\n"; #endif return; } @@ -1240,13 +1308,13 @@ namespace libtorrent { address_v4::bytes_type bytes; std::copy(my_ip.begin(), my_ip.end(), bytes.begin()); - m_ses.m_external_address = address_v4(bytes); + m_ses.set_external_address(address_v4(bytes)); } else if (my_ip.size() == address_v6::bytes_type::static_size) { address_v6::bytes_type bytes; std::copy(my_ip.begin(), my_ip.end(), bytes.begin()); - m_ses.m_external_address = address_v6(bytes); + m_ses.set_external_address(address_v6(bytes)); } } } @@ -1263,6 +1331,7 @@ namespace libtorrent buffer::const_interval recv_buffer = receive_buffer(); + TORRENT_ASSERT(recv_buffer.left() >= 1); int packet_type = recv_buffer[0]; if (packet_type < 0 || packet_type >= num_supported_messages @@ -1279,9 +1348,10 @@ namespace libtorrent } #endif - throw protocol_error("unknown message id: " - + boost::lexical_cast(packet_type) - + " size: " + boost::lexical_cast(packet_size())); + std::stringstream msg; + msg << "unkown message id: " << packet_type << " size: " << packet_size(); + disconnect(msg.str().c_str()); + return packet_finished(); } TORRENT_ASSERT(m_message_handler[packet_type] != 0); @@ -1366,6 +1436,14 @@ namespace libtorrent send_allowed_set(); return; } + else if (t->num_pieces() == 0) + { + // don't send a bitfield if we don't have any pieces +#ifndef NDEBUG + m_sent_bitfield = true; +#endif + return; + } int num_pieces = bitfield.size(); int lazy_pieces[50]; @@ -1409,6 +1487,7 @@ namespace libtorrent const int packet_size = (num_pieces + 7) / 8 + 5; buffer::interval i = allocate_send_buffer(packet_size); + if (i.begin == 0) return; // out of memory detail::write_int32(packet_size - 4, i.begin); detail::write_uint8(msg_bitfield, i.begin); @@ -1497,6 +1576,7 @@ namespace libtorrent // make room for message buffer::interval i = allocate_send_buffer(6 + msg.size()); + if (i.begin == 0) return; // out of memory // write the length of the message detail::write_int32((int)msg.size() + 2, i.begin); @@ -1573,7 +1653,7 @@ namespace libtorrent send_buffer(msg, sizeof(msg)); } - void bt_peer_connection::write_piece(peer_request const& r, char* buffer) + void bt_peer_connection::write_piece(peer_request const& r, disk_buffer_holder& buffer) { INVARIANT_CHECK; @@ -1591,9 +1671,10 @@ namespace libtorrent detail::write_int32(r.start, ptr); send_buffer(msg, sizeof(msg)); - append_send_buffer(buffer, r.length + append_send_buffer(buffer.buffer(), r.length , boost::bind(&session_impl::free_disk_buffer , boost::ref(m_ses), _1)); + buffer.release(); m_payloads.push_back(range(send_buffer_size() - r.length, r.length)); setup_send(); @@ -1641,8 +1722,9 @@ namespace libtorrent TORRENT_ASSERT(in_handshake() || !m_rc4_encrypted || m_encrypted); if (m_rc4_encrypted && m_encrypted) { - buffer::interval wr_buf = wr_recv_buffer(); - m_RC4_handler->decrypt((wr_buf.end - bytes_transferred), bytes_transferred); + std::pair wr_buf = wr_recv_buffers(bytes_transferred); + m_RC4_handler->decrypt(wr_buf.first.begin, wr_buf.first.left()); + if (wr_buf.second.left()) m_RC4_handler->decrypt(wr_buf.second.begin, wr_buf.second.left()); } #endif @@ -1654,10 +1736,10 @@ namespace libtorrent // for outgoing if (m_state == read_pe_dhkey) { - assert (!m_encrypted); - assert (!m_rc4_encrypted); - assert (packet_size() == dh_key_len); - assert (recv_buffer == receive_buffer()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(packet_size() == dh_key_len); + TORRENT_ASSERT(recv_buffer == receive_buffer()); if (!packet_finished()) return; @@ -1696,7 +1778,7 @@ namespace libtorrent // vc,crypto_select,len(pad),pad, encrypt(handshake) // 8+4+2+0+handshake_len - reset_recv_buffer(8+4+2+0+handshake_len); + reset_recv_buffer(8+4+2+0+handshake_len); } else { @@ -1720,10 +1802,7 @@ namespace libtorrent if (recv_buffer.left() < 20) { if (packet_finished()) - { - throw protocol_error ("sync hash not found"); - } - // else + disconnect("sync hash not found"); return; } @@ -1748,7 +1827,10 @@ namespace libtorrent std::size_t bytes_processed = recv_buffer.left() - 20; m_sync_bytes_read += bytes_processed; if (m_sync_bytes_read >= 512) - throw protocol_error("sync hash not found within 532 bytes"); + { + disconnect("sync hash not found within 532 bytes"); + return; + } cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+20) - m_sync_bytes_read)); @@ -1815,6 +1897,8 @@ namespace libtorrent if (!t) { attach_to_torrent(info_hash); + if (is_disconnecting()) return; + t = associated_torrent().lock(); TORRENT_ASSERT(t); } @@ -1828,7 +1912,10 @@ namespace libtorrent } if (!m_RC4_handler.get()) - throw protocol_error("invalid streamkey identifier (info hash) in encrypted handshake"); + { + disconnect("invalid streamkey identifier (info hash) in encrypted handshake"); + return; + } // verify constant buffer::interval wr_recv_buf = wr_recv_buffer(); @@ -1838,7 +1925,8 @@ namespace libtorrent const char sh_vc[] = {0,0,0,0, 0,0,0,0}; if (!std::equal(sh_vc, sh_vc+8, recv_buffer.begin + 20)) { - throw protocol_error("unable to verify constant"); + disconnect("unable to verify constant"); + return; } #ifdef TORRENT_VERBOSE_LOGGING @@ -1859,10 +1947,7 @@ namespace libtorrent if (recv_buffer.left() < 8) { if (packet_finished()) - { - throw protocol_error ("sync verification constant not found"); - } - // else + disconnect("sync verification constant not found"); return; } @@ -1886,7 +1971,10 @@ namespace libtorrent std::size_t bytes_processed = recv_buffer.left() - 8; m_sync_bytes_read += bytes_processed; if (m_sync_bytes_read >= 512) - throw protocol_error("sync verification constant not found within 520 bytes"); + { + disconnect("sync verification constant not found within 520 bytes"); + return; + } cut_receive_buffer(bytes_processed, (std::min)(packet_size(), (512+8) - m_sync_bytes_read)); @@ -1944,22 +2032,23 @@ namespace libtorrent // select a crypto method switch (m_ses.get_pe_settings().allowed_enc_level) { - case (pe_settings::plaintext): - { + case pe_settings::plaintext: if (!(crypto_field & 0x01)) - throw protocol_error("plaintext not provided"); + { + disconnect("plaintext not provided"); + return; + } crypto_select = 0x01; - } - break; - case (pe_settings::rc4): - { + break; + case pe_settings::rc4: if (!(crypto_field & 0x02)) - throw protocol_error("rc4 not provided"); + { + disconnect("rc4 not provided"); + return; + } crypto_select = 0x02; - } - break; - case (pe_settings::both): - { + break; + case pe_settings::both: if (m_ses.get_pe_settings().prefer_rc4) { if (crypto_field & 0x02) @@ -1975,8 +2064,11 @@ namespace libtorrent crypto_select = 0x02; } if (!crypto_select) - throw protocol_error("rc4/plaintext not provided"); - } + { + disconnect("rc4/plaintext not provided"); + return; + } + break; } // switch // write the pe4 step @@ -1990,22 +2082,34 @@ namespace libtorrent if (crypto_field == 0x02) { if (allowed_enc_level == pe_settings::plaintext) - throw protocol_error("rc4 selected by peer when not provided"); + { + disconnect("rc4 selected by peer when not provided"); + return; + } m_rc4_encrypted = true; } else if (crypto_field == 0x01) { if (allowed_enc_level == pe_settings::rc4) - throw protocol_error("plaintext selected by peer when not provided"); + { + disconnect("plaintext selected by peer when not provided"); + return; + } m_rc4_encrypted = false; } else - throw protocol_error("unsupported crypto method selected by peer"); + { + disconnect("unsupported crypto method selected by peer"); + return; + } } int len_pad = detail::read_int16(recv_buffer.begin); if (len_pad < 0 || len_pad > 512) - throw protocol_error("invalid pad length"); + { + disconnect("invalid pad length"); + return; + } m_state = read_pe_pad; if (!is_local()) @@ -2039,7 +2143,11 @@ namespace libtorrent recv_buffer.begin += pad_size; int len_ia = detail::read_int16(recv_buffer.begin); - if (len_ia < 0) throw protocol_error("invalid len_ia in handshake"); + if (len_ia < 0) + { + disconnect("invalid len_ia in handshake"); + return; + } #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " len(IA) : " << len_ia << "\n"; @@ -2136,7 +2244,7 @@ namespace libtorrent if (m_state == read_protocol_identifier) { - assert (packet_size() == 20); + TORRENT_ASSERT(packet_size() == 20); if (!packet_finished()) return; recv_buffer = receive_buffer(); @@ -2149,7 +2257,10 @@ namespace libtorrent { #ifndef TORRENT_DISABLE_ENCRYPTION if (!is_local() && m_ses.get_pe_settings().in_enc_policy == pe_settings::disabled) - throw protocol_error("encrypted incoming connections disabled"); + { + disconnect("encrypted incoming connections disabled"); + return; + } // Don't attempt to perform an encrypted handshake // within an encrypted connection @@ -2164,18 +2275,22 @@ namespace libtorrent return; } - assert ((!is_local() && m_encrypted) || is_local()); + TORRENT_ASSERT((!is_local() && m_encrypted) || is_local()); #endif // #ifndef TORRENT_DISABLE_ENCRYPTION - throw protocol_error("incorrect protocol identifier"); + disconnect("incorrect protocol identifier"); + return; } #ifndef TORRENT_DISABLE_ENCRYPTION - assert (m_state != read_pe_dhkey); + TORRENT_ASSERT(m_state != read_pe_dhkey); if (!is_local() && (m_ses.get_pe_settings().in_enc_policy == pe_settings::forced) && !m_encrypted) - throw protocol_error("non encrypted incoming connections disabled"); + { + disconnect("non encrypted incoming connections disabled"); + return; + } #endif #ifdef TORRENT_VERBOSE_LOGGING @@ -2234,6 +2349,7 @@ namespace libtorrent , (char*)info_hash.begin()); attach_to_torrent(info_hash); + if (is_disconnecting()) return; } else { @@ -2244,7 +2360,8 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " received invalid info_hash\n"; #endif - throw protocol_error("invalid info-hash in handshake"); + disconnect("invalid info-hash in handshake"); + return; } #ifdef TORRENT_VERBOSE_LOGGING @@ -2261,6 +2378,8 @@ namespace libtorrent // if (t->valid_metadata()) // write_bitfield(t->pieces()); + if (is_disconnecting()) return; + TORRENT_ASSERT(t->get_policy().has_connection(this)); m_state = read_peer_id; @@ -2318,18 +2437,20 @@ namespace libtorrent // if not, we should close the outgoing one. if (pid < m_ses.get_peer_id() && is_local()) { - i->second.connection->disconnect(); + i->second.connection->disconnect("duplicate peer-id, connection closed"); } else { - throw protocol_error("duplicate peer-id, connection closed"); + disconnect("duplicate peer-id, connection closed"); + return; } } } if (pid == m_ses.get_peer_id()) { - throw protocol_error("closing connection to ourself"); + disconnect("closing connection to ourself"); + return; } m_client_version = identify_client(pid); @@ -2343,7 +2464,10 @@ namespace libtorrent // 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"); + { + disconnect("closing connection to ourself"); + return; + } #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() @@ -2382,7 +2506,7 @@ namespace libtorrent #endif m_state = read_packet_size; - reset_recv_buffer(4); + reset_recv_buffer(5); if (t->valid_metadata()) { write_bitfield(t->pieces()); @@ -2400,11 +2524,12 @@ namespace libtorrent if (m_state == read_packet_size) { // Make sure this is not fallen though into - assert (recv_buffer == receive_buffer()); + TORRENT_ASSERT(recv_buffer == receive_buffer()); if (!t) return; m_statistics.received_bytes(0, bytes_transferred); - if (!packet_finished()) return; + + if (recv_buffer.left() < 4) return; const char* ptr = recv_buffer.begin; int packet_size = detail::read_int32(ptr); @@ -2413,9 +2538,10 @@ namespace libtorrent if (packet_size > 1024*1024 || packet_size < 0) { // packet too large - throw std::runtime_error("packet > 1 MB (" - + boost::lexical_cast( - (unsigned int)packet_size) + " bytes)"); + std::stringstream msg; + msg << "packet > 1 MB (" << (unsigned int)packet_size << " bytes)"; + disconnect(msg.str().c_str()); + return; } if (packet_size == 0) @@ -2423,15 +2549,19 @@ namespace libtorrent incoming_keepalive(); // keepalive message m_state = read_packet_size; - reset_recv_buffer(4); + cut_receive_buffer(4, 4); + return; } else { + if (recv_buffer.left() < 5) return; + m_state = read_packet; - reset_recv_buffer(packet_size); + cut_receive_buffer(4, packet_size); + bytes_transferred = 1; + recv_buffer = receive_buffer(); + TORRENT_ASSERT(recv_buffer.left() == 1); } - TORRENT_ASSERT(!packet_finished()); - return; } if (m_state == read_packet) @@ -2441,7 +2571,7 @@ namespace libtorrent if (dispatch_message(bytes_transferred)) { m_state = read_packet_size; - reset_recv_buffer(4); + reset_recv_buffer(5); } TORRENT_ASSERT(!packet_finished()); return; diff --git a/libtorrent/src/connection_queue.cpp b/libtorrent/src/connection_queue.cpp index a48456ed5..ded24dbda 100644 --- a/libtorrent/src/connection_queue.cpp +++ b/libtorrent/src/connection_queue.cpp @@ -45,25 +45,49 @@ namespace libtorrent #ifndef NDEBUG , m_in_timeout_function(false) #endif - {} + { +#ifdef TORRENT_CONNECTION_LOGGING + m_log.open("connection_queue.log"); +#endif + } - bool connection_queue::free_slots() const - { return m_num_connecting < m_half_open_limit || m_half_open_limit <= 0; } + int connection_queue::free_slots() const + { + mutex_t::scoped_lock l(m_mutex); + return m_half_open_limit == 0 ? (std::numeric_limits::max)() + : m_half_open_limit - m_queue.size(); + } void connection_queue::enqueue(boost::function const& on_connect , boost::function const& on_timeout - , time_duration timeout) + , time_duration timeout, int priority) { mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; - m_queue.push_back(entry()); - entry& e = m_queue.back(); - e.on_connect = on_connect; - e.on_timeout = on_timeout; - e.ticket = m_next_ticket; - e.timeout = timeout; + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(priority < 2); + + entry* e = 0; + + switch (priority) + { + case 0: + m_queue.push_back(entry()); + e = &m_queue.back(); + break; + case 1: + m_queue.push_front(entry()); + e = &m_queue.front(); + break; + } + + e->priority = priority; + e->on_connect = on_connect; + e->on_timeout = on_timeout; + e->ticket = m_next_ticket; + e->timeout = timeout; ++m_next_ticket; try_connect(); } @@ -88,11 +112,15 @@ namespace libtorrent void connection_queue::close() { - m_timer.cancel(); + asio::error_code ec; + m_timer.cancel(ec); } void connection_queue::limit(int limit) - { m_half_open_limit = limit; } + { + TORRENT_ASSERT(limit >= 0); + m_half_open_limit = limit; + } int connection_queue::limit() const { return m_half_open_limit; } @@ -116,12 +144,17 @@ namespace libtorrent { INVARIANT_CHECK; - if (!free_slots()) - return; +#ifdef TORRENT_CONNECTION_LOGGING + m_log << log_time() << " " << free_slots() << std::endl; +#endif + + if (m_num_connecting >= m_half_open_limit + && m_half_open_limit > 0) return; if (m_queue.empty()) { - m_timer.cancel(); + asio::error_code ec; + m_timer.cancel(ec); return; } @@ -133,7 +166,8 @@ namespace libtorrent ptime expire = time_now() + i->timeout; if (m_num_connecting == 0) { - m_timer.expires_at(expire); + asio::error_code ec; + m_timer.expires_at(expire, ec); m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); } i->connecting = true; @@ -144,9 +178,20 @@ namespace libtorrent entry& ent = *i; ++i; - try { ent.on_connect(ent.ticket); } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + ent.on_connect(ent.ticket); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif - if (!free_slots()) break; +#ifdef TORRENT_CONNECTION_LOGGING + m_log << log_time() << " " << free_slots() << std::endl; +#endif + + if (m_num_connecting >= m_half_open_limit + && m_half_open_limit > 0) break; i = std::find_if(i, m_queue.end(), boost::bind(&entry::connecting, _1) == false); } } @@ -206,7 +251,8 @@ namespace libtorrent if (next_expire < max_time()) { - m_timer.expires_at(next_expire); + asio::error_code ec; + m_timer.expires_at(next_expire, ec); m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); } try_connect(); diff --git a/libtorrent/src/disk_buffer_holder.cpp b/libtorrent/src/disk_buffer_holder.cpp new file mode 100644 index 000000000..01e0f21ce --- /dev/null +++ b/libtorrent/src/disk_buffer_holder.cpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + disk_buffer_holder::disk_buffer_holder(aux::session_impl& ses, char* buf) + : m_iothread(ses.m_disk_thread), m_buf(buf) + { + TORRENT_ASSERT(buf == 0 || m_iothread.is_disk_buffer(buf)); + } + + disk_buffer_holder::disk_buffer_holder(disk_io_thread& iothread, char* buf) + : m_iothread(iothread), m_buf(buf) + { + TORRENT_ASSERT(buf == 0 || m_iothread.is_disk_buffer(buf)); + } + + char* disk_buffer_holder::release() + { + char* ret = m_buf; + m_buf = 0; + return ret; + } + + disk_buffer_holder::~disk_buffer_holder() + { + if (m_buf) m_iothread.free_buffer(m_buf); + } +} + diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index f9a572348..ecb18b6de 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -33,23 +33,34 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/storage.hpp" #include #include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include + +#ifdef _WIN32 +#include +#define alloca(s) _alloca(s) +#endif #ifdef TORRENT_DISK_STATS - #include "libtorrent/time.hpp" - #endif namespace libtorrent { - disk_io_thread::disk_io_thread(int block_size) + disk_io_thread::disk_io_thread(asio::io_service& ios, int block_size) : m_abort(false) , m_queue_buffer_size(0) + , m_cache_size(512) // 512 * 16kB = 8MB + , m_cache_expiry(60) // 1 minute + , m_coalesce_writes(true) + , m_coalesce_reads(true) + , m_use_read_cache(true) +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR , m_pool(block_size) -#ifndef NDEBUG - , m_block_size(block_size) #endif + , m_block_size(block_size) + , m_ios(ios) , m_disk_io_thread(boost::ref(*this)) { #ifdef TORRENT_STATS @@ -65,31 +76,6 @@ namespace libtorrent TORRENT_ASSERT(m_abort == true); } -#ifndef NDEBUG - disk_io_job disk_io_thread::find_job(boost::intrusive_ptr s - , int action, int piece) const - { - mutex_t::scoped_lock l(m_mutex); - for (std::list::const_iterator i = m_jobs.begin(); - i != m_jobs.end(); ++i) - { - if (i->storage != s) - continue; - if ((i->action == action || action == -1) && i->piece == piece) - return *i; - } - if ((m_current.action == action || action == -1) - && m_current.piece == piece) - return m_current; - - disk_io_job ret; - ret.action = (disk_io_job::action_t)-1; - ret.piece = -1; - return ret; - } - -#endif - void disk_io_thread::join() { mutex_t::scoped_lock l(m_mutex); @@ -100,6 +86,47 @@ namespace libtorrent m_disk_io_thread.join(); } + void disk_io_thread::get_cache_info(sha1_hash const& ih, std::vector& ret) const + { + mutex_t::scoped_lock l(m_mutex); + ret.clear(); + ret.reserve(m_pieces.size()); + for (cache_t::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i) + { + torrent_info const& ti = *i->storage->info(); + if (ti.info_hash() != ih) continue; + cached_piece_info info; + info.piece = i->piece; + info.last_use = i->last_use; + int blocks_in_piece = (ti.piece_size(i->piece) + (m_block_size) - 1) / m_block_size; + info.blocks.resize(blocks_in_piece); + for (int b = 0; b < blocks_in_piece; ++b) + if (i->blocks[b]) info.blocks[b] = true; + ret.push_back(info); + } + } + + cache_status disk_io_thread::status() const + { + mutex_t::scoped_lock l(m_mutex); + return m_cache_stats; + } + + void disk_io_thread::set_cache_size(int s) + { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(s >= 0); + m_cache_size = s; + } + + void disk_io_thread::set_cache_expiry(int ex) + { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(ex > 0); + m_cache_expiry = ex; + } + // aborts read operations void disk_io_thread::stop(boost::intrusive_ptr s) { @@ -115,7 +142,7 @@ namespace libtorrent } if (i->action == disk_io_job::read) { - i->callback(-1, *i); + if (i->callback) m_ios.post(bind(i->callback, -1, *i)); m_jobs.erase(i++); continue; } @@ -133,7 +160,7 @@ namespace libtorrent namespace { // The semantic of this operator is: - // shouls lhs come before rhs in the job queue + // should lhs come before rhs in the job queue bool operator<(disk_io_job const& lhs, disk_io_job const& rhs) { // NOTE: comparison inverted to make higher priority @@ -150,14 +177,459 @@ namespace libtorrent return false; } } + + disk_io_thread::cache_t::iterator disk_io_thread::find_cached_piece( + disk_io_thread::cache_t& cache + , disk_io_job const& j, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + for (cache_t::iterator i = cache.begin() + , end(cache.end()); i != end; ++i) + { + if (i->storage != j.storage || i->piece != j.piece) continue; + return i; + } + return cache.end(); + } + void disk_io_thread::flush_expired_pieces(mutex_t::scoped_lock& l) + { + ptime now = time_now(); + + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + for (;;) + { + cache_t::iterator i = std::min_element( + m_pieces.begin(), m_pieces.end() + , bind(&cached_piece_entry::last_use, _1) + < bind(&cached_piece_entry::last_use, _2)); + if (i == m_pieces.end()) return; + int age = total_seconds(now - i->last_use); + if (age < m_cache_expiry) return; + flush_and_remove(i, l); + } + } + + void disk_io_thread::free_piece(cached_piece_entry& p, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + int piece_size = p.storage->info()->piece_size(p.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + + for (int i = 0; i < blocks_in_piece; ++i) + { + if (p.blocks[i] == 0) continue; + free_buffer(p.blocks[i], l); + p.blocks[i] = 0; + --p.num_blocks; + --m_cache_stats.cache_size; + --m_cache_stats.read_cache_size; + } + } + + bool disk_io_thread::clear_oldest_read_piece( + cache_t::iterator ignore + , mutex_t::scoped_lock& l) + { + INVARIANT_CHECK; + + cache_t::iterator i = std::min_element( + m_read_pieces.begin(), m_read_pieces.end() + , bind(&cached_piece_entry::last_use, _1) + < bind(&cached_piece_entry::last_use, _2)); + if (i != m_read_pieces.end() && i != ignore) + { + // don't replace an entry that is less than one second old + if (time_now() - i->last_use < seconds(1)) return false; + free_piece(*i, l); + m_read_pieces.erase(i); + return true; + } + return false; + } + + void disk_io_thread::flush_oldest_piece(mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + // first look if there are any read cache entries that can + // be cleared + if (clear_oldest_read_piece(m_read_pieces.end(), l)) return; + + cache_t::iterator i = std::min_element( + m_pieces.begin(), m_pieces.end() + , bind(&cached_piece_entry::last_use, _1) + < bind(&cached_piece_entry::last_use, _2)); + if (i == m_pieces.end()) return; + flush_and_remove(i, l); + } + + void disk_io_thread::flush_and_remove(disk_io_thread::cache_t::iterator e + , mutex_t::scoped_lock& l) + { + flush(e, l); + m_pieces.erase(e); + } + + void disk_io_thread::flush(disk_io_thread::cache_t::iterator e + , mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + cached_piece_entry& p = *e; + int piece_size = p.storage->info()->piece_size(p.piece); +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " flushing " << piece_size << std::endl; +#endif + TORRENT_ASSERT(piece_size > 0); + boost::scoped_array buf; + if (m_coalesce_writes) buf.reset(new (std::nothrow) char[piece_size]); + + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + int buffer_size = 0; + int offset = 0; + for (int i = 0; i <= blocks_in_piece; ++i) + { + if (i == blocks_in_piece || p.blocks[i] == 0) + { + if (buffer_size == 0) continue; + TORRENT_ASSERT(buf); + + TORRENT_ASSERT(buffer_size <= i * m_block_size); + l.unlock(); + p.storage->write_impl(buf.get(), p.piece, (std::min)( + i * m_block_size, piece_size) - buffer_size, buffer_size); + l.lock(); + ++m_cache_stats.writes; +// std::cerr << " flushing p: " << p.piece << " bytes: " << buffer_size << std::endl; + buffer_size = 0; + offset = 0; + continue; + } + int block_size = (std::min)(piece_size - i * m_block_size, m_block_size); + TORRENT_ASSERT(offset + block_size <= piece_size); + TORRENT_ASSERT(offset + block_size > 0); + if (!buf) + { + l.unlock(); + p.storage->write_impl(p.blocks[i], p.piece, i * m_block_size, block_size); + l.lock(); + ++m_cache_stats.writes; + } + else + { + std::memcpy(buf.get() + offset, p.blocks[i], block_size); + offset += m_block_size; + buffer_size += block_size; + } + free_buffer(p.blocks[i], l); + p.blocks[i] = 0; + TORRENT_ASSERT(p.num_blocks > 0); + --p.num_blocks; + ++m_cache_stats.blocks_written; + --m_cache_stats.cache_size; + } + TORRENT_ASSERT(buffer_size == 0); +// std::cerr << " flushing p: " << p.piece << " cached_blocks: " << m_cache_stats.cache_size << std::endl; +#ifndef NDEBUG + for (int i = 0; i < blocks_in_piece; ++i) + TORRENT_ASSERT(p.blocks[i] == 0); +#endif + } + + void disk_io_thread::cache_block(disk_io_job& j, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + TORRENT_ASSERT(find_cached_piece(m_pieces, j, l) == m_pieces.end()); + cached_piece_entry p; + + int piece_size = j.storage->info()->piece_size(j.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + + p.piece = j.piece; + p.storage = j.storage; + p.last_use = time_now(); + p.num_blocks = 1; + p.blocks.reset(new char*[blocks_in_piece]); + std::memset(&p.blocks[0], 0, blocks_in_piece * sizeof(char*)); + int block = j.offset / m_block_size; +// std::cerr << " adding cache entry for p: " << j.piece << " block: " << block << " cached_blocks: " << m_cache_stats.cache_size << std::endl; + p.blocks[block] = j.buffer; + ++m_cache_stats.cache_size; + m_pieces.push_back(p); + } + + // fills a piece with data from disk, returns the total number of bytes + // read or -1 if there was an error + int disk_io_thread::read_into_piece(cached_piece_entry& p, int start_block, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + + int piece_size = p.storage->info()->piece_size(p.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + + int end_block = start_block; + for (int i = start_block; i < blocks_in_piece + && m_cache_stats.cache_size < m_cache_size; ++i) + { + // this is a block that is already allocated + // stop allocating and don't read more than + // what we've allocated now + if (p.blocks[i]) break; + p.blocks[i] = allocate_buffer(l); + + // the allocation failed, break + if (p.blocks[i] == 0) break; + ++p.num_blocks; + ++m_cache_stats.cache_size; + ++m_cache_stats.read_cache_size; + ++end_block; + } + + if (end_block == start_block) return -2; + + int buffer_size = piece_size - (end_block - 1) * m_block_size + (end_block - start_block - 1) * m_block_size; + TORRENT_ASSERT(buffer_size <= piece_size); + TORRENT_ASSERT(buffer_size + start_block * m_block_size <= piece_size); + boost::scoped_array buf; + if (m_coalesce_reads) buf.reset(new (std::nothrow) char[buffer_size]); + int ret = 0; + if (buf) + { + l.unlock(); + ret += p.storage->read_impl(buf.get(), p.piece, start_block * m_block_size, buffer_size); + l.lock(); + if (!p.storage->error().empty()) { return -1; } + ++m_cache_stats.reads; + } + + int piece_offset = start_block * m_block_size; + int offset = 0; + for (int i = start_block; i < end_block; ++i) + { + int block_size = (std::min)(piece_size - piece_offset, m_block_size); + if (p.blocks[i] == 0) break; + TORRENT_ASSERT(offset <= buffer_size); + TORRENT_ASSERT(piece_offset <= piece_size); + if (buf) + { + std::memcpy(p.blocks[i], buf.get() + offset, block_size); + } + else + { + l.unlock(); + ret += p.storage->read_impl(p.blocks[i], p.piece, piece_offset, block_size); + if (!p.storage->error().empty()) { return -1; } + l.lock(); + ++m_cache_stats.reads; + } + offset += m_block_size; + piece_offset += m_block_size; + } + TORRENT_ASSERT(ret <= buffer_size); + return (ret != buffer_size) ? -1 : ret; + } + + bool disk_io_thread::make_room(int num_blocks + , cache_t::iterator ignore + , mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + + if (m_cache_size - m_cache_stats.cache_size < num_blocks) + { + // there's not enough room in the cache, clear a piece + // from the read cache + if (!clear_oldest_read_piece(ignore, l)) return false; + } + + return m_cache_size - m_cache_stats.cache_size >= num_blocks; + } + + // returns -1 on read error, -2 if there isn't any space in the cache + // or the number of bytes read + int disk_io_thread::cache_read_block(disk_io_job const& j, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + + INVARIANT_CHECK; + + int piece_size = j.storage->info()->piece_size(j.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + + int start_block = j.offset / m_block_size; + + if (!make_room(blocks_in_piece - start_block + , m_read_pieces.end(), l)) return -2; + + cached_piece_entry p; + p.piece = j.piece; + p.storage = j.storage; + p.last_use = time_now(); + p.num_blocks = 0; + p.blocks.reset(new char*[blocks_in_piece]); + std::memset(&p.blocks[0], 0, blocks_in_piece * sizeof(char*)); + int ret = read_into_piece(p, start_block, l); + + if (ret == -1) + free_piece(p, l); + else + m_read_pieces.push_back(p); + + return ret; + } + +#ifndef NDEBUG + void disk_io_thread::check_invariant() const + { + int cached_write_blocks = 0; + for (cache_t::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i) + { + cached_piece_entry const& p = *i; + TORRENT_ASSERT(p.blocks); + + int piece_size = p.storage->info()->piece_size(p.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + int blocks = 0; + for (int k = 0; k < blocks_in_piece; ++k) + { + if (p.blocks[k]) + { +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + TORRENT_ASSERT(m_pool.is_from(p.blocks[k])); +#endif + ++blocks; + } + } +// TORRENT_ASSERT(blocks == p.num_blocks); + cached_write_blocks += blocks; + } + + int cached_read_blocks = 0; + for (cache_t::const_iterator i = m_read_pieces.begin() + , end(m_read_pieces.end()); i != end; ++i) + { + cached_piece_entry const& p = *i; + TORRENT_ASSERT(p.blocks); + + int piece_size = p.storage->info()->piece_size(p.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + int blocks = 0; + for (int k = 0; k < blocks_in_piece; ++k) + { + if (p.blocks[k]) + { +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + TORRENT_ASSERT(m_pool.is_from(p.blocks[k])); +#endif + ++blocks; + } + } +// TORRENT_ASSERT(blocks == p.num_blocks); + cached_read_blocks += blocks; + } + + TORRENT_ASSERT(cached_read_blocks + cached_write_blocks == m_cache_stats.cache_size); + TORRENT_ASSERT(cached_read_blocks == m_cache_stats.read_cache_size); + + // when writing, there may be a one block difference, right before an old piece + // is flushed + TORRENT_ASSERT(m_cache_stats.cache_size <= m_cache_size + 1); + } +#endif + + int disk_io_thread::try_read_from_cache(disk_io_job const& j, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + TORRENT_ASSERT(j.buffer); + + if (!m_use_read_cache) return -2; + + cache_t::iterator p + = find_cached_piece(m_read_pieces, j, l); + + bool hit = true; + int ret = 0; + + // if the piece cannot be found in the cache, + // read the whole piece starting at the block + // we got a request for. + if (p == m_read_pieces.end()) + { + ret = cache_read_block(j, l); + hit = false; + if (ret < 0) return ret; + p = m_read_pieces.end(); + --p; + TORRENT_ASSERT(!m_read_pieces.empty()); + TORRENT_ASSERT(p->piece == j.piece); + TORRENT_ASSERT(p->storage == j.storage); + } + + if (p != m_read_pieces.end()) + { + // copy from the cache and update the last use timestamp + int block = j.offset / m_block_size; + int block_offset = j.offset % m_block_size; + int buffer_offset = 0; + int size = j.buffer_size; + if (p->blocks[block] == 0) + { + int piece_size = j.storage->info()->piece_size(j.piece); + int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + int end_block = block; + while (end_block < blocks_in_piece && p->blocks[end_block] == 0) ++end_block; + if (!make_room(end_block - block, p, l)) return -2; + ret = read_into_piece(*p, block, l); + hit = false; + if (ret < 0) return ret; + TORRENT_ASSERT(p->blocks[block]); + } + + p->last_use = time_now(); + while (size > 0) + { + TORRENT_ASSERT(p->blocks[block]); + int to_copy = (std::min)(m_block_size + - block_offset, size); + std::memcpy(j.buffer + buffer_offset + , p->blocks[block] + block_offset + , to_copy); + size -= to_copy; + block_offset = 0; + buffer_offset += to_copy; + } + ret = j.buffer_size; + ++m_cache_stats.blocks_read; + if (hit) ++m_cache_stats.blocks_read_hit; + } + return ret; + } + void disk_io_thread::add_job(disk_io_job const& j , boost::function const& f) { TORRENT_ASSERT(!j.callback); TORRENT_ASSERT(j.storage); + TORRENT_ASSERT(j.buffer_size <= m_block_size); mutex_t::scoped_lock l(m_mutex); - +#ifndef NDEBUG + if (j.action == disk_io_job::write) + { + cache_t::iterator p + = find_cached_piece(m_pieces, j, l); + if (p != m_pieces.end()) + { + int block = j.offset / m_block_size; + char const* buffer = p->blocks[block]; + TORRENT_ASSERT(buffer == 0); + } + } +#endif + std::list::reverse_iterator i = m_jobs.rbegin(); if (j.action == disk_io_job::read) { @@ -209,22 +681,54 @@ namespace libtorrent m_signal.notify_all(); } +#ifndef NDEBUG + bool disk_io_thread::is_disk_buffer(char* buffer) const + { +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + return true; +#else + mutex_t::scoped_lock l(m_mutex); + return m_pool.is_from(buffer); +#endif + } +#endif + char* disk_io_thread::allocate_buffer() { mutex_t::scoped_lock l(m_mutex); -#ifdef TORRENT_STATS - ++m_allocations; -#endif - return (char*)m_pool.ordered_malloc(); + return allocate_buffer(l); } void disk_io_thread::free_buffer(char* buf) { mutex_t::scoped_lock l(m_mutex); + free_buffer(buf, l); + } + + char* disk_io_thread::allocate_buffer(mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); +#ifdef TORRENT_STATS + ++m_allocations; +#endif +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + return (char*)malloc(m_block_size); +#else + return (char*)m_pool.ordered_malloc(); +#endif + } + + void disk_io_thread::free_buffer(char* buf, mutex_t::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); #ifdef TORRENT_STATS --m_allocations; #endif +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + free(buf); +#else m_pool.ordered_free(buf); +#endif } void disk_io_thread::operator()() @@ -235,124 +739,358 @@ namespace libtorrent m_log << log_time() << " idle" << std::endl; #endif mutex_t::scoped_lock l(m_mutex); -#ifndef NDEBUG - m_current.action = (disk_io_job::action_t)-1; - m_current.piece = -1; -#endif + while (m_jobs.empty() && !m_abort) m_signal.wait(l); if (m_abort && m_jobs.empty()) return; + // if there's a buffer in this job, it will be freed + // when this holder is destructed, unless it has been + // released. + disk_buffer_holder holder(*this + , m_jobs.front().action != disk_io_job::check_fastresume + ? m_jobs.front().buffer : 0); + boost::function handler; handler.swap(m_jobs.front().callback); -#ifndef NDEBUG - m_current = m_jobs.front(); -#endif + disk_io_job j = m_jobs.front(); m_jobs.pop_front(); m_queue_buffer_size -= j.buffer_size; + + flush_expired_pieces(l); l.unlock(); int ret = 0; - bool free_current_buffer = true; - try - { - TORRENT_ASSERT(j.storage); + TORRENT_ASSERT(j.storage); #ifdef TORRENT_DISK_STATS - ptime start = time_now(); + ptime start = time_now(); #endif -// std::cerr << "DISK THREAD: executing job: " << j.action << std::endl; - switch (j.action) - { - case disk_io_job::read: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " read " << j.buffer_size << std::endl; +#ifndef BOOST_NO_EXCEPTIONS + try { #endif - free_current_buffer = false; - if (j.buffer == 0) - { - j.buffer = allocate_buffer(); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - if (j.buffer == 0) - { - ret = -1; - j.str = "out of memory"; - break; - } - } - ret = int(j.storage->read_impl(j.buffer, j.piece, j.offset - , j.buffer_size)); - // simulates slow drives - // usleep(300); - break; - case disk_io_job::write: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " write " << j.buffer_size << std::endl; + switch (j.action) + { + case disk_io_job::read: + { + std::string const& error_string = j.storage->error(); + if (!error_string.empty()) + { +#ifndef NDEBUG + std::cout << "ERROR: '" << error_string << "' " << j.error_file << std::endl; #endif - TORRENT_ASSERT(j.buffer); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - j.storage->write_impl(j.buffer, j.piece, j.offset + j.str = error_string; + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + ret = -1; + break; + } +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " read " << j.buffer_size << std::endl; +#endif + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + TORRENT_ASSERT(j.buffer == 0); + j.buffer = allocate_buffer(); + TORRENT_ASSERT(j.buffer_size <= m_block_size); + if (j.buffer == 0) + { + ret = -1; + j.str = "out of memory"; + break; + } + + disk_buffer_holder read_holder(*this, j.buffer); + ret = try_read_from_cache(j, l); + + // -2 means there's no space in the read cache + // or that the read cache is disabled + if (ret == -1) + { + j.buffer = 0; + j.str = j.storage->error(); + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + break; + } + else if (ret == -2) + { + l.unlock(); + ret = j.storage->read_impl(j.buffer, j.piece, j.offset , j.buffer_size); - - // simulates a slow drive - // usleep(300); - break; - case disk_io_job::hash: + if (ret < 0) { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " hash" << std::endl; -#endif - sha1_hash h = j.storage->hash_for_piece_impl(j.piece); - j.str.resize(20); - std::memcpy(&j.str[0], &h[0], 20); + j.str = j.storage->error(); + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + break; } - break; - case disk_io_job::move_storage: -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " move" << std::endl; + l.lock(); + ++m_cache_stats.blocks_read; + } + read_holder.release(); + break; + } + case disk_io_job::write: + { + std::string const& error_string = j.storage->error(); + if (!error_string.empty()) + { +#ifndef NDEBUG + std::cout << "ERROR: '" << error_string << "' " << j.error_file << std::endl; #endif - ret = j.storage->move_storage_impl(j.str) ? 1 : 0; - j.str = j.storage->save_path().string(); + j.str = error_string; + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + ret = -1; break; - case disk_io_job::release_files: + } #ifdef TORRENT_DISK_STATS - m_log << log_time() << " release" << std::endl; + m_log << log_time() << " write " << j.buffer_size << std::endl; #endif - j.storage->release_files_impl(); - break; - case disk_io_job::delete_files: + mutex_t::scoped_lock l(m_mutex); + INVARIANT_CHECK; + cache_t::iterator p + = find_cached_piece(m_pieces, j, l); + int block = j.offset / m_block_size; + TORRENT_ASSERT(j.buffer); + TORRENT_ASSERT(j.buffer_size <= m_block_size); + if (p != m_pieces.end()) + { + TORRENT_ASSERT(p->blocks[block] == 0); + if (p->blocks[block]) + { + free_buffer(p->blocks[block]); + --p->num_blocks; + } + p->blocks[block] = j.buffer; + ++m_cache_stats.cache_size; + ++p->num_blocks; + p->last_use = time_now(); + } + else + { + cache_block(j, l); + } + // we've now inserted the buffer + // in the cache, we should not + // free it at the end + holder.release(); + if (m_cache_stats.cache_size >= m_cache_size) + flush_oldest_piece(l); + break; + } + case disk_io_job::hash: + { #ifdef TORRENT_DISK_STATS - m_log << log_time() << " delete" << std::endl; + m_log << log_time() << " hash" << std::endl; #endif - j.storage->delete_files_impl(); + mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + cache_t::iterator i + = find_cached_piece(m_pieces, j, l); + if (i != m_pieces.end()) + { + flush_and_remove(i, l); + std::string const& e = j.storage->error(); + if (!e.empty()) + { + j.str = e; + j.error_file = j.storage->error_file(); + ret = -1; + j.storage->clear_error(); + j.storage->mark_failed(j.piece); + break; + } + } + l.unlock(); + sha1_hash h = j.storage->hash_for_piece_impl(j.piece); + std::string const& e = j.storage->error(); + if (!e.empty()) + { + j.str = e; + j.error_file = j.storage->error_file(); + ret = -1; + j.storage->clear_error(); + j.storage->mark_failed(j.piece); break; + } + ret = (j.storage->info()->hash_for_piece(j.piece) == h)?0:-2; + if (ret == -2) j.storage->mark_failed(j.piece); + break; + } + case disk_io_job::move_storage: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " move" << std::endl; +#endif + TORRENT_ASSERT(j.buffer == 0); + ret = j.storage->move_storage_impl(j.str) ? 1 : 0; + if (ret != 0) + { + j.str = j.storage->error(); + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + break; + } + j.str = j.storage->save_path().string(); + break; + } + case disk_io_job::release_files: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " release" << std::endl; +#endif + TORRENT_ASSERT(j.buffer == 0); + mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + for (cache_t::iterator i = m_pieces.begin(); i != m_pieces.end();) + { + if (i->storage == j.storage) + { + flush(i, l); + i = m_pieces.erase(i); + } + else + { + ++i; + } + } +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + m_pool.release_memory(); +#endif + l.unlock(); + ret = j.storage->release_files_impl(); + if (ret != 0) + { + j.str = j.storage->error(); + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + } + break; + } + case disk_io_job::delete_files: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " delete" << std::endl; +#endif + TORRENT_ASSERT(j.buffer == 0); + mutex_t::scoped_lock l(m_mutex); + + INVARIANT_CHECK; + + cache_t::iterator i = std::remove_if( + m_pieces.begin(), m_pieces.end(), bind(&cached_piece_entry::storage, _1) == j.storage); + + for (cache_t::iterator k = i; k != m_pieces.end(); ++k) + { + torrent_info const& ti = *k->storage->info(); + int blocks_in_piece = (ti.piece_size(k->piece) + m_block_size - 1) / m_block_size; + for (int j = 0; j < blocks_in_piece; ++j) + { + if (k->blocks[j] == 0) continue; + free_buffer(k->blocks[j], l); + k->blocks[j] = 0; + } + } + m_pieces.erase(i, m_pieces.end()); +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + m_pool.release_memory(); +#endif + l.unlock(); + ret = j.storage->delete_files_impl(); + if (ret != 0) + { + j.str = j.storage->error(); + j.error_file = j.storage->error_file(); + j.storage->clear_error(); + } + break; + } + case disk_io_job::check_fastresume: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " check fastresume" << std::endl; +#endif + entry const* rd = (entry const*)j.buffer; + TORRENT_ASSERT(rd != 0); + ret = j.storage->check_fastresume(*rd, j.str); + break; + } + case disk_io_job::check_files: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " check files" << std::endl; +#endif + int piece_size = j.storage->info()->piece_length(); + for (int processed = 0; processed < 4 * 1024 * 1024; processed += piece_size) + { + ret = j.storage->check_files(j.piece, j.offset, j.str); + +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + TORRENT_ASSERT(handler); + if (handler && ret == piece_manager::need_full_check) + m_ios.post(bind(handler, ret, j)); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif + if (ret != piece_manager::need_full_check) break; + } + // if the check is not done, add it at the end of the job queue + if (ret == piece_manager::need_full_check) + { + mutex_t::scoped_lock l(m_mutex); + m_jobs.push_back(j); + m_jobs.back().callback.swap(handler); + continue; + } + break; + } + case disk_io_job::save_resume_data: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " save resume data" << std::endl; +#endif + j.resume_data.reset(new entry(entry::dictionary_t)); + j.storage->write_resume_data(*j.resume_data); + ret = 0; + break; } } - catch (std::exception& e) +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception& e) { -// std::cerr << "DISK THREAD: exception: " << e.what() << std::endl; + ret = -1; try { j.str = e.what(); } catch (std::exception&) {} - ret = -1; } +#endif // if (!handler) std::cerr << "DISK THREAD: no callback specified" << std::endl; // else std::cerr << "DISK THREAD: invoking callback" << std::endl; - try { if (handler) handler(ret, j); } - catch (std::exception&) {} - -#ifndef NDEBUG - m_current.storage = 0; - m_current.callback.clear(); +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + if (handler) m_ios.post(bind(handler, ret, j)); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) + { + TORRENT_ASSERT(false); + } #endif - - if (j.buffer && free_current_buffer) free_buffer(j.buffer); } + TORRENT_ASSERT(false); } } diff --git a/libtorrent/src/entry.cpp b/libtorrent/src/entry.cpp index c3e91044a..2b8410115 100755 --- a/libtorrent/src/entry.cpp +++ b/libtorrent/src/entry.cpp @@ -142,9 +142,38 @@ namespace libtorrent } #endif + entry::entry() + : m_type(undefined_t) + { +#ifndef NDEBUG + m_type_queried = true; +#endif + } + + entry::entry(data_type t) + : m_type(undefined_t) + { + construct(t); +#ifndef NDEBUG + m_type_queried = true; +#endif + } + + entry::entry(const entry& e) + : m_type(undefined_t) + { + copy(e); +#ifndef NDEBUG + m_type_queried = e.m_type_queried; +#endif + } + entry::entry(dictionary_type const& v) : m_type(undefined_t) { +#ifndef NDEBUG + m_type_queried = true; +#endif new(data) dictionary_type(v); m_type = dictionary_t; } @@ -152,6 +181,9 @@ namespace libtorrent entry::entry(string_type const& v) : m_type(undefined_t) { +#ifndef NDEBUG + m_type_queried = true; +#endif new(data) string_type(v); m_type = string_t; } @@ -159,6 +191,9 @@ namespace libtorrent entry::entry(list_type const& v) : m_type(undefined_t) { +#ifndef NDEBUG + m_type_queried = true; +#endif new(data) list_type(v); m_type = list_t; } @@ -166,6 +201,9 @@ namespace libtorrent entry::entry(integer_type const& v) : m_type(undefined_t) { +#ifndef NDEBUG + m_type_queried = true; +#endif new(data) integer_type(v); m_type = int_t; } @@ -175,6 +213,9 @@ namespace libtorrent destruct(); new(data) dictionary_type(v); m_type = dictionary_t; +#ifndef NDEBUG + m_type_queried = true; +#endif } void entry::operator=(string_type const& v) @@ -182,6 +223,9 @@ namespace libtorrent destruct(); new(data) string_type(v); m_type = string_t; +#ifndef NDEBUG + m_type_queried = true; +#endif } void entry::operator=(list_type const& v) @@ -189,6 +233,9 @@ namespace libtorrent destruct(); new(data) list_type(v); m_type = list_t; +#ifndef NDEBUG + m_type_queried = true; +#endif } void entry::operator=(integer_type const& v) @@ -196,6 +243,9 @@ namespace libtorrent destruct(); new(data) integer_type(v); m_type = int_t; +#ifndef NDEBUG + m_type_queried = true; +#endif } bool entry::operator==(entry const& e) const @@ -235,16 +285,17 @@ namespace libtorrent new (data) dictionary_type; break; default: - TORRENT_ASSERT(m_type == undefined_t); - m_type = undefined_t; - return; + TORRENT_ASSERT(t == undefined_t); } m_type = t; +#ifndef NDEBUG + m_type_queried = true; +#endif } void entry::copy(entry const& e) { - switch(e.m_type) + switch (e.type()) { case int_t: new(data) integer_type(e.integer()); @@ -259,10 +310,12 @@ namespace libtorrent new (data) dictionary_type(e.dict()); break; default: - m_type = undefined_t; - return; + TORRENT_ASSERT(e.type() == undefined_t); } - m_type = e.m_type; + m_type = e.type(); +#ifndef NDEBUG + m_type_queried = true; +#endif } void entry::destruct() @@ -286,6 +339,9 @@ namespace libtorrent break; } m_type = undefined_t; +#ifndef NDEBUG + m_type_queried = false; +#endif } void entry::swap(entry& e) diff --git a/libtorrent/src/enum_net.cpp b/libtorrent/src/enum_net.cpp index ec3238d52..ff3c108c3 100644 --- a/libtorrent/src/enum_net.cpp +++ b/libtorrent/src/enum_net.cpp @@ -31,12 +31,13 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "libtorrent/config.hpp" - + #if defined TORRENT_BSD || defined TORRENT_LINUX #include #include #include #include +#include #elif defined TORRENT_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN @@ -46,8 +47,8 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "libtorrent/enum_net.hpp" -// for is_loopback and is_any #include "libtorrent/broadcast_socket.hpp" +#include namespace libtorrent { @@ -71,6 +72,17 @@ namespace libtorrent } return address(); } + +#ifdef TORRENT_BSD + bool verify_sockaddr(sockaddr_in* sin) + { + return (sin->sin_len == sizeof(sockaddr_in) + && sin->sin_family == AF_INET) + || (sin->sin_len == sizeof(sockaddr_in6) + && sin->sin_family == AF_INET6); + } +#endif + } bool in_subnet(address const& addr, ip_interface const& iface) @@ -101,7 +113,7 @@ namespace libtorrent std::vector enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) { std::vector ret; - +// covers linux, MacOS X and BSD distributions #if defined TORRENT_LINUX || defined TORRENT_BSD int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) @@ -201,7 +213,7 @@ namespace libtorrent #warning THIS OS IS NOT RECOGNIZED, enum_net_interfaces WILL PROBABLY NOT WORK // make a best guess of the interface we're using and its IP udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(ec), "0")); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(ec), "0"), ec); if (ec) return ret; ip_interface iface; for (;i != udp::resolver_iterator(); ++i) @@ -215,15 +227,116 @@ namespace libtorrent return ret; } - address router_for_interface(address const interface, asio::error_code& ec) + address get_default_gateway(asio::io_service& ios, address const& interface, asio::error_code& ec) { -#ifdef TORRENT_WINDOWS + +#if defined TORRENT_BSD + + struct rt_msg + { + rt_msghdr m_rtm; + char buf[512]; + }; + + rt_msg m; + int len = sizeof(rt_msg); + bzero(&m, len); + m.m_rtm.rtm_type = RTM_GET; + m.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY; + m.m_rtm.rtm_version = RTM_VERSION; + m.m_rtm.rtm_addrs = RTA_DST | RTA_GATEWAY; + m.m_rtm.rtm_seq = 0; + m.m_rtm.rtm_msglen = len; + + int s = socket(PF_ROUTE, SOCK_RAW, AF_INET); + if (s == -1) + { + ec = asio::error_code(errno, asio::error::system_category); + return address_v4::any(); + } + + int n = write(s, &m, len); + if (n == -1) + { + ec = asio::error_code(errno, asio::error::system_category); + close(s); + return address_v4::any(); + } + else if (n != len) + { + ec = asio::error::operation_not_supported; + close(s); + return address_v4::any(); + } + bzero(&m, len); + + n = read(s, &m, len); + if (n == -1) + { + ec = asio::error_code(errno, asio::error::system_category); + close(s); + return address_v4::any(); + } + close(s); + + TORRENT_ASSERT(m.m_rtm.rtm_seq == 0); + TORRENT_ASSERT(m.m_rtm.rtm_pid == getpid()); + if (m.m_rtm.rtm_errno) + { + ec = asio::error_code(m.m_rtm.rtm_errno, asio::error::system_category); + return address_v4::any(); + } + if (m.m_rtm.rtm_flags & RTF_UP == 0 + || m.m_rtm.rtm_flags & RTF_GATEWAY == 0) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + if (m.m_rtm.rtm_addrs & RTA_DST == 0 + || m.m_rtm.rtm_addrs & RTA_GATEWAY == 0) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + if (m.m_rtm.rtm_msglen > len) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + int min_len = sizeof(rt_msghdr) + 2 * sizeof(struct sockaddr_in); + if (m.m_rtm.rtm_msglen < min_len) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + + // default route + char* p = m.buf; + sockaddr_in* sin = (sockaddr_in*)p; + if (!verify_sockaddr(sin)) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + + // default gateway + p += sin->sin_len; + sin = (sockaddr_in*)p; + if (!verify_sockaddr(sin)) + { + ec = asio::error::operation_not_supported; + return address_v4::any(); + } + + return sockaddr_to_address((sockaddr*)sin); + +#elif defined TORRENT_WINDOWS // Load Iphlpapi library HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); if (!iphlp) { - ec = asio::error::fault; + ec = asio::error::operation_not_supported; return address_v4::any(); } @@ -233,7 +346,7 @@ namespace libtorrent if (!GetAdaptersInfo) { FreeLibrary(iphlp); - ec = asio::error::fault; + ec = asio::error::operation_not_supported; return address_v4::any(); } @@ -242,7 +355,7 @@ namespace libtorrent if (GetAdaptersInfo(adapter_info, &out_buf_size) != ERROR_BUFFER_OVERFLOW) { FreeLibrary(iphlp); - ec = asio::error::fault; + ec = asio::error::operation_not_supported; return address_v4::any(); } @@ -250,14 +363,13 @@ namespace libtorrent if (!adapter_info) { FreeLibrary(iphlp); - ec = asio::error::fault; + ec = asio::error::no_memory; return address_v4::any(); } address ret; if (GetAdaptersInfo(adapter_info, &out_buf_size) == NO_ERROR) { - for (PIP_ADAPTER_INFO adapter = adapter_info; adapter != 0; adapter = adapter->Next) { @@ -282,11 +394,12 @@ namespace libtorrent return ret; +//#elif defined TORRENT_LINUX +// No linux implementation yet #else - // TODO: temporary implementation if (!interface.is_v4()) { - ec = asio::error::fault; + ec = asio::error::operation_not_supported; return address_v4::any(); } return address_v4((interface.to_v4().to_ulong() & 0xffffff00) | 1); diff --git a/libtorrent/src/escape_string.cpp b/libtorrent/src/escape_string.cpp index 323a3e12b..186b34f88 100755 --- a/libtorrent/src/escape_string.cpp +++ b/libtorrent/src/escape_string.cpp @@ -38,6 +38,9 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include + +#include #include "libtorrent/assert.hpp" @@ -60,23 +63,41 @@ namespace libtorrent { ++i; if (i == s.end()) +#ifdef BOOST_NO_EXCEPTIONS + return ret; +#else throw std::runtime_error("invalid escaped string"); +#endif int high; if(*i >= '0' && *i <= '9') high = *i - '0'; else if(*i >= 'A' && *i <= 'F') high = *i + 10 - 'A'; else if(*i >= 'a' && *i <= 'f') high = *i + 10 - 'a'; - else throw std::runtime_error("invalid escaped string"); + else +#ifdef BOOST_NO_EXCEPTIONS + return ret; +#else + throw std::runtime_error("invalid escaped string"); +#endif ++i; if (i == s.end()) +#ifdef BOOST_NO_EXCEPTIONS + return ret; +#else throw std::runtime_error("invalid escaped string"); +#endif int low; if(*i >= '0' && *i <= '9') low = *i - '0'; else if(*i >= 'A' && *i <= 'F') low = *i + 10 - 'A'; else if(*i >= 'a' && *i <= 'f') low = *i + 10 - 'a'; - else throw std::runtime_error("invalid escaped string"); + else +#ifdef BOOST_NO_EXCEPTIONS + return ret; +#else + throw std::runtime_error("invalid escaped string"); +#endif ret += char(high * 16 + low); } @@ -84,7 +105,6 @@ namespace libtorrent return ret; } - std::string escape_string(const char* str, int len) { TORRENT_ASSERT(str != 0); @@ -148,4 +168,189 @@ namespace libtorrent } return ret.str(); } + + std::string base64encode(const std::string& s) + { + static const char base64_table[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + unsigned char inbuf[3]; + unsigned char outbuf[4]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + // available input is 1,2 or 3 bytes + // since we read 3 bytes at a time at most + int available_input = (std::min)(3, (int)std::distance(i, s.end())); + + // clear input buffer + std::fill(inbuf, inbuf+3, 0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xfc) >> 2; + outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf [1] & 0xf0) >> 4); + outbuf[2] = ((inbuf[1] & 0x0f) << 2) | ((inbuf [2] & 0xc0) >> 6); + outbuf[3] = inbuf[2] & 0x3f; + + // write output + for (int j = 0; j < available_input+1; ++j) + { + ret += base64_table[outbuf[j]]; + } + + // write pad + for (int j = 0; j < 3 - available_input; ++j) + { + ret += '='; + } + } + return ret; + } + + std::string base32encode(std::string const& s) + { + static const char base32_table[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7' + }; + + int input_output_mapping[] = {0, 2, 4, 5, 7, 8}; + + unsigned char inbuf[5]; + unsigned char outbuf[8]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + int available_input = (std::min)(5, (int)std::distance(i, s.end())); + + // clear input buffer + std::fill(inbuf, inbuf+5, 0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xf8) >> 3; + outbuf[1] = ((inbuf[0] & 0x07) << 2) | ((inbuf[1] & 0xc0) >> 6); + outbuf[2] = ((inbuf[1] & 0x3e) >> 1); + outbuf[3] = ((inbuf[1] & 0x01) << 4) | ((inbuf[2] & 0xf0) >> 4); + outbuf[4] = ((inbuf[2] & 0x0f) << 1) | ((inbuf[3] & 0x80) >> 7); + outbuf[5] = ((inbuf[3] & 0x7c) >> 2); + outbuf[6] = ((inbuf[3] & 0x03) << 3) | ((inbuf[4] & 0xe0) >> 5); + outbuf[7] = inbuf[4] & 0x1f; + + // write output + int num_out = input_output_mapping[available_input]; + for (int j = 0; j < num_out; ++j) + { + ret += base32_table[outbuf[j]]; + } + + // write pad + for (int j = 0; j < 8 - num_out; ++j) + { + ret += '='; + } + } + return ret; + } + + std::string base32decode(std::string const& s) + { + unsigned char inbuf[8]; + unsigned char outbuf[5]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + int available_input = (std::min)(8, (int)std::distance(i, s.end())); + + int pad_start = 0; + if (available_input < 8) pad_start = available_input; + + // clear input buffer + std::fill(inbuf, inbuf+8, 0); + for (int j = 0; j < available_input; ++j) + { + char in = std::toupper(*i++); + if (in >= 'A' && in <= 'Z') + inbuf[j] = in - 'A'; + else if (in >= '2' && in <= '7') + inbuf[j] = in - '2' + ('Z' - 'A') + 1; + else if (in == '=') + { + inbuf[j] = 0; + if (pad_start == 0) pad_start = j; + } + else if (in == '1') + inbuf[j] = 'I' - 'A'; + else + return std::string(); + TORRENT_ASSERT(inbuf[j] == (inbuf[j] & 0x1f)); + } + + // decode inbuf to outbuf + outbuf[0] = inbuf[0] << 3; + outbuf[0] |= inbuf[1] >> 2; + outbuf[1] = (inbuf[1] & 0x3) << 6; + outbuf[1] |= inbuf[2] << 1; + outbuf[1] |= (inbuf[3] & 0x10) >> 4; + outbuf[2] = (inbuf[3] & 0x0f) << 4; + outbuf[2] |= (inbuf[4] & 0x1e) >> 1; + outbuf[3] = (inbuf[4] & 0x01) << 7; + outbuf[3] |= (inbuf[5] & 0x1f) << 2; + outbuf[3] |= (inbuf[6] & 0x18) >> 3; + outbuf[4] = (inbuf[6] & 0x07) << 5; + outbuf[4] |= inbuf[7]; + + int input_output_mapping[] = {5, 1, 1, 2, 2, 3, 4, 4, 5}; + int num_out = input_output_mapping[pad_start]; + + // write output + std::copy(outbuf, outbuf + num_out, std::back_inserter(ret)); + } + return ret; + } + + boost::optional url_has_argument( + std::string const& url, std::string argument) + { + size_t i = url.find('?'); + if (i == std::string::npos) return boost::optional(); + ++i; + + argument += '='; + + if (url.compare(i, argument.size(), argument) == 0) + { + size_t pos = i + argument.size(); + return url.substr(pos, url.find('&', pos) - pos); + } + argument.insert(0, "&"); + i = url.find(argument, i); + if (i == std::string::npos) return boost::optional(); + size_t pos = i + argument.size(); + return url.substr(pos, url.find('&', pos) - pos); + } + } + diff --git a/libtorrent/src/file.cpp b/libtorrent/src/file.cpp index 5e01e1ae5..46d0e24e3 100755 --- a/libtorrent/src/file.cpp +++ b/libtorrent/src/file.cpp @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" +#include #ifdef _WIN32 // windows part #include "libtorrent/utf8.hpp" @@ -157,7 +158,7 @@ namespace libtorrent close(); } - void open(fs::path const& path, int mode) + bool open(fs::path const& path, int mode) { TORRENT_ASSERT(path.is_complete()); close(); @@ -186,9 +187,12 @@ namespace libtorrent std::stringstream msg; msg << "open failed: '" << path.native_file_string() << "'. " << std::strerror(errno); - throw file_error(msg.str()); + if (!m_error) m_error.reset(new std::string); + *m_error = msg.str(); + return false; } m_open_mode = mode; + return true; } void close() @@ -218,7 +222,8 @@ namespace libtorrent { std::stringstream msg; msg << "read failed: " << std::strerror(errno); - throw file_error(msg.str()); + if (!m_error) m_error.reset(new std::string); + *m_error = msg.str(); } return ret; } @@ -242,12 +247,13 @@ namespace libtorrent { std::stringstream msg; msg << "write failed: " << std::strerror(errno); - throw file_error(msg.str()); + if (!m_error) m_error.reset(new std::string); + *m_error = msg.str(); } return ret; } - void set_size(size_type s) + bool set_size(size_type s) { #ifdef _WIN32 #error file.cpp is for posix systems only. use file_win.cpp on windows @@ -256,8 +262,11 @@ namespace libtorrent { std::stringstream msg; msg << "ftruncate failed: '" << std::strerror(errno); - throw file_error(msg.str()); + if (!m_error) m_error.reset(new std::string); + *m_error = msg.str(); + return false; } + return true; #endif } @@ -283,7 +292,9 @@ namespace libtorrent << "' fd: " << m_fd << " offset: " << offset << " seekdir: " << seekdir; - throw file_error(msg.str()); + if (!m_error) m_error.reset(new std::string); + *m_error = msg.str(); + return -1; } return ret; } @@ -300,8 +311,15 @@ namespace libtorrent #endif } + std::string const& error() const + { + if (!m_error) m_error.reset(new std::string); + return *m_error; + } + int m_fd; int m_open_mode; + mutable boost::scoped_ptr m_error; }; // pimpl forwardings @@ -314,9 +332,9 @@ namespace libtorrent file::~file() {} - void file::open(fs::path const& p, file::open_mode m) + bool file::open(fs::path const& p, file::open_mode m) { - m_impl->open(p, m.m_mask); + return m_impl->open(p, m.m_mask); } void file::close() @@ -334,9 +352,9 @@ namespace libtorrent return m_impl->read(buf, num_bytes); } - void file::set_size(size_type s) + bool file::set_size(size_type s) { - m_impl->set_size(s); + return m_impl->set_size(s); } size_type file::seek(size_type pos, file::seek_mode m) @@ -349,4 +367,9 @@ namespace libtorrent return m_impl->tell(); } + std::string const& file::error() const + { + return m_impl->error(); + } + } diff --git a/libtorrent/src/file_pool.cpp b/libtorrent/src/file_pool.cpp index 7bdf24085..6baeb64d0 100644 --- a/libtorrent/src/file_pool.cpp +++ b/libtorrent/src/file_pool.cpp @@ -41,7 +41,8 @@ namespace libtorrent using boost::multi_index::nth_index; using boost::multi_index::get; - boost::shared_ptr file_pool::open_file(void* st, fs::path const& p, file::open_mode m) + boost::shared_ptr file_pool::open_file(void* st, fs::path const& p + , file::open_mode m, std::string& error) { TORRENT_ASSERT(st != 0); TORRENT_ASSERT(p.is_complete()); @@ -59,8 +60,9 @@ namespace libtorrent { // this means that another instance of the storage // is using the exact same file. - throw file_error("torrent uses the same file as another torrent " - "(" + p.string() + ")"); + error = "torrent uses the same file as another torrent " + "(" + p.string() + ")"; + return boost::shared_ptr(); } e.key = st; @@ -70,8 +72,13 @@ namespace libtorrent // the new read/write privilages i->file_ptr.reset(); TORRENT_ASSERT(e.file_ptr.unique()); - e.file_ptr.reset(); - e.file_ptr.reset(new file(p, m)); + e.file_ptr->close(); + if (!e.file_ptr->open(p, m)) + { + error = e.file_ptr->error(); + m_files.erase(i); + return boost::shared_ptr(); + } e.mode = m; } pt.replace(i, e); @@ -89,7 +96,18 @@ namespace libtorrent TORRENT_ASSERT(lt.size() == 1 || (i->last_use <= boost::next(i)->last_use)); lt.erase(i); } - lru_file_entry e(boost::shared_ptr(new file(p, m))); + lru_file_entry e; + e.file_ptr.reset(new file); + if (!e.file_ptr) + { + error = "no memory"; + return e.file_ptr; + } + if (!e.file_ptr->open(p, m)) + { + error = e.file_ptr->error(); + return boost::shared_ptr(); + } e.mode = m; e.key = st; e.file_path = p; diff --git a/libtorrent/src/file_win.cpp b/libtorrent/src/file_win.cpp index 7fde0028f..fb4f0ce9a 100644 --- a/libtorrent/src/file_win.cpp +++ b/libtorrent/src/file_win.cpp @@ -41,6 +41,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace { @@ -81,36 +82,7 @@ namespace return s; } } - - void throw_exception(const char* thrower) - { - DWORD err = GetLastError(); - -#ifdef UNICODE - wchar_t *wbuffer = 0; - FormatMessage( - FORMAT_MESSAGE_FROM_SYSTEM - |FORMAT_MESSAGE_ALLOCATE_BUFFER - , 0, err, 0, (LPWSTR)&wbuffer, 0, 0); - auto_localfree auto_free(wbuffer); - std::string tmp_utf8; - libtorrent::wchar_utf8(wbuffer, tmp_utf8); - char const* buffer = tmp_utf8.c_str(); -#else - char* buffer = 0; - FormatMessage( - FORMAT_MESSAGE_FROM_SYSTEM - |FORMAT_MESSAGE_ALLOCATE_BUFFER - , 0, err, 0, (LPSTR)&buffer, 0, 0); - auto_localfree auto_free(buffer); -#endif - - std::stringstream s; - s << (thrower ? thrower : "NULL") << ": " << (buffer ? buffer : "NULL"); - - throw libtorrent::file_error(s.str()); } -} namespace libtorrent { @@ -130,12 +102,42 @@ namespace libtorrent seek_end = FILE_END }; + void set_error(const char* thrower) + { + DWORD err = GetLastError(); + +#ifdef UNICODE + wchar_t *wbuffer = 0; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + , 0, err, 0, (LPWSTR)&wbuffer, 0, 0); + auto_localfree auto_free(wbuffer); + std::string tmp_utf8; + libtorrent::wchar_utf8(wbuffer, tmp_utf8); + char const* buffer = tmp_utf8.c_str(); +#else + char* buffer = 0; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + , 0, err, 0, (LPSTR)&buffer, 0, 0); + auto_localfree auto_free(buffer); +#endif + + std::stringstream s; + s << (thrower ? thrower : "NULL") << ": " << (buffer ? buffer : "NULL"); + + if (!m_error) m_error.reset(new std::string); + *m_error = s.str(); + } + impl() { m_file_handle = INVALID_HANDLE_VALUE; } - void open(const char *file_name, open_flags flags) + bool open(const char *file_name, open_flags flags) { TORRENT_ASSERT(file_name); TORRENT_ASSERT(flags & (read_flag | write_flag)); @@ -170,7 +172,10 @@ namespace libtorrent #endif if (new_handle == INVALID_HANDLE_VALUE) - throw_exception(file_name); + { + set_error(file_name); + return false; + } // try to make the file sparse if supported if (access_mask & GENERIC_WRITE) { @@ -181,6 +186,7 @@ namespace libtorrent // will only close old file if the open succeeded close(); m_file_handle = new_handle; + return true; } void close() @@ -211,7 +217,8 @@ namespace libtorrent , &bytes_written , 0)) { - throw_exception("file::write"); + set_error("file::write"); + return -1; } } return bytes_written; @@ -233,20 +240,23 @@ namespace libtorrent , &bytes_read , 0)) { - throw_exception("file::read"); + set_error("file::set_size"); + return -1; } } return bytes_read; } - void set_size(size_type s) + bool set_size(size_type s) { size_type pos = tell(); seek(s, seek_begin); if (FALSE == ::SetEndOfFile(m_file_handle)) - throw_exception("file::set_size"); - - seek(pos, seek_begin); + { + set_error("file::set_size"); + return false; + } + return true; } size_type seek(size_type pos, seek_mode from_where) @@ -261,7 +271,8 @@ namespace libtorrent , &offs , from_where)) { - throw_exception("file::seek"); + set_error("file::seek"); + return -1; } return offs.QuadPart; } @@ -278,7 +289,8 @@ namespace libtorrent , &offs , FILE_CURRENT)) { - throw_exception("file::tell"); + set_error("file::tell"); + return -1; } size_type pos = offs.QuadPart; @@ -299,9 +311,17 @@ namespace libtorrent return size; } */ + + std::string const& error() const + { + if (!m_error) m_error.reset(new std::string); + return *m_error; + } + private: HANDLE m_file_handle; + mutable boost::scoped_ptr m_error; }; } @@ -329,10 +349,10 @@ namespace libtorrent { } - void file::open(boost::filesystem::path const& p, open_mode m) + bool file::open(boost::filesystem::path const& p, open_mode m) { TORRENT_ASSERT(p.is_complete()); - m_impl->open(p.native_file_string().c_str(), impl::open_flags(m.m_mask)); + return m_impl->open(p.native_file_string().c_str(), impl::open_flags(m.m_mask)); } void file::close() @@ -350,9 +370,9 @@ namespace libtorrent return m_impl->read(buffer, num_bytes); } - void file::set_size(size_type s) + bool file::set_size(size_type s) { - m_impl->set_size(s); + return m_impl->set_size(s); } size_type file::seek(size_type pos, seek_mode m) @@ -364,4 +384,9 @@ namespace libtorrent { return m_impl->tell(); } + + std::string const& file::error() const + { + return m_impl->error(); + } } diff --git a/libtorrent/src/gzip.cpp b/libtorrent/src/gzip.cpp new file mode 100644 index 000000000..929816240 --- /dev/null +++ b/libtorrent/src/gzip.cpp @@ -0,0 +1,212 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/assert.hpp" + +#include "zlib.h" + +#include + +namespace +{ + enum + { + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + FRESERVED = 0xe0, + + GZIP_MAGIC0 = 0x1f, + GZIP_MAGIC1 = 0x8b + }; + +} + +namespace libtorrent +{ + // returns -1 if gzip header is invalid or the header size in bytes + int gzip_header(const char* buf, int size) + { + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(size > 0); + + const unsigned char* buffer = reinterpret_cast(buf); + const int total_size = size; + + // The zip header cannot be shorter than 10 bytes + if (size < 10) return -1; + + // check the magic header of gzip + if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; + + int method = buffer[2]; + int flags = buffer[3]; + + // check for reserved flag and make sure it's compressed with the correct metod + if (method != Z_DEFLATED || (flags & FRESERVED) != 0) return -1; + + // skip time, xflags, OS code + size -= 10; + buffer += 10; + + if (flags & FEXTRA) + { + int extra_len; + + if (size < 2) return -1; + + extra_len = (buffer[1] << 8) | buffer[0]; + + if (size < (extra_len+2)) return -1; + size -= (extra_len + 2); + buffer += (extra_len + 2); + } + + if (flags & FNAME) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FCOMMENT) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FHCRC) + { + if (size < 2) return -1; + + size -= 2; + buffer += 2; + } + + return total_size - size; + } + + bool inflate_gzip( + char const* in + , int size + , std::vector& buffer + , int maximum_size + , std::string& error) + { + TORRENT_ASSERT(maximum_size > 0); + + int header_len = gzip_header(in, size); + if (header_len < 0) + { + error = "invalid gzip header in tracker response"; + return true; + } + + // start off with one kilobyte and grow + // if needed + buffer.resize(1024); + + // initialize the zlib-stream + z_stream str; + + // subtract 8 from the end of the buffer since that's CRC32 and input size + // and those belong to the gzip file + str.avail_in = (int)size - header_len - 8; + str.next_in = reinterpret_cast(const_cast(in + header_len)); + str.next_out = reinterpret_cast(&buffer[0]); + str.avail_out = (int)buffer.size(); + str.zalloc = Z_NULL; + str.zfree = Z_NULL; + str.opaque = 0; + // -15 is really important. It will make inflate() not look for a zlib header + // and just deflate the buffer + if (inflateInit2(&str, -15) != Z_OK) + { + error = "gzip out of memory"; + return true; + } + + // inflate and grow inflate_buffer as needed + int ret = inflate(&str, Z_SYNC_FLUSH); + while (ret == Z_OK) + { + if (str.avail_out == 0) + { + if (buffer.size() >= (unsigned)maximum_size) + { + inflateEnd(&str); + error = "response too large"; + return true; + } + int new_size = (int)buffer.size() * 2; + if (new_size > maximum_size) + new_size = maximum_size; + int old_size = (int)buffer.size(); + + buffer.resize(new_size); + str.next_out = reinterpret_cast(&buffer[old_size]); + str.avail_out = new_size - old_size; + } + + ret = inflate(&str, Z_SYNC_FLUSH); + } + + buffer.resize(buffer.size() - str.avail_out); + inflateEnd(&str); + + if (ret != Z_STREAM_END) + { + error = "gzip error"; + return true; + } + + // commit the resulting buffer + return false; + } + +} + diff --git a/libtorrent/src/http_connection.cpp b/libtorrent/src/http_connection.cpp index 92db5c485..0442d455c 100644 --- a/libtorrent/src/http_connection.cpp +++ b/libtorrent/src/http_connection.cpp @@ -31,6 +31,10 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "libtorrent/http_connection.hpp" +#include "libtorrent/escape_string.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/tracker_manager.hpp" #include #include @@ -39,13 +43,14 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; -namespace libtorrent -{ +namespace libtorrent { - enum { max_bottled_buffer = 1024 * 1024 }; +enum { max_bottled_buffer = 1024 * 1024 }; -void http_connection::get(std::string const& url, time_duration timeout - , int handle_redirects) + +void http_connection::get(std::string const& url, time_duration timeout, int prio + , proxy_settings const* ps, int handle_redirects, std::string const& user_agent + , address const& bind_addr) { std::string protocol; std::string auth; @@ -53,37 +58,125 @@ void http_connection::get(std::string const& url, time_duration timeout std::string path; int port; boost::tie(protocol, auth, hostname, port, path) = parse_url_components(url); + + TORRENT_ASSERT(prio >= 0 && prio < 2); + + bool ssl = false; + if (protocol == "https") ssl = true; +#ifndef TORRENT_USE_OPENSSL + if (ssl) + { + callback(asio::error::socket_type_not_supported); + return; + } +#endif + std::stringstream headers; - headers << "GET " << path << " HTTP/1.0\r\n" - "Host:" << hostname << - "\r\nConnection: close\r\n"; + if (ps && (ps->type == proxy_settings::http + || ps->type == proxy_settings::http_pw) + && !ssl) + { + // if we're using an http proxy and not an ssl + // connection, just do a regular http proxy request + headers << "GET " << url << " HTTP/1.0\r\n"; + if (ps->type == proxy_settings::http_pw) + headers << "Proxy-Authorization: Basic " << base64encode( + ps->username + ":" + ps->password) << "\r\n"; + hostname = ps->hostname; + port = ps->port; + ps = 0; + } + else + { + headers << "GET " << path << " HTTP/1.0\r\n" + "Host:" << hostname << "\r\n"; + } + if (!auth.empty()) headers << "Authorization: Basic " << base64encode(auth) << "\r\n"; - headers << "\r\n"; + + if (!user_agent.empty()) + headers << "User-Agent: " << user_agent << "\r\n"; + + headers << + "Connection: close\r\n" + "Accept-Encoding: gzip\r\n" + "\r\n"; + sendbuffer = headers.str(); - start(hostname, boost::lexical_cast(port), timeout, handle_redirects); + start(hostname, boost::lexical_cast(port), timeout, prio + , ps, ssl, handle_redirects, bind_addr); } void http_connection::start(std::string const& hostname, std::string const& port - , time_duration timeout, int handle_redirects) + , time_duration timeout, int prio, proxy_settings const* ps, bool ssl, int handle_redirects + , address const& bind_addr) { - m_redirects = handle_redirects; + TORRENT_ASSERT(prio >= 0 && prio < 2); + + m_redirects = handle_redirects; + if (ps) m_proxy = *ps; + m_timeout = timeout; - m_timer.expires_from_now(m_timeout); + asio::error_code ec; + m_timer.expires_from_now(m_timeout, ec); m_timer.async_wait(bind(&http_connection::on_timeout , boost::weak_ptr(shared_from_this()), _1)); m_called = false; - m_parser.reset(); - m_recvbuffer.clear(); - m_read_pos = 0; - if (m_sock.is_open() && m_hostname == hostname && m_port == port) + m_parser.reset(); + m_recvbuffer.clear(); + m_read_pos = 0; + m_priority = prio; + + if (ec) + { + callback(ec); + return; + } + + if (m_sock.is_open() && m_hostname == hostname && m_port == port + && m_ssl == ssl && m_bind_addr == bind_addr) { asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } else { - m_sock.close(); + m_ssl = ssl; + m_bind_addr = bind_addr; + asio::error_code ec; + m_sock.close(ec); + +#ifdef TORRENT_USE_OPENSSL + if (m_ssl) + { + m_sock.instantiate >(m_resolver.get_io_service()); + ssl_stream& s = m_sock.get >(); + bool ret = instantiate_connection(m_resolver.get_io_service(), m_proxy, s.next_layer()); + TORRENT_ASSERT(ret); + } + else + { + m_sock.instantiate(m_resolver.get_io_service()); + bool ret = instantiate_connection(m_resolver.get_io_service() + , m_proxy, m_sock.get()); + TORRENT_ASSERT(ret); + } +#else + bool ret = instantiate_connection(m_resolver.get_io_service(), m_proxy, m_sock); + TORRENT_ASSERT(ret); +#endif + if (m_bind_addr != address_v4::any()) + { + asio::error_code ec; + m_sock.bind(tcp::endpoint(m_bind_addr, 0), ec); + if (ec) + { + callback(ec); + return; + } + } + tcp::resolver::query query(hostname, port); m_resolver.async_resolve(query, bind(&http_connection::on_resolve , shared_from_this(), _1, _2)); @@ -119,16 +212,17 @@ void http_connection::on_timeout(boost::weak_ptr p } if (!c->m_sock.is_open()) return; - - c->m_timer.expires_at(c->m_last_receive + c->m_timeout); + asio::error_code ec; + c->m_timer.expires_at(c->m_last_receive + c->m_timeout, ec); c->m_timer.async_wait(bind(&http_connection::on_timeout, p, _1)); } void http_connection::close() { - m_timer.cancel(); - m_limiter_timer.cancel(); - m_sock.close(); + asio::error_code ec; + m_timer.cancel(ec); + m_limiter_timer.cancel(ec); + m_sock.close(ec); m_hostname.clear(); m_port.clear(); @@ -139,7 +233,7 @@ void http_connection::close() } void http_connection::on_resolve(asio::error_code const& e - , tcp::resolver::iterator i) + , tcp::resolver::iterator i) { if (e) { @@ -148,9 +242,24 @@ void http_connection::on_resolve(asio::error_code const& e return; } TORRENT_ASSERT(i != tcp::resolver::iterator()); - m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i) + + // look for an address that has the same kind as the one + // we're binding to. To make sure a tracker get our + // correct listening address. + tcp::resolver::iterator target = i; + tcp::resolver::iterator end; + tcp::endpoint target_address = *i; + for (; target != end && target->endpoint().address().is_v4() + != m_bind_addr.is_v4(); ++target); + + if (target != end) + { + target_address = *target; + } + + m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, target_address) , bind(&http_connection::on_connect_timeout, shared_from_this()) - , m_timeout); + , m_timeout, m_priority); } void http_connection::connect(int ticket, tcp::endpoint target_address) @@ -176,7 +285,7 @@ void http_connection::on_connect(asio::error_code const& e m_sock.close(); m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i) , bind(&http_connection::on_connect_timeout, shared_from_this()) - , m_timeout); + , m_timeout, m_priority); } */ else { @@ -189,7 +298,25 @@ void http_connection::callback(asio::error_code const& e, char const* data, int { if (!m_bottled || !m_called) { + std::vector buf; + if (m_bottled && m_parser.finished()) + { + std::string const& encoding = m_parser.header("content-encoding"); + if (encoding == "gzip" || encoding == "x-gzip") + { + std::string error; + if (inflate_gzip(data, size, buf, max_bottled_buffer, error)) + { + callback(asio::error::fault, data, size); + close(); + return; + } + data = &buf[0]; + size = int(buf.size()); + } + } m_called = true; + m_timer.cancel(); if (m_handler) m_handler(e, m_parser, data, size); } } @@ -262,15 +389,13 @@ void http_connection::on_read(asio::error_code const& e { libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0] , &m_recvbuffer[0] + m_read_pos); - try + bool error = false; + m_parser.incoming(rcv_buf, error); + if (error) { - m_parser.incoming(rcv_buf); - } - catch (std::exception&) - { - m_timer.cancel(); - m_handler(asio::error::fault, m_parser, 0, 0); - m_handler.clear(); + // HTTP parse error + asio::error_code ec = asio::error::fault; + callback(ec, 0, 0); return; } @@ -292,7 +417,7 @@ void http_connection::on_read(asio::error_code const& e asio::error_code ec; m_sock.close(ec); - get(url, m_timeout, m_redirects - 1); + get(url, m_timeout, m_priority, &m_proxy, m_redirects - 1); return; } @@ -309,7 +434,8 @@ void http_connection::on_read(asio::error_code const& e } else if (m_bottled && m_parser.finished()) { - m_timer.cancel(); + asio::error_code ec; + m_timer.cancel(ec); callback(e, m_parser.get_body().begin, m_parser.get_body().left()); } } @@ -373,8 +499,9 @@ void http_connection::on_assign_bandwidth(asio::error_code const& e) , bind(&http_connection::on_read , shared_from_this(), _1, _2)); + asio::error_code ec; m_limiter_timer_active = true; - m_limiter_timer.expires_from_now(milliseconds(250)); + m_limiter_timer.expires_from_now(milliseconds(250), ec); m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth , shared_from_this(), _1)); } @@ -385,8 +512,9 @@ void http_connection::rate_limit(int limit) if (!m_limiter_timer_active) { + asio::error_code ec; m_limiter_timer_active = true; - m_limiter_timer.expires_from_now(milliseconds(250)); + m_limiter_timer.expires_from_now(milliseconds(250), ec); m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth , shared_from_this(), _1)); } diff --git a/libtorrent/src/http_parser.cpp b/libtorrent/src/http_parser.cpp new file mode 100755 index 000000000..4c2839178 --- /dev/null +++ b/libtorrent/src/http_parser.cpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/assert.hpp" + +using namespace libtorrent; + +namespace +{ + char to_lower(char c) { return std::tolower(c); } +} + +namespace libtorrent +{ + http_parser::http_parser() + : m_recv_pos(0) + , m_status_code(-1) + , m_content_length(-1) + , 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, bool& error) + { + TORRENT_ASSERT(recv_buffer.left() >= m_recv_buffer.left()); + boost::tuple ret(0, 0); + + // early exit if there's nothing new in the receive buffer + if (recv_buffer.left() == m_recv_buffer.left()) return ret; + m_recv_buffer = recv_buffer; + + if (m_state == error_state) + { + error = true; + return ret; + } + + char const* pos = recv_buffer.begin + m_recv_pos; + if (m_state == read_status) + { + TORRENT_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) + { + m_state = error_state; + error = true; + return ret; + } + + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + + std::istringstream line(std::string(pos, line_end)); + ++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/") + { + line >> m_status_code; + std::getline(line, m_server_message); + } + else + { + m_method = m_protocol; + std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); + m_protocol.clear(); + line >> m_path >> m_protocol; + m_status_code = 0; + } + m_state = read_header; + } + + if (m_state == read_header) + { + TORRENT_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 the LF character is preceeded by a CR + // charachter, don't copy it into the line string. + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + line.assign(pos, line_end); + ++newline; + 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) + { + // this means we got a blank line, + // the header is finished and the body + // starts. + m_state = read_body; + m_body_start_pos = m_recv_pos; + break; + } + + std::string name = line.substr(0, separator); + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + ++separator; + // skip whitespace + while (separator < line.size() + && (line[separator] == ' ' || line[separator] == '\t')) + ++separator; + std::string value = line.substr(separator, std::string::npos); + m_header.insert(std::make_pair(name, value)); + + if (name == "content-length") + { +#ifdef WIN32 + m_content_length = _atoi64(value.c_str()); +#else + m_content_length = atoll(value.c_str()); +#endif + } + else if (name == "content-range") + { + std::stringstream range_str(value); + char dummy; + std::string bytes; + size_type range_start, range_end; + // apparently some web servers do not send the "bytes" + // in their content-range + if (value.find(' ') != std::string::npos) + range_str >> bytes; + range_str >> range_start >> dummy >> range_end; + if (!range_str || range_end < range_start) + { + m_state = error_state; + error = true; + return ret; + } + // the http range is inclusive + m_content_length = range_end - range_start + 1; + } + + TORRENT_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; + + TORRENT_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() const + { + TORRENT_ASSERT(m_state == read_body); + if (m_content_length >= 0) + return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos + , m_recv_buffer.begin + (std::min)(size_type(m_recv_pos) + , m_body_start_pos + m_content_length)); + else + return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos + , m_recv_buffer.begin + m_recv_pos); + } + + void http_parser::reset() + { + 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_recv_buffer.begin = 0; + m_recv_buffer.end = 0; + m_header.clear(); + } + +} + diff --git a/libtorrent/src/http_stream.cpp b/libtorrent/src/http_stream.cpp index 0973af798..78603ca8b 100644 --- a/libtorrent/src/http_stream.cpp +++ b/libtorrent/src/http_stream.cpp @@ -33,7 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/pch.hpp" #include "libtorrent/http_stream.hpp" -#include "libtorrent/tracker_manager.hpp" // for base64encode +#include "libtorrent/escape_string.hpp" // for base64encode namespace libtorrent { @@ -44,7 +44,8 @@ namespace libtorrent if (e || i == tcp::resolver::iterator()) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -57,7 +58,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -89,7 +91,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -104,7 +107,8 @@ namespace libtorrent if (e) { (*h)(e); - close(); + asio::error_code ec; + close(ec); return; } @@ -134,7 +138,8 @@ namespace libtorrent if (status == 0) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } @@ -143,7 +148,8 @@ namespace libtorrent if (code != 200) { (*h)(asio::error::operation_not_supported); - close(); + asio::error_code ec; + close(ec); return; } diff --git a/libtorrent/src/http_tracker_connection.cpp b/libtorrent/src/http_tracker_connection.cpp index d99c6f94a..3adad2ca1 100755 --- a/libtorrent/src/http_tracker_connection.cpp +++ b/libtorrent/src/http_tracker_connection.cpp @@ -40,7 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/config.hpp" -#include "zlib.h" +#include "libtorrent/gzip.hpp" #ifdef _MSC_VER #pragma warning(push, 1) @@ -54,809 +54,213 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/tracker_manager.hpp" #include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/http_connection.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/bencode.hpp" #include "libtorrent/torrent.hpp" #include "libtorrent/io.hpp" -#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/socket.hpp" using namespace libtorrent; using boost::bind; -namespace -{ - enum - { - minimum_tracker_response_length = 3, - http_buffer_size = 2048 - }; - - - enum - { - FTEXT = 0x01, - FHCRC = 0x02, - FEXTRA = 0x04, - FNAME = 0x08, - FCOMMENT = 0x10, - FRESERVED = 0xe0, - - GZIP_MAGIC0 = 0x1f, - GZIP_MAGIC1 = 0x8b - }; - -} - -namespace -{ - bool url_has_argument(std::string const& url, std::string argument) - { - size_t i = url.find('?'); - if (i == std::string::npos) return false; - - argument += '='; - - if (url.compare(i + 1, argument.size(), argument) == 0) return true; - argument.insert(0, "&"); - return url.find(argument, i) - != std::string::npos; - } - - char to_lower(char c) { return std::tolower(c); } -} - namespace libtorrent { - http_parser::http_parser() - : m_recv_pos(0) - , m_status_code(-1) - , m_content_length(-1) - , 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) - { - TORRENT_ASSERT(recv_buffer.left() >= m_recv_buffer.left()); - boost::tuple ret(0, 0); - - // early exit if there's nothing new in the receive buffer - if (recv_buffer.left() == m_recv_buffer.left()) return ret; - m_recv_buffer = recv_buffer; - - char const* pos = recv_buffer.begin + m_recv_pos; - if (m_state == read_status) - { - TORRENT_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"); - - char const* line_end = newline; - if (pos != line_end && *(line_end - 1) == '\r') --line_end; - - std::istringstream line(std::string(pos, line_end)); - ++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/") - { - line >> m_status_code; - std::getline(line, m_server_message); - } - else - { - m_method = m_protocol; - std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); - m_protocol.clear(); - line >> m_path >> m_protocol; - m_status_code = 0; - } - m_state = read_header; - } - - if (m_state == read_header) - { - TORRENT_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 the LF character is preceeded by a CR - // charachter, don't copy it into the line string. - char const* line_end = newline; - if (pos != line_end && *(line_end - 1) == '\r') --line_end; - line.assign(pos, line_end); - ++newline; - 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) - { - // this means we got a blank line, - // the header is finished and the body - // starts. - m_state = read_body; - m_body_start_pos = m_recv_pos; - break; - } - - std::string name = line.substr(0, separator); - std::transform(name.begin(), name.end(), name.begin(), &to_lower); - ++separator; - // skip whitespace - while (separator < line.size() - && (line[separator] == ' ' || line[separator] == '\t')) - ++separator; - std::string value = line.substr(separator, 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-range") - { - std::stringstream range_str(value); - char dummy; - std::string bytes; - size_type range_start, range_end; - // apparently some web servers do not send the "bytes" - // in their content-range - if (value.find(' ') != std::string::npos) - range_str >> bytes; - range_str >> range_start >> dummy >> range_end; - if (!range_str || range_end < range_start - || range_end - range_start + 1 >= (std::numeric_limits::max)()) - { - throw std::runtime_error("invalid content-range in HTTP response: " + range_str.str()); - } - // the http range is inclusive - m_content_length = int(range_end - range_start + 1); - } - - TORRENT_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; - - TORRENT_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() const - { - TORRENT_ASSERT(m_state == read_body); - if (m_content_length >= 0) - return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos - , m_recv_buffer.begin + (std::min)(m_recv_pos - , m_body_start_pos + m_content_length)); - else - return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos - , m_recv_buffer.begin + m_recv_pos); - } - - void http_parser::reset() - { - 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_recv_buffer.begin = 0; - m_recv_buffer.end = 0; - m_header.clear(); - } http_tracker_connection::http_tracker_connection( - asio::strand& str + io_service& ios , connection_queue& cc , tracker_manager& man , tracker_request const& req - , std::string const& hostname - , unsigned short port - , std::string request , address bind_infc , boost::weak_ptr c , session_settings const& stn , proxy_settings const& ps , std::string const& auth) - : tracker_connection(man, req, str, bind_infc, c) + : tracker_connection(man, req, ios, bind_infc, c) , m_man(man) - , m_strand(str) - , m_name_lookup(m_strand.io_service()) - , m_port(port) - , m_recv_pos(0) - , m_buffer(http_buffer_size) - , m_settings(stn) - , m_proxy(ps) - , m_password(auth) - , m_timed_out(false) - , m_connection_ticket(-1) - , m_cc(cc) { - m_send_buffer.assign("GET "); + // TODO: authentication + std::string url = req.url; - // should we use the proxy? - if (m_proxy.type == proxy_settings::http - || m_proxy.type == proxy_settings::http_pw) - { - m_send_buffer += "http://"; - m_send_buffer += hostname; - if (port != 80) - { - m_send_buffer += ":"; - m_send_buffer += boost::lexical_cast(port); - } - } - - if (tracker_req().kind == tracker_request::scrape_request) + if (req.kind == tracker_request::scrape_request) { // find and replace "announce" with "scrape" // in request - std::size_t pos = request.find("announce"); + std::size_t pos = url.find("announce"); if (pos == std::string::npos) - throw std::runtime_error("scrape is not available on url: '" - + tracker_req().url +"'"); - request.replace(pos, 8, "scrape"); + { + fail(-1, ("scrape is not available on url: '" + + req.url +"'").c_str()); + return; + } + url.replace(pos, 8, "scrape"); } - - m_send_buffer += request; - + // if request-string already contains // some parameters, append an ampersand instead // of a question mark - size_t arguments_start = request.find('?'); + size_t arguments_start = url.find('?'); if (arguments_start != std::string::npos) - m_send_buffer += "&"; + url += "&"; else - m_send_buffer += "?"; + url += "?"; - if (!url_has_argument(request, "info_hash")) + url += "info_hash="; + url += escape_string( + reinterpret_cast(req.info_hash.begin()), 20); + + if (req.kind == tracker_request::announce_request) { - m_send_buffer += "info_hash="; - m_send_buffer += escape_string( - reinterpret_cast(req.info_hash.begin()), 20); - m_send_buffer += '&'; - } + url += "&peer_id="; + url += escape_string( + reinterpret_cast(req.pid.begin()), 20); - if (tracker_req().kind == tracker_request::announce_request) - { - if (!url_has_argument(request, "peer_id")) - { - m_send_buffer += "peer_id="; - m_send_buffer += escape_string( - reinterpret_cast(req.pid.begin()), 20); - m_send_buffer += '&'; - } + url += "&port="; + url += boost::lexical_cast(req.listen_port); - if (!url_has_argument(request, "port")) - { - m_send_buffer += "port="; - m_send_buffer += boost::lexical_cast(req.listen_port); - m_send_buffer += '&'; - } + url += "&uploaded="; + url += boost::lexical_cast(req.uploaded); - if (!url_has_argument(request, "uploaded")) - { - m_send_buffer += "uploaded="; - m_send_buffer += boost::lexical_cast(req.uploaded); - m_send_buffer += '&'; - } + url += "&downloaded="; + url += boost::lexical_cast(req.downloaded); - if (!url_has_argument(request, "downloaded")) - { - m_send_buffer += "downloaded="; - m_send_buffer += boost::lexical_cast(req.downloaded); - m_send_buffer += '&'; - } - - if (!url_has_argument(request, "left")) - { - m_send_buffer += "left="; - m_send_buffer += boost::lexical_cast(req.left); - m_send_buffer += '&'; - } + url += "&left="; + url += boost::lexical_cast(req.left); if (req.event != tracker_request::none) { - if (!url_has_argument(request, "event")) - { - const char* event_string[] = {"completed", "started", "stopped"}; - m_send_buffer += "event="; - m_send_buffer += event_string[req.event - 1]; - m_send_buffer += '&'; - } - } - if (!url_has_argument(request, "key")) - { - m_send_buffer += "key="; - std::stringstream key_string; - key_string << std::hex << req.key; - m_send_buffer += key_string.str(); - m_send_buffer += '&'; + const char* event_string[] = {"completed", "started", "stopped"}; + url += "&event="; + url += event_string[req.event - 1]; } - if (!url_has_argument(request, "compact")) + url += "&key="; + std::stringstream key_string; + key_string << std::hex << req.key; + url += key_string.str(); + + url += "&compact=1"; + + url += "&numwant="; + url += boost::lexical_cast( + (std::min)(req.num_want, 999)); + + if (stn.announce_ip != address()) { - m_send_buffer += "compact=1&"; - } - if (!url_has_argument(request, "numwant")) - { - m_send_buffer += "numwant="; - m_send_buffer += boost::lexical_cast( - (std::min)(req.num_want, 999)); - m_send_buffer += '&'; - } - if (m_settings.announce_ip != address() && !url_has_argument(request, "ip")) - { - m_send_buffer += "ip="; - m_send_buffer += m_settings.announce_ip.to_string(); - m_send_buffer += '&'; + url += "&ip="; + url += stn.announce_ip.to_string(); } #ifndef TORRENT_DISABLE_ENCRYPTION - m_send_buffer += "supportcrypto=1&"; + url += "&supportcrypto=1"; #endif - - if (!url_has_argument(request, "ipv6") && !req.ipv6.empty()) - { - m_send_buffer += "ipv6="; - m_send_buffer += req.ipv6; - m_send_buffer += '&'; - } + url += "&ipv6="; + url += req.ipv6; // extension that tells the tracker that // we don't need any peer_id's in the response - if (!url_has_argument(request, "no_peer_id")) - { - m_send_buffer += "no_peer_id=1"; - } - else - { - // remove the trailing '&' - m_send_buffer.resize(m_send_buffer.size() - 1); - } + url += "&no_peer_id=1"; } - m_send_buffer += " HTTP/1.0\r\nAccept-Encoding: gzip\r\n" - "User-Agent: "; - m_send_buffer += m_settings.user_agent; - m_send_buffer += "\r\n" - "Host: "; - m_send_buffer += hostname; - if (port != 80) - { - m_send_buffer += ':'; - m_send_buffer += boost::lexical_cast(port); - } - if (m_proxy.type == proxy_settings::http_pw) - { - m_send_buffer += "\r\nProxy-Authorization: Basic "; - m_send_buffer += base64encode(m_proxy.username + ":" + m_proxy.password); - } - if (!auth.empty()) - { - m_send_buffer += "\r\nAuthorization: Basic "; - m_send_buffer += base64encode(auth); - } - m_send_buffer += "\r\n\r\n"; + m_tracker_connection.reset(new http_connection(ios, cc + , boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4))); + + int timeout = req.event==tracker_request::stopped + ?stn.stop_tracker_timeout + :stn.tracker_completion_timeout; + + m_tracker_connection->get(url, seconds(timeout) + , 1, &ps, 5, stn.user_agent, bind_infc); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) boost::shared_ptr cb = requester(); if (cb) { - cb->debug_log("==> TRACKER_REQUEST [ str: " + m_send_buffer + " ]"); - std::stringstream info_hash_str; - info_hash_str << req.info_hash; - cb->debug_log("info_hash: " - + boost::lexical_cast(req.info_hash)); - cb->debug_log("name lookup: " + hostname); + cb->debug_log("==> TRACKER_REQUEST [ url: " + url + " ]"); } #endif - - tcp::resolver::query q(hostname - , boost::lexical_cast(m_port)); - m_name_lookup.async_resolve(q, m_strand.wrap( - boost::bind(&http_tracker_connection::name_lookup, self(), _1, _2))); - set_timeout(req.event == tracker_request::stopped - ? m_settings.stop_tracker_timeout - : m_settings.tracker_completion_timeout - , m_settings.tracker_receive_timeout); - } - - void http_tracker_connection::on_timeout() - { - m_timed_out = true; - m_socket.close(); - m_name_lookup.cancel(); - if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); - m_connection_ticket = -1; - fail_timeout(); } void http_tracker_connection::close() { - asio::error_code ec; - m_socket.close(ec); - m_name_lookup.cancel(); - if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); - m_connection_ticket = -1; - m_timed_out = true; + if (m_tracker_connection) + { + m_tracker_connection->close(); + m_tracker_connection.reset(); + } tracker_connection::close(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - std::stringstream msg; - msg << "http_tracker_connection::close() " << m_man.num_requests(); - if (cb) cb->debug_log(msg.str()); -#endif } - void http_tracker_connection::name_lookup(asio::error_code const& error - , tcp::resolver::iterator i) try + void http_tracker_connection::on_response(asio::error_code const& ec + , http_parser const& parser, char const* data, int size) { - boost::shared_ptr cb = requester(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("tracker name lookup handler called"); -#endif - if (error == asio::error::operation_aborted) return; - if (m_timed_out) return; + // keep this alive + boost::intrusive_ptr me(this); - if (error || i == tcp::resolver::iterator()) + if (ec && ec != asio::error::eof) { - fail(-1, error.message().c_str()); + fail(-1, ec.message().c_str()); return; } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("tracker name lookup successful"); -#endif - restart_read_timeout(); - - // look for an address that has the same kind as the one - // we're listening on. To make sure the tracker get our - // correct listening address. - tcp::resolver::iterator target = i; - tcp::resolver::iterator end; - tcp::endpoint target_address = *i; - for (; target != end && target->endpoint().address().is_v4() - != bind_interface().is_v4(); ++target); - if (target == end) - { - TORRENT_ASSERT(target_address.address().is_v4() != bind_interface().is_v4()); - if (cb) - { - std::string tracker_address_type = target_address.address().is_v4() ? "IPv4" : "IPv6"; - std::string bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; - cb->tracker_warning("the tracker only resolves to an " - + tracker_address_type + " address, and you're listening on an " - + bind_address_type + " socket. This may prevent you from receiving incoming connections."); - } - } - else - { - target_address = *target; - } - - if (cb) cb->m_tracker_address = target_address; - bool ret = instantiate_connection(m_strand.io_service(), m_proxy, m_socket); - - TORRENT_ASSERT(ret); - - if (m_proxy.type == proxy_settings::http - || m_proxy.type == proxy_settings::http_pw) - { - // the tracker connection will talk immediately to - // the proxy, without requiring CONNECT support - m_socket.get().set_no_connect(true); - } - - m_socket.open(target_address.protocol()); - m_socket.bind(tcp::endpoint(bind_interface(), 0)); - m_cc.enqueue(bind(&http_tracker_connection::connect, self(), _1, target_address) - , bind(&http_tracker_connection::on_timeout, self()) - , seconds(m_settings.tracker_receive_timeout)); - } - catch (std::exception& e) - { - fail(-1, e.what()); - }; - - void http_tracker_connection::connect(int ticket, tcp::endpoint target_address) - { - m_connection_ticket = ticket; - m_socket.async_connect(target_address, bind(&http_tracker_connection::connected, self(), _1)); - } - - void http_tracker_connection::connected(asio::error_code const& error) try - { - if (m_connection_ticket > -1) m_cc.done(m_connection_ticket); - m_connection_ticket = -1; - if (error == asio::error::operation_aborted) return; - if (m_timed_out) return; - if (error) - { - fail(-1, error.message().c_str()); - return; - } - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker connection successful"); -#endif - - 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) - { - fail(-1, e.what()); - } - - void http_tracker_connection::sent(asio::error_code const& error) try - { - if (error == asio::error::operation_aborted) return; - if (m_timed_out) return; - if (error) - { - fail(-1, error.message().c_str()); - return; - } - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker send data completed"); -#endif - restart_read_timeout(); - TORRENT_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) - { - fail(-1, e.what()); - }; // msvc 7.1 seems to require this semi-colon - - - void http_tracker_connection::receive(asio::error_code 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.message().c_str()); - return; - } - - restart_read_timeout(); - TORRENT_ASSERT(bytes_transferred > 0); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) cb->debug_log("tracker connection reading " - + boost::lexical_cast(bytes_transferred)); -#endif - - m_recv_pos += bytes_transferred; - m_parser.incoming(buffer::const_interval(&m_buffer[0] - , &m_buffer[0] + m_recv_pos)); - - // 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) - { - fail(200, "too large tracker response"); - return; - } - TORRENT_ASSERT(http_buffer_size > 0); - 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); - } - - if (m_parser.header_finished()) - { - int cl = atoi(m_parser.header("content-length").c_str()); - if (cl > m_settings.tracker_maximum_response_length) - { - fail(-1, "content-length is greater than maximum response length"); - return; - } - - if (cl > 0 && cl < minimum_tracker_response_length && m_parser.status_code() == 200) - { - fail(-1, "content-length is smaller than minimum response length"); - return; - } - } - - if (m_parser.finished()) - { - on_response(); - close(); - return; - } - - TORRENT_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) - { - fail(-1, e.what()); - }; - - void http_tracker_connection::on_response() - { - if (!m_parser.header_finished()) + if (!parser.header_finished()) { fail(-1, "premature end of file"); return; } - - std::string location = m_parser.header("location"); - boost::shared_ptr cb = requester(); + if (parser.status_code() != 200) + { + fail(parser.status_code(), parser.message().c_str()); + return; + } + + if (ec && ec != asio::error::eof) + { + fail(parser.status_code(), ec.message().c_str()); + return; + } - if (m_parser.status_code() >= 300 && m_parser.status_code() < 400) - { - if (location.empty()) - { - std::string error_str = "got redirection response ("; - error_str += boost::lexical_cast(m_parser.status_code()); - error_str += ") without 'Location' header"; - fail(-1, error_str.c_str()); - return; - } - - // if the protocol isn't specified, assume http - if (location.compare(0, 7, "http://") != 0 - && location.compare(0, 6, "udp://") != 0) - { - location.insert(0, "http://"); - } - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("Redirecting to \"" + location + "\""); -#endif - if (cb) cb->tracker_warning("Redirecting to \"" + location + "\""); - tracker_request req = tracker_req(); - - req.url = location; - - m_man.queue_request(m_strand, m_cc, req - , m_password, bind_interface(), m_requester); - close(); - return; - } - - if (m_parser.status_code() != 200) - { - fail(m_parser.status_code(), m_parser.message().c_str()); - return; - } - - buffer::const_interval buf(&m_buffer[0] + m_parser.body_start(), &m_buffer[0] + m_recv_pos); - - std::string content_encoding = m_parser.header("content-encoding"); - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("content-encoding: \"" + content_encoding + "\""); -#endif - - if (content_encoding == "gzip" || content_encoding == "x-gzip") - { - if (!cb) - { - close(); - return; - } - m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_parser.body_start()); - if (inflate_gzip(m_buffer, tracker_req(), cb.get(), - m_settings.tracker_maximum_response_length)) - { - close(); - return; - } - buf.begin = &m_buffer[0]; - buf.end = &m_buffer[0] + m_buffer.size(); - } - else if (!content_encoding.empty()) - { - std::string error_str = "unknown content encoding in response: \""; - error_str += content_encoding; - error_str += "\""; - fail(-1, error_str.c_str()); - return; - } - // handle tracker response - try + entry e = bdecode(data, data + size); + + if (e.type() != entry::undefined_t) { - entry e = bdecode(buf.begin, buf.end); - parse(e); + parse(parser.status_code(), e); } - catch (std::exception& e) + else { - std::string error_str(e.what()); - error_str += ": \""; - for (char const* i = buf.begin, *end(buf.end); i != end; ++i) + std::string error_str("invalid bencoding of tracker response: \""); + for (char const* i = data, *end(data + size); i != end; ++i) { if (std::isprint(*i)) error_str += *i; else error_str += "0x" + boost::lexical_cast((unsigned int)*i) + " "; } error_str += "\""; - fail(m_parser.status_code(), error_str.c_str()); + fail(parser.status_code(), error_str.c_str()); } - #ifndef NDEBUG - catch (...) - { - TORRENT_ASSERT(false); - } - #endif close(); } - peer_entry http_tracker_connection::extract_peer_info(const entry& info) + bool http_tracker_connection::extract_peer_info(const entry& info, peer_entry& ret) { - peer_entry ret; - // extract peer id (if any) + if (info.type() != entry::dictionary_t) + { + fail(-1, "invalid response from tracker (invalid peer entry)"); + return false; + } entry const* i = info.find_key("peer id"); if (i != 0) { - if (i->string().length() != 20) - throw std::runtime_error("invalid response from tracker"); + if (i->type() != entry::string_t || i->string().length() != 20) + { + fail(-1, "invalid response from tracker (invalid peer id)"); + return false; + } std::copy(i->string().begin(), i->string().end(), ret.pid.begin()); } else @@ -867,134 +271,169 @@ namespace libtorrent // extract ip i = info.find_key("ip"); - if (i == 0) throw std::runtime_error("invalid response from tracker"); + if (i == 0 || i->type() != entry::string_t) + { + fail(-1, "invalid response from tracker"); + return false; + } ret.ip = i->string(); // extract port i = info.find_key("port"); - if (i == 0) throw std::runtime_error("invalid response from tracker"); + if (i == 0 || i->type() != entry::int_t) + { + fail(-1, "invalid response from tracker"); + return false; + } ret.port = (unsigned short)i->integer(); - return ret; + return true; } - void http_tracker_connection::parse(entry const& e) + void http_tracker_connection::parse(int status_code, entry const& e) { boost::shared_ptr cb = requester(); if (!cb) return; - try + // parse the response + entry const* failure = e.find_key("failure reason"); + if (failure && failure->type() == entry::string_t) { - // parse the response - try - { - entry const& failure = e["failure reason"]; + fail(status_code, failure->string().c_str()); + return; + } - fail(m_parser.status_code(), failure.string().c_str()); + entry const* warning = e.find_key("warning message"); + if (warning && warning->type() == entry::string_t) + { + cb->tracker_warning(warning->string()); + } + + std::vector peer_list; + + if (tracker_req().kind == tracker_request::scrape_request) + { + std::string ih; + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() + , std::back_inserter(ih)); + + entry const* files = e.find_key("files"); + if (files == 0 || files->type() != entry::dictionary_t) + { + fail(-1, "invalid or missing 'files' entry in scrape response"); return; } - catch (type_error const&) {} - try + entry const* scrape_data = e.find_key(ih.c_str()); + if (scrape_data == 0 || scrape_data->type() != entry::dictionary_t) { - entry const& warning = e["warning message"]; - cb->tracker_warning(warning.string()); + fail(-1, "missing or invalid info-hash entry in scrape response"); + return; } - catch(type_error const&) {} - - if (tracker_req().kind == tracker_request::scrape_request) + entry const* complete = scrape_data->find_key("complete"); + entry const* incomplete = scrape_data->find_key("incomplete"); + entry const* downloaded = scrape_data->find_key("downloaded"); + if (complete == 0 || incomplete == 0 || downloaded == 0 + || complete->type() != entry::int_t + || incomplete->type() != entry::int_t + || downloaded->type() != entry::int_t) { - std::string ih; - std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() - , std::back_inserter(ih)); - entry scrape_data = e["files"][ih]; + fail(-1, "missing 'complete' or 'incomplete' entries in scrape response"); + return; + } + cb->tracker_scrape_response(tracker_req(), int(complete->integer()) + , int(incomplete->integer()), int(downloaded->integer())); + return; + } - int complete = -1; - int incomplete = -1; - int downloaded = -1; + entry const* interval = e.find_key("interval"); + if (interval == 0 || interval->type() != entry::int_t) + { + fail(-1, "missing or invalid 'interval' entry in tracker response"); + return; + } - entry const* complete_ent = scrape_data.find_key("complete"); - if (complete_ent && complete_ent->type() == entry::int_t) - complete = int(complete_ent->integer()); + entry const* peers_ent = e.find_key("peers"); + if (peers_ent == 0) + { + fail(-1, "missing 'peers' entry in tracker response"); + return; + } + + if (peers_ent->type() == entry::string_t) + { + std::string const& peers = peers_ent->string(); + for (std::string::const_iterator i = peers.begin(); + i != peers.end();) + { + if (std::distance(i, peers.end()) < 6) break; + + peer_entry p; + p.pid.clear(); + p.ip = detail::read_v4_address(i).to_string(); + p.port = detail::read_uint16(i); + peer_list.push_back(p); + } + } + else if (peers_ent->type() == entry::list_t) + { + entry::list_type const& l = peers_ent->list(); + for(entry::list_type::const_iterator i = l.begin(); i != l.end(); ++i) + { + peer_entry p; + if (!extract_peer_info(*i, p)) return; + peer_list.push_back(p); + } + } + else + { + fail(-1, "invalid 'peers' entry in tracker response"); + return; + } + + entry const* ipv6_peers = e.find_key("peers6"); + if (ipv6_peers && ipv6_peers->type() == entry::string_t) + { + std::string const& peers = ipv6_peers->string(); + for (std::string::const_iterator i = peers.begin(); + i != peers.end();) + { + if (std::distance(i, peers.end()) < 18) break; + + peer_entry p; + p.pid.clear(); + p.ip = detail::read_v6_address(i).to_string(); + p.port = detail::read_uint16(i); + peer_list.push_back(p); + } + } + + // look for optional scrape info + int complete = -1; + int incomplete = -1; + address external_ip; + + entry const* ip_ent = e.find_key("external ip"); + if (ip_ent && ip_ent->type() == entry::string_t) + { + std::string const& ip = ip_ent->string(); + char const* p = &ip[0]; + if (ip.size() == address_v4::bytes_type::static_size) + external_ip = detail::read_v4_address(p); + else if (ip.size() == address_v6::bytes_type::static_size) + external_ip = detail::read_v6_address(p); + } - entry const* incomplete_ent = scrape_data.find_key("incomplete"); - if (incomplete_ent && incomplete_ent->type() == entry::int_t) - incomplete = int(incomplete_ent->integer()); + entry const* complete_ent = e.find_key("complete"); + if (complete_ent && complete_ent->type() == entry::int_t) + complete = int(complete_ent->integer()); - entry const* downloaded_ent = scrape_data.find_key("downloaded"); - if (downloaded_ent && downloaded_ent->type() == entry::int_t) - downloaded = int(downloaded_ent->integer()); + entry const* incomplete_ent = e.find_key("incomplete"); + if (incomplete_ent && incomplete_ent->type() == entry::int_t) + incomplete = int(incomplete_ent->integer()); - cb->tracker_scrape_response(tracker_req(), complete - , incomplete, downloaded); - return; - } - - std::vector peer_list; - int interval = (int)e["interval"].integer(); - - if (e["peers"].type() == entry::string_t) - { - std::string const& peers = e["peers"].string(); - for (std::string::const_iterator i = peers.begin(); - i != peers.end();) - { - if (std::distance(i, peers.end()) < 6) break; - - peer_entry p; - p.pid.clear(); - p.ip = detail::read_v4_address(i).to_string(); - p.port = detail::read_uint16(i); - peer_list.push_back(p); - } - } - else - { - entry::list_type const& l = e["peers"].list(); - for(entry::list_type::const_iterator i = l.begin(); i != l.end(); ++i) - { - peer_entry p = extract_peer_info(*i); - peer_list.push_back(p); - } - } - - if (entry const* ipv6_peers = e.find_key("peers6")) - { - std::string const& peers = ipv6_peers->string(); - for (std::string::const_iterator i = peers.begin(); - i != peers.end();) - { - if (std::distance(i, peers.end()) < 18) break; - - peer_entry p; - p.pid.clear(); - p.ip = detail::read_v6_address(i).to_string(); - p.port = detail::read_uint16(i); - peer_list.push_back(p); - } - } - - // look for optional scrape info - int complete = -1; - int incomplete = -1; - - try { complete = int(e["complete"].integer()); } - catch(type_error&) {} - - try { incomplete = int(e["incomplete"].integer()); } - catch(type_error&) {} - - cb->tracker_response(tracker_req(), peer_list, interval, complete - , incomplete); - } - catch(type_error& e) - { - cb->tracker_request_error(tracker_req(), m_parser.status_code(), e.what()); - } - catch(std::runtime_error& e) - { - cb->tracker_request_error(tracker_req(), m_parser.status_code(), e.what()); - } + cb->tracker_response(tracker_req(), peer_list, interval->integer(), complete + , incomplete, external_ip); } } diff --git a/libtorrent/src/kademlia/closest_nodes.cpp b/libtorrent/src/kademlia/closest_nodes.cpp index 7e24182de..bd9957945 100644 --- a/libtorrent/src/kademlia/closest_nodes.cpp +++ b/libtorrent/src/kademlia/closest_nodes.cpp @@ -100,6 +100,7 @@ closest_nodes::closest_nodes( void closest_nodes::invoke(node_id const& id, udp::endpoint addr) { + TORRENT_ASSERT(m_rpc.allocation_size() >= sizeof(closest_nodes_observer)); observer_ptr o(new (m_rpc.allocator().malloc()) closest_nodes_observer(this, id, m_target)); #ifndef NDEBUG o->m_in_constructor = false; diff --git a/libtorrent/src/kademlia/dht_tracker.cpp b/libtorrent/src/kademlia/dht_tracker.cpp index 56bc765de..bc638d045 100644 --- a/libtorrent/src/kademlia/dht_tracker.cpp +++ b/libtorrent/src/kademlia/dht_tracker.cpp @@ -58,7 +58,6 @@ using libtorrent::dht::node_impl; using libtorrent::dht::node_id; using libtorrent::dht::packet_t; using libtorrent::dht::msg; -using libtorrent::dht::packet_iterator; namespace messages = libtorrent::dht::messages; using namespace libtorrent::detail; @@ -145,27 +144,23 @@ namespace libtorrent { namespace dht // class that puts the networking and the kademlia node in a single // unit and connecting them together. - dht_tracker::dht_tracker(asio::io_service& ios, dht_settings const& settings - , asio::ip::address listen_interface, entry const& bootstrap) - : m_strand(ios) - , m_socket(ios, udp::endpoint(listen_interface, settings.service_port)) - , m_dht(bind(&dht_tracker::send_packet, this, _1), settings + dht_tracker::dht_tracker(udp_socket& sock, dht_settings const& settings + , entry const& bootstrap) + : m_dht(bind(&dht_tracker::send_packet, this, _1), settings , read_id(bootstrap)) - , m_buffer(0) + , m_sock(sock) , m_last_new_key(time_now() - minutes(key_refresh)) - , m_timer(ios) - , m_connection_timer(ios) - , m_refresh_timer(ios) + , m_timer(sock.get_io_service()) + , m_connection_timer(sock.get_io_service()) + , m_refresh_timer(sock.get_io_service()) , m_settings(settings) , m_refresh_bucket(160) , m_abort(false) - , m_host_resolver(ios) + , m_host_resolver(sock.get_io_service()) , m_refs(0) { using boost::bind; - m_in_buf[0].resize(1000); - m_in_buf[1].resize(1000); #ifdef TORRENT_DHT_VERBOSE_LOGGING m_counter = 0; std::fill_n(m_replies_bytes_sent, 5, 0); @@ -203,18 +198,15 @@ namespace libtorrent { namespace dht } catch (std::exception&) {} } - m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] - , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] - , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); m_timer.expires_from_now(seconds(1)); - m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); + m_timer.async_wait(bind(&dht_tracker::tick, self(), _1)); m_connection_timer.expires_from_now(seconds(10)); - m_connection_timer.async_wait(m_strand.wrap( - bind(&dht_tracker::connection_timeout, self(), _1))); + m_connection_timer.async_wait( + bind(&dht_tracker::connection_timeout, self(), _1)); m_refresh_timer.expires_from_now(seconds(5)); - m_refresh_timer.async_wait(m_strand.wrap(bind(&dht_tracker::refresh_timeout, self(), _1))); + m_refresh_timer.async_wait(bind(&dht_tracker::refresh_timeout, self(), _1)); m_dht.bootstrap(initial_nodes, bind(&dht_tracker::on_bootstrap, self())); } @@ -226,7 +218,6 @@ namespace libtorrent { namespace dht m_timer.cancel(); m_connection_timer.cancel(); m_refresh_timer.cancel(); - m_socket.close(); m_host_resolver.cancel(); } @@ -243,10 +234,9 @@ namespace libtorrent { namespace dht mutex_t::scoped_lock l(m_mutex); if (e || m_abort) return; - if (!m_socket.is_open()) return; time_duration d = m_dht.connection_timeout(); m_connection_timer.expires_from_now(d); - m_connection_timer.async_wait(m_strand.wrap(bind(&dht_tracker::connection_timeout, self(), _1))); + m_connection_timer.async_wait(bind(&dht_tracker::connection_timeout, self(), _1)); } catch (std::exception& exc) { @@ -263,37 +253,24 @@ namespace libtorrent { namespace dht mutex_t::scoped_lock l(m_mutex); if (e || m_abort) return; - if (!m_socket.is_open()) return; time_duration d = m_dht.refresh_timeout(); m_refresh_timer.expires_from_now(d); - m_refresh_timer.async_wait(m_strand.wrap( - bind(&dht_tracker::refresh_timeout, self(), _1))); + m_refresh_timer.async_wait( + bind(&dht_tracker::refresh_timeout, self(), _1)); } catch (std::exception&) { TORRENT_ASSERT(false); }; - void dht_tracker::rebind(asio::ip::address listen_interface, int listen_port) - { - m_socket.close(); - udp::endpoint ep(listen_interface, listen_port); - m_socket.open(ep.protocol()); - m_socket.bind(ep); - m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] - , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] - , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); - } - void dht_tracker::tick(asio::error_code const& e) try { mutex_t::scoped_lock l(m_mutex); if (e || m_abort) return; - if (!m_socket.is_open()) return; m_timer.expires_from_now(minutes(tick_period)); - m_timer.async_wait(m_strand.wrap(bind(&dht_tracker::tick, self(), _1))); + m_timer.async_wait(bind(&dht_tracker::tick, self(), _1)); ptime now = time_now(); if (now - m_last_new_key > minutes(key_refresh)) @@ -400,26 +377,15 @@ namespace libtorrent { namespace dht // translate bittorrent kademlia message into the generice kademlia message // used by the library - void dht_tracker::on_receive(asio::error_code const& error, size_t bytes_transferred) + void dht_tracker::on_receive(udp::endpoint const& ep, char const* buf, int bytes_transferred) try { - if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; - - int current_buffer = m_buffer; - m_buffer = (m_buffer + 1) & 1; - m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] - , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] - , m_strand.wrap(bind(&dht_tracker::on_receive, self(), _1, _2))); - - if (error) return; - node_ban_entry* match = 0; node_ban_entry* min = m_ban_nodes; ptime now = time_now(); for (node_ban_entry* i = m_ban_nodes; i < m_ban_nodes + num_ban_nodes; ++i) { - if (i->src == m_remote_endpoint[current_buffer]) + if (i->src == ep) { match = i; break; @@ -438,8 +404,7 @@ namespace libtorrent { namespace dht if (match->count == 20) { TORRENT_LOG(dht_tracker) << time_now_string() << " BANNING PEER [ ip: " - << m_remote_endpoint[current_buffer] << " | " - "time: " << total_seconds((now - match->limit) + seconds(5)) + << ep << " | time: " << total_milliseconds((now - match->limit) + seconds(5)) / 1000.f << " | count: " << match->count << " ]"; } #endif @@ -459,7 +424,7 @@ namespace libtorrent { namespace dht { min->count = 1; min->limit = now + seconds(5); - min->src = m_remote_endpoint[current_buffer]; + min->src = ep; } #ifdef TORRENT_DHT_VERBOSE_LOGGING @@ -474,17 +439,16 @@ namespace libtorrent { namespace dht TORRENT_ASSERT(bytes_transferred > 0); - entry e = bdecode(m_in_buf[current_buffer].begin() - , m_in_buf[current_buffer].end()); + entry e = bdecode(buf, buf + bytes_transferred); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(dht_tracker) << time_now_string() << " RECEIVED [" - << m_remote_endpoint[current_buffer] << "]:"; + << ep << "]:"; #endif libtorrent::dht::msg m; m.message_id = 0; - m.addr = m_remote_endpoint[current_buffer]; + m.addr = ep; m.transaction_id = e["t"].string(); #ifdef TORRENT_DHT_VERBOSE_LOGGING @@ -721,9 +685,7 @@ namespace libtorrent { namespace dht catch (std::exception& e) { #ifdef TORRENT_DHT_VERBOSE_LOGGING - int current_buffer = (m_buffer + 1) & 1; - std::string msg(m_in_buf[current_buffer].begin() - , m_in_buf[current_buffer].begin() + bytes_transferred); + std::string msg(buf, buf + bytes_transferred); TORRENT_LOG(dht_tracker) << "invalid incoming packet: " << e.what() << "\n" << msg << "\n"; #endif @@ -773,15 +735,14 @@ namespace libtorrent { namespace dht void dht_tracker::add_node(std::pair const& node) { udp::resolver::query q(node.first, lexical_cast(node.second)); - m_host_resolver.async_resolve(q, m_strand.wrap( - bind(&dht_tracker::on_name_lookup, self(), _1, _2))); + m_host_resolver.async_resolve(q, + bind(&dht_tracker::on_name_lookup, self(), _1, _2)); } void dht_tracker::on_name_lookup(asio::error_code const& e , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; - if (!m_socket.is_open()) return; add_node(host->endpoint()); } catch (std::exception&) @@ -792,15 +753,14 @@ namespace libtorrent { namespace dht void dht_tracker::add_router_node(std::pair const& node) { udp::resolver::query q(node.first, lexical_cast(node.second)); - m_host_resolver.async_resolve(q, m_strand.wrap( - bind(&dht_tracker::on_router_name_lookup, self(), _1, _2))); + m_host_resolver.async_resolve(q, + bind(&dht_tracker::on_router_name_lookup, self(), _1, _2)); } void dht_tracker::on_router_name_lookup(asio::error_code const& e , udp::resolver::iterator host) try { if (e || host == udp::resolver::iterator()) return; - if (!m_socket.is_open()) return; m_dht.add_router_node(host->endpoint()); } catch (std::exception&) @@ -998,9 +958,7 @@ namespace libtorrent { namespace dht m_send_buf.clear(); bencode(std::back_inserter(m_send_buf), e); asio::error_code ec; - m_socket.send_to(asio::buffer(&m_send_buf[0] - , (int)m_send_buf.size()), m.addr, 0, ec); - if (ec) return; + m_sock.send(m.addr, &m_send_buf[0], (int)m_send_buf.size(), ec); #ifdef TORRENT_DHT_VERBOSE_LOGGING m_total_out_bytes += m_send_buf.size(); diff --git a/libtorrent/src/kademlia/find_data.cpp b/libtorrent/src/kademlia/find_data.cpp index 9dc161888..9d11b5aeb 100644 --- a/libtorrent/src/kademlia/find_data.cpp +++ b/libtorrent/src/kademlia/find_data.cpp @@ -109,6 +109,7 @@ void find_data::invoke(node_id const& id, asio::ip::udp::endpoint addr) return; } + TORRENT_ASSERT(m_rpc.allocation_size() >= sizeof(find_data_observer)); observer_ptr o(new (m_rpc.allocator().malloc()) find_data_observer(this, id, m_target)); #ifndef NDEBUG o->m_in_constructor = false; diff --git a/libtorrent/src/kademlia/node.cpp b/libtorrent/src/kademlia/node.cpp index 60dfa2d20..8654b8978 100644 --- a/libtorrent/src/kademlia/node.cpp +++ b/libtorrent/src/kademlia/node.cpp @@ -43,7 +43,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/random_sample.hpp" #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/rpc_manager.hpp" -#include "libtorrent/kademlia/packet_iterator.hpp" #include "libtorrent/kademlia/routing_table.hpp" #include "libtorrent/kademlia/node.hpp" diff --git a/libtorrent/src/kademlia/refresh.cpp b/libtorrent/src/kademlia/refresh.cpp index 916b6d06a..8edabb2c1 100644 --- a/libtorrent/src/kademlia/refresh.cpp +++ b/libtorrent/src/kademlia/refresh.cpp @@ -103,6 +103,7 @@ void ping_observer::timeout() void refresh::invoke(node_id const& nid, udp::endpoint addr) { + TORRENT_ASSERT(m_rpc.allocation_size() >= sizeof(refresh_observer)); observer_ptr o(new (m_rpc.allocator().malloc()) refresh_observer( this, nid, m_target)); #ifndef NDEBUG @@ -157,6 +158,7 @@ void refresh::invoke_pings_or_finish(bool prevent_request) try { + TORRENT_ASSERT(m_rpc.allocation_size() >= sizeof(ping_observer)); observer_ptr o(new (m_rpc.allocator().malloc()) ping_observer( this, node.id)); #ifndef NDEBUG diff --git a/libtorrent/src/kademlia/routing_table.cpp b/libtorrent/src/kademlia/routing_table.cpp index 359577613..1a55126f3 100644 --- a/libtorrent/src/kademlia/routing_table.cpp +++ b/libtorrent/src/kademlia/routing_table.cpp @@ -355,6 +355,19 @@ bool routing_table::need_bootstrap() const return true; } +template +DstIter copy_if_n(SrcIter begin, SrcIter end, DstIter target, size_t n, Pred p) +{ + for (; n > 0 && begin != end; ++begin) + { + if (!p(*begin)) continue; + *target = *begin; + --n; + ++target; + } + return target; +} + // fills the vector with the k nodes from our buckets that // are nearest to the given id. void routing_table::find_node(node_id const& target @@ -369,8 +382,8 @@ void routing_table::find_node(node_id const& target // copy all nodes that hasn't failed into the target // vector. - std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) - , bind(&node_entry::fail_count, _1)); + copy_if_n(b.begin(), b.end(), std::back_inserter(l) + , (std::min)(size_t(count), b.size()), bind(&node_entry::fail_count, _1) == 0); TORRENT_ASSERT((int)l.size() <= count); if ((int)l.size() == count) @@ -393,19 +406,23 @@ void routing_table::find_node(node_id const& target , bind(&node_entry::fail_count, _1)); } - std::random_shuffle(tmpb.begin(), tmpb.end()); - size_t to_copy = (std::min)(m_bucket_size - l.size() - , tmpb.size()); - std::copy(tmpb.begin(), tmpb.begin() + to_copy - , std::back_inserter(l)); + if (count - l.size() < tmpb.size()) + { + std::random_shuffle(tmpb.begin(), tmpb.end()); + size_t to_copy = count - l.size(); + std::copy(tmpb.begin(), tmpb.begin() + to_copy, std::back_inserter(l)); + } + else + { + std::copy(tmpb.begin(), tmpb.end(), std::back_inserter(l)); + } - TORRENT_ASSERT((int)l.size() <= m_bucket_size); + TORRENT_ASSERT((int)l.size() <= count); // return if we have enough nodes or if the bucket index // is the biggest index available (there are no more buckets) // to look in. - if ((int)l.size() == count - || bucket_index == (int)m_buckets.size() - 1) + if ((int)l.size() == count) { TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); @@ -416,18 +433,17 @@ void routing_table::find_node(node_id const& target { bucket_t& b = m_buckets[i].first; - std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) - , bind(&node_entry::fail_count, _1)); - if ((int)l.size() >= count) + size_t to_copy = (std::min)(count - l.size(), b.size()); + copy_if_n(b.begin(), b.end(), std::back_inserter(l) + , to_copy, bind(&node_entry::fail_count, _1) == 0); + TORRENT_ASSERT((int)l.size() <= count); + if ((int)l.size() == count) { - l.erase(l.begin() + count, l.end()); TORRENT_ASSERT(std::count_if(l.begin(), l.end() , boost::bind(&node_entry::fail_count, _1) != 0) == 0); return; } } - TORRENT_ASSERT((int)l.size() == count - || std::distance(l.begin(), l.end()) < m_bucket_size); TORRENT_ASSERT((int)l.size() <= count); TORRENT_ASSERT(std::count_if(l.begin(), l.end() diff --git a/libtorrent/src/kademlia/rpc_manager.cpp b/libtorrent/src/kademlia/rpc_manager.cpp index 7295cf0bf..4319fc4b8 100644 --- a/libtorrent/src/kademlia/rpc_manager.cpp +++ b/libtorrent/src/kademlia/rpc_manager.cpp @@ -121,6 +121,7 @@ rpc_manager::rpc_manager(fun const& f, node_id const& our_id rpc_manager::~rpc_manager() { + TORRENT_ASSERT(!m_destructing); m_destructing = true; #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(rpc) << "Destructing"; @@ -136,6 +137,12 @@ rpc_manager::~rpc_manager() } #ifndef NDEBUG +size_t rpc_manager::allocation_size() const +{ + size_t s = sizeof(mpl::deref::type); + return s; +} + void rpc_manager::check_invariant() const { TORRENT_ASSERT(m_oldest_transaction_id >= 0); @@ -302,7 +309,7 @@ time_duration rpc_manager::tick() // clear the aborted transactions, will likely // generate new requests. We need to swap, since the // destrutors may add more observers to the m_aborted_transactions - std::vector().swap(m_aborted_transactions); + std::vector().swap(m_aborted_transactions); return milliseconds(timeout_ms); } @@ -429,6 +436,7 @@ void rpc_manager::reply_with_ping(msg& m) std::back_insert_iterator out(m.ping_transaction_id); io::write_uint16(m_next_transaction_id, out); + TORRENT_ASSERT(allocation_size() >= sizeof(null_observer)); observer_ptr o(new (allocator().malloc()) null_observer(allocator())); #ifndef NDEBUG o->m_in_constructor = false; diff --git a/libtorrent/src/lazy_bdecode.cpp b/libtorrent/src/lazy_bdecode.cpp new file mode 100644 index 000000000..2e2662585 --- /dev/null +++ b/libtorrent/src/lazy_bdecode.cpp @@ -0,0 +1,367 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/lazy_entry.hpp" +#include +#include + +namespace libtorrent +{ + int fail_bdecode() { return -1; } + + // fills in 'val' with what the string between start and the + // first occurance of the delimiter is interpreted as an int. + // return the pointer to the delimiter, or 0 if there is a + // parse error. val should be initialized to zero + char const* parse_int(char const* start, char const* end, char delimiter, boost::int64_t& val) + { + while (start < end && *start != delimiter) + { + using namespace std; + if (!isdigit(*start)) { fail_bdecode(); return 0; } + val *= 10; + val += *start - '0'; + ++start; + } + return start; + } + + char const* find_char(char const* start, char const* end, char delimiter) + { + while (start < end && *start != delimiter) ++start; + return start; + } + + // return 0 = success + int lazy_bdecode(char const* start, char const* end, lazy_entry& ret, int depth_limit) + { + ret.clear(); + if (start == end) return 0; + + std::vector stack; + + stack.push_back(&ret); + while (start < end) + { + if (stack.empty()) break; // done! + + lazy_entry* top = stack.back(); + + if (int(stack.size()) > depth_limit) return fail_bdecode(); + if (start == end) return fail_bdecode(); + char t = *start; + ++start; + if (start == end && t != 'e') return fail_bdecode(); + + switch (top->type()) + { + case lazy_entry::dict_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + boost::int64_t len = t - '0'; + start = parse_int(start, end, ':', len); + if (start == 0 || start + len + 3 > end || *start != ':') return fail_bdecode(); + ++start; + lazy_entry* ent = top->dict_append(start); + start += len; + stack.push_back(ent); + t = *start; + ++start; + break; + } + case lazy_entry::list_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + lazy_entry* ent = top->list_append(); + stack.push_back(ent); + break; + } + default: break; + } + + top = stack.back(); + switch (t) + { + case 'd': + top->construct_dict(start - 1); + continue; + case 'l': + top->construct_list(start - 1); + continue; + case 'i': + { + char const* int_start = start; + start = find_char(start, end, 'e'); + top->construct_int(int_start, start - int_start); + if (start == end) return fail_bdecode(); + TORRENT_ASSERT(*start == 'e'); + ++start; + stack.pop_back(); + continue; + } + default: + { + using namespace std; + if (!isdigit(t)) return fail_bdecode(); + + boost::int64_t len = t - '0'; + start = parse_int(start, end, ':', len); + if (start == 0 || start + len + 1 > end || *start != ':') return fail_bdecode(); + ++start; + top->construct_string(start, int(len)); + stack.pop_back(); + start += len; + continue; + } + } + return 0; + } + return 0; + } + + boost::int64_t lazy_entry::int_value() const + { + TORRENT_ASSERT(m_type == int_t); + boost::int64_t val = 0; + bool negative = false; + if (*m_data.start == '-') negative = true; + parse_int(negative?m_data.start+1:m_data.start, m_data.start + m_size, 'e', val); + if (negative) val = -val; + return val; + } + + lazy_entry* lazy_entry::dict_append(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + TORRENT_ASSERT(m_size <= m_capacity); + if (m_capacity == 0) + { + int capacity = 10; + m_data.dict = new (std::nothrow) std::pair[capacity]; + if (m_data.dict == 0) return 0; + m_capacity = capacity; + } + else if (m_size == m_capacity) + { + int capacity = m_capacity * 2; + std::pair* tmp = new (std::nothrow) std::pair[capacity]; + if (tmp == 0) return 0; + std::memcpy(tmp, m_data.dict, sizeof(std::pair) * m_size); + delete[] m_data.dict; + m_data.dict = tmp; + m_capacity = capacity; + } + + TORRENT_ASSERT(m_size < m_capacity); + std::pair& ret = m_data.dict[m_size++]; + ret.first = name; + return &ret.second; + } + + namespace + { + // the number of decimal digits needed + // to represent the given value + int num_digits(int val) + { + int ret = 1; + while (val > 10) + { + ++ret; + val /= 10; + } + return ret; + } + } + + void lazy_entry::construct_string(char const* start, int length) + { + TORRENT_ASSERT(m_type == none_t); + m_type = string_t; + m_data.start = start; + m_size = length; + m_begin = start - 1 - num_digits(length); + m_end = start + length; + } + + namespace + { + // str1 is null-terminated + // str2 is not, str2 is len2 chars + bool string_equal(char const* str1, char const* str2, int len2) + { + while (len2 > 0) + { + if (*str1 != *str2) return false; + if (*str1 == 0) return false; + ++str1; + ++str2; + --len2; + } + return true; + } + } + + lazy_entry* lazy_entry::dict_find(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + for (int i = 0; i < m_size; ++i) + { + std::pair const& e = m_data.dict[i]; + if (string_equal(name, e.first, e.second.m_begin - e.first)) + return &m_data.dict[i].second; + } + return 0; + } + + lazy_entry* lazy_entry::list_append() + { + TORRENT_ASSERT(m_type == list_t); + TORRENT_ASSERT(m_size <= m_capacity); + if (m_capacity == 0) + { + int capacity = 10; + m_data.list = new (std::nothrow) lazy_entry[capacity]; + if (m_data.list == 0) return 0; + m_capacity = capacity; + } + else if (m_size == m_capacity) + { + int capacity = m_capacity * 2; + lazy_entry* tmp = new (std::nothrow) lazy_entry[capacity]; + if (tmp == 0) return 0; + std::memcpy(tmp, m_data.list, sizeof(lazy_entry) * m_size); + delete[] m_data.list; + m_data.list = tmp; + m_capacity = capacity; + } + + TORRENT_ASSERT(m_size < m_capacity); + return m_data.list + (m_size++); + } + + void lazy_entry::clear() + { + switch (m_type) + { + case list_t: delete[] m_data.list; break; + case dict_t: delete[] m_data.dict; break; + default: break; + } + m_size = 0; + m_capacity = 0; + m_type = none_t; + } + + std::pair lazy_entry::data_section() + { + typedef std::pair return_t; + return return_t(m_begin, m_end - m_begin); + } + + std::ostream& operator<<(std::ostream& os, lazy_entry const& e) + { + switch (e.type()) + { + case lazy_entry::none_t: return os << "none"; + case lazy_entry::int_t: return os << e.int_value(); + case lazy_entry::string_t: + { + bool printable = true; + char const* str = e.string_ptr(); + for (int i = 0; i < e.string_length(); ++i) + { + using namespace std; + if (isprint(str[i])) continue; + printable = false; + break; + } + if (printable) return os << e.string_value(); + for (int i = 0; i < e.string_length(); ++i) + os << std::hex << int((unsigned char)(str[i])); + return os; + } + case lazy_entry::list_t: + { + os << "["; + bool one_liner = (e.list_size() == 0 + || e.list_at(0)->type() == lazy_entry::int_t + || (e.list_at(0)->type() == lazy_entry::string_t + && e.list_at(0)->string_length() < 5)) + && e.list_size() < 5; + if (!one_liner) os << "\n"; + for (int i = 0; i < e.list_size(); ++i) + { + if (i == 0 && one_liner) os << " "; + os << *e.list_at(i); + if (i < e.list_size() - 1) os << (one_liner?", ":",\n"); + else os << (one_liner?" ":"\n"); + } + return os << "]"; + } + case lazy_entry::dict_t: + { + os << "{"; + bool one_liner = (e.dict_size() == 0 + || e.dict_at(0).second->type() == lazy_entry::int_t + || (e.dict_at(0).second->type() == lazy_entry::string_t + && e.dict_at(0).second->string_length() < 4) + || strlen(e.dict_at(0).first) < 10) + && e.dict_size() < 5; + + if (!one_liner) os << "\n"; + for (int i = 0; i < e.dict_size(); ++i) + { + if (i == 0 && one_liner) os << " "; + std::pair ent = e.dict_at(i); + os << "'" << ent.first << "': " << *ent.second; + if (i < e.dict_size() - 1) os << (one_liner?", ":",\n"); + else os << (one_liner?" ":"\n"); + } + return os << "}"; + } + } + return os; + } + +}; + diff --git a/libtorrent/src/lsd.cpp b/libtorrent/src/lsd.cpp index 8ec1a5daf..a0b6426a8 100644 --- a/libtorrent/src/lsd.cpp +++ b/libtorrent/src/lsd.cpp @@ -35,6 +35,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/http_parser.hpp" #include #include @@ -95,11 +97,11 @@ void lsd::announce(sha1_hash const& ih, int listen_port) << " ==> announce: ih: " << ih << " port: " << listen_port << std::endl; #endif - m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); m_broadcast_timer.async_wait(bind(&lsd::resend_announce, self(), _1, msg)); } -void lsd::resend_announce(asio::error_code const& e, std::string msg) try +void lsd::resend_announce(asio::error_code const& e, std::string msg) { if (e) return; @@ -110,11 +112,9 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try if (m_retry_count >= 5) return; - m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); m_broadcast_timer.async_wait(bind(&lsd::resend_announce, self(), _1, msg)); } -catch (std::exception&) -{} void lsd::on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) @@ -123,9 +123,11 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer http_parser p; - p.incoming(buffer::const_interval(buffer, buffer + bytes_transferred)); + bool error = false; + p.incoming(buffer::const_interval(buffer, buffer + bytes_transferred) + , error); - if (!p.header_finished()) + if (!p.header_finished() || error) { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() @@ -176,15 +178,22 @@ void lsd::on_announce(udp::endpoint const& from, char* buffer << ":" << port << " ih: " << ih << std::endl; #endif // we got an announce, pass it on through the callback - try { m_callback(tcp::endpoint(from.address(), port), ih); } +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + m_callback(tcp::endpoint(from.address(), port), ih); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } } void lsd::close() { m_socket.close(); - m_broadcast_timer.cancel(); + asio::error_code ec; + m_broadcast_timer.cancel(ec); m_disabled = true; m_callback.clear(); } diff --git a/libtorrent/src/magnet_uri.cpp b/libtorrent/src/magnet_uri.cpp new file mode 100644 index 000000000..bd582476a --- /dev/null +++ b/libtorrent/src/magnet_uri.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/escape_string.hpp" + +#include +#include + +namespace libtorrent +{ + std::string make_magnet_uri(torrent_handle const& handle) + { + std::stringstream ret; + if (!handle.is_valid()) return ret.str(); + + std::string name = handle.name(); + + ret << "magnet:?xt=urn:btih:" << base32encode( + std::string((char*)handle.info_hash().begin(), 20)); + if (!name.empty()) + ret << "&dn=" << escape_string(name.c_str(), name.length()); + torrent_status st = handle.status(); + if (!st.current_tracker.empty()) + { + ret << "&tr=" << escape_string(st.current_tracker.c_str() + , st.current_tracker.length()); + } + else + { + std::vector const& tr = handle.trackers(); + if (!tr.empty()) + { + ret << "&tr=" << escape_string(tr[0].url.c_str() + , tr[0].url.length()); + } + } + return ret.str(); + } + + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , fs::path const& save_path + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + std::string name; + std::string tracker; + + boost::optional display_name = url_has_argument(uri, "dn"); + if (display_name) name = unescape_string(display_name->c_str()); + boost::optional tracker_string = url_has_argument(uri, "tr"); + if (tracker_string) tracker = unescape_string(tracker_string->c_str()); + + boost::optional btih = url_has_argument(uri, "xt"); + if (!btih) return torrent_handle(); + + if (btih->compare(0, 9, "urn:btih:") != 0) return torrent_handle(); + + sha1_hash info_hash(base32decode(btih->substr(9))); + + return ses.add_torrent(tracker.empty() ? 0 : tracker.c_str(), info_hash + , name.empty() ? 0 : name.c_str(), save_path, entry() + , storage_mode, paused, sc, userdata); + } +} + + diff --git a/libtorrent/src/mapped_storage.cpp b/libtorrent/src/mapped_storage.cpp new file mode 100644 index 000000000..bb1380d88 --- /dev/null +++ b/libtorrent/src/mapped_storage.cpp @@ -0,0 +1,812 @@ +/* + +Copyright (c) 2007, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/size_type.hpp" +#include "libtorrent/file.hpp" +#include + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +using boost::iostreams::mapped_file; +using boost::iostreams::mapped_file_params; + +namespace libtorrent +{ + + namespace fs = boost::filesystem; + + struct mapped_file_pool + { + mapped_file_pool(int size = 40): m_size(size) {} + + private: + + enum { view_size = 100 * 1024 * 1024 }; + int m_size; + + struct file_entry + { + file_entry() : key(0), references(0) {} + bool open(fs::path const& path, std::ios::openmode openmode + , size_type start, size_type size, void* key_, size_type file_size = 0) + { +#ifndef NDEBUG + if (file_size > 0) + { + fs::system_error_type ec; + fs::file_status st = fs::status(path, ec); + TORRENT_ASSERT(!fs::exists(st)); + } +#endif + key = key_; + last_use = time_now(); + params.path = path.string(); + params.mode = openmode; + params.offset = start; + params.length = size; + params.new_file_size = file_size; + file.open(params); + return file.is_open(); + } + mapped_file_params params; + mapped_file file; + void* key; + ptime last_use; + int references; + }; + + typedef std::list files_t; + files_t m_files; + + public: + + struct file_view + { + explicit file_view(file_entry* e): m_entry(e) { ++m_entry->references; } + file_view(): m_entry(0) {} + file_view(file_view const& f): m_entry(f.m_entry) + { if (m_entry) ++m_entry->references; } + ~file_view() + { + TORRENT_ASSERT(m_entry == 0 || m_entry->references > 0); + if (m_entry) --m_entry->references; + } + file_view& operator=(file_view const& v) + { + TORRENT_ASSERT(m_entry == 0 || m_entry->references > 0); + if (m_entry) --m_entry->references; + m_entry = v.m_entry; + if (m_entry) ++m_entry->references; + return *this; + } + + bool valid() const { return m_entry && m_entry->file.const_data(); } + + char* addr() const + { + TORRENT_ASSERT(m_entry); + return m_entry->file.data(); + } + + char const* const_addr() const + { + TORRENT_ASSERT(m_entry); + return m_entry->file.const_data(); + } + + size_type offset() const + { + TORRENT_ASSERT(m_entry); + return m_entry->params.offset; + } + + size_type size() const + { + TORRENT_ASSERT(m_entry); + return m_entry->params.length; + } + + private: + file_entry* m_entry; + }; + + file_view open_file(fs::path const& p, std::ios::openmode mode + , size_type offset, size_type length, void* key + , size_type file_size) + { + TORRENT_ASSERT(file_size > 0); + files_t::iterator min = m_files.end(); + for (std::list::iterator i = m_files.begin() + , end(m_files.end()); i != end; ++i) + { + if (i->params.path == p.string() + && i->params.offset <= offset + && i->params.offset + i->params.length >= offset + length) + { + if (i->key != key) return file_view(); + if ((mode & std::ios::out) && (i->params.mode & std::ios::out) == 0) + { + TORRENT_ASSERT(i->references == 0); + i->file.close(); + m_files.erase(i); + min = m_files.end(); + break; + } + i->last_use = time_now(); + return file_view(&(*i)); + } + if ((min == m_files.end() || i->last_use < min->last_use) + && i->references == 0) + { + min = i; + } + } + + if (int(m_files.size()) >= m_size && min != m_files.end()) + { + TORRENT_ASSERT(min->references == 0); + min->file.close(); + m_files.erase(min); + } + + size_type start = (offset / view_size) * view_size; + TORRENT_ASSERT(start + view_size >= offset + length); + + fs::system_error_type ec; + fs::file_status st = fs::status(p, ec); + + m_files.push_back(file_entry()); + bool ret = false; + if (!exists(st)) + { + ret = m_files.back().open(p, mode | std::ios::out, start, view_size, key, file_size); + } + else + { + if (is_directory(st)) return file_view(); + size_type s = fs::file_size(p); +#ifdef WIN32 + // TODO: SetFileSize() + if (s < file_size) {} +#else + if (s < file_size) truncate(p.string().c_str(), file_size); +#endif + ret = m_files.back().open(p, mode, start, view_size, key); + } + + + if (!ret) + { + m_files.erase(boost::prior(m_files.end())); + return file_view(); + } + return file_view(&m_files.back()); + } + + void release(void* key) + { + for (std::list::iterator i = m_files.begin(); + !m_files.empty() && i != m_files.end();) + { + if (i->key == key) + { + TORRENT_ASSERT(i->references == 0); + i->file.close(); + m_files.erase(i++); + continue; + } + ++i; + } + } + + }; + + + struct mapped_storage: storage_interface + { + mapped_storage(boost::intrusive_ptr const& info, fs::path save_path) + : m_info(info) + , m_save_path(save_path) + {} + + bool initialize(bool allocate_files) { return false; } + + int read(char* buf, int slot, int offset, int size) + { + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(slot >= 0 && slot < m_info->num_pieces()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset < m_info->piece_size(slot)); + TORRENT_ASSERT(size > 0); + + size_type result = -1; + try + { + +#ifndef NDEBUG + std::vector slices + = m_info->map_block(slot, offset, size, true); + TORRENT_ASSERT(!slices.empty()); +#endif + size_type start = slot * (size_type)m_info->piece_length() + offset; + TORRENT_ASSERT(start + size <= m_info->total_size()); + + // find the file iterator and file offset + size_type file_offset = start; + std::vector::const_iterator file_iter; + + for (file_iter = m_info->begin_files(true);;) + { + if (file_offset < file_iter->size) + break; + + file_offset -= file_iter->size; + ++file_iter; + } + + TORRENT_ASSERT(file_iter->size > 0); + mapped_file_pool::file_view view = m_pool.open_file( + m_save_path / file_iter->path, std::ios::in + , file_offset + file_iter->file_base, size, this + , file_iter->size + file_iter->file_base); + + if (!view.valid()) + { + set_error((m_save_path / file_iter->path).string(), "failed to open file for reading"); + return -1; + } + TORRENT_ASSERT(view.const_addr() != 0); + + int left_to_read = size; + int buf_pos = 0; + result = left_to_read; +#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); + + if (read_bytes > 0) + { +#ifndef NDEBUG + TORRENT_ASSERT(int(slices.size()) > counter); + size_type slice_size = slices[counter].size; + TORRENT_ASSERT(slice_size == read_bytes); + TORRENT_ASSERT(m_info->file_at(slices[counter].file_index, true).path + == file_iter->path); +#endif + + TORRENT_ASSERT(file_offset + file_iter->file_base >= view.offset()); + TORRENT_ASSERT(view.const_addr() != 0); + std::memcpy(buf + buf_pos + , view.const_addr() + (file_offset + file_iter->file_base - view.offset()) + , read_bytes); + + left_to_read -= read_bytes; + buf_pos += read_bytes; + TORRENT_ASSERT(buf_pos >= 0); + file_offset += read_bytes; + } + + if (left_to_read > 0) + { + ++file_iter; + // skip empty files + while (file_iter != m_info->end_files(true) && file_iter->size == 0) + ++file_iter; + +#ifndef NDEBUG + // empty files are not returned by map_block, so if + // this file was empty, don't increment the slice counter + if (read_bytes > 0) ++counter; +#endif + fs::path path = m_save_path / file_iter->path; + + file_offset = 0; + + view = m_pool.open_file(path, std::ios::in, file_offset + file_iter->file_base + , left_to_read, this + , file_iter->size + file_iter->file_base); + + if (!view.valid()) + { + set_error((m_save_path / file_iter->path).string(), "failed to open for reading"); + return -1; + } + TORRENT_ASSERT(view.const_addr() != 0); + } + } + } + catch (std::exception& e) + { + set_error("", e.what()); + return -1; + } + + return result; + } + + int write(const char* buf, int slot, int offset, int size) + { + TORRENT_ASSERT(buf != 0); + TORRENT_ASSERT(slot >= 0 && slot < m_info->num_pieces()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset < m_info->piece_size(slot)); + TORRENT_ASSERT(size > 0); + +#ifndef NDEBUG + std::vector slices + = m_info->map_block(slot, offset, size, true); + TORRENT_ASSERT(!slices.empty()); +#endif + size_type start = slot * (size_type)m_info->piece_length() + offset; + TORRENT_ASSERT(start + size <= m_info->total_size()); + + // find the file iterator and file offset + size_type file_offset = start; + std::vector::const_iterator file_iter; + + for (file_iter = m_info->begin_files(true);;) + { + if (file_offset < file_iter->size) + break; + + file_offset -= file_iter->size; + ++file_iter; + } + + TORRENT_ASSERT(file_iter->size > 0); + try + { + + mapped_file_pool::file_view view = m_pool.open_file( + m_save_path / file_iter->path, std::ios::in | std::ios::out + , file_offset + file_iter->file_base, size, this + , file_iter->size + file_iter->file_base); + + if (!view.valid()) + { + set_error((m_save_path / file_iter->path).string(), "failed to open file for writing"); + return -1; + } + TORRENT_ASSERT(view.addr() != 0); + + int left_to_write = size; + int buf_pos = 0; +#ifndef NDEBUG + int counter = 0; +#endif + while (left_to_write > 0) + { + int write_bytes = left_to_write; + if (file_offset + write_bytes > file_iter->size) + write_bytes = static_cast(file_iter->size - file_offset); + + if (write_bytes > 0) + { +#ifndef NDEBUG + TORRENT_ASSERT(int(slices.size()) > counter); + size_type slice_size = slices[counter].size; + TORRENT_ASSERT(slice_size == write_bytes); + TORRENT_ASSERT(m_info->file_at(slices[counter].file_index, true).path + == file_iter->path); +#endif + + TORRENT_ASSERT(file_offset + file_iter->file_base >= view.offset()); + TORRENT_ASSERT(view.addr() != 0); + std::memcpy(view.addr() + (file_offset + file_iter->file_base - view.offset()) + , buf + buf_pos + , write_bytes); + + left_to_write -= write_bytes; + buf_pos += write_bytes; + TORRENT_ASSERT(buf_pos >= 0); + file_offset += write_bytes; + } + + if (left_to_write > 0) + { + ++file_iter; + while (file_iter != m_info->end_files(true) && file_iter->size == 0) + ++file_iter; +#ifndef NDEBUG + // empty files are not returned by map_block, so if + // this file was empty, don't increment the slice counter + if (write_bytes > 0) ++counter; +#endif + fs::path path = m_save_path / file_iter->path; + + file_offset = 0; + view = m_pool.open_file(path, std::ios::in | std::ios::out + , file_offset + file_iter->file_base, left_to_write, this + , file_iter->size + file_iter->file_base); + + if (!view.valid()) + { + set_error((m_save_path / file_iter->path).string(), "failed to open file for reading"); + return -1; + } + TORRENT_ASSERT(view.addr() != 0); + } + } + } + catch (std::exception& e) + { + set_error((m_save_path / file_iter->path).string(), e.what()); + return -1; + } + return size; + } + + bool move_storage(fs::path save_path) + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 + fs::wpath old_path; + fs::wpath new_path; +#else + fs::path old_path; + fs::path new_path; +#endif + + save_path = complete(save_path); + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + std::wstring wsave_path(safe_convert(save_path.native_file_string())); + if (!exists_win(save_path)) + CreateDirectory(wsave_path.c_str(), 0); + else if ((GetFileAttributes(wsave_path.c_str()) & FILE_ATTRIBUTE_DIRECTORY) == 0) + return false; +#elif defined(_WIN32) && defined(UNICODE) + fs::wpath wp = safe_convert(save_path.string()); + if (!exists(wp)) + create_directory(wp); + else if (!is_directory(wp)) + return false; +#else + if (!exists(save_path)) + create_directory(save_path); + else if (!is_directory(save_path)) + return false; +#endif + + m_pool.release(this); + +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400 + old_path = safe_convert((m_save_path / m_info->name()).string()); + new_path = safe_convert((save_path / m_info->name()).string()); +#else + old_path = m_save_path / m_info->name(); + new_path = save_path / m_info->name(); +#endif + + try + { +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + rename_win(old_path, new_path); + rename(old_path, new_path); +#else + rename(old_path, new_path); +#endif + m_save_path = save_path; + return true; + } + catch (std::exception& e) + { +#ifndef NDEBUG + std::cerr << "ERROR: " << e.what() << std::endl; +#endif + } + return false; + } + + bool verify_resume_data(entry const& rd, std::string& error) + { + if (rd.type() != entry::dictionary_t) + { + error = "invalid fastresume file"; + return true; + } + + std::vector > file_sizes; + entry const* file_sizes_ent = rd.find_key("file sizes"); + if (file_sizes_ent == 0 || file_sizes_ent->type() != entry::list_t) + { + error = "missing or invalid 'file sizes' entry in resume data"; + return false; + } + + entry::list_type const& l = file_sizes_ent->list(); + + for (entry::list_type::const_iterator i = l.begin(); + i != l.end(); ++i) + { + if (i->type() != entry::list_t) break; + entry::list_type const& pair = i->list(); + if (pair.size() != 2 || pair.front().type() != entry::int_t + || pair.back().type() != entry::int_t) + break; + file_sizes.push_back(std::pair( + pair.front().integer(), pair.back().integer())); + } + + if (file_sizes.empty()) + { + error = "the number of files in resume data is 0"; + return false; + } + + entry const* slots_ent = rd.find_key("slots"); + if (slots_ent == 0 || slots_ent->type() != entry::list_t) + { + error = "missing or invalid 'slots' entry in resume data"; + return false; + } + + entry::list_type const& slots = slots_ent->list(); + bool seed = int(slots.size()) == m_info->num_pieces() + && std::find_if(slots.begin(), slots.end() + , boost::bind(std::less() + , boost::bind((size_type const& (entry::*)() const) + &entry::integer, _1), 0)) == slots.end(); + + bool full_allocation_mode = false; + entry const* allocation_mode = rd.find_key("allocation"); + if (allocation_mode && allocation_mode->type() == entry::string_t) + full_allocation_mode = allocation_mode->string() == "full"; + + if (seed) + { + if (m_info->num_files(true) != (int)file_sizes.size()) + { + error = "the number of files does not match the torrent (num: " + + boost::lexical_cast(file_sizes.size()) + " actual: " + + boost::lexical_cast(m_info->num_files(true)) + ")"; + return false; + } + + std::vector >::iterator + fs = file_sizes.begin(); + // the resume data says we have the entire torrent + // make sure the file sizes are the right ones + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i, ++fs) + { + if (i->size != fs->first) + { + error = "file size for '" + i->path.native_file_string() + + "' was expected to be " + + boost::lexical_cast(i->size) + " bytes"; + return false; + } + } + return true; + } + + return match_filesizes(*m_info, m_save_path, file_sizes + , !full_allocation_mode, &error); + } + + bool write_resume_data(entry& rd) const + { + if (rd.type() != entry::dictionary_t) + { + set_error("", "invalid fastresume file"); + return true; + } + std::vector > file_sizes + = get_filesizes(*m_info, m_save_path); + + entry::list_type& fl = rd["file sizes"].list(); + for (std::vector >::iterator i + = file_sizes.begin(), end(file_sizes.end()); i != end; ++i) + { + entry::list_type p; + p.push_back(entry(i->first)); + p.push_back(entry(i->second)); + fl.push_back(entry(p)); + } + return false; + } + + bool move_slot(int src_slot, int dst_slot) + { + // TODO: this can be optimized by mapping both slots and do a straight memcpy + int piece_size = m_info->piece_size(dst_slot); + m_scratch_buffer.resize(piece_size); + size_type ret1 = read(&m_scratch_buffer[0], src_slot, 0, piece_size); + size_type ret2 = write(&m_scratch_buffer[0], dst_slot, 0, piece_size); + return ret1 != piece_size || ret2 != piece_size; + } + + bool swap_slots(int slot1, int slot2) + { + // TODO: this can be optimized by mapping both slots and do a straight memcpy + // the size of the target slot is the size of the piece + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot1); + m_scratch_buffer.resize(piece_size * 2); + size_type ret1 = read(&m_scratch_buffer[0], slot1, 0, piece1_size); + size_type ret2 = read(&m_scratch_buffer[piece_size], slot2, 0, piece2_size); + size_type ret3 = write(&m_scratch_buffer[0], slot2, 0, piece1_size); + size_type ret4 = write(&m_scratch_buffer[piece_size], slot1, 0, piece2_size); + return ret1 != piece1_size || ret2 != piece2_size + || ret3 != piece1_size || ret4 != piece2_size; + } + + bool swap_slots3(int slot1, int slot2, int slot3) + { + // TODO: this can be optimized by mapping both slots and do a straight memcpy + // the size of the target slot is the size of the piece + int piece_size = m_info->piece_length(); + int piece1_size = m_info->piece_size(slot2); + int piece2_size = m_info->piece_size(slot3); + int piece3_size = m_info->piece_size(slot1); + m_scratch_buffer.resize(piece_size * 2); + size_type ret1 = read(&m_scratch_buffer[0], slot1, 0, piece1_size); + size_type ret2 = read(&m_scratch_buffer[piece_size], slot2, 0, piece2_size); + size_type ret3 = write(&m_scratch_buffer[0], slot2, 0, piece1_size); + size_type ret4 = read(&m_scratch_buffer[0], slot3, 0, piece3_size); + size_type ret5 = write(&m_scratch_buffer[piece_size], slot3, 0, piece2_size); + size_type ret6 = write(&m_scratch_buffer[0], slot1, 0, piece3_size); + return ret1 != piece1_size || ret2 != piece2_size + || ret3 != piece1_size || ret4 != piece3_size + || ret5 != piece2_size || ret6 != piece3_size; + } + + sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size) + { +#ifndef NDEBUG + hasher partial; + hasher whole; + int slot_size1 = piece_size; + m_scratch_buffer.resize(slot_size1); + read(&m_scratch_buffer[0], slot, 0, slot_size1); + if (ph.offset > 0) + partial.update(&m_scratch_buffer[0], ph.offset); + whole.update(&m_scratch_buffer[0], slot_size1); + hasher partial_copy = ph.h; + TORRENT_ASSERT(ph.offset == 0 || partial_copy.final() == partial.final()); +#endif + int slot_size = piece_size - ph.offset; + if (slot_size > 0) + { + m_scratch_buffer.resize(slot_size); + read(&m_scratch_buffer[0], slot, ph.offset, slot_size); + ph.h.update(&m_scratch_buffer[0], slot_size); + } +#ifndef NDEBUG + sha1_hash ret = ph.h.final(); + TORRENT_ASSERT(ret == whole.final()); + return ret; +#else + return ph.h.final(); +#endif + } + + bool release_files() + { + m_pool.release(this); + return false; + } + + bool delete_files() + { + // make sure we don't have the files open + m_pool.release(this); + buffer().swap(m_scratch_buffer); + + int result = 0; + std::string error; + std::string error_file; + + // delete the files from disk + std::set directories; + typedef std::set::iterator iter_t; + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i) + { + std::string p = (m_save_path / i->path).string(); + fs::path bp = i->path.branch_path(); + std::pair ret; + ret.second = true; + while (ret.second && !bp.empty()) + { + std::pair ret = directories.insert((m_save_path / bp).string()); + bp = bp.branch_path(); + } + if (std::remove(p.c_str()) != 0 && errno != ENOENT) + { + error = std::strerror(errno); + error_file = p; + result = errno; + } + } + + // remove the directories. Reverse order to delete + // subdirectories first + + for (std::set::reverse_iterator i = directories.rbegin() + , end(directories.rend()); i != end; ++i) + { + if (std::remove(i->c_str()) != 0 && errno != ENOENT) + { + error = std::strerror(errno); + error_file = *i; + result = errno; + } + } + + if (!error.empty()) set_error(error_file, error); + return result != 0; + } + + private: + + boost::intrusive_ptr m_info; + fs::path m_save_path; + + // temporary storage for moving pieces + buffer m_scratch_buffer; + + static mapped_file_pool m_pool; + }; + + storage_interface* mapped_storage_constructor(boost::intrusive_ptr ti + , fs::path const& path, file_pool& fp) + { + return new mapped_storage(ti, path); + } + + mapped_file_pool mapped_storage::m_pool; + +} + diff --git a/libtorrent/src/memdebug.cpp b/libtorrent/src/memdebug.cpp new file mode 100644 index 000000000..bdc5aa3f2 --- /dev/null +++ b/libtorrent/src/memdebug.cpp @@ -0,0 +1,224 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if defined __linux__ && defined __GNUC__ +#include + +// Prototypes for __malloc_hook, __free_hook +#include +#include +#include +#include +#include +#include +#include +#include +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" + +using boost::multi_index_container; +using namespace boost::multi_index; +using libtorrent::time_now; + +struct memdebug +{ + memdebug() + { + malloc_log.open("memory.log"); + malloc_index_log.open("memory_index.log"); + + assert(old_malloc_hook == 0); + assert(old_free_hook == 0); + old_malloc_hook = __malloc_hook; + old_free_hook = __free_hook; + __malloc_hook = my_malloc_hook; + __free_hook = my_free_hook; + } + + static void my_free_hook(void *ptr, const void *caller); + static void* my_malloc_hook(size_t size, const void *caller); + + static boost::mutex mutex; + static std::ofstream malloc_log; + static std::ofstream malloc_index_log; + + // the original library functions + static void* (*old_malloc_hook)(size_t, const void *); + static void (*old_free_hook)(void*, const void *); + + struct allocation_point_t + { + allocation_point_t() + : allocated(0) + , peak_allocated(0) + , spacetime(0) + , last_update(time_now()) {} + + int index; + // total number of bytes allocated from this point + int allocated; + // the maximum total number of bytes allocated + // from this point + int peak_allocated; + // total number of bytes allocated times the number of + // milliseconds they were allocated from this point + boost::int64_t spacetime; + // the last malloc or free operation on + // this allocation point. The spacetime + // should be updated from this point to + // the current operation + libtorrent::ptime last_update; + }; + + typedef boost::array stacktrace_t; + typedef std::map allocation_map_t; + static allocation_map_t allocation_points; + static std::map > allocations; + static int allocation_point_index; + static libtorrent::ptime start_time; +}; + +boost::mutex memdebug::mutex; +int memdebug::allocation_point_index = 0; +std::ofstream memdebug::malloc_log; +std::ofstream memdebug::malloc_index_log; +void* (*memdebug::old_malloc_hook)(size_t, const void *) = 0; +void (*memdebug::old_free_hook)(void*, const void *) = 0; +memdebug::allocation_map_t memdebug::allocation_points; +std::map > memdebug::allocations; +libtorrent::ptime memdebug::start_time = time_now(); + +void* memdebug::my_malloc_hook(size_t size, const void *caller) +{ + boost::mutex::scoped_lock l(mutex); + /* Restore all old hooks */ + __malloc_hook = old_malloc_hook; + __free_hook = old_free_hook; + /* Call recursively */ + void* result = malloc(size); + /* Save underlying hooks */ + old_malloc_hook = __malloc_hook; + old_free_hook = __free_hook; + + stacktrace_t stack; + int stacksize = backtrace(&stack[0], stack.size()); + libtorrent::ptime now = time_now(); + + allocation_map_t::iterator i = allocation_points.lower_bound(stack); + if (i == allocation_points.end() || i->first != stack) + { + i = allocation_points.insert(i, std::make_pair(stack, allocation_point_t())); + i->second.index = allocation_point_index++; + i->second.allocated = size; + + malloc_index_log << i->second.index << "#"; + char** symbols = backtrace_symbols(&stack[0], stacksize); + for (int j = 2; j < stacksize; ++j) + malloc_index_log << demangle(symbols[j]) << "#"; + malloc_index_log << std::endl; + } + else + { + allocation_point_t& ap = i->second; + ap.spacetime += libtorrent::total_milliseconds(now - ap.last_update) * ap.allocated; + ap.allocated += size; + if (ap.allocated > ap.peak_allocated) ap.peak_allocated = ap.allocated; + ap.last_update = now; + } + allocation_point_t& ap = i->second; + + allocations[result] = std::make_pair(i, size); + malloc_log << "#" << ap.index << " " + << libtorrent::total_milliseconds(time_now() - start_time) << " A " + << result << " " << size << " " << ap.allocated << " " << ap.spacetime + << " " << ap.peak_allocated << std::endl; + + /* Restore our own hooks */ + __malloc_hook = my_malloc_hook; + __free_hook = my_free_hook; + return result; +} + +void memdebug::my_free_hook(void *ptr, const void *caller) +{ + boost::mutex::scoped_lock l(mutex); + /* Restore all old hooks */ + __malloc_hook = old_malloc_hook; + __free_hook = old_free_hook; + /* Call recursively */ + free(ptr); + /* Save underlying hooks */ + old_malloc_hook = __malloc_hook; + old_free_hook = __free_hook; + + std::map >::iterator i + = allocations.find(ptr); + + if (i != allocations.end()) + { + allocation_point_t& ap = i->second.first->second; + int size = i->second.second; + ap.allocated -= size; + malloc_log << "#" << ap.index << " " + << libtorrent::total_milliseconds(time_now() - start_time) << " F " + << ptr << " " << size << " " << ap.allocated << " " << ap.spacetime + << " " << ap.peak_allocated << std::endl; + + allocations.erase(i); + } + + /* Restore our own hooks */ + __malloc_hook = my_malloc_hook; + __free_hook = my_free_hook; +} + +static int ref_count = 0; + +void start_malloc_debug() +{ + boost::mutex::scoped_lock l(memdebug::mutex); + static memdebug mi; + ++ref_count; +} + +void stop_malloc_debug() +{ + boost::mutex::scoped_lock l(memdebug::mutex); + if (--ref_count == 0) + { + __malloc_hook = memdebug::old_malloc_hook; + __free_hook = memdebug::old_free_hook; + } +} + +#endif + diff --git a/libtorrent/src/metadata_transfer.cpp b/libtorrent/src/metadata_transfer.cpp index 0d1aea195..f5dcab181 100644 --- a/libtorrent/src/metadata_transfer.cpp +++ b/libtorrent/src/metadata_transfer.cpp @@ -185,7 +185,16 @@ namespace libtorrent { namespace } entry metadata = bdecode(m_metadata.begin(), m_metadata.end()); - m_torrent.set_metadata(metadata); + std::string error; + if (!m_torrent.set_metadata(metadata, error)) + { + // this means the metadata is correct, since we + // verified it against the info-hash, but we + // failed to parse it. Pause the torrent + // TODO: Post an alert! + m_torrent.pause(); + return false; + } // clear the storage for the bitfield std::vector().swap(m_have_metadata); @@ -272,17 +281,14 @@ namespace libtorrent { namespace // called when the extension handshake from the other end is received virtual bool on_extension_handshake(entry const& h) { - entry const& messages = h["m"]; - if (entry const* index = messages.find_key("LT_metadata")) - { - m_message_index = int(index->integer()); - return true; - } - else - { - m_message_index = 0; - return false; - } + m_message_index = 0; + entry const* messages = h.find_key("m"); + if (!messages || messages->type() != entry::dictionary_t) return false; + + entry const* index = messages->find_key("LT_metadata"); + if (!index || index->type() != entry::int_t) return false; + m_message_index = int(index->integer()); + return true; } void write_metadata_request(std::pair req) @@ -367,7 +373,10 @@ namespace libtorrent { namespace if (m_message_index == 0) return false; if (length > 500 * 1024) - throw protocol_error("LT_metadata message larger than 500 kB"); + { + m_pc.disconnect("LT_metadata message larger than 500 kB"); + return true; + } if (body.left() < 1) return true; int type = detail::read_uint8(body.begin); @@ -383,7 +392,8 @@ namespace libtorrent { namespace if (length != 3) { // invalid metadata request - throw protocol_error("invalid metadata request"); + m_pc.disconnect("invalid metadata request"); + return true; } write_metadata(std::make_pair(start, size)); @@ -398,13 +408,25 @@ namespace libtorrent { namespace int data_size = length - 9; if (total_size > 500 * 1024) - throw protocol_error("metadata size larger than 500 kB"); + { + m_pc.disconnect("metadata size larger than 500 kB"); + return true; + } if (total_size <= 0) - throw protocol_error("invalid metadata size"); + { + m_pc.disconnect("invalid metadata size"); + return true; + } if (offset > total_size || offset < 0) - throw protocol_error("invalid metadata offset"); + { + m_pc.disconnect("invalid metadata offset"); + return true; + } if (offset + data_size > total_size) - throw protocol_error("invalid metadata message"); + { + m_pc.disconnect("invalid metadata message"); + return true; + } m_tp.metadata_progress(total_size , body.left() - m_metadata_progress); @@ -425,8 +447,11 @@ namespace libtorrent { namespace m_waiting_metadata_request = false; break; default: - throw protocol_error("unknown metadata extension message: " - + boost::lexical_cast(type)); + { + std::stringstream msg; + msg << "unknown metadata extension message: " << type; + m_pc.disconnect(msg.str().c_str()); + } } return true; } diff --git a/libtorrent/src/natpmp.cpp b/libtorrent/src/natpmp.cpp index 38319d18f..dea34516e 100644 --- a/libtorrent/src/natpmp.cpp +++ b/libtorrent/src/natpmp.cpp @@ -43,15 +43,6 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; using namespace libtorrent; -enum { num_mappings = 2 }; - -namespace libtorrent -{ - // defined in upnp.cpp - bool is_local(address const& a); - address guess_local_address(asio::io_service&); -} - natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb) : m_callback(cb) , m_currently_mapping(-1) @@ -59,99 +50,180 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac , m_socket(ios) , m_send_timer(ios) , m_refresh_timer(ios) + , m_next_refresh(-1) , m_disabled(false) { - m_mappings[0].protocol = 2; // tcp - m_mappings[1].protocol = 1; // udp - #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif rebind(listen_interface); } -void natpmp::rebind(address const& listen_interface) try +void natpmp::rebind(address const& listen_interface) { - address local = address_v4::any(); - if (listen_interface != address_v4::any()) - { - local = listen_interface; - } - else - { - local = guess_local_address(m_socket.io_service()); - - if (local == address_v4::any()) - { - throw std::runtime_error("local host is probably not on a NATed " - "network. disabling NAT-PMP"); - } - } + mutex_t::scoped_lock l(m_mutex); + asio::error_code ec; + address gateway = get_default_gateway(m_socket.get_io_service(), listen_interface, ec); + if (ec) + { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << time_now_string() - << " local ip: " << local.to_string() << std::endl; + m_log << time_now_string() << " failed to find default router: " + << ec.message() << std::endl; #endif - - if (!is_local(local)) - { - // the local address seems to be an external - // internet address. Assume it is not behind a NAT - throw std::runtime_error("local IP is not on a local network"); + disable("failed to find default router"); + return; } m_disabled = false; - asio::error_code ec; - udp::endpoint nat_endpoint(router_for_interface(local, ec), 5351); - if (ec) - throw std::runtime_error("cannot retrieve router address"); - + udp::endpoint nat_endpoint(gateway, 5351); if (nat_endpoint == m_nat_endpoint) return; m_nat_endpoint = nat_endpoint; #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "assuming router is at: " << m_nat_endpoint.address().to_string() << std::endl; + m_log << time_now_string() << " found router at: " + << m_nat_endpoint.address() << std::endl; #endif - m_socket.open(udp::v4()); - m_socket.bind(udp::endpoint(address_v4::any(), 0)); - - for (int i = 0; i < num_mappings; ++i) + m_socket.open(udp::v4(), ec); + if (ec) { - if (m_mappings[i].local_port == 0) + disable(ec.message().c_str()); + return; + } + m_socket.bind(udp::endpoint(address_v4::any(), 0), ec); + if (ec) + { + disable(ec.message().c_str()); + return; + } + + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol != none + || i->action != mapping_t::action_none) continue; - refresh_mapping(i); + i->action = mapping_t::action_add; + update_mapping(i - m_mappings.begin()); } } -catch (std::exception& e) + +void natpmp::disable(char const* message) { m_disabled = true; - std::stringstream msg; - msg << "NAT-PMP disabled: " << e.what(); -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << msg.str() << std::endl; -#endif - m_callback(0, 0, msg.str()); -}; -void natpmp::set_mappings(int tcp, int udp) + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none) continue; + i->protocol = none; + m_callback(i - m_mappings.begin(), 0, message); + } + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() << " NAT-PMP disabled: " << message << std::endl; +#endif + close(); +} +void natpmp::delete_mapping(int index) { - if (m_disabled) return; - update_mapping(0, tcp); - update_mapping(1, udp); + TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0); + if (index >= int(m_mappings.size()) || index < 0) return; + mapping_t& m = m_mappings[index]; + + if (m.protocol == none) return; + + m.action = mapping_t::action_delete; + update_mapping(index); } -void natpmp::update_mapping(int i, int port) +int natpmp::add_mapping(protocol_type p, int external_port, int local_port) { - natpmp::mapping& m = m_mappings[i]; - if (port <= 0) return; - if (m.local_port != port) - m.need_update = true; + mutex_t::scoped_lock l(m_mutex); - m.local_port = port; - // prefer the same external port as the local port - if (m.external_port == 0) m.external_port = port; + if (m_disabled) return -1; + + std::vector::iterator i = std::find_if(m_mappings.begin() + , m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none)); + if (i == m_mappings.end()) + { + m_mappings.push_back(mapping_t()); + i = m_mappings.end() - 1; + } + i->protocol = p; + i->external_port = external_port; + i->local_port = local_port; + i->action = mapping_t::action_add; + + int mapping_index = i - m_mappings.begin(); + + update_mapping(mapping_index); + return mapping_index; +} + +void natpmp::try_next_mapping(int i) +{ +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() << " try_next_mapping [ " << i << " ]" << std::endl; +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + ptime now = time_now(); + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + m_log << " " << (i - m_mappings.begin()) << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; + } +#endif + + if (i < int(m_mappings.size()) - 1) + { + update_mapping(i + 1); + return; + } + + std::vector::iterator m = std::find_if( + m_mappings.begin(), m_mappings.end() + , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none)); + + if (m == m_mappings.end()) + { + if (m_abort) + { + asio::error_code ec; + m_send_timer.cancel(ec); + m_socket.close(ec); + } +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << " done" << (m_abort?" shutting down":"") << std::endl; +#endif + return; + } + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << " updating " << (m - m_mappings.begin()) << std::endl; +#endif + + update_mapping(m - m_mappings.begin()); +} + +void natpmp::update_mapping(int i) +{ + natpmp::mapping_t& m = m_mappings[i]; + if (m.action == mapping_t::action_none + || m.protocol == none) + { + try_next_mapping(i); + return; + } if (m_currently_mapping == -1) { @@ -164,14 +236,15 @@ void natpmp::update_mapping(int i, int port) } } -void natpmp::send_map_request(int i) try +void natpmp::send_map_request(int i) { using namespace libtorrent::detail; TORRENT_ASSERT(m_currently_mapping == -1 || m_currently_mapping == i); m_currently_mapping = i; - mapping& m = m_mappings[i]; + mapping_t& m = m_mappings[i]; + TORRENT_ASSERT(m.action != mapping_t::action_none); char buf[12]; char* out = buf; write_uint8(0, out); // NAT-PMP version @@ -179,36 +252,39 @@ void natpmp::send_map_request(int i) try write_uint16(0, out); // reserved write_uint16(m.local_port, out); // private port write_uint16(m.external_port, out); // requested public port - int ttl = m.external_port == 0 ? 0 : 3600; + int ttl = m.action == mapping_t::action_add ? 3600 : 0; write_uint32(ttl, out); // port mapping lifetime #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() - << " ==> port map request: " << (m.protocol == 1 ? "udp" : "tcp") + << " ==> port map [" + << " action: " << (m.action == mapping_t::action_add ? "add" : "delete") << " " + << " proto: " << (m.protocol == udp ? "udp" : "tcp") << " local: " << m.local_port << " external: " << m.external_port - << " ttl: " << ttl << std::endl; + << " ttl: " << ttl << " ]" << std::endl; #endif - m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint); + asio::error_code ec; + m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint, 0, ec); // linear back-off instead of exponential ++m_retry_count; - m_send_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_send_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); m_send_timer.async_wait(bind(&natpmp::resend_request, self(), i, _1)); } -catch (std::exception& e) -{ - std::string err = e.what(); -}; void natpmp::resend_request(int i, asio::error_code const& e) { if (e) return; + + mutex_t::scoped_lock l(m_mutex); if (m_currently_mapping != i) return; if (m_retry_count >= 9) { - m_mappings[i].need_update = false; + m_currently_mapping = -1; + m_mappings[i].action = mapping_t::action_none; // try again in two hours m_mappings[i].expires = time_now() + hours(2); + try_next_mapping(i); return; } send_map_request(i); @@ -220,179 +296,195 @@ void natpmp::on_reply(asio::error_code const& e using namespace libtorrent::detail; if (e) return; - try + if (m_remote != m_nat_endpoint) { - - if (m_remote != m_nat_endpoint) - { - m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) - , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); - return; - } - - m_send_timer.cancel(); - - TORRENT_ASSERT(m_currently_mapping >= 0); - int i = m_currently_mapping; - mapping& m = m_mappings[i]; - - char* in = m_response_buffer; - int version = read_uint8(in); - int cmd = read_uint8(in); - int result = read_uint16(in); - int time = read_uint32(in); - int private_port = read_uint16(in); - int public_port = read_uint16(in); - int lifetime = read_uint32(in); - - (void)time; // to remove warning - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << time_now_string() - << " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp") - << " local: " << private_port << " external: " << public_port - << " ttl: " << lifetime << std::endl; -#endif - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - if (version != 0) - { - m_log << "*** unexpected version: " << version << std::endl; - } -#endif - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - if (private_port != m.local_port) - { - m_log << "*** unexpected local port: " << private_port << std::endl; - } -#endif - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - if (cmd != 128 + m.protocol) - { - m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl; - } -#endif - - if (public_port == 0 || lifetime == 0) - { - // this means the mapping was - // successfully closed - m.local_port = 0; - } - else - { - m.expires = time_now() + seconds(int(lifetime * 0.7f)); - m.external_port = public_port; - } - - if (result != 0) - { -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "*** ERROR: " << result << std::endl; -#endif - std::stringstream errmsg; - errmsg << "NAT router reports error (" << result << ") "; - switch (result) - { - case 1: errmsg << "Unsupported protocol version"; break; - case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break; - case 3: errmsg << "Network failure"; break; - case 4: errmsg << "Out of resources"; break; - case 5: errmsg << "Unsupported opcode"; break; - } - throw std::runtime_error(errmsg.str()); - } - - // don't report when we remove mappings - if (m.local_port != 0) - { - int tcp_port = 0; - int udp_port = 0; - if (m.protocol == 1) udp_port = m.external_port; - else tcp_port = public_port; - m_callback(tcp_port, udp_port, ""); - } - } - catch (std::exception& e) - { - // try again in two hours - m_mappings[m_currently_mapping].expires = time_now() + hours(2); - m_callback(0, 0, e.what()); + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); + return; } + + mutex_t::scoped_lock l(m_mutex); + + asio::error_code ec; + m_send_timer.cancel(ec); + + TORRENT_ASSERT(m_currently_mapping >= 0); int i = m_currently_mapping; + mapping_t& m = m_mappings[i]; + + char* in = m_response_buffer; + int version = read_uint8(in); + int cmd = read_uint8(in); + int result = read_uint16(in); + int time = read_uint32(in); + int private_port = read_uint16(in); + int public_port = read_uint16(in); + int lifetime = read_uint32(in); + + (void)time; // to remove warning + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() + << " <== port map [" + << " protocol: " << (cmd - 128 == 1 ? "udp" : "tcp") + << " local: " << private_port << " external: " << public_port + << " ttl: " << lifetime << " ]" << std::endl; +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (version != 0) + { + m_log << "*** unexpected version: " << version << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (private_port != m.local_port) + { + m_log << "*** unexpected local port: " << private_port << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (cmd != 128 + m.protocol) + { + m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl; + } +#endif + + if (public_port == 0 || lifetime == 0) + { + // this means the mapping was + // successfully closed + m.protocol = none; + } + else + { + m.expires = time_now() + seconds(int(lifetime * 0.7f)); + m.external_port = public_port; + } + + if (result != 0) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "*** ERROR: " << result << std::endl; +#endif + std::stringstream errmsg; + errmsg << "NAT router reports error (" << result << ") "; + switch (result) + { + case 1: errmsg << "Unsupported protocol version"; break; + case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break; + case 3: errmsg << "Network failure"; break; + case 4: errmsg << "Out of resources"; break; + case 5: errmsg << "Unsupported opcode"; break; + } + m.expires = time_now() + hours(2); + m_callback(i, 0, errmsg.str()); + } + else if (m.action == mapping_t::action_add) + { + m_callback(i, m.external_port, ""); + } + m_currently_mapping = -1; - m_mappings[i].need_update = false; - m_send_timer.cancel(); + m.action = mapping_t::action_none; + m_send_timer.cancel(ec); update_expiration_timer(); try_next_mapping(i); } void natpmp::update_expiration_timer() { + if (m_abort) return; + ptime now = time_now(); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() << " update_expiration_timer " << std::endl; + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + m_log << " " << (i - m_mappings.begin()) << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; + } +#endif + ptime min_expire = now + seconds(3600); int min_index = -1; - for (int i = 0; i < num_mappings; ++i) - if (m_mappings[i].expires < min_expire - && m_mappings[i].local_port != 0) + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none + || i->action != mapping_t::action_none) continue; + if (i->expires < min_expire) { - min_expire = m_mappings[i].expires; - min_index = i; + min_expire = i->expires; + min_index = i - m_mappings.begin(); } + } + + // this is already the mapping we're waiting for + if (m_next_refresh == min_index) return; if (min_index >= 0) { - m_refresh_timer.expires_from_now(min_expire - now); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() << " next expiration [" + " i: " << min_index + << " ttl: " << total_seconds(min_expire - time_now()) + << " ]" << std::endl; +#endif + asio::error_code ec; + if (m_next_refresh >= 0) m_refresh_timer.cancel(ec); + m_refresh_timer.expires_from_now(min_expire - now, ec); m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index)); + m_next_refresh = min_index; } } void natpmp::mapping_expired(asio::error_code const& e, int i) { if (e) return; + mutex_t::scoped_lock l(m_mutex); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "*** mapping " << i << " expired, updating" << std::endl; + m_log << time_now_string() << " mapping expired [ i: " << i << " ]" << std::endl; #endif - refresh_mapping(i); -} - -void natpmp::refresh_mapping(int i) -{ - m_mappings[i].need_update = true; - if (m_currently_mapping == -1) - { - // the socket is not currently in use - // send out a mapping request - m_retry_count = 0; - send_map_request(i); - m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) - , m_remote, bind(&natpmp::on_reply, self(), _1, _2)); - } -} - -void natpmp::try_next_mapping(int i) -{ - ++i; - if (i >= num_mappings) i = 0; - if (m_mappings[i].need_update) - refresh_mapping(i); + m_mappings[i].action = mapping_t::action_add; + if (m_next_refresh == i) m_next_refresh = -1; + update_mapping(i); } void natpmp::close() { + mutex_t::scoped_lock l(m_mutex); + m_abort = true; asio::error_code ec; - m_socket.close(ec); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << time_now_string() << " close" << std::endl; +#endif if (m_disabled) return; - for (int i = 0; i < num_mappings; ++i) + ptime now = time_now(); + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) { - if (m_mappings[i].local_port == 0) - continue; - m_mappings[i].external_port = 0; - refresh_mapping(i); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << " " << (i - m_mappings.begin()) << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; +#endif + if (i->protocol == none) continue; + i->action = mapping_t::action_delete; } - m_refresh_timer.cancel(); - m_send_timer.cancel(); + m_refresh_timer.cancel(ec); + update_mapping(0); } diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index b105b63cb..1dae48110 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -59,7 +59,6 @@ using libtorrent::aux::session_impl; namespace libtorrent { - // outbound connection peer_connection::peer_connection( session_impl& ses @@ -81,6 +80,8 @@ namespace libtorrent , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) + , m_disk_recv_buffer_size(0) + , m_disk_recv_buffer(0) , m_reading_bytes(0) , m_last_receive(time_now()) , m_last_sent(time_now()) @@ -105,8 +106,6 @@ namespace libtorrent , m_became_uninteresting(time_now()) , m_connecting(true) , m_queued(true) - , m_writing(false) - , m_reading(false) , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_priority(1) @@ -120,25 +119,45 @@ namespace libtorrent , m_remote_dl_update(time_now()) , m_outstanding_writing_bytes(0) , m_fast_reconnect(false) + , m_rtt(0) + , m_downloaded_at_last_unchoke(0) + , m_download_rate_peak(0) + , m_upload_rate_peak(0) #ifndef NDEBUG , m_in_constructor(true) #endif { + m_channel_state[upload_channel] = peer_info::bw_idle; + m_channel_state[download_channel] = peer_info::bw_idle; + + TORRENT_ASSERT(peerinfo == 0 || peerinfo->banned == false); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES std::fill(m_country, m_country + 2, 0); +#ifndef TORRENT_DISABLE_GEO_IP + if (m_ses.has_country_db()) + { + char const *country = m_ses.country_for_ip(m_remote.address()); + if (country != 0) + { + m_country[0] = country[0]; + m_country[1] = country[1]; + } + } #endif -#ifdef TORRENT_VERBOSE_LOGGING +#endif +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING m_logger = m_ses.create_log(m_remote.address().to_string() + "_" + boost::lexical_cast(m_remote.port()), m_ses.listen_port()); (*m_logger) << "*** OUTGOING CONNECTION\n"; #endif +#ifndef NDEBUG + piece_failed = false; +#endif +#ifndef TORRENT_DISABLE_GEO_IP + m_inet_as_name = m_ses.as_name_for_ip(m_remote.address()); +#endif - boost::shared_ptr t = m_torrent.lock(); - TORRENT_ASSERT(t); std::fill(m_peer_id.begin(), m_peer_id.end(), 0); - - if (t->ready_for_connections()) - init(); } // incoming connection @@ -160,6 +179,8 @@ namespace libtorrent , m_last_unchoke(min_time()) , m_packet_size(0) , m_recv_pos(0) + , m_disk_recv_buffer_size(0) + , m_disk_recv_buffer(0) , m_reading_bytes(0) , m_last_receive(time_now()) , m_last_sent(time_now()) @@ -182,8 +203,6 @@ namespace libtorrent , m_became_uninteresting(time_now()) , m_connecting(false) , m_queued(false) - , m_writing(false) - , m_reading(false) , m_prefer_whole_pieces(false) , m_request_large_blocks(false) , m_priority(1) @@ -197,27 +216,103 @@ namespace libtorrent , m_remote_dl_update(time_now()) , m_outstanding_writing_bytes(0) , m_fast_reconnect(false) + , m_rtt(0) + , m_downloaded_at_last_unchoke(0) + , m_download_rate_peak(0) + , m_upload_rate_peak(0) #ifndef NDEBUG , m_in_constructor(true) #endif { - tcp::socket::non_blocking_io ioc(true); - m_socket->io_control(ioc); + m_channel_state[upload_channel] = peer_info::bw_idle; + m_channel_state[download_channel] = peer_info::bw_idle; + #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES std::fill(m_country, m_country + 2, 0); +#ifndef TORRENT_DISABLE_GEO_IP + if (m_ses.has_country_db()) + { + char const *country = m_ses.country_for_ip(m_remote.address()); + if (country != 0) + { + m_country[0] = country[0]; + m_country[1] = country[1]; + } + } +#endif #endif - m_remote = m_socket->remote_endpoint(); -#ifdef TORRENT_VERBOSE_LOGGING - TORRENT_ASSERT(m_socket->remote_endpoint() == remote()); - m_logger = m_ses.create_log(remote().address().to_string() + "_" +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + asio::error_code ec; + TORRENT_ASSERT(m_socket->remote_endpoint() == remote() || ec); + m_logger = m_ses.create_log(remote().address().to_string(ec) + "_" + boost::lexical_cast(remote().port()), m_ses.listen_port()); (*m_logger) << "*** INCOMING CONNECTION\n"; #endif +#ifndef TORRENT_DISABLE_GEO_IP + m_inet_as_name = m_ses.as_name_for_ip(m_remote.address()); +#endif +#ifndef NDEBUG + piece_failed = false; +#endif std::fill(m_peer_id.begin(), m_peer_id.end(), 0); } + bool peer_connection::unchoke_compare(boost::intrusive_ptr const& p) const + { + TORRENT_ASSERT(p); + peer_connection const& rhs = *p; + + // first compare how many bytes they've sent us + size_type c1 = m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; + size_type c2 = rhs.m_statistics.total_payload_download() - rhs.m_downloaded_at_last_unchoke; + if (c1 > c2) return true; + if (c1 < c2) return false; + + // if they are equal, compare how much we have uploaded + if (m_peer_info) c1 = m_peer_info->total_upload(); + else c1 = m_statistics.total_payload_upload(); + if (rhs.m_peer_info) c2 = rhs.m_peer_info->total_upload(); + else c2 = rhs.m_statistics.total_payload_upload(); + + return c1 < c2; + } + + void peer_connection::reset_choke_counters() + { + m_downloaded_at_last_unchoke = m_statistics.total_payload_download(); + } + + void peer_connection::start() + { + boost::shared_ptr t = m_torrent.lock(); + + if (!t) + { + tcp::socket::non_blocking_io ioc(true); + asio::error_code ec; + m_socket->io_control(ioc, ec); + if (ec) + { + disconnect(ec.message().c_str()); + return; + } + m_remote = m_socket->remote_endpoint(ec); + if (ec) + { + disconnect(ec.message().c_str()); + return; + } + if (m_remote.address().is_v4()) + m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec); + } + else if (t->ready_for_connections()) + { + init(); + } + } + void peer_connection::update_interest() { INVARIANT_CHECK; @@ -328,12 +423,15 @@ namespace libtorrent TORRENT_ASSERT(t->ready_for_connections()); m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); + if (m_have_all) m_num_pieces = t->torrent_file().num_pieces(); // now that we have a piece_picker, - // update it with this peers pieces + // update it with this peer's pieces - int num_pieces = std::count(m_have_piece.begin(), m_have_piece.end(), true); - if (num_pieces == int(m_have_piece.size())) + TORRENT_ASSERT(m_num_pieces == std::count(m_have_piece.begin() + , m_have_piece.end(), true)); + + if (m_num_pieces == int(m_have_piece.size())) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " *** THIS IS A SEED ***\n"; @@ -343,25 +441,24 @@ namespace libtorrent // if we're a seed too, disconnect if (t->is_finished()) { - throw std::runtime_error("seed to seed connection redundant, disconnecting"); + disconnect("seed to seed connection redundant"); + return; } - m_num_pieces = num_pieces; t->peer_has_all(); if (!t->is_finished()) t->get_policy().peer_is_interesting(*this); return; } - m_num_pieces = num_pieces; // if we're a seed, we don't keep track of piece availability if (!t->is_seed()) { + t->peer_has(m_have_piece); bool interesting = false; for (int i = 0; i < int(m_have_piece.size()); ++i) { if (m_have_piece[i]) { - t->peer_has(i); // if the peer has a piece and we don't, the peer is interesting if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) @@ -379,14 +476,25 @@ namespace libtorrent TORRENT_ASSERT(!m_in_constructor); TORRENT_ASSERT(m_disconnecting); -#ifdef TORRENT_VERBOSE_LOGGING + if (m_disk_recv_buffer) + { + m_ses.free_disk_buffer(m_disk_recv_buffer); + m_disk_recv_buffer = 0; + m_disk_recv_buffer_size = 0; + } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING if (m_logger) { (*m_logger) << time_now_string() << " *** CONNECTION CLOSED\n"; } #endif + TORRENT_ASSERT(!m_ses.has_peer(this)); #ifndef NDEBUG + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer(this)); if (m_peer_info) TORRENT_ASSERT(m_peer_info->connection == 0); @@ -396,12 +504,13 @@ namespace libtorrent void peer_connection::fast_reconnect(bool r) { - if (peer_info_struct() && peer_info_struct()->fast_reconnects > 1) return; + if (!peer_info_struct() || peer_info_struct()->fast_reconnects > 1) + return; m_fast_reconnect = r; peer_info_struct()->connected = time_now() - seconds(m_ses.settings().min_reconnect_time * m_ses.settings().max_failcount); - if (peer_info_struct()) ++peer_info_struct()->fast_reconnects; + ++peer_info_struct()->fast_reconnects; } void peer_connection::announce_piece(int index) @@ -476,7 +585,11 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { +#ifdef BOOST_NO_EXCEPTIONS + (*i)->on_piece_pass(index); +#else try { (*i)->on_piece_pass(index); } catch (std::exception&) {} +#endif } #endif } @@ -489,7 +602,11 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { +#ifdef BOOST_NO_EXCEPTIONS + (*i)->on_piece_failed(index); +#else try { (*i)->on_piece_failed(index); } catch (std::exception&) {} +#endif } #endif @@ -497,7 +614,7 @@ namespace libtorrent { peer_info_struct()->on_parole = true; ++peer_info_struct()->hashfails; - int& trust_points = peer_info_struct()->trust_points; + boost::int8_t& trust_points = peer_info_struct()->trust_points; // we decrease more than we increase, to keep the // allowed failed/passed ratio low. @@ -558,7 +675,7 @@ namespace libtorrent if (t && t->is_aborted()) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << " *** the torrent has been aborted\n"; #endif t.reset(); @@ -567,7 +684,7 @@ namespace libtorrent if (!t) { // we couldn't find the torrent! -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << " *** couldn't find a torrent with the given info_hash: " << ih << "\n"; (*m_logger) << " torrents:\n"; session_impl::torrent_map const& torrents = m_ses.m_torrents; @@ -577,23 +694,37 @@ namespace libtorrent (*m_logger) << " " << i->second->torrent_file().info_hash() << "\n"; } #endif - throw std::runtime_error("got info-hash that is not in our session"); + disconnect("got invalid info-hash"); + return; } if (t->is_paused()) { // paused torrents will not accept // incoming connections -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << " rejected connection to paused torrent\n"; #endif - throw std::runtime_error("connection rejected by paused torrent"); + disconnect("connection rejected bacause torrent is paused"); + return; } TORRENT_ASSERT(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. +#ifndef NDEBUG + try + { +#endif t->attach_peer(this); +#ifndef NDEBUG + } + catch (std::exception& e) + { + std::cout << e.what() << std::endl; + TORRENT_ASSERT(false); + } +#endif if (m_disconnecting) return; m_torrent = wpt; @@ -726,7 +857,7 @@ namespace libtorrent p.abort_download(b); } } -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING else { (*m_logger) << time_now_string() @@ -900,14 +1031,34 @@ namespace libtorrent << " <== HAVE [ piece: " << index << "]\n"; #endif + if (!t->valid_metadata() && index > int(m_have_piece.size())) + { + if (index < 65536) + { + // if we don't have metadata + // and we might not have received a bitfield + // extend the bitmask to fit the new + // have message + m_have_piece.resize(index + 1, false); + } + else + { + // unless the index > 64k, in which case + // we just ignore it + return; + } + } + // 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 (index >= int(m_have_piece.size()) || index < 0) + { + disconnect("got 'have'-message with higher index than the number of pieces"); + return; + } if (m_have_piece[index]) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << " got redundant HAVE message for index: " << index << "\n"; #endif } @@ -947,7 +1098,8 @@ namespace libtorrent m_peer_info->seed = true; if (t->is_finished()) { - throw protocol_error("seed to seed connection redundant, disconnecting"); + disconnect("seed to seed connection redundant"); + return; } } } @@ -987,11 +1139,14 @@ namespace libtorrent // verify the bitfield size if (t->valid_metadata() && (bitfield.size() / 8) != (m_have_piece.size() / 8)) - throw protocol_error("got bitfield with invalid size: " - + boost::lexical_cast(bitfield.size() / 8) - + "bytes. expected: " - + boost::lexical_cast(m_have_piece.size() / 8) - + "bytes"); + { + std::stringstream msg; + msg << "got bitfield with invalid size: " << (bitfield.size() / 8) + << "bytes. expected: " << (m_have_piece.size() / 8) + << " bytes"; + disconnect(msg.str().c_str()); + return; + } // if we don't have metadata yet // just remember the bitmask @@ -1018,7 +1173,8 @@ namespace libtorrent // if we're a seed too, disconnect if (t->is_finished()) { - throw protocol_error("seed to seed connection redundant, disconnecting"); + disconnect("seed to seed connection redundant, disconnecting"); + return; } std::fill(m_have_piece.begin(), m_have_piece.end(), true); @@ -1032,49 +1188,31 @@ namespace libtorrent // let the torrent know which pieces the // peer has // if we're a seed, we don't keep track of piece availability + bool interesting = false; if (!t->is_seed()) { - bool interesting = false; + t->peer_has(bitfield); + for (int i = 0; i < (int)m_have_piece.size(); ++i) { bool have = bitfield[i]; if (have && !m_have_piece[i]) { - m_have_piece[i] = true; - ++m_num_pieces; - t->peer_has(i); if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) interesting = true; } else if (!have && m_have_piece[i]) { // this should probably not be allowed - m_have_piece[i] = false; - --m_num_pieces; t->peer_lost(i); } } + } - if (interesting) t->get_policy().peer_is_interesting(*this); - } - else - { - for (int i = 0; i < (int)m_have_piece.size(); ++i) - { - bool have = bitfield[i]; - if (have && !m_have_piece[i]) - { - m_have_piece[i] = true; - ++m_num_pieces; - } - else if (!have && m_have_piece[i]) - { - // this should probably not be allowed - m_have_piece[i] = false; - --m_num_pieces; - } - } - } + m_have_piece = bitfield; + m_num_pieces = num_pieces; + + if (interesting) t->get_policy().peer_is_interesting(*this); } // ----------------------------- @@ -1100,7 +1238,7 @@ namespace libtorrent { // if we don't have valid metadata yet, // we shouldn't get a request -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " <== UNEXPECTED_REQUEST [ " "piece: " << r.piece << " | " @@ -1109,32 +1247,32 @@ namespace libtorrent "i: " << m_peer_interested << " | " "t: " << t->torrent_file().piece_size(r.piece) << " | " "n: " << t->torrent_file().num_pieces() << " ]\n"; -#endif - write_reject_request(r); - return; - } - - if (int(m_requests.size()) > m_ses.settings().max_allowed_in_request_queue) - { - // don't allow clients to abuse our - // memory consumption. - // ignore requests if the client - // is making too many of them. -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " <== TOO MANY REQUESTS [ " - "piece: " << r.piece << " | " - "s: " << r.start << " | " - "l: " << r.length << " | " - "i: " << m_peer_interested << " | " - "t: " << t->torrent_file().piece_size(r.piece) << " | " - "n: " << t->torrent_file().num_pieces() << " ]\n"; (*m_logger) << time_now_string() << " ==> REJECT_PIECE [ " "piece: " << r.piece << " | " "s: " << r.start << " | " "l: " << r.length << " ]\n"; +#endif + write_reject_request(r); + return; + } + + if (int(m_requests.size()) > m_ses.settings().max_allowed_in_request_queue) + { + // don't allow clients to abuse our + // memory consumption. + // ignore requests if the client + // is making too many of them. +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_logger) << time_now_string() + << " <== TOO MANY REQUESTS [ " + "piece: " << r.piece << " | " + "s: " << r.start << " | " + "l: " << r.length << " | " + "i: " << m_peer_interested << " | " + "t: " << t->torrent_file().piece_size(r.piece) << " | " + "n: " << t->torrent_file().num_pieces() << " ]\n"; (*m_logger) << time_now_string() << " ==> REJECT_PIECE [ " @@ -1168,7 +1306,7 @@ namespace libtorrent if (m_choked && m_accept_fast.find(r.piece) == m_accept_fast.end()) { write_reject_request(r); -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]\n"; (*m_logger) << time_now_string() @@ -1187,7 +1325,7 @@ namespace libtorrent } else { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " <== INVALID_REQUEST [ " "piece: " << r.piece << " | " @@ -1262,12 +1400,27 @@ namespace libtorrent // ----------------------------- void peer_connection::incoming_piece(peer_request const& p, char const* data) + { + char* buffer = m_ses.allocate_disk_buffer(); + if (buffer == 0) + { + disconnect("out of memory"); + return; + } + disk_buffer_holder holder(m_ses, buffer); + incoming_piece(p, holder); + } + + void peer_connection::incoming_piece(peer_request const& p, disk_buffer_holder& data) { INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); + TORRENT_ASSERT(m_disk_recv_buffer == 0); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -1278,8 +1431,10 @@ namespace libtorrent #ifndef NDEBUG check_postcondition post_checker_(t); +#if !defined TORRENT_DISABLE_INVARIANT_CHECKS t->check_invariant(); #endif +#endif #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() @@ -1292,13 +1447,14 @@ namespace libtorrent if (!verify_piece(p)) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " <== INVALID_PIECE [ piece: " << p.piece << " | " "start: " << p.start << " | " "length: " << p.length << " ]\n"; #endif - throw protocol_error("got invalid piece packet"); + disconnect("got invalid piece packet"); + return; } // if we're already seeding, don't bother, @@ -1324,33 +1480,7 @@ namespace libtorrent , m_download_queue.end() , block_finished); - if (b != m_download_queue.end()) - { - if (m_assume_fifo) - { - for (std::deque::iterator i = m_download_queue.begin(); - i != b; ++i) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() - << " *** 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 - // 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(), b); - b = m_download_queue.begin(); - TORRENT_ASSERT(*b == block_finished); - } - } - else + if (b == m_download_queue.end()) { if (t->alerts().should_post(alert::debug)) { @@ -1360,7 +1490,7 @@ namespace libtorrent , m_peer_id , "got a block that was not in the request queue")); } -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << " *** The block we just got was not in the " "request queue ***\n"; #endif @@ -1370,6 +1500,30 @@ namespace libtorrent return; } + if (m_assume_fifo) + { + for (std::deque::iterator i = m_download_queue.begin(); + i != b; ++i) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_logger) << time_now_string() + << " *** 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 + // 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(), b); + b = m_download_queue.begin(); + TORRENT_ASSERT(*b == block_finished); + } + // if the block we got is already finished, then ignore it if (picker.is_downloaded(block_finished)) { @@ -1384,18 +1538,17 @@ namespace libtorrent fs.async_write(p, data, bind(&peer_connection::on_disk_write_complete , self(), _1, _2, p, t)); m_outstanding_writing_bytes += p.length; - TORRENT_ASSERT(!m_reading); + TORRENT_ASSERT(m_channel_state[download_channel] == peer_info::bw_idle); m_download_queue.erase(b); // did we request this block from any other peers? bool multi = picker.num_peers(block_finished) > 1; - picker.mark_as_writing(block_finished, peer_info_struct()); // if we requested this block from other peers, cancel it now if (multi) t->cancel_block(block_finished); -#ifndef NDEBUG +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS t->check_invariant(); #endif request_a_block(*t, *this); @@ -1413,8 +1566,8 @@ namespace libtorrent TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << time_now_string() << " *** DISK_WRITE_COMPLETE [ p: " - << p.piece << " o: " << p.start << " ]\n"; +// (*m_ses.m_logger) << time_now_string() << " *** DISK_WRITE_COMPLETE [ p: " +// << p.piece << " o: " << p.start << " ]\n"; #endif // in case the outstanding bytes just dropped down // to allow to receive more data @@ -1424,18 +1577,17 @@ namespace libtorrent if (ret == -1 || !t) { - if (t->has_picker()) t->picker().abort_download(block_finished); + if (t->has_picker()) t->picker().write_failed(block_finished); if (!t) { - m_ses.connection_failed(self(), remote(), j.str.c_str()); + disconnect(j.str.c_str()); return; } if (t->alerts().should_post(alert::fatal)) { - std::string err = "torrent paused: disk write error, " + j.str; - t->alerts().post_alert(file_error_alert(t->get_handle(), err)); + t->alerts().post_alert(file_error_alert(j.error_file, t->get_handle(), j.str)); } t->pause(); return; @@ -1454,11 +1606,6 @@ namespace libtorrent block_finished.block_index, block_finished.piece_index, "block finished")); } -#ifndef NDEBUG - try - { -#endif - // did we just finish the piece? if (picker.is_piece_finished(p.piece)) { @@ -1469,15 +1616,6 @@ namespace libtorrent , p.piece, _1)); } -#ifndef NDEBUG - } - catch (std::exception const& e) - { - std::cerr << e.what() << std::endl; - TORRENT_ASSERT(false); - } -#endif - if (!t->is_seed() && !m_torrent.expired()) { // this is a free function defined in policy.cpp @@ -1525,7 +1663,7 @@ namespace libtorrent } else { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** GOT CANCEL NOT IN THE QUEUE\n"; #endif } @@ -1594,7 +1732,10 @@ namespace libtorrent // if we're a seed too, disconnect if (t->is_finished()) - throw protocol_error("seed to seed connection redundant, disconnecting"); + { + disconnect("seed to seed connection redundant, disconnecting"); + return; + } TORRENT_ASSERT(!m_have_piece.empty()); std::fill(m_have_piece.begin(), m_have_piece.end(), true); @@ -1657,7 +1798,7 @@ namespace libtorrent if (index < 0 || index >= int(m_have_piece.size())) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " <== INVALID_ALLOWED_FAST [ " << index << " | s: " << int(m_have_piece.size()) << " ]\n"; #endif @@ -1715,7 +1856,6 @@ namespace libtorrent TORRENT_ASSERT(std::find(m_download_queue.begin(), m_download_queue.end(), block) == m_download_queue.end()); TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end(), block) == m_request_queue.end()); - piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); char const* speedmsg = 0; @@ -1985,26 +2125,27 @@ namespace libtorrent m_last_piece = time_now(); } - - void close_socket_ignore_error(boost::shared_ptr s) - { - try { s->close(); } catch (std::exception&) {} - } - void peer_connection::timed_out() { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string() + TORRENT_ASSERT(m_connecting); + TORRENT_ASSERT(m_connection_ticket >= 0); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " CONNECTION TIMED OUT: " << m_remote.address().to_string() << "\n"; #endif - m_ses.connection_failed(self(), m_remote, "timed out"); + set_failed(); + disconnect("timed out"); } - void peer_connection::disconnect() + void peer_connection::disconnect(char const* message) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - TORRENT_ASSERT(!m_in_constructor); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_logger) << "*** CONNECTION FAILED " << message << "\n"; +#endif + // we cannot do this in a constructor + TORRENT_ASSERT(m_in_constructor == false); boost::intrusive_ptr me(this); INVARIANT_CHECK; @@ -2018,6 +2159,15 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); + if (message && m_ses.m_alerts.should_post(alert::debug)) + { + m_ses.m_alerts.post_alert( + peer_error_alert( + remote() + , pid() + , message)); + } + if (t) { if (t->has_picker()) @@ -2040,9 +2190,18 @@ namespace libtorrent m_torrent.reset(); } +#ifndef NDEBUG + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer(this)); +#endif + m_disconnecting = true; - m_ses.close_connection(me); - m_ses.m_io_service.post(boost::bind(&close_socket_ignore_error, m_socket)); + asio::error_code ec; + m_socket->close(ec); + m_ses.close_connection(this, message); } void peer_connection::set_upload_limit(int limit) @@ -2096,6 +2255,9 @@ namespace libtorrent { TORRENT_ASSERT(!associated_torrent().expired()); + p.download_rate_peak = m_download_rate_peak; + p.upload_rate_peak = m_upload_rate_peak; + p.rtt = m_rtt; p.down_speed = statistics().download_rate(); p.up_speed = statistics().upload_rate(); p.payload_down_speed = statistics().download_payload_rate(); @@ -2103,6 +2265,11 @@ namespace libtorrent p.pid = pid(); p.ip = remote(); p.pending_disk_bytes = m_outstanding_writing_bytes; + p.send_quota = m_bandwidth_limit[upload_channel].quota_left(); + p.receive_quota = m_bandwidth_limit[download_channel].quota_left(); +#ifndef TORRENT_DISABLE_GEO_IP + p.inet_as_name = m_inet_as_name; +#endif #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES p.country[0] = m_country[0]; @@ -2155,12 +2322,15 @@ namespace libtorrent p.flags |= is_seed() ? peer_info::seed : 0; if (peer_info_struct()) { - p.source = peer_info_struct()->source; - p.failcount = peer_info_struct()->failcount; - p.num_hashfails = peer_info_struct()->hashfails; - p.flags |= peer_info_struct()->on_parole ? peer_info::on_parole : 0; - p.flags |= peer_info_struct()->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; - p.remote_dl_rate = m_remote_dl_rate; + policy::peer* pi = peer_info_struct(); + p.source = pi->source; + p.failcount = pi->failcount; + p.num_hashfails = pi->hashfails; + p.flags |= pi->on_parole ? peer_info::on_parole : 0; + p.flags |= pi->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; +#ifndef TORRENT_DISABLE_GEO_IP + p.inet_as = pi->inet_as->first; +#endif } else { @@ -2168,12 +2338,59 @@ namespace libtorrent p.failcount = 0; p.num_hashfails = 0; p.remote_dl_rate = 0; +#ifndef TORRENT_DISABLE_GEO_IP + p.inet_as = 0xffff; +#endif } + p.remote_dl_rate = m_remote_dl_rate; p.send_buffer_size = m_send_buffer.capacity(); p.used_send_buffer = m_send_buffer.size(); + p.receive_buffer_size = m_recv_buffer.capacity() + m_disk_recv_buffer_size; + p.used_receive_buffer = m_recv_pos; + p.write_state = m_channel_state[upload_channel]; + p.read_state = m_channel_state[download_channel]; } + // allocates a disk buffer of size 'disk_buffer_size' and replaces the + // end of the current receive buffer with it. i.e. the receive pos + // must be <= packet_size - disk_buffer_size + // the disk buffer can be accessed through release_disk_receive_buffer() + // when it is queried, the responsibility to free it is transferred + // to the caller + bool peer_connection::allocate_disk_receive_buffer(int disk_buffer_size) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_packet_size > 0); + TORRENT_ASSERT(m_recv_pos <= m_packet_size - disk_buffer_size); + TORRENT_ASSERT(m_disk_recv_buffer == 0); + TORRENT_ASSERT(disk_buffer_size <= 16 * 1024); + + if (disk_buffer_size > 16 * 1024) + { + disconnect("invalid piece size"); + return false; + } + + m_disk_recv_buffer = m_ses.allocate_disk_buffer(); + if (m_disk_recv_buffer == 0) + { + disconnect("out of memory"); + return false; + } + m_disk_recv_buffer_size = disk_buffer_size; + return true; + } + + char* peer_connection::release_disk_receive_buffer() + { + char* ret = m_disk_recv_buffer; + m_disk_recv_buffer = 0; + m_disk_recv_buffer_size = 0; + return ret; + } + void peer_connection::cut_receive_buffer(int size, int packet_size) { INVARIANT_CHECK; @@ -2193,20 +2410,22 @@ namespace libtorrent #endif m_packet_size = packet_size; - if (m_packet_size >= m_recv_pos) m_recv_buffer.resize(m_packet_size); } void peer_connection::second_tick(float tick_interval) { INVARIANT_CHECK; - try - { - ptime now(time_now()); boost::shared_ptr t = m_torrent.lock(); - TORRENT_ASSERT(t); + if (!t || m_disconnecting) + { + m_ses.m_half_open.done(m_connection_ticket); + m_connecting = false; + disconnect("torrent aborted"); + return; + } on_tick(); @@ -2223,6 +2442,23 @@ namespace libtorrent m_statistics.second_tick(tick_interval); + if (m_statistics.upload_payload_rate() > m_upload_rate_peak) + { + m_upload_rate_peak = m_statistics.upload_payload_rate(); + } + if (m_statistics.download_payload_rate() > m_download_rate_peak) + { + m_download_rate_peak = m_statistics.download_payload_rate(); +#ifndef TORRENT_DISABLE_GEO_IP + if (peer_info_struct()) + { + std::pair* as_stats = peer_info_struct()->inet_as; + if (as_stats && as_stats->second < m_download_rate_peak) + as_stats->second = m_download_rate_peak; + } +#endif + } + if (!t->valid_metadata()) return; // calculate the desired download queue size @@ -2251,7 +2487,7 @@ namespace libtorrent // requested (this has been observed by BitComet) // in this case we'll clear our download queue and // re-request the blocks. -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** PIECE_REQUESTS TIMED OUT [ " << (int)m_download_queue.size() << " " << total_seconds(now - m_last_piece) << "] ***\n"; @@ -2348,14 +2584,6 @@ namespace libtorrent } fill_send_buffer(); - } - catch (std::exception& e) - { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "**ERROR**: " << e.what() << "\n"; -#endif - m_ses.connection_failed(self(), remote(), e.what()); - } } void peer_connection::fill_send_buffer() @@ -2369,8 +2597,9 @@ namespace libtorrent // otherwise there will be no end to how large it will be! int buffer_size_watermark = int(m_statistics.upload_rate()) / 2; - if (buffer_size_watermark < 1024) buffer_size_watermark = 1024; - else if (buffer_size_watermark > 80 * 1024) buffer_size_watermark = 80 * 1024; + if (buffer_size_watermark < 512) buffer_size_watermark = 512; + else if (buffer_size_watermark > m_ses.settings().send_buffer_watermark) + buffer_size_watermark = m_ses.settings().send_buffer_watermark; while (!m_requests.empty() && (send_buffer_size() + m_reading_bytes < buffer_size_watermark)) @@ -2398,25 +2627,20 @@ namespace libtorrent m_reading_bytes -= r.length; + disk_buffer_holder buffer(m_ses, j.buffer); + if (ret != r.length || m_torrent.expired()) { - if (j.buffer) m_ses.free_disk_buffer(j.buffer); boost::shared_ptr t = m_torrent.lock(); if (!t) { - m_ses.connection_failed(self(), remote(), j.str.c_str()); + disconnect(j.str.c_str()); return; } if (t->alerts().should_post(alert::fatal)) { - std::string err = "torrent paused: disk read error"; - if (!j.str.empty()) - { - err += ", "; - err += j.str; - } - t->alerts().post_alert(file_error_alert(t->get_handle(), err)); + t->alerts().post_alert(file_error_alert(j.error_file, t->get_handle(), j.str)); } t->pause(); return; @@ -2428,7 +2652,7 @@ namespace libtorrent << " | l: " << r.length << " ]\n"; #endif - write_piece(r, j.buffer); + write_piece(r, buffer); setup_send(); } @@ -2441,16 +2665,14 @@ namespace libtorrent #endif m_bandwidth_limit[channel].assign(amount); + TORRENT_ASSERT(m_channel_state[channel] == peer_info::bw_global); + m_channel_state[channel] = peer_info::bw_idle; if (channel == upload_channel) { - TORRENT_ASSERT(m_writing); - m_writing = false; setup_send(); } else if (channel == download_channel) { - TORRENT_ASSERT(m_reading); - m_reading = false; setup_receive(); } } @@ -2476,7 +2698,7 @@ namespace libtorrent INVARIANT_CHECK; - if (m_writing) return; + if (m_channel_state[upload_channel] != peer_info::bw_idle) return; shared_ptr t = m_torrent.lock(); @@ -2496,10 +2718,10 @@ namespace libtorrent (*m_logger) << time_now_string() << " *** REQUEST_BANDWIDTH [ upload ]\n"; #endif - TORRENT_ASSERT(!m_writing); // peers that we are not interested in are non-prioritized - m_writing = true; + m_channel_state[upload_channel] = peer_info::bw_torrent; t->request_bandwidth(upload_channel, self() + , m_send_buffer.size() , is_interesting() * 2); } return; @@ -2518,8 +2740,6 @@ namespace libtorrent return; } - TORRENT_ASSERT(!m_writing); - // send the actual buffer if (!m_send_buffer.empty()) { @@ -2536,7 +2756,7 @@ namespace libtorrent std::list const& vec = m_send_buffer.build_iovec(amount_to_send); m_socket->async_write_some(vec, bind(&peer_connection::on_send_data, self(), _1, _2)); - m_writing = true; + m_channel_state[upload_channel] = peer_info::bw_network; } } @@ -2546,10 +2766,7 @@ namespace libtorrent INVARIANT_CHECK; -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << time_now_string() << " *** SETUP_RECEIVE [ reading: " << (m_reading?"yes":"no") << "]\n"; -#endif - if (m_reading) return; + if (m_channel_state[download_channel] != peer_info::bw_idle) return; shared_ptr t = m_torrent.lock(); @@ -2563,8 +2780,10 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " *** REQUEST_BANDWIDTH [ download ]\n"; #endif - m_reading = true; - t->request_bandwidth(download_channel, self(), m_priority); + TORRENT_ASSERT(m_channel_state[download_channel] == peer_info::bw_idle); + m_channel_state[download_channel] = peer_info::bw_torrent; + t->request_bandwidth(download_channel, self() + , m_download_queue.size() * 16 * 1024 + 30, m_priority); } return; } @@ -2592,16 +2811,88 @@ namespace libtorrent TORRENT_ASSERT(m_recv_pos >= 0); TORRENT_ASSERT(m_packet_size > 0); - TORRENT_ASSERT(can_read()); #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " *** ASYNC_READ [ max: " << max_receive << " bytes ]\n"; #endif - 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; + + int regular_buffer_size = m_packet_size - m_disk_recv_buffer_size; + + if (int(m_recv_buffer.size()) < regular_buffer_size) + m_recv_buffer.resize(regular_buffer_size); + + if (m_disk_recv_buffer == 0 || regular_buffer_size >= m_recv_pos + max_receive) + { + // only receive into regular buffer + TORRENT_ASSERT(m_recv_pos + max_receive <= int(m_recv_buffer.size())); + m_socket->async_read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , max_receive), bind(&peer_connection::on_receive_data, self(), _1, _2)); + } + else if (m_recv_pos >= regular_buffer_size) + { + // only receive into disk buffer + TORRENT_ASSERT(m_recv_pos - regular_buffer_size >= 0); + TORRENT_ASSERT(m_recv_pos - regular_buffer_size + max_receive <= m_disk_recv_buffer_size); + m_socket->async_read_some(asio::buffer(m_disk_recv_buffer + m_recv_pos - regular_buffer_size + , max_receive) + , bind(&peer_connection::on_receive_data, self(), _1, _2)); + } + else + { + // receive into both regular and disk buffer + TORRENT_ASSERT(max_receive + m_recv_pos > regular_buffer_size); + TORRENT_ASSERT(m_recv_pos < regular_buffer_size); + TORRENT_ASSERT(max_receive - regular_buffer_size + + m_recv_pos <= m_disk_recv_buffer_size); + + boost::array vec; + vec[0] = asio::buffer(&m_recv_buffer[m_recv_pos] + , regular_buffer_size - m_recv_pos); + vec[1] = asio::buffer(m_disk_recv_buffer + , max_receive - regular_buffer_size + m_recv_pos); + m_socket->async_read_some(vec, bind(&peer_connection::on_receive_data + , self(), _1, _2)); + } + m_channel_state[download_channel] = peer_info::bw_network; } +#ifndef TORRENT_DISABLE_ENCRYPTION + + // returns the last 'bytes' from the receive buffer + std::pair peer_connection::wr_recv_buffers(int bytes) + { + TORRENT_ASSERT(bytes <= m_recv_pos); + + std::pair vec; + int regular_buffer_size = m_packet_size - m_disk_recv_buffer_size; + TORRENT_ASSERT(regular_buffer_size >= 0); + if (m_disk_recv_buffer == 0 || regular_buffer_size >= m_recv_pos) + { + vec.first = buffer::interval(&m_recv_buffer[0] + + m_recv_pos - bytes, &m_recv_buffer[0] + m_recv_pos); + vec.second = buffer::interval(0,0); + } + else if (m_recv_pos - bytes >= regular_buffer_size) + { + vec.first = buffer::interval(m_disk_recv_buffer + m_recv_pos + - regular_buffer_size - bytes, m_disk_recv_buffer + m_recv_pos + - regular_buffer_size); + vec.second = buffer::interval(0,0); + } + else + { + TORRENT_ASSERT(m_recv_pos - bytes < regular_buffer_size); + TORRENT_ASSERT(m_recv_pos > regular_buffer_size); + vec.first = buffer::interval(&m_recv_buffer[0] + m_recv_pos - bytes + , &m_recv_buffer[0] + regular_buffer_size); + vec.second = buffer::interval(m_disk_recv_buffer + , m_disk_recv_buffer + m_recv_pos - regular_buffer_size); + } + TORRENT_ASSERT(vec.first.left() + vec.second.left() == bytes); + return vec; + } +#endif + void peer_connection::reset_recv_buffer(int packet_size) { TORRENT_ASSERT(packet_size > 0); @@ -2612,8 +2903,6 @@ namespace libtorrent } m_recv_pos = 0; m_packet_size = packet_size; - if (int(m_recv_buffer.size()) < m_packet_size) - m_recv_buffer.resize(m_packet_size); } void peer_connection::send_buffer(char const* buf, int size) @@ -2634,6 +2923,11 @@ namespace libtorrent if (size <= 0) return; std::pair buffer = m_ses.allocate_buffer(size); + if (buffer.first == 0) + { + disconnect("out of memory"); + return; + } TORRENT_ASSERT(buffer.second >= size); std::memcpy(buffer.first, buf, size); m_send_buffer.append_buffer(buffer.first, buffer.second, size @@ -2654,6 +2948,11 @@ namespace libtorrent if (insert == 0) { std::pair buffer = m_ses.allocate_buffer(size); + if (buffer.first == 0) + { + disconnect("out of memory"); + return buffer::interval(0, 0); + } TORRENT_ASSERT(buffer.second >= size); m_send_buffer.append_buffer(buffer.first, buffer.second, size , bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second)); @@ -2692,25 +2991,28 @@ namespace libtorrent // throws exception when the client should be disconnected void peer_connection::on_receive_data(const asio::error_code& error - , std::size_t bytes_transferred) try + , std::size_t bytes_transferred) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; - TORRENT_ASSERT(m_reading); - m_reading = false; + TORRENT_ASSERT(m_channel_state[download_channel] == peer_info::bw_network); + m_channel_state[download_channel] = peer_info::bw_idle; if (error) { -#ifdef TORRENT_VERBOSE_LOGGING - (*m_logger) << "**ERROR**: " << error.message() << "[in peer_connection::on_receive_data]\n"; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_logger) << time_now_string() << " **ERROR**: " + << error.message() << "[in peer_connection::on_receive_data]\n"; #endif set_failed(); on_receive(error, bytes_transferred); - throw std::runtime_error(error.message()); + disconnect(error.message().c_str()); + return; } + int max_receive = 0; do { #ifdef TORRENT_VERBOSE_LOGGING @@ -2727,8 +3029,9 @@ namespace libtorrent m_last_receive = time_now(); m_recv_pos += bytes_transferred; - TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size())); - + TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size() + + m_disk_recv_buffer_size)); + on_receive(error, bytes_transferred); TORRENT_ASSERT(m_packet_size > 0); @@ -2740,23 +3043,63 @@ namespace libtorrent buffer(m_packet_size).swap(m_recv_buffer); } - int max_receive = m_packet_size - m_recv_pos; + max_receive = m_packet_size - m_recv_pos; int quota_left = m_bandwidth_limit[download_channel].quota_left(); if (!m_ignore_bandwidth_limits && max_receive > quota_left) max_receive = quota_left; if (max_receive == 0) break; + int regular_buffer_size = m_packet_size - m_disk_recv_buffer_size; + + if (int(m_recv_buffer.size()) < regular_buffer_size) + m_recv_buffer.resize(regular_buffer_size); + asio::error_code ec; - bytes_transferred = m_socket->read_some(asio::buffer(&m_recv_buffer[m_recv_pos] - , max_receive), ec); + if (m_disk_recv_buffer == 0 || regular_buffer_size >= m_recv_pos + max_receive) + { + // only receive into regular buffer + TORRENT_ASSERT(m_recv_pos + max_receive <= int(m_recv_buffer.size())); + bytes_transferred = m_socket->read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , max_receive), ec); + } + else if (m_recv_pos >= regular_buffer_size) + { + // only receive into disk buffer + TORRENT_ASSERT(m_recv_pos - regular_buffer_size >= 0); + TORRENT_ASSERT(m_recv_pos - regular_buffer_size + max_receive <= m_disk_recv_buffer_size); + bytes_transferred = m_socket->read_some(asio::buffer(m_disk_recv_buffer + + m_recv_pos - regular_buffer_size, (std::min)(m_packet_size + - m_recv_pos, max_receive)), ec); + } + else + { + // receive into both regular and disk buffer + TORRENT_ASSERT(max_receive + m_recv_pos > regular_buffer_size); + TORRENT_ASSERT(m_recv_pos < regular_buffer_size); + TORRENT_ASSERT(max_receive - regular_buffer_size + + m_recv_pos <= m_disk_recv_buffer_size); + + boost::array vec; + vec[0] = asio::buffer(&m_recv_buffer[m_recv_pos] + , regular_buffer_size - m_recv_pos); + vec[1] = asio::buffer(m_disk_recv_buffer + , (std::min)(m_disk_recv_buffer_size + , max_receive - regular_buffer_size + m_recv_pos)); + bytes_transferred = m_socket->read_some(vec, ec); + } if (ec && ec != asio::error::would_block) - throw asio::system_error(ec); + { + disconnect(ec.message().c_str()); + return; + } + if (ec == asio::error::would_block) break; } while (bytes_transferred > 0); setup_receive(); } + /* catch (file_error& e) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -2764,7 +3107,7 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t) { - m_ses.connection_failed(self(), remote(), e.what()); + disconnect(e.what()); return; } @@ -2776,18 +3119,7 @@ namespace libtorrent } t->pause(); } - catch (std::exception& e) - { - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), e.what()); - } - catch (...) - { - // all exceptions should derive from std::exception - TORRENT_ASSERT(false); - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), "connection failed for unknown reason"); - } +*/ bool peer_connection::can_write() const { @@ -2811,10 +3143,6 @@ namespace libtorrent && m_outstanding_writing_bytes < m_ses.settings().max_outstanding_disk_bytes_per_connection; -#if defined(TORRENT_VERBOSE_LOGGING) - (*m_logger) << time_now_string() << " *** can_read() " << (ret?"yes":"no") << " reading: " << m_reading << "\n"; -#endif - return ret; } @@ -2822,32 +3150,49 @@ namespace libtorrent { INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "CONNECTING: " << m_remote.address().to_string() + asio::error_code ec; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " CONNECTING: " << m_remote.address().to_string(ec) << ":" << m_remote.port() << "\n"; #endif m_connection_ticket = ticket; boost::shared_ptr t = m_torrent.lock(); - if (!t || m_disconnecting) - { - m_ses.m_half_open.done(m_connection_ticket); - m_connecting = false; - disconnect(); - return; - } m_queued = false; TORRENT_ASSERT(m_connecting); - m_socket->open(t->get_interface().protocol()); + + if (!t) + { + disconnect("torrent aborted"); + return; + } + + m_socket->open(t->get_interface().protocol(), ec); + if (ec) + { + disconnect(ec.message().c_str()); + return; + } // set the socket to non-blocking, so that we can // read the entire buffer on each read event we get tcp::socket::non_blocking_io ioc(true); - m_socket->io_control(ioc); - m_socket->bind(t->get_interface()); + m_socket->io_control(ioc, ec); + if (ec) + { + disconnect(ec.message().c_str()); + return; + } + m_socket->bind(t->get_interface(), ec); + if (ec) + { + disconnect(ec.message().c_str()); + return; + } m_socket->async_connect(m_remote , bind(&peer_connection::on_connection_complete, self(), _1)); + m_connect = time_now(); if (t->alerts().should_post(alert::debug)) { @@ -2856,12 +3201,16 @@ namespace libtorrent } } - void peer_connection::on_connection_complete(asio::error_code const& e) try + void peer_connection::on_connection_complete(asio::error_code const& e) { + ptime completed = time_now(); + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; + m_rtt = total_milliseconds(completed - m_connect); + if (m_disconnecting) return; m_connecting = false; @@ -2869,12 +3218,12 @@ namespace libtorrent if (e) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "CONNECTION FAILED: " << m_remote.address().to_string() +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " CONNECTION FAILED: " << m_remote.address().to_string() << ": " << e.message() << "\n"; #endif set_failed(); - m_ses.connection_failed(self(), m_remote, e.message().c_str()); + disconnect(e.message().c_str()); return; } @@ -2883,26 +3232,21 @@ namespace libtorrent // 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"; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + (*m_ses.m_logger) << time_now_string() << " COMPLETED: " << m_remote.address().to_string() + << " rtt = " << m_rtt << "\n"; #endif + if (m_remote.address().is_v4()) + { + asio::error_code ec; + m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec); + } + on_connected(); setup_send(); setup_receive(); } - catch (std::exception& ex) - { - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), ex.what()); - } - catch (...) - { - // all exceptions should derive from std::exception - TORRENT_ASSERT(false); - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), "connection failed for unkown reason"); - } // -------------------------- // SEND DATA @@ -2910,17 +3254,17 @@ namespace libtorrent // throws exception when the client should be disconnected void peer_connection::on_send_data(asio::error_code const& error - , std::size_t bytes_transferred) try + , std::size_t bytes_transferred) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; - TORRENT_ASSERT(m_writing); + TORRENT_ASSERT(m_channel_state[upload_channel] == peer_info::bw_network); m_send_buffer.pop_front(bytes_transferred); - m_writing = false; + m_channel_state[upload_channel] = peer_info::bw_idle; if (!m_ignore_bandwidth_limits) m_bandwidth_limit[upload_channel].use_quota(bytes_transferred); @@ -2931,11 +3275,12 @@ namespace libtorrent if (error) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << "**ERROR**: " << error.message() << " [in peer_connection::on_send_data]\n"; #endif set_failed(); - throw std::runtime_error(error.message()); + disconnect(error.message().c_str()); + return; } if (m_disconnecting) return; @@ -2949,23 +3294,25 @@ namespace libtorrent setup_send(); } - catch (std::exception& e) - { - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), e.what()); - } - catch (...) - { - // all exceptions should derive from std::exception - TORRENT_ASSERT(false); - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - m_ses.connection_failed(self(), remote(), "connection failed for unknown reason"); - } - #ifndef NDEBUG void peer_connection::check_invariant() const { + TORRENT_ASSERT((m_disk_recv_buffer != 0) == (m_disk_recv_buffer_size > 0)); + + boost::shared_ptr t = m_torrent.lock(); + if (m_disconnecting) + { + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer((peer_connection*)this)); + TORRENT_ASSERT(!t); + } + else if (!m_in_constructor) + { + TORRENT_ASSERT(m_ses.has_peer((peer_connection*)this)); + } + for (int i = 0; i < 2; ++i) { // this peer is in the bandwidth history iff max_assignable < limit @@ -2973,12 +3320,22 @@ namespace libtorrent == m_ses.m_bandwidth_manager[i]->is_in_history(this) || m_bandwidth_limit[i].throttle() == bandwidth_limit::inf); } + + if (m_channel_state[download_channel] == peer_info::bw_torrent + || m_channel_state[download_channel] == peer_info::bw_global) + TORRENT_ASSERT(m_bandwidth_limit[download_channel].quota_left() == 0); + if (m_channel_state[upload_channel] == peer_info::bw_torrent + || m_channel_state[upload_channel] == peer_info::bw_global) + TORRENT_ASSERT(m_bandwidth_limit[upload_channel].quota_left() == 0); + std::set unique; std::copy(m_download_queue.begin(), m_download_queue.end(), std::inserter(unique, unique.begin())); std::copy(m_request_queue.begin(), m_request_queue.end(), std::inserter(unique, unique.begin())); TORRENT_ASSERT(unique.size() == m_download_queue.size() + m_request_queue.size()); if (m_peer_info) { + TORRENT_ASSERT(m_peer_info->prev_amount_upload == 0); + TORRENT_ASSERT(m_peer_info->prev_amount_download == 0); TORRENT_ASSERT(m_peer_info->connection == this || m_peer_info->connection == 0); @@ -2986,9 +3343,38 @@ namespace libtorrent TORRENT_ASSERT(!is_choked()); } - boost::shared_ptr t = m_torrent.lock(); - if (!t) return; + if (!t) + { + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer((peer_connection*)this)); + return; + } + if (t->has_picker()) + { + std::map num_requests; + for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) + { + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); + peer_connection const& p = *(*i); + for (std::deque::const_iterator i = p.request_queue().begin() + , end(p.request_queue().end()); i != end; ++i) + ++num_requests[*i]; + for (std::deque::const_iterator i = p.download_queue().begin() + , end(p.download_queue().end()); i != end; ++i) + ++num_requests[*i]; + } + for (std::map::iterator i = num_requests.begin() + , end(num_requests.end()); i != end; ++i) + { + if (!t->picker().is_downloaded(i->first)) + TORRENT_ASSERT(t->picker().num_peers(i->first) == i->second); + } + } if (m_peer_info) { policy::const_iterator i; @@ -3019,13 +3405,17 @@ namespace libtorrent complete = false; break; } - if (complete) +/* +// this invariant is not valid anymore since the completion event +// might be queued in the io service + if (complete && !piece_failed) { disk_io_job ret = m_ses.m_disk_thread.find_job( &t->filesystem(), -1, i->index); TORRENT_ASSERT(ret.action == disk_io_job::hash || ret.action == disk_io_job::write); TORRENT_ASSERT(ret.piece == i->index); } +*/ } } // expensive when using checked iterators @@ -3092,7 +3482,7 @@ namespace libtorrent d = now - m_last_receive; if (d > seconds(m_timeout)) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** LAST ACTIVITY [ " << total_seconds(d) << " seconds ago ] ***\n"; #endif @@ -3102,7 +3492,7 @@ namespace libtorrent // do not stall waiting for a handshake if (in_handshake() && d > seconds(m_ses.settings().handshake_timeout)) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** NO HANDSHAKE [ waited " << total_seconds(d) << " seconds ] ***\n"; #endif @@ -3120,7 +3510,7 @@ namespace libtorrent && t && t->is_finished() && d > seconds(20)) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** NO REQUEST [ t: " << total_seconds(d) << " ] ***\n"; #endif @@ -3141,7 +3531,7 @@ namespace libtorrent time_duration time_limit = seconds( m_ses.settings().inactivity_timeout); - // don't bother disconnect peers we haven't been intersted + // don't bother disconnect peers we haven't been interested // in (and that hasn't been interested in us) for a while // unless we have used up all our connection slots if (!m_interesting @@ -3151,7 +3541,7 @@ namespace libtorrent && (m_ses.num_connections() >= m_ses.max_connections() || (t && t->num_peers() >= t->max_connections()))) { -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*m_logger) << time_now_string() << " *** MUTUAL NO INTEREST [ " "t1: " << total_seconds(d1) << " | " "t2: " << total_seconds(d2) << " ] ***\n"; @@ -3195,7 +3585,7 @@ namespace libtorrent // if the last send has not completed yet, do not send a keep // alive - if (m_writing) return; + if (m_channel_state[upload_channel] != peer_info::bw_idle) return; #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() << " ==> KEEPALIVE\n"; diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 9bd02f3c4..cdad7425e 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -49,19 +49,27 @@ POSSIBILITY OF SUCH DAMAGE. #endif //#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK +//#define TORRENT_NO_EXPENSIVE_INVARIANT_CHECK #define TORRENT_PIECE_PICKER_INVARIANT_CHECK +//#define TORRENT_PICKER_LOG + namespace libtorrent { piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks) - : m_piece_info(2) + : m_seeds(0) + , m_priority_boundries(1, int(m_pieces.size())) , m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece) , m_num_filtered(0) , m_num_have_filtered(0) , m_num_have(0) - , m_sequenced_download_threshold(100) + , m_sequential_download(-1) + , m_dirty(false) { +#ifdef TORRENT_PICKER_LOG + std::cout << "new piece_picker" << std::endl; +#endif TORRENT_ASSERT(blocks_per_piece > 0); TORRENT_ASSERT(total_num_blocks >= 0); #ifndef NDEBUG @@ -84,13 +92,37 @@ namespace libtorrent std::fill(m_piece_map.begin(), m_piece_map.end() , piece_pos(0, 0)); m_num_have = 0; +#ifndef NDEBUG + check_invariant(); +#endif + } + + void piece_picker::sequential_download(bool sd) + { + if (sd == sequential_download()) return; + + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + if (sd) + { + std::vector().swap(m_pieces); + std::vector().swap(m_priority_boundries); + + // initialize m_sdquential_download + m_sequential_download = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_sequential_download); + } + else + { + m_sequential_download = -1; + m_dirty = true; + } } // pieces is a bitmask with the pieces we have - void piece_picker::files_checked( - std::vector const& pieces - , std::vector const& unfinished - , std::vector& verify_pieces) + void piece_picker::init(std::vector const& pieces) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; #ifndef NDEBUG @@ -103,6 +135,7 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; if (*i) { + if (m_sequential_download == index) ++m_sequential_download; ++m_num_have; p.set_have(); if (p.filtered()) @@ -117,25 +150,6 @@ namespace libtorrent p.index = 0; } } - - // if we have fast resume info - // use it - if (!unfinished.empty()) - { - for (std::vector::const_iterator i - = unfinished.begin(); i != unfinished.end(); ++i) - { - for (int j = 0; j < m_blocks_per_piece; ++j) - { - if (i->info[j].state == block_info::state_finished) - mark_as_finished(piece_block(i->index, j), 0); - } - if (is_piece_finished(i->index)) - { - verify_pieces.push_back(i->index); - } - } - } } void piece_picker::piece_info(int index, piece_picker::downloading_piece& st) const @@ -167,68 +181,6 @@ namespace libtorrent st.finished = 0; } - void piece_picker::set_sequenced_download_threshold( - int sequenced_download_threshold) - { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; - - if (sequenced_download_threshold == m_sequenced_download_threshold) - return; - - TORRENT_ASSERT(sequenced_download_threshold > 0); - if (sequenced_download_threshold <= 0) 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 = *i; - int prev_priority = p.priority(old_limit); - if (prev_priority == 0) continue; - move(prev_priority, p.index); - } - } - - typedef std::vector info_t; - - if (old_limit < sequenced_download_threshold) - { - // the threshold was incremented, in case - // the previous max availability was reached - // we need to shuffle that bucket, if not, we - // don't have to do anything - if (int(m_piece_info.size()) > old_limit * 2) - { - info_t& in = m_piece_info[old_limit * 2]; - std::random_shuffle(in.begin(), in.end()); - int c = 0; - for (info_t::iterator i = in.begin() - , end(in.end()); i != end; ++i) - { - m_piece_map[*i].index = c++; - TORRENT_ASSERT(m_piece_map[*i].priority(old_limit) == old_limit * 2); - } - } - } - else if (int(m_piece_info.size()) > sequenced_download_threshold * 2) - { - info_t& in = m_piece_info[sequenced_download_threshold * 2]; - std::sort(in.begin(), in.end()); - int c = 0; - for (info_t::iterator i = in.begin() - , end(in.end()); i != end; ++i) - { - m_piece_map[*i].index = c++; - TORRENT_ASSERT(m_piece_map[*i].priority( - sequenced_download_threshold) == sequenced_download_threshold * 2); - } - } - } - piece_picker::downloading_piece& piece_picker::add_download_piece() { int num_downloads = m_downloads.size(); @@ -289,6 +241,45 @@ namespace libtorrent } } + void piece_picker::verify_priority(int range_start, int range_end, int prio) const + { + TORRENT_ASSERT(range_start <= range_end); + TORRENT_ASSERT(range_end <= int(m_pieces.size())); + for (std::vector::const_iterator i = m_pieces.begin() + range_start + , end(m_pieces.begin() + range_end); i != end; ++i) + { + int index = *i; + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + int p = m_piece_map[index].priority(this); + TORRENT_ASSERT(p == prio); + } + } + + void piece_picker::print_pieces() const + { + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + std::cout << *i << " "; + } + std::cout << std::endl; + int index = 0; + std::vector::const_iterator j = m_priority_boundries.begin(); + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + if (*i == -1) break; + while (j != m_priority_boundries.end() && *j <= index) + { + std::cout << "| "; + ++j; + } + std::cout << *i << "(" << m_piece_map[*i].index << ") "; + } + std::cout << std::endl; + } + void piece_picker::check_invariant(const torrent* t) const { TORRENT_ASSERT(sizeof(piece_pos) == 4); @@ -296,8 +287,6 @@ namespace libtorrent TORRENT_ASSERT(m_num_have_filtered >= 0); TORRENT_ASSERT(m_num_filtered >= 0); - TORRENT_ASSERT(m_piece_info.empty() || m_piece_info[0].empty()); - if (!m_downloads.empty()) { for (std::vector::const_iterator i = m_downloads.begin(); @@ -312,9 +301,6 @@ namespace libtorrent if (t != 0) TORRENT_ASSERT((int)m_piece_map.size() == t->torrent_file().num_pieces()); - for (int i = m_sequenced_download_threshold * 2 + 1; i < int(m_piece_info.size()); ++i) - TORRENT_ASSERT(m_piece_info[i].empty()); - for (std::vector::const_iterator i = m_downloads.begin() , end(m_downloads.end()); i != end; ++i) { @@ -328,17 +314,18 @@ namespace libtorrent if (i->info[k].state == block_info::state_finished) { ++num_finished; - continue; + TORRENT_ASSERT(i->info[k].num_peers == 0); } - if (i->info[k].state == block_info::state_requested) + else if (i->info[k].state == block_info::state_requested) { ++num_requested; blocks_requested = true; TORRENT_ASSERT(i->info[k].num_peers > 0); } - if (i->info[k].state == block_info::state_writing) + else if (i->info[k].state == block_info::state_writing) { ++num_writing; + TORRENT_ASSERT(i->info[k].num_peers == 0); } } TORRENT_ASSERT(blocks_requested == (i->state != none)); @@ -346,7 +333,32 @@ namespace libtorrent TORRENT_ASSERT(num_writing == i->writing); TORRENT_ASSERT(num_finished == i->finished); } +#ifdef TORRENT_NO_EXPENSIVE_INVARIANT_CHECK + return; +#endif + if (m_sequential_download == -1 && !m_dirty) + { + TORRENT_ASSERT(!m_priority_boundries.empty()); + int prio = 0; + int start = 0; + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + verify_priority(start, *i, prio); + ++prio; + start = *i; + } + TORRENT_ASSERT(m_priority_boundries.back() == int(m_pieces.size())); + } + else if (m_sequential_download >= 0) + { + int index = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++index); + TORRENT_ASSERT(m_sequential_download == index); + } int num_filtered = 0; int num_have_filtered = 0; @@ -355,14 +367,16 @@ namespace libtorrent i != m_piece_map.end(); ++i) { int index = static_cast(i - m_piece_map.begin()); - if (i->filtered()) + piece_pos const& p = *i; + + if (p.filtered()) { - if (i->index != piece_pos::we_have_index) + if (p.index != piece_pos::we_have_index) ++num_filtered; else ++num_have_filtered; } - if (i->index == piece_pos::we_have_index) + if (p.index == piece_pos::we_have_index) ++num_have; #if 0 @@ -400,46 +414,39 @@ namespace libtorrent } #endif - if (i->index == piece_pos::we_have_index) + if (p.index == piece_pos::we_have_index) { TORRENT_ASSERT(t == 0 || t->have_piece(index)); - TORRENT_ASSERT(i->downloading == 0); -/* - // make sure there's no entry - // with this index. (there shouldn't - // be since the piece_map is piece_pos::we_have_index) - for (int i = 0; i < int(m_piece_info.size()); ++i) - { - for (int j = 0; j < int(m_piece_info[i].size()); ++j) - { - TORRENT_ASSERT(m_piece_info[i][j] != index); - } - } -*/ + TORRENT_ASSERT(p.downloading == 0); } - else - { - if (t != 0) - TORRENT_ASSERT(!t->have_piece(index)); - int prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); - if (prio > 0) + if (t != 0) + TORRENT_ASSERT(!t->have_piece(index)); + + if (m_sequential_download == -1 && !m_dirty) + { + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio >= 0) { - const std::vector& vec = m_piece_info[prio]; - assert (i->index < vec.size()); - TORRENT_ASSERT(vec[i->index] == index); + TORRENT_ASSERT(p.index < m_pieces.size()); + TORRENT_ASSERT(m_pieces[p.index] == index); } -/* - for (int k = 0; k < int(m_piece_info.size()); ++k) + else { - for (int j = 0; j < int(m_piece_info[k].size()); ++j) - { - TORRENT_ASSERT(int(m_piece_info[k][j]) != index - || (prio > 0 && prio == k && int(i->index) == j)); - } + TORRENT_ASSERT(prio == -1); + // make sure there's no entry + // with this index. (there shouldn't + // be since the priority is -1) + TORRENT_ASSERT(std::find(m_pieces.begin(), m_pieces.end(), index) + == m_pieces.end()); } -*/ + } + else if (m_sequential_download >= 0) + { + TORRENT_ASSERT(m_pieces.empty()); + TORRENT_ASSERT(m_priority_boundries.empty()); } int count = std::count_if(m_downloads.begin(), m_downloads.end() @@ -456,6 +463,15 @@ namespace libtorrent TORRENT_ASSERT(num_have == m_num_have); TORRENT_ASSERT(num_filtered == m_num_filtered); TORRENT_ASSERT(num_have_filtered == m_num_have_filtered); + + if (!m_dirty) + { + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(m_piece_map[*i].priority(this) >= 0); + } + } } #endif @@ -492,174 +508,258 @@ namespace libtorrent } } TORRENT_ASSERT(integer_part + fraction_part == num_pieces); - return float(min_availability) + (fraction_part / num_pieces); + return float(min_availability + m_seeds) + (fraction_part / num_pieces); + } + + void piece_picker::priority_range(int prio, int* start, int* end) + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio == 0) *start = 0; + else *start = m_priority_boundries[prio - 1]; + *end = m_priority_boundries[prio]; + TORRENT_ASSERT(*start <= *end); } void piece_picker::add(int index) { + TORRENT_ASSERT(!m_dirty); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < int(m_piece_map.size())); piece_pos& p = m_piece_map[index]; TORRENT_ASSERT(!p.filtered()); TORRENT_ASSERT(!p.have()); + TORRENT_ASSERT(m_sequential_download == -1); - int priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(priority > 0); - if (int(m_piece_info.size()) <= priority) - m_piece_info.resize(priority + 1); + int priority = p.priority(this); + TORRENT_ASSERT(priority >= 0); + if (int(m_priority_boundries.size()) <= priority) + m_priority_boundries.resize(priority + 1, m_pieces.size()); - TORRENT_ASSERT(int(m_piece_info.size()) > priority); + TORRENT_ASSERT(int(m_priority_boundries.size()) >= priority); - if (is_ordered(priority)) + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + int new_index; + if (range_end == range_start) new_index = range_start; + else new_index = rand() % (range_end - range_start) + range_start; + +#ifdef TORRENT_PICKER_LOG + std::cout << "add " << index << " (" << priority << ")" << std::endl; + print_pieces(); +#endif + m_pieces.push_back(-1); + + for (;;) { - // the piece should be inserted ordered, not randomly - std::vector& v = m_piece_info[priority]; -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - 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) + TORRENT_ASSERT(new_index < int(m_pieces.size())); + int temp = m_pieces[new_index]; + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + index = temp; + do { - ++m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); - } -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + temp = m_priority_boundries[priority]++; + ++priority; + } while (temp == new_index && priority < int(m_priority_boundries.size())); + new_index = temp; +#ifdef TORRENT_PICKER_LOG + print_pieces(); + std::cout << " index: " << index + << " prio: " << priority + << " new_index: " << new_index + << std::endl; +#endif + if (priority >= int(m_priority_boundries.size())) break; + TORRENT_ASSERT(temp >= 0); } - else if (m_piece_info[priority].size() < 2) + if (index != -1) { - p.index = m_piece_info[priority].size(); - m_piece_info[priority].push_back(index); - } - else - { - // find a random position in the destination vector where we will place - // this entry. - int dst_index = rand() % m_piece_info[priority].size(); - - // copy the entry at that position to the back - m_piece_map[m_piece_info[priority][dst_index]].index - = m_piece_info[priority].size(); - m_piece_info[priority].push_back(m_piece_info[priority][dst_index]); + TORRENT_ASSERT(new_index == int(m_pieces.size() - 1)); + m_pieces[new_index] = index; + m_piece_map[index].index = new_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; - m_piece_info[priority][p.index] = index; +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif } } - // will update the piece with the given properties (priority, elem_index) - // to place it at the correct position in the vectors. - void piece_picker::move(int priority, int elem_index) + void piece_picker::remove(int priority, int elem_index) { - TORRENT_ASSERT(priority > 0); + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(m_sequential_download == -1); + +#ifdef TORRENT_PICKER_LOG + std::cout << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; +#endif + int next_index = elem_index; + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == -1); + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + int temp; + do + { + temp = --m_priority_boundries[priority]; + ++priority; + } while (next_index == temp && priority < int(m_priority_boundries.size())); + if (next_index == temp) break; + next_index = temp; + + int piece = m_pieces[next_index]; + m_pieces[elem_index] = piece; + m_piece_map[piece].index = elem_index; + TORRENT_ASSERT(m_piece_map[piece].priority(this) == priority - 1); + TORRENT_ASSERT(elem_index < int(m_pieces.size() - 1)); + elem_index = next_index; + + if (priority == int(m_priority_boundries.size())) + break; + } + m_pieces.pop_back(); + TORRENT_ASSERT(next_index == int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + } + + // will update the piece with the given properties (priority, elem_index) + // to place it at the correct position + void piece_picker::update(int priority, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); TORRENT_ASSERT(elem_index >= 0); TORRENT_ASSERT(m_files_checked_called); + TORRENT_ASSERT(m_sequential_download == -1); - TORRENT_ASSERT(int(m_piece_info.size()) > priority); - TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); + TORRENT_ASSERT(int(m_priority_boundries.size()) > priority); - int index = m_piece_info[priority][elem_index]; + int index = m_pieces[elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; TORRENT_ASSERT(int(p.index) == elem_index || p.have()); - int new_priority = p.priority(m_sequenced_download_threshold); + int new_priority = p.priority(this); if (new_priority == priority) return; - if (int(m_piece_info.size()) <= new_priority - && new_priority > 0) + if (new_priority == -1) { - m_piece_info.resize(new_priority + 1); - TORRENT_ASSERT(int(m_piece_info.size()) > new_priority); + remove(priority, elem_index); + return; } - if (new_priority == 0) + if (int(m_priority_boundries.size()) <= new_priority) + m_priority_boundries.resize(new_priority + 1, m_pieces.size()); + +#ifdef TORRENT_PICKER_LOG + std::cout << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; +#endif + if (priority > new_priority) { - // this means the piece should not have an entry - } - else if (is_ordered(new_priority)) - { - // the piece should be inserted ordered, not randomly - std::vector& v = m_piece_info[new_priority]; -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - 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) + int new_index; + int temp = index; + for (;;) { - ++m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + --priority; + new_index = m_priority_boundries[priority]++; + TORRENT_ASSERT(new_index < int(m_pieces.size())); + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + } + elem_index = new_index; + if (priority == new_priority) break; } -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - } - else if (m_piece_info[new_priority].size() < 2) - { - p.index = m_piece_info[new_priority].size(); - m_piece_info[new_priority].push_back(index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); } else { - // find a random position in the destination vector where we will place - // this entry. - int dst_index = rand() % m_piece_info[new_priority].size(); - - // copy the entry at that position to the back - m_piece_map[m_piece_info[new_priority][dst_index]].index - = m_piece_info[new_priority].size(); - m_piece_info[new_priority].push_back(m_piece_info[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; - m_piece_info[new_priority][p.index] = index; - } - TORRENT_ASSERT(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); - TORRENT_ASSERT(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); - - if (is_ordered(priority)) - { - // remove the element from the source vector and preserve the order - std::vector& v = m_piece_info[priority]; - v.erase(v.begin() + elem_index); - for (std::vector::iterator i = v.begin() + elem_index; - i != v.end(); ++i) + int new_index; + int temp = index; + for (;;) { - --m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + new_index = --m_priority_boundries[priority]; + TORRENT_ASSERT(new_index < int(m_pieces.size())); + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + } + elem_index = new_index; + ++priority; + if (priority == new_priority) break; } +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); } - else - { - // this will remove elem from the source vector without - // preserving order, but the order is random anyway - int replace_index = m_piece_info[priority][elem_index] = m_piece_info[priority].back(); - if (index != replace_index) - { - // update the entry we moved from the back - m_piece_map[replace_index].index = elem_index; + } - TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); - // this may not necessarily be the case. If we've just updated the threshold and are updating - // the piece map -// TORRENT_ASSERT((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); - TORRENT_ASSERT(int(m_piece_map[replace_index].index) == elem_index); - TORRENT_ASSERT(m_piece_info[priority][elem_index] == replace_index); - } - else - { - TORRENT_ASSERT(int(m_piece_info[priority].size()) == elem_index+1); - } + void piece_picker::shuffle(int priority, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(elem_index >= 0); + TORRENT_ASSERT(m_sequential_download == -1); + + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + TORRENT_ASSERT(range_start < range_end); + int other_index = rand() % (range_end - range_start) + range_start; - m_piece_info[priority].pop_back(); - } + if (other_index == elem_index) return; + + // swap other_index with elem_index + piece_pos& p1 = m_piece_map[m_pieces[other_index]]; + piece_pos& p2 = m_piece_map[m_pieces[elem_index]]; + + int temp = p1.index; + p1.index = p2.index; + p2.index = temp; + std::swap(m_pieces[other_index], m_pieces[elem_index]); } void piece_picker::sort_piece(std::vector::iterator dp) @@ -689,26 +789,27 @@ namespace libtorrent TORRENT_ASSERT(m_piece_map[index].downloading == 1); std::vector::iterator i - = std::find_if(m_downloads.begin(), - m_downloads.end(), - has_index(index)); + = std::find_if(m_downloads.begin(), m_downloads.end() + , has_index(index)); + TORRENT_ASSERT(i != m_downloads.end()); erase_download_piece(i); piece_pos& p = m_piece_map[index]; - int prev_priority = p.priority(m_sequenced_download_threshold); + int prev_priority = p.priority(this); p.downloading = 0; - int new_priority = p.priority(m_sequenced_download_threshold); + int new_priority = p.priority(this); if (new_priority == prev_priority) return; - - if (prev_priority == 0) + if (m_sequential_download >= 0) return; + if (m_dirty) return; + if (prev_priority == -1) { add(index); } else { - move(prev_priority, p.index); + update(prev_priority, p.index); } } @@ -716,97 +817,14 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(m_files_checked_called); - - // in general priority = availability * 2 - // see piece_block::priority() - - // this will insert two empty vectors at the start of the - // piece_info vector. It is done like this as an optimization, - // to swap vectors instead of copying them - while (m_piece_info.size() < 3 - || (!m_piece_info.rbegin()->empty()) - || (!(m_piece_info.rbegin()+1)->empty())) + ++m_seeds; + if (m_sequential_download >= 0) return; + if (m_seeds == 1) { - m_piece_info.push_back(std::vector()); - } - TORRENT_ASSERT(m_piece_info.rbegin()->empty()); - TORRENT_ASSERT((m_piece_info.rbegin()+1)->empty()); - typedef std::vector > piece_info_t; - for (piece_info_t::reverse_iterator i = m_piece_info.rbegin(), j(i+1) - , k(j+1), end(m_piece_info.rend()); k != end; ++i, ++j, ++k) - { - k->swap(*i); - } - TORRENT_ASSERT(m_piece_info.begin()->empty()); - TORRENT_ASSERT((m_piece_info.begin()+1)->empty()); - - // if we have some priorities that are clamped to the - // sequenced download, move that vector back down - int last_index = m_piece_info.size() - 1; - int cap_index = m_sequenced_download_threshold * 2; - if (last_index == cap_index) - { - // this is the case when the top bucket - // was moved up into the sequenced download bucket. - m_piece_info.push_back(std::vector()); - m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); - ++last_index; - } - else if (last_index > cap_index) - { - if (last_index - cap_index == 1) - { - m_piece_info.push_back(std::vector()); - ++last_index; - } - m_piece_info[cap_index+1].swap(m_piece_info[cap_index+2]); - m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); - } - - // now, increase the peer count of all the pieces. - // because of different priorities, some pieces may have - // ended up in the wrong priority bucket. Adjust that. - for (std::vector::iterator i = m_piece_map.begin() - , end(m_piece_map.end()); i != end; ++i) - { - int prev_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); - ++i->peer_count; - // if the assumption that the priority would - // increase by 2 when increasing the availability - // by one isn't true for this particular piece, correct it. - // that assumption is true for all pieces with priority 0 or 1 - int new_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(new_prio <= cap_index); - if (prev_prio == 0 && new_prio > 0) - { - add(i - m_piece_map.begin()); - continue; - } - if (new_prio == 0) - { - TORRENT_ASSERT(prev_prio == 0); - continue; - } - if (prev_prio == cap_index) - { - TORRENT_ASSERT(new_prio == cap_index); - continue; - } - if (new_prio == prev_prio + 2 && new_prio != cap_index) - { - TORRENT_ASSERT(new_prio != cap_index); - continue; - } - if (prev_prio + 2 >= cap_index) - { - // these two vectors will have moved one extra step - // passed the sequenced download threshold - ++prev_prio; - } - TORRENT_ASSERT(prev_prio + 2 != cap_index); - TORRENT_ASSERT(prev_prio + 2 != new_prio); - move(prev_prio + 2, i->index); + // when m_seeds is increased from 0 to 1 + // we may have to add pieces that previously + // didn't have any peers + m_dirty = true; } } @@ -814,150 +832,188 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(m_piece_info.size() >= 2); - TORRENT_ASSERT(m_piece_info.front().empty()); - // swap all vectors two steps down - if (m_piece_info.size() > 2) - { - typedef std::vector > piece_info_t; - for (piece_info_t::iterator i = m_piece_info.begin(), j(i+1) - , k(j+1), end(m_piece_info.end()); k != end; ++i, ++j, ++k) - { - k->swap(*i); - } - } - else - { - m_piece_info.resize(3); - } - int last_index = m_piece_info.size() - 1; - if ((m_piece_info.size() & 1) == 0) - { - // if there's an even number of vectors, swap - // the last two to get the same layout in both cases - m_piece_info[last_index].swap(m_piece_info[last_index-1]); - } - TORRENT_ASSERT(m_piece_info.back().empty()); - int pushed_out_index = m_piece_info.size() - 2; - int cap_index = m_sequenced_download_threshold * 2; - TORRENT_ASSERT(m_piece_info[last_index].empty()); - if (last_index >= cap_index) + if (m_sequential_download >= 0) { - TORRENT_ASSERT(pushed_out_index == cap_index - 1 - || m_piece_info[cap_index - 1].empty()); - m_piece_info[cap_index].swap(m_piece_info[cap_index - 2]); - if (cap_index == pushed_out_index) - pushed_out_index = cap_index - 2; + --m_seeds; + return; } - // first is the vector that were - // bumped down to 0. The should always be moved - // since they have to be removed or reinserted - std::vector().swap(m_piece_info.front()); + if (m_seeds > 0) + { + --m_seeds; + if (m_seeds == 0) + { + // when m_seeds is decreased from 1 to 0 + // we may have to remove pieces that previously + // didn't have any peers + m_dirty = true; + } + return; + } for (std::vector::iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i) { - int prev_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); - TORRENT_ASSERT(pushed_out_index < int(m_piece_info.size())); TORRENT_ASSERT(i->peer_count > 0); --i->peer_count; - // if the assumption that the priority would - // decrease by 2 when decreasing the availability - // by one isn't true for this particular piece, correct it. - // that assumption is true for all pieces with priority 0 or 1 - if (prev_prio == 0) - { - TORRENT_ASSERT(i->priority(m_sequenced_download_threshold) == 0); - continue; - } - - int new_prio = i->priority(m_sequenced_download_threshold); - if (prev_prio == cap_index) - { - if (new_prio == cap_index) continue; - prev_prio += 2; - } - else if (new_prio == prev_prio - 2) - { - continue; - } - else if (prev_prio == 2) - { - // if this piece was pushed down to priority 0, it was - // removed - TORRENT_ASSERT(new_prio > 0); - add(i - m_piece_map.begin()); - continue; - } - else if (prev_prio == 1) - { - // if this piece was one of the vectors that was pushed to the - // top, adjust the prev_prio to point to that vector, so that - // the pieces are moved from there - prev_prio = pushed_out_index + 2; - } - move(prev_prio - 2, i->index); } + + m_dirty = true; } - void piece_picker::inc_refcount(int i) + void piece_picker::inc_refcount(int index) { -// TORRENT_PIECE_PICKER_INVARIANT_CHECK; - TORRENT_ASSERT(i >= 0); - TORRENT_ASSERT(i < (int)m_piece_map.size()); - TORRENT_ASSERT(m_files_checked_called); + TORRENT_PIECE_PICKER_INVARIANT_CHECK; - piece_pos& p = m_piece_map[i]; - int index = p.index; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); - - TORRENT_ASSERT(p.peer_count < piece_pos::max_peer_count); - p.peer_count++; - TORRENT_ASSERT(p.peer_count != 0); - - // if we have the piece or if it's filtered - // we don't have to move any entries in the piece_info vector - if (p.priority(m_sequenced_download_threshold) == prev_priority) return; - - if (prev_priority == 0) + piece_pos& p = m_piece_map[index]; + if (m_sequential_download >= 0) { - add(i); + ++p.peer_count; + return; } + + int prev_priority = p.priority(this); + ++p.peer_count; + if (m_dirty) return; + int new_priority = p.priority(this); + if (prev_priority == new_priority) return; + if (prev_priority == -1) + add(index); else + update(prev_priority, p.index); + } + + void piece_picker::dec_refcount(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + piece_pos& p = m_piece_map[index]; + if (m_sequential_download >= 0) { - move(prev_priority, index); + --p.peer_count; + return; } + int prev_priority = p.priority(this); + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + if (m_dirty) return; + if (prev_priority >= 0) update(prev_priority, p.index); + } + + void piece_picker::inc_refcount(std::vector const& bitmask) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + TORRENT_ASSERT(bitmask.size() == m_piece_map.size()); + + int index = 0; + bool updated = false; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { + ++m_piece_map[index].peer_count; + updated = true; + } + } + + if (updated && m_sequential_download == -1) m_dirty = true; + } + + void piece_picker::dec_refcount(std::vector const& bitmask) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + TORRENT_ASSERT(bitmask.size() == m_piece_map.size()); + + int index = 0; + bool updated = false; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { + --m_piece_map[index].peer_count; + updated = true; + } + } + + if (updated && m_sequential_download == -1) m_dirty = true; + } + + void piece_picker::update_pieces() const + { + TORRENT_ASSERT(m_dirty); + TORRENT_ASSERT(m_sequential_download == -1); + if (m_priority_boundries.empty()) m_priority_boundries.resize(1, 0); +#ifdef TORRENT_PICKER_LOG + std::cout << "update_pieces" << std::endl; +#endif + std::fill(m_priority_boundries.begin(), m_priority_boundries.end(), 0); + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int prio = i->priority(this); + if (prio == -1) continue; + if (prio >= int(m_priority_boundries.size())) + m_priority_boundries.resize(prio + 1, 0); + i->index = m_priority_boundries[prio]; + ++m_priority_boundries[prio]; + } + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + int index = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + *i += index; + index = *i; + } + m_pieces.resize(index, 0); + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + index = 0; + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i, ++index) + { + piece_pos& p = *i; + int prio = p.priority(this); + if (prio == -1) continue; + int new_index = (prio == 0 ? 0 : m_priority_boundries[prio - 1]) + p.index; + m_pieces[new_index] = index; + } + + int start = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + if (start == *i) continue; + std::random_shuffle(&m_pieces[0] + start, &m_pieces[0] + *i); + start = *i; + } + + index = 0; + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + TORRENT_ASSERT(*i >= 0 && *i < int(m_piece_map.size())); + m_piece_map[*i].index = index; + } + + m_dirty = false; +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif #ifndef NDEBUG -// integrity_check(); + check_invariant(); #endif - return; - } - - void piece_picker::dec_refcount(int i) - { -// TORRENT_PIECE_PICKER_INVARIANT_CHECK; - - TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(i >= 0); - TORRENT_ASSERT(i < (int)m_piece_map.size()); - - piece_pos& p = m_piece_map[i]; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); - int index = p.index; - TORRENT_ASSERT(p.peer_count > 0); - - if (p.peer_count > 0) - p.peer_count--; - - if (p.priority(m_sequenced_download_threshold) == prev_priority) return; - - move(prev_priority, index); } // this is used to indicate that we succesfully have @@ -972,8 +1028,8 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; int info_index = p.index; - int priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(priority < int(m_piece_info.size())); + int priority = p.priority(this); + TORRENT_ASSERT(priority < int(m_priority_boundries.size())); if (p.downloading) { @@ -989,6 +1045,13 @@ namespace libtorrent TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(index)) == m_downloads.end()); + if (m_sequential_download == index) + { + ++m_sequential_download; + for (std::vector::const_iterator i = m_piece_map.begin() + m_sequential_download + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_sequential_download); + } if (p.have()) return; if (p.filtered()) { @@ -997,12 +1060,12 @@ namespace libtorrent } ++m_num_have; p.set_have(); - if (priority == 0) return; - TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); - move(priority, info_index); + if (priority == -1) return; + if (m_dirty) return; + remove(priority, info_index); + TORRENT_ASSERT(p.priority(this) == -1); } - bool piece_picker::set_piece_priority(int index, int new_piece_priority) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1016,8 +1079,8 @@ namespace libtorrent // if the priority isn't changed, don't do anything if (new_piece_priority == int(p.piece_priority)) return false; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); + int prev_priority = p.priority(this); + TORRENT_ASSERT(prev_priority < int(m_priority_boundries.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1040,18 +1103,20 @@ namespace libtorrent TORRENT_ASSERT(m_num_have_filtered >= 0); p.piece_priority = new_piece_priority; - int new_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); + int new_priority = p.priority(this); - if (new_priority == prev_priority) return false; - - if (prev_priority == 0) + if (prev_priority == new_priority) return false; + + TORRENT_ASSERT(prev_priority < int(m_priority_boundries.size())); + + if (m_dirty) return ret; + if (prev_priority == -1) { add(index); } else { - move(prev_priority, p.index); + update(prev_priority, p.index); } return ret; } @@ -1116,7 +1181,9 @@ namespace libtorrent TORRENT_ASSERT(pieces.size() == m_piece_map.size()); TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(m_piece_info.begin() != m_piece_info.end()); + TORRENT_ASSERT(!m_priority_boundries.empty() + || m_sequential_download >= 0 + || m_dirty); // this will be filled with blocks that we should not request // unless we can't find num_blocks among the other ones. @@ -1125,8 +1192,6 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; - // suggested pieces for each vector is put in this vector - std::vector suggested_bucket; const std::vector empty_vector; // When prefer_whole_pieces is set (usually set when downloading from @@ -1140,50 +1205,38 @@ namespace libtorrent if (num_blocks <= 0) return; - if (rarest_first) + if (!suggested_pieces.empty()) { - // this loop will loop from pieces with priority 1 and up - // until we either reach the end of the piece list or - // has filled the interesting_blocks with num_blocks - // blocks. + num_blocks = add_blocks(suggested_pieces, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, empty_vector); + if (num_blocks == 0) return; + } - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains - // pieces that 0 other peers have. bucket will point to a bucket with - // pieces with the same priority. It will be iterated in priority - // order (high priority/rare pices first). The content of each - // bucket is randomized - for (std::vector >::const_iterator bucket - = m_piece_info.begin() + 1; num_blocks > 0 && bucket != m_piece_info.end(); - ++bucket) + if (m_sequential_download >= 0) + { + for (int i = m_sequential_download; + i < int(m_piece_map.size()) && num_blocks > 0; ++i) { - if (bucket->empty()) continue; - if (!suggested_pieces.empty()) + if (!can_pick(i, pieces)) continue; + int num_blocks_in_piece = blocks_in_piece(i); + if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) { - int bucket_index = bucket - m_piece_info.begin(); - suggested_bucket.clear(); - for (std::vector::const_iterator i = suggested_pieces.begin() - , end(suggested_pieces.end()); i != end; ++i) - { - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(*i < int(m_piece_map.size())); - if (!can_pick(*i, pieces)) continue; - if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) - suggested_bucket.push_back(*i); - } - if (!suggested_bucket.empty()) - { - num_blocks = add_blocks(suggested_bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, empty_vector); - if (num_blocks == 0) break; - } + interesting_blocks.push_back(piece_block(i, j)); + --num_blocks; } - num_blocks = add_blocks(*bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, suggested_bucket); - TORRENT_ASSERT(num_blocks >= 0); } } + else if (rarest_first) + { + if (m_dirty) update_pieces(); + num_blocks = add_blocks(m_pieces, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, suggested_pieces); + TORRENT_ASSERT(num_blocks >= 0); + } else { // we're not using rarest first (only for the first @@ -1215,7 +1268,7 @@ namespace libtorrent for (int k = start; k < end; ++k) { TORRENT_ASSERT(m_piece_map[piece].downloading == false); - TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[k].priority(this) >= 0); int num_blocks_in_piece = blocks_in_piece(k); if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; @@ -1299,19 +1352,17 @@ namespace libtorrent TORRENT_ASSERT(*i < (int)m_piece_map.size()); // if the peer doesn't have the piece + // or if it's set to 0 priority // skip it - if (!pieces[*i]) continue; + if (!can_pick(*i, pieces)) continue; // ignore pieces found in the ignore list if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; - // skip the piece is the priority is 0 - TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); - - int num_blocks_in_piece = blocks_in_piece(*i); - + TORRENT_ASSERT(m_piece_map[*i].priority(this) >= 0); TORRENT_ASSERT(m_piece_map[*i].downloading == 0); - TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + + int num_blocks_in_piece = blocks_in_piece(*i); // pick a new piece if (prefer_whole_pieces == 0) @@ -1328,7 +1379,7 @@ namespace libtorrent boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); for (int k = start; k < end; ++k) { - TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); num_blocks_in_piece = blocks_in_piece(k); for (int j = 0; j < num_blocks_in_piece; ++j) { @@ -1639,12 +1690,9 @@ namespace libtorrent return i->info[block.block_index].state == block_info::state_finished; } - bool piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; - TORRENT_ASSERT(block.piece_index >= 0); TORRENT_ASSERT(block.block_index >= 0); TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); @@ -1654,11 +1702,14 @@ namespace libtorrent piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { - int prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_sequential_download >= 0 + || m_dirty); TORRENT_ASSERT(prio > 0); p.downloading = 1; - move(prio, p.index); + if (prio >= 0 && m_sequential_download == -1 && !m_dirty) update(prio, p.index); downloading_piece& dp = add_download_piece(); dp.state = state; @@ -1671,6 +1722,7 @@ namespace libtorrent } else { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); TORRENT_ASSERT(i != m_downloads.end()); @@ -1719,7 +1771,7 @@ namespace libtorrent std::vector::iterator j = avail.begin(); for (std::vector::const_iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i, ++j) - *j = i->peer_count; + *j = i->peer_count + m_seeds; } void piece_picker::mark_as_writing(piece_block block, void* peer) @@ -1744,7 +1796,8 @@ namespace libtorrent TORRENT_ASSERT(info.state != block_info::state_writing); ++i->writing; info.state = block_info::state_writing; - if (info.num_peers > 0) --info.num_peers; + TORRENT_ASSERT(info.num_peers > 0); + info.num_peers = 0; if (i->requested == 0) { @@ -1754,6 +1807,31 @@ namespace libtorrent } sort_piece(i); } + + void piece_picker::write_failed(piece_block block) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + std::vector::iterator i + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); + TORRENT_ASSERT(i != m_downloads.end()); + block_info& info = i->info[block.block_index]; + TORRENT_ASSERT(info.state == block_info::state_writing); + + --i->writing; + if (info.num_peers > 0) + { + // there are other peers on this block + // turn it back into requested + ++i->requested; + info.state = block_info::state_requested; + } + else + { + info.state = block_info::state_none; + } + info.peer = 0; + } void piece_picker::mark_as_finished(piece_block block, void* peer) { @@ -1769,11 +1847,11 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(peer == 0); - int prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); p.downloading = 1; - if (prio > 0) move(prio, p.index); - else TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); + if (prio >= 0 && !m_dirty) update(prio, p.index); downloading_piece& dp = add_download_piece(); dp.state = none; @@ -1781,6 +1859,7 @@ namespace libtorrent block_info& info = dp.info[block.block_index]; info.peer = peer; TORRENT_ASSERT(info.state == block_info::state_none); + TORRENT_ASSERT(info.num_peers == 0); if (info.state != block_info::state_finished) { ++dp.finished; @@ -1796,6 +1875,7 @@ namespace libtorrent = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; + TORRENT_ASSERT(info.num_peers == 0); info.peer = peer; TORRENT_ASSERT(info.state == block_info::state_writing || peer == 0); @@ -1845,6 +1925,8 @@ namespace libtorrent return i->info[block.block_index].peer; } + // this is called when a request is rejected or when + // a peer disconnects. The piece might be in any state void piece_picker::abort_download(piece_block block) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1866,37 +1948,40 @@ namespace libtorrent TORRENT_ASSERT(i != m_downloads.end()); block_info& info = i->info[block.block_index]; - --info.num_peers; - if (info.num_peers > 0) return; - if (i->info[block.block_index].state == block_info::state_finished - || i->info[block.block_index].state == block_info::state_writing) - { + if (i->info[block.block_index].state != block_info::state_requested) return; - } + + if (info.num_peers > 0) --info.num_peers; TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); - TORRENT_ASSERT(i->info[block.block_index].state == block_info::state_requested); + + // if there are other peers, leave the block requested + if (info.num_peers > 0) return; + + // clear the downloader of this block + info.peer = 0; // clear this block as being downloaded info.state = block_info::state_none; --i->requested; - // clear the downloader of this block - info.peer = 0; - // if there are no other blocks in this piece // that's being downloaded, remove it from the list if (i->requested + i->finished + i->writing == 0) { erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; - int prev_prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); + int prev_prio = p.priority(this); + TORRENT_ASSERT(prev_prio < int(m_priority_boundries.size()) + || m_dirty); p.downloading = 0; - int prio = p.priority(m_sequenced_download_threshold); - if (prev_prio == 0 && prio > 0) add(block.piece_index); - else if (prio > 0) move(prio, p.index); + if (m_sequential_download == -1 && !m_dirty) + { + int prio = p.priority(this); + if (prev_prio == -1 && prio >= 0) add(block.piece_index); + else if (prev_prio >= 0) update(prev_prio, p.index); + } TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index 1b6a60cba..94c9ad549 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -224,7 +224,10 @@ namespace libtorrent // 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. - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); +#ifndef NDEBUG + asio::error_code ec; + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); +#endif piece_picker::piece_state_t state; peer_connection::peer_speed_t speed = c.peer_speed(); @@ -343,7 +346,7 @@ namespace libtorrent policy::policy(torrent* t) : m_torrent(t) , m_available_free_upload(0) -// , m_last_optimistic_disconnect(min_time()) + , m_num_connect_candidates(0) { TORRENT_ASSERT(t); } // disconnects and removes all peers that are now filtered @@ -364,7 +367,7 @@ namespace libtorrent if (i->second.connection) { - i->second.connection->disconnect(); + i->second.connection->disconnect("peer banned by IP filter"); if (ses.m_alerts.should_post(alert::info)) { ses.m_alerts.post_alert(peer_blocked_alert(i->second.ip.address() @@ -382,92 +385,26 @@ namespace libtorrent } } if (p) p->clear_peer(&i->second); + if (i->second.seed) --m_num_seeds; m_peers.erase(i++); } } -/* - // finds the peer that has the worst download rate - // and returns it. May return 0 if all peers are - // choked. - policy::iterator policy::find_choke_candidate() + + bool policy::is_connect_candidate(peer const& p, bool finished) { - INVARIANT_CHECK; - - iterator worst_peer = m_peers.end(); - size_type min_weight = (std::numeric_limits::min)(); - -#ifndef NDEBUG - int unchoked_counter = m_num_unchoked; -#endif + if (p.connection + || p.banned + || p.type == peer::not_connectable + || (p.seed && finished) + || p.failcount >= m_torrent->settings().max_failcount) + return false; - // TODO: make this selection better - - for (iterator i = m_peers.begin(); - i != m_peers.end(); ++i) - { - peer_connection* c = i->connection; - - if (c == 0) continue; - if (c->is_choked()) continue; -#ifndef NDEBUG - unchoked_counter--; -#endif - if (c->is_disconnecting()) continue; - // if the peer isn't interested, just choke it - if (!c->is_peer_interested()) - return i; - - size_type diff = i->total_download() - - i->total_upload(); - - size_type weight = static_cast(c->statistics().download_rate() * 10.f) - + diff - + ((c->is_interesting() && c->has_peer_choked())?-10:10)*1024; - - if (weight >= min_weight && worst_peer != m_peers.end()) continue; - - min_weight = weight; - worst_peer = i; - continue; - } - TORRENT_ASSERT(unchoked_counter == 0); - return worst_peer; + aux::session_impl& ses = m_torrent->session(); + if (ses.m_port_filter.access(p.ip.port()) & port_filter::blocked) + return false; + return true; } - policy::iterator policy::find_unchoke_candidate() - { - INVARIANT_CHECK; - - // if all of our peers are unchoked, there's - // no left to unchoke - if (m_num_unchoked == m_torrent->num_peers()) - return m_peers.end(); - - iterator unchoke_peer = m_peers.end(); - ptime min_time = libtorrent::min_time(); - float max_down_speed = 0.f; - - // TODO: make this selection better - - for (iterator i = m_peers.begin(); - i != m_peers.end(); ++i) - { - peer_connection* c = i->connection; - if (c == 0) continue; - if (c->is_disconnecting()) continue; - if (!c->is_choked()) continue; - if (!c->is_peer_interested()) continue; - if (c->share_diff() < -free_upload_amount - && m_torrent->ratio() != 0) continue; - if (c->statistics().download_rate() < max_down_speed) continue; - - min_time = i->last_optimistically_unchoked; - max_down_speed = c->statistics().download_rate(); - unchoke_peer = i; - } - return unchoke_peer; - } -*/ policy::iterator policy::find_disconnect_candidate() { INVARIANT_CHECK; @@ -520,13 +457,14 @@ namespace libtorrent ptime min_connect_time(now); iterator candidate = m_peers.end(); - int max_failcount = m_torrent->settings().max_failcount; int min_reconnect_time = m_torrent->settings().min_reconnect_time; - int min_cidr_distance = (std::numeric_limits::max)(); bool finished = m_torrent->is_finished(); - address external_ip = m_torrent->session().m_external_address; - if (external_ip == address()) + int min_cidr_distance = (std::numeric_limits::max)(); + address external_ip = m_torrent->session().external_address(); + + // don't bias any particular peers when seeding + if (finished || external_ip == address()) { // set external_ip to a random value, to // radomize which peers we prefer @@ -535,15 +473,18 @@ namespace libtorrent external_ip = address_v4(bytes); } - aux::session_impl& ses = m_torrent->session(); +#ifndef TORRENT_DISABLE_GEO_IP + int max_inet_as_rate = -1; + bool has_db = m_torrent->session().has_asnum_db(); +#endif + int connect_candidates = 0; + int seeds = 0; for (iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - if (i->second.connection) continue; - if (i->second.banned) continue; - if (i->second.type == peer::not_connectable) continue; - if (i->second.seed && finished) continue; - if (i->second.failcount >= max_failcount) continue; + if (i->second.seed) ++seeds; + if (!is_connect_candidate(i->second, finished)) continue; + ++connect_candidates; // prefer peers with lower failcount if (candidate != m_peers.end() @@ -552,26 +493,51 @@ namespace libtorrent if (now - i->second.connected < seconds(i->second.failcount * min_reconnect_time)) continue; - if (ses.m_port_filter.access(i->second.ip.port()) & port_filter::blocked) - continue; TORRENT_ASSERT(i->second.connected <= now); - if (i->second.connected > min_connect_time) continue; - int distance = cidr_distance(external_ip, i->second.ip.address()); - if (distance > min_cidr_distance) continue; + // don't replace a candidate that is on the local + // network with one that isn't. Local peers should + // always be tried first + if (candidate != m_peers.end() + && is_local(candidate->second.ip.address()) + && !is_local(i->second.ip.address())) + continue; + + if (i->second.connected > min_connect_time) continue; + +#ifndef TORRENT_DISABLE_GEO_IP + if (!finished && has_db) + { + // don't bias fast peers when seeding + std::pair* inet_as = i->second.inet_as; + int peak_rate = inet_as ? inet_as->second : 0; + if (peak_rate <= max_inet_as_rate) continue; + max_inet_as_rate = peak_rate; + } + + if (max_inet_as_rate <= 0) +#endif + { + int distance = cidr_distance(external_ip, i->second.ip.address()); + if (distance > min_cidr_distance) continue; + + min_cidr_distance = distance; + } - min_cidr_distance = distance; min_connect_time = i->second.connected; candidate = i; } + m_num_connect_candidates = connect_candidates; + m_num_seeds = seeds; TORRENT_ASSERT(min_connect_time <= now); #if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING if (candidate != m_peers.end()) { - (*m_torrent->session().m_logger) << "*** FOUND CONNECTION CANDIDATE [" + (*m_torrent->session().m_logger) << time_now_string() + << " *** FOUND CONNECTION CANDIDATE [" " ip: " << candidate->second.ip << " d: " << min_cidr_distance << " external: " << external_ip << @@ -582,113 +548,7 @@ namespace libtorrent return candidate; } -/* - policy::iterator policy::find_seed_choke_candidate() - { - INVARIANT_CHECK; - TORRENT_ASSERT(m_num_unchoked > 0); - // first choice candidate. - // it is a candidate we owe nothing to and which has been unchoked - // the longest. - iterator candidate = m_peers.end(); - - // not valid when candidate == 0 - ptime last_unchoke = min_time(); - - // second choice candidate. - // if there is no first choice candidate, this candidate will be chosen. - // it is the candidate that we owe the least to. - iterator second_candidate = m_peers.end(); - size_type lowest_share_diff = 0; // not valid when secondCandidate==0 - - for (iterator i = m_peers.begin(); - i != m_peers.end(); ++i) - { - peer_connection* c = i->connection; - // ignore peers that are choked or - // whose connection is closed - if (c == 0) continue; - - if (c->is_choked()) continue; - if (c->is_disconnecting()) continue; - - size_type share_diff = c->share_diff(); - - // select as second candidate the one that we owe the least - // to - if (second_candidate == m_peers.end() - || share_diff <= lowest_share_diff) - { - lowest_share_diff = share_diff; - second_candidate = i; - } - - // select as first candidate the one that we don't owe anything to - // and has been waiting for an unchoke the longest - if (share_diff > 0) continue; - if (candidate == m_peers.end() - || last_unchoke > i->last_optimistically_unchoked) - { - last_unchoke = i->last_optimistically_unchoked; - candidate = i; - } - } - if (candidate != m_peers.end()) return candidate; - TORRENT_ASSERT(second_candidate != m_peers.end()); - return second_candidate; - } - - policy::iterator policy::find_seed_unchoke_candidate() - { - INVARIANT_CHECK; - - iterator candidate = m_peers.end(); - ptime last_unchoke = time_now(); - - for (iterator i = m_peers.begin(); - i != m_peers.end(); ++i) - { - peer_connection* c = i->connection; - if (c == 0) continue; - if (!c->is_choked()) continue; - if (!c->is_peer_interested()) continue; - if (c->is_disconnecting()) continue; - if (last_unchoke < i->last_optimistically_unchoked) continue; - last_unchoke = i->last_optimistically_unchoked; - candidate = i; - } - return candidate; - } - - bool policy::seed_unchoke_one_peer() - { - INVARIANT_CHECK; - - iterator p = find_seed_unchoke_candidate(); - if (p != m_peers.end()) - { - TORRENT_ASSERT(p->connection->is_choked()); - p->connection->send_unchoke(); - p->last_optimistically_unchoked = time_now(); - ++m_num_unchoked; - } - return p != m_peers.end(); - } - - void policy::seed_choke_one_peer() - { - INVARIANT_CHECK; - - iterator p = find_seed_choke_candidate(); - if (p != m_peers.end()) - { - TORRENT_ASSERT(!p->connection->is_choked()); - p->connection->send_choke(); - --m_num_unchoked; - } - } -*/ void policy::pulse() { INVARIANT_CHECK; @@ -699,19 +559,39 @@ namespace libtorrent if (m_torrent->has_picker()) p = &m_torrent->picker(); +#ifndef TORRENT_DISABLE_DHT + bool pinged = false; +#endif + ptime now = time_now(); // remove old disconnected peers from the list for (iterator i = m_peers.begin(); i != m_peers.end();) { + peer& pe = i->second; + +#ifndef TORRENT_DISABLE_DHT + // try to send a DHT ping to this peer + // as well, to figure out if it supports + // DHT (uTorrent and BitComet doesn't + // advertise support) + if (!pinged && !pe.added_to_dht) + { + udp::endpoint node(pe.ip.address(), pe.ip.port()); + m_torrent->session().add_dht_node(node); + pe.added_to_dht = true; + pinged = true; + } +#endif // this timeout has to be customizable! // don't remove banned peers, they should // remain banned - if (i->second.connection == 0 - && i->second.connected != min_time() - && !i->second.banned - && now - i->second.connected > minutes(120)) + if (pe.connection == 0 + && pe.connected != min_time() + && !pe.banned + && now - pe.connected > minutes(120)) { - if (p) p->clear_peer(&i->second); + if (p) p->clear_peer(&pe); + if (pe.seed) --m_num_seeds; m_peers.erase(i++); } else @@ -796,123 +676,6 @@ namespace libtorrent , m_torrent->end() , m_available_free_upload); } -/* - // ------------------------ - // seed choking policy - // ------------------------ - if (m_torrent->is_seed()) - { - if (m_num_unchoked > m_torrent->m_uploads_quota.given) - { - do - { - iterator p = find_seed_choke_candidate(); - --m_num_unchoked; - TORRENT_ASSERT(p != m_peers.end()); - if (p == m_peers.end()) break; - - TORRENT_ASSERT(!p->connection->is_choked()); - p->connection->send_choke(); - } while (m_num_unchoked > m_torrent->m_uploads_quota.given); - } - else if (m_num_unchoked > 0) - { - // optimistic unchoke. trade the 'worst' - // unchoked peer with one of the choked - // TODO: This rotation should happen - // far less frequent than this! - TORRENT_ASSERT(m_num_unchoked <= m_torrent->num_peers()); - iterator p = find_seed_unchoke_candidate(); - if (p != m_peers.end()) - { - TORRENT_ASSERT(p->connection->is_choked()); - seed_choke_one_peer(); - p->connection->send_unchoke(); - ++m_num_unchoked; - } - - } - - // make sure we have enough - // unchoked peers - while (m_num_unchoked < m_torrent->m_uploads_quota.given) - { - if (!seed_unchoke_one_peer()) break; - } -#ifndef NDEBUG - check_invariant(); -#endif - } - - // ---------------------------- - // downloading choking policy - // ---------------------------- - else - { - if (m_torrent->ratio() != 0) - { - // choke peers that have leeched too much without giving anything back - for (iterator i = m_peers.begin(); - i != m_peers.end(); ++i) - { - peer_connection* c = i->connection; - if (c == 0) continue; - - size_type diff = i->connection->share_diff(); - if (diff < -free_upload_amount - && !c->is_choked()) - { - // if we have uploaded more than a piece for free, choke peer and - // wait until we catch up with our download. - c->send_choke(); - --m_num_unchoked; - } - } - } - - if (m_torrent->m_uploads_quota.given < m_torrent->num_peers()) - { - TORRENT_ASSERT(m_torrent->m_uploads_quota.given >= 0); - - // make sure we don't have too many - // unchoked peers - if (m_num_unchoked > m_torrent->m_uploads_quota.given) - { - do - { - iterator p = find_choke_candidate(); - if (p == m_peers.end()) break; - TORRENT_ASSERT(p != m_peers.end()); - TORRENT_ASSERT(!p->connection->is_choked()); - p->connection->send_choke(); - --m_num_unchoked; - } while (m_num_unchoked > m_torrent->m_uploads_quota.given); - } - // this should prevent the choke/unchoke - // problem, since it will not unchoke unless - // there actually are any choked peers - else if (count_choked() > 0) - { - // optimistic unchoke. trade the 'worst' - // unchoked peer with one of the choked - TORRENT_ASSERT(m_num_unchoked <= m_torrent->num_peers()); - iterator p = find_unchoke_candidate(); - if (p != m_peers.end()) - { - TORRENT_ASSERT(p->connection->is_choked()); - choke_one_peer(); - p->connection->send_unchoke(); - ++m_num_unchoked; - } - } - } - - // make sure we have enough - // unchoked peers - while (m_num_unchoked < m_torrent->m_uploads_quota.given - && unchoke_one_peer()); - } -*/ } int policy::count_choked() const @@ -931,7 +694,7 @@ namespace libtorrent return ret; } - void policy::new_connection(peer_connection& c) + bool policy::new_connection(peer_connection& c) { TORRENT_ASSERT(!c.is_local()); @@ -943,16 +706,20 @@ namespace libtorrent // TODO: only allow _one_ connection to use this // override at a time - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); + asio::error_code ec; + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); + aux::session_impl& ses = m_torrent->session(); + if (m_torrent->num_peers() >= m_torrent->max_connections() - && m_torrent->session().num_connections() >= m_torrent->session().max_connections() + && ses.num_connections() >= ses.max_connections() && c.remote().address() != m_torrent->current_tracker().address()) { - throw protocol_error("too many connections, refusing incoming connection"); // cause a disconnect + c.disconnect("too many connections, refusing incoming connection"); + return false; } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING if (c.remote().address() == m_torrent->current_tracker().address()) { m_torrent->debug_log("overriding connection limit for tracker NAT-check"); @@ -977,7 +744,10 @@ namespace libtorrent if (i != m_peers.end()) { if (i->second.banned) - throw protocol_error("ip address banned, closing"); + { + c.disconnect("ip address banned, closing"); + return false; + } if (i->second.connection != 0) { @@ -986,16 +756,18 @@ namespace libtorrent // or the current one is already connected if (!i->second.connection->is_connecting() || c.is_local()) { - throw protocol_error("duplicate connection, closing"); + c.disconnect("duplicate connection, closing"); + return false; } else { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING m_torrent->debug_log("duplicate connection. existing connection" " is connecting and this connection is incoming. closing existing " "connection in favour of this one"); #endif - i->second.connection->disconnect(); + i->second.connection->disconnect("incoming duplicate connection " + "with higher priority, closing"); } } } @@ -1003,10 +775,18 @@ namespace libtorrent { // we don't have any info about this peer. // add a new entry - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint()); + asio::error_code ec; + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); peer p(c.remote(), peer::not_connectable, 0); i = m_peers.insert(std::make_pair(c.remote().address(), p)); +#ifndef TORRENT_DISABLE_GEO_IP + int as = ses.as_for_ip(c.remote().address()); +#ifndef NDEBUG + i->second.inet_as_num = as; +#endif + i->second.inet_as = ses.lookup_as(as); +#endif } c.set_peer_info(&i->second); @@ -1018,13 +798,17 @@ namespace libtorrent TORRENT_ASSERT(i->second.connection); if (!c.fast_reconnect()) i->second.connected = time_now(); -// m_last_optimistic_disconnect = time_now(); + if (m_num_connect_candidates > 0) + --m_num_connect_candidates; + return true; } - void policy::update_peer_port(int port, policy::peer* p, int src) + bool policy::update_peer_port(int port, policy::peer* p, int src) { TORRENT_ASSERT(p != 0); - if (p->ip.port() == port) return; + TORRENT_ASSERT(p->connection); + + if (p->ip.port() == port) return true; if (m_torrent->settings().allow_multiple_connections_per_ip) { @@ -1037,10 +821,12 @@ namespace libtorrent policy::peer& pp = i->second; if (pp.connection) { - throw protocol_error("duplicate connection"); + p->connection->disconnect("duplicate connection"); + return false; } if (m_torrent->has_picker()) m_torrent->picker().clear_peer(&i->second); + if (i->second.seed) --m_num_seeds; m_peers.erase(i); } } @@ -1050,6 +836,18 @@ namespace libtorrent } p->ip.port(port); p->source |= src; + return true; + } + + bool policy::has_peer(policy::peer const* p) const + { + // find p in m_peers + for (std::multimap::const_iterator i = m_peers.begin() + , end(m_peers.end()); i != end; ++i) + { + if (&i->second == p) return true; + } + return false; } policy::peer* policy::peer_from_tracker(tcp::endpoint const& remote, peer_id const& pid @@ -1075,88 +873,101 @@ namespace libtorrent return 0; } - try + iterator i; + + if (m_torrent->settings().allow_multiple_connections_per_ip) { - iterator i; - - if (m_torrent->settings().allow_multiple_connections_per_ip) + std::pair range = m_peers.equal_range(remote.address()); + i = std::find_if(range.first, range.second, match_peer_endpoint(remote)); + if (i == range.second) i = m_peers.end(); + } + else + { + i = m_peers.find(remote.address()); + } + + if (i == m_peers.end()) + { + // if the IP is blocked, don't add it + if (ses.m_ip_filter.access(remote.address()) & ip_filter::blocked) { - std::pair range = m_peers.equal_range(remote.address()); - i = std::find_if(range.first, range.second, match_peer_endpoint(remote)); - if (i == range.second) i = m_peers.end(); - } - else - { - i = m_peers.find(remote.address()); - } - - if (i == m_peers.end()) - { - // if the IP is blocked, don't add it - if (ses.m_ip_filter.access(remote.address()) & ip_filter::blocked) + if (ses.m_alerts.should_post(alert::info)) { - if (ses.m_alerts.should_post(alert::info)) - { - ses.m_alerts.post_alert(peer_blocked_alert(remote.address() - , "blocked peer not added to peer list")); - } - return 0; + ses.m_alerts.post_alert(peer_blocked_alert(remote.address() + , "blocked peer not added to peer list")); } - - // we don't have any info about this peer. - // add a new entry - i = m_peers.insert(std::make_pair(remote.address() + return 0; + } + + // we don't have any info about this peer. + // add a new entry + i = m_peers.insert(std::make_pair(remote.address() , peer(remote, peer::connectable, src))); #ifndef TORRENT_DISABLE_ENCRYPTION - if (flags & 0x01) i->second.pe_support = true; + if (flags & 0x01) i->second.pe_support = true; #endif - if (flags & 0x02) i->second.seed = true; - } - else + if (flags & 0x02) { - i->second.type = peer::connectable; - - i->second.ip = remote; - i->second.source |= src; - - // if this peer has failed before, decrease the - // counter to allow it another try, since somebody - // else is appearantly able to connect to it - // if it comes from the DHT it might be stale though - if (i->second.failcount > 0 && src != peer_info::dht) - --i->second.failcount; - - // if we're connected to this peer - // we already know if it's a seed or not - // so we don't have to trust this source - if ((flags & 0x02) && !i->second.connection) i->second.seed = true; - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (i->second.connection) - { - // this means we're already connected - // to this peer. don't connect to - // it again. - - m_torrent->debug_log("already connected to peer: " + remote.address().to_string() + ":" - + boost::lexical_cast(remote.port()) + " " - + boost::lexical_cast(i->second.connection->pid())); - - TORRENT_ASSERT(i->second.connection->associated_torrent().lock().get() == m_torrent); - } -#endif + i->second.seed = true; + ++m_num_seeds; } - return &i->second; + +#ifndef TORRENT_DISABLE_GEO_IP + int as = ses.as_for_ip(remote.address()); +#ifndef NDEBUG + i->second.inet_as_num = as; +#endif + i->second.inet_as = ses.lookup_as(as); +#endif + if (is_connect_candidate(i->second, m_torrent->is_finished())) + ++m_num_connect_candidates; } - catch(std::exception& e) + else { - if (m_torrent->alerts().should_post(alert::debug)) + bool was_conn_cand = is_connect_candidate(i->second, m_torrent->is_finished()); + + i->second.type = peer::connectable; + + i->second.ip = remote; + i->second.source |= src; + + // if this peer has failed before, decrease the + // counter to allow it another try, since somebody + // else is appearantly able to connect to it + // only trust this if it comes from the tracker + if (i->second.failcount > 0 && src == peer_info::tracker) + --i->second.failcount; + + // if we're connected to this peer + // we already know if it's a seed or not + // so we don't have to trust this source + if ((flags & 0x02) && !i->second.connection) { - m_torrent->alerts().post_alert( - peer_error_alert(remote, pid, e.what())); + if (!i->second.seed) ++m_num_seeds; + i->second.seed = true; } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + if (i->second.connection) + { + // this means we're already connected + // to this peer. don't connect to + // it again. + + m_torrent->debug_log("already connected to peer: " + remote.address().to_string() + ":" + + boost::lexical_cast(remote.port()) + " " + + boost::lexical_cast(i->second.connection->pid())); + + TORRENT_ASSERT(i->second.connection->associated_torrent().lock().get() == m_torrent); + } +#endif + + if (was_conn_cand != is_connect_candidate(i->second, m_torrent->is_finished())) + if (was_conn_cand) --m_num_connect_candidates; + else ++m_num_connect_candidates; } - return 0; + + return &i->second; } // this is called when we are choked by a peer @@ -1209,6 +1020,8 @@ namespace libtorrent , boost::bind(std::equal_to(), bind(&peer::connection , bind(&iterator::value_type::second, _1)), &c)) != m_peers.end()); + aux::session_impl& ses = m_torrent->session(); + // if the peer is choked and we have upload slots left, // then unchoke it. Another condition that has to be met // is that the torrent doesn't keep track of the individual @@ -1220,23 +1033,23 @@ namespace libtorrent // In that case we don't care if people are leeching, they // can't pay for their downloads anyway. if (c.is_choked() - && m_torrent->session().num_uploads() < m_torrent->session().max_uploads() + && ses.num_uploads() < ses.max_uploads() && (m_torrent->ratio() == 0 || c.share_diff() >= -free_upload_amount || m_torrent->is_finished())) { - m_torrent->session().unchoke_peer(c); + ses.unchoke_peer(c); } -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING else if (c.is_choked()) { std::string reason; - if (m_torrent->session().num_uploads() >= m_torrent->session().max_uploads()) + if (ses.num_uploads() >= ses.max_uploads()) { reason = "the number of uploads (" - + boost::lexical_cast(m_torrent->session().num_uploads()) + + boost::lexical_cast(ses.num_uploads()) + ") is more than or equal to the limit (" - + boost::lexical_cast(m_torrent->session().max_uploads()) + + boost::lexical_cast(ses.max_uploads()) + ")"; } else @@ -1326,28 +1139,12 @@ namespace libtorrent TORRENT_ASSERT(!p->second.connection); TORRENT_ASSERT(p->second.type == peer::connectable); - try + if (!m_torrent->connect_to_peer(&p->second)) { - if (!m_torrent->connect_to_peer(&p->second)) - { - ++p->second.failcount; - return false; - } - p->second.connection->add_stat(p->second.prev_amount_download, p->second.prev_amount_upload); - p->second.prev_amount_download = 0; - p->second.prev_amount_upload = 0; - return true; - } - catch (std::exception& e) - { -#if defined(TORRENT_VERBOSE_LOGGING) - (*m_torrent->session().m_logger) << "*** CONNECTION FAILED '" - << e.what() << "'\n"; -#endif - std::cerr << e.what() << std::endl; ++p->second.failcount; return false; } + return true; } bool policy::disconnect_one_peer() @@ -1355,16 +1152,16 @@ namespace libtorrent iterator p = find_disconnect_candidate(); if (p == m_peers.end()) return false; -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*p->second.connection->m_logger) << "*** CLOSING CONNECTION 'too many connections'\n"; #endif - p->second.connection->disconnect(); + p->second.connection->disconnect("too many connections, closing"); return true; } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c) throw() + void policy::connection_closed(const peer_connection& c) { // too expensive // INVARIANT_CHECK; @@ -1397,6 +1194,9 @@ namespace libtorrent // p->connected = time_now(); } + if (is_connect_candidate(*p, m_torrent->is_finished())) + ++m_num_connect_candidates; + // if the share ratio is 0 (infinite), the // m_available_free_upload isn't used, // because it isn't necessary. @@ -1406,6 +1206,8 @@ namespace libtorrent TORRENT_ASSERT(c.share_diff() < (std::numeric_limits::max)()); m_available_free_upload += c.share_diff(); } + TORRENT_ASSERT(p->prev_amount_upload == 0); + TORRENT_ASSERT(p->prev_amount_download == 0); p->prev_amount_download += c.statistics().total_payload_download(); p->prev_amount_upload += c.statistics().total_payload_upload(); } @@ -1428,8 +1230,8 @@ namespace libtorrent // INVARIANT_CHECK; TORRENT_ASSERT(c); - try { TORRENT_ASSERT(c->remote() == c->get_socket()->remote_endpoint()); } - catch (std::exception&) {} + asio::error_code ec; + TORRENT_ASSERT(c->remote() == c->get_socket()->remote_endpoint(ec) || ec); return std::find_if( m_peers.begin() @@ -1439,6 +1241,7 @@ namespace libtorrent void policy::check_invariant() const { + TORRENT_ASSERT(m_num_connect_candidates >= 0); if (m_torrent->is_aborted()) return; int connected_peers = 0; @@ -1451,6 +1254,9 @@ namespace libtorrent i != m_peers.end(); ++i) { peer const& p = i->second; +#ifndef TORRENT_DISABLE_GEO_IP + TORRENT_ASSERT(p.inet_as == 0 || p.inet_as->first == p.inet_as_num); +#endif if (!m_torrent->settings().allow_multiple_connections_per_ip) { TORRENT_ASSERT(m_peers.count(p.ip.address()) == 1); @@ -1467,6 +1273,8 @@ namespace libtorrent { continue; } + TORRENT_ASSERT(p.prev_amount_upload == 0); + TORRENT_ASSERT(p.prev_amount_download == 0); if (p.optimistically_unchoked) { TORRENT_ASSERT(p.connection); @@ -1536,25 +1344,32 @@ namespace libtorrent policy::peer::peer(const tcp::endpoint& ip_, peer::connection_type t, int src) : ip(ip_) +#ifndef TORRENT_DISABLE_GEO_IP + , inet_as(0) +#endif + , failcount(0) + , trust_points(0) + , source(src) + , hashfails(0) , type(t) + , fast_reconnects(0) #ifndef TORRENT_DISABLE_ENCRYPTION , pe_support(true) #endif - , failcount(0) - , hashfails(0) - , seed(false) - , fast_reconnects(0) , optimistically_unchoked(false) - , last_optimistically_unchoked(min_time()) - , connected(min_time()) - , trust_points(0) + , seed(false) , on_parole(false) + , banned(false) +#ifndef TORRENT_DISABLE_DHT + , added_to_dht(false) +#endif + , connection(0) , prev_amount_upload(0) , prev_amount_download(0) - , banned(false) - , source(src) - , connection(0) + , last_optimistically_unchoked(min_time()) + , connected(min_time()) { + TORRENT_ASSERT((src & 0xff) == src); TORRENT_ASSERT(connected < time_now()); } diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 331ffa377..538ae47d5 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -80,6 +80,11 @@ using boost::bind; using boost::mutex; using libtorrent::aux::session_impl; +#ifdef TORRENT_MEMDEBUG +void start_malloc_debug(); +void stop_malloc_debug(); +#endif + namespace libtorrent { @@ -107,16 +112,19 @@ namespace libtorrent fingerprint const& id , std::pair listen_port_range , char const* listen_interface -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path logpath #endif ) : m_impl(new session_impl(listen_port_range, id, listen_interface -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , logpath #endif )) { +#ifdef TORRENT_MEMDEBUG + start_malloc_debug(); +#endif // turn off the filename checking in boost.filesystem TORRENT_ASSERT(listen_port_range.first > 0); TORRENT_ASSERT(listen_port_range.first < listen_port_range.second); @@ -130,16 +138,19 @@ namespace libtorrent } session::session(fingerprint const& id -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path logpath #endif ) -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING : m_impl(new session_impl(std::make_pair(0, 0), id, "0.0.0.0", logpath)) #else : m_impl(new session_impl(std::make_pair(0, 0), id, "0.0.0.0")) #endif { +#ifdef TORRENT_MEMDEBUG + start_malloc_debug(); +#endif #ifndef NDEBUG boost::function0 test = boost::ref(*m_impl); TORRENT_ASSERT(!test.empty()); @@ -148,6 +159,9 @@ namespace libtorrent session::~session() { +#ifdef TORRENT_MEMDEBUG + stop_malloc_debug(); +#endif TORRENT_ASSERT(m_impl); // if there is at least one destruction-proxy // abort the session and let the destructor @@ -161,6 +175,28 @@ namespace libtorrent m_impl->add_extension(ext); } +#ifndef TORRENT_DISABLE_GEO_IP + bool session::load_asnum_db(char const* file) + { + return m_impl->load_asnum_db(file); + } + + bool session::load_country_db(char const* file) + { + return m_impl->load_country_db(file); + } +#endif + + void session::load_state(entry const& ses_state) + { + m_impl->load_state(ses_state); + } + + entry session::state() const + { + return m_impl->state(); + } + void session::set_ip_filter(ip_filter const& f) { m_impl->set_ip_filter(f); @@ -263,6 +299,17 @@ namespace libtorrent return m_impl->status(); } + void session::get_cache_info(sha1_hash const& ih + , std::vector& ret) const + { + m_impl->m_disk_thread.get_cache_info(ih, ret); + } + + cache_status session::get_cache_status() const + { + return m_impl->m_disk_thread.status(); + } + #ifndef TORRENT_DISABLE_DHT void session::start_dht(entry const& startup_state) @@ -437,14 +484,14 @@ namespace libtorrent m_impl->start_lsd(); } - void session::start_natpmp() + natpmp* session::start_natpmp() { - m_impl->start_natpmp(); + return m_impl->start_natpmp(); } - void session::start_upnp() + upnp* session::start_upnp() { - m_impl->start_upnp(); + return m_impl->start_upnp(); } void session::stop_lsd() diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 025437977..edb5a4f03 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -116,423 +116,8 @@ namespace detail } - } namespace aux { - // This is the checker thread - // it is looping in an infinite loop - // until the session is aborted. It will - // normally just block in a wait() call, - // waiting for a signal from session that - // there's a new torrent to check. - - void checker_impl::operator()() - { - eh_initializer(); - // if we're currently performing a full file check, - // this is the torrent being processed - boost::shared_ptr processing; - boost::shared_ptr t; - for (;;) - { - // temporary torrent used while checking fastresume data - try - { - t.reset(); - { - boost::mutex::scoped_lock l(m_mutex); - - INVARIANT_CHECK; - - // if the job queue is empty and - // we shouldn't abort - // wait for a signal - while (m_torrents.empty() && !m_abort && !processing) - m_cond.wait(l); - - if (m_abort) - { - // no lock is needed here, because the main thread - // has already been shut down by now - processing.reset(); - t.reset(); - std::for_each(m_torrents.begin(), m_torrents.end() - , boost::bind(&torrent::abort - , boost::bind(&shared_ptr::get - , boost::bind(&piece_checker_data::torrent_ptr, _1)))); - m_torrents.clear(); - std::for_each(m_processing.begin(), m_processing.end() - , boost::bind(&torrent::abort - , boost::bind(&shared_ptr::get - , boost::bind(&piece_checker_data::torrent_ptr, _1)))); - m_processing.clear(); - return; - } - - if (!m_torrents.empty()) - { - t = m_torrents.front(); - if (t->abort) - { - // make sure the locking order is - // consistent to avoid dead locks - // we need to lock the session because closing - // torrents assume to have access to it - l.unlock(); - session_impl::mutex_t::scoped_lock l2(m_ses.m_mutex); - l.lock(); - - t->torrent_ptr->abort(); - m_torrents.pop_front(); - continue; - } - } - } - - if (t) - { - std::string error_msg; - t->parse_resume_data(t->resume_data, t->torrent_ptr->torrent_file() - , error_msg); - - // lock the session to add the new torrent - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - - if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) - { - m_ses.m_alerts.post_alert(fastresume_rejected_alert( - t->torrent_ptr->get_handle() - , error_msg)); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "fastresume data for " - << t->torrent_ptr->torrent_file().name() << " rejected: " - << error_msg << "\n"; -#endif - } - - mutex::scoped_lock l2(m_mutex); - - if (m_torrents.empty() || m_torrents.front() != t) - { - // this means the torrent was removed right after it was - // added. Abort the checking. - t.reset(); - continue; - } - - // clear the resume data now that it has been used - // (the fast resume data is now parsed and stored in t) - t->resume_data = entry(); - bool up_to_date = t->torrent_ptr->check_fastresume(*t); - - if (up_to_date) - { - INVARIANT_CHECK; - - TORRENT_ASSERT(!m_torrents.empty()); - TORRENT_ASSERT(m_torrents.front() == t); - - t->torrent_ptr->files_checked(t->unfinished_pieces); - m_torrents.pop_front(); - - // we cannot add the torrent if the session is aborted. - if (!m_ses.is_aborted()) - { - m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr)); - if (m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_checked_alert( - t->torrent_ptr->get_handle() - , "torrent finished checking")); - } - if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_finished_alert( - t->torrent_ptr->get_handle() - , "torrent is complete")); - } - - peer_id id; - std::fill(id.begin(), id.end(), 0); - for (std::vector::const_iterator i = t->peers.begin(); - i != t->peers.end(); ++i) - { - t->torrent_ptr->get_policy().peer_from_tracker(*i, id - , peer_info::resume_data, 0); - } - - for (std::vector::const_iterator i = t->banned_peers.begin(); - i != t->banned_peers.end(); ++i) - { - policy::peer* p = t->torrent_ptr->get_policy().peer_from_tracker(*i, id - , peer_info::resume_data, 0); - if (p) p->banned = true; - } - } - else - { - t->torrent_ptr->abort(); - } - t.reset(); - continue; - } - - l.unlock(); - - // move the torrent from - // m_torrents to m_processing - TORRENT_ASSERT(m_torrents.front() == t); - - m_torrents.pop_front(); - m_processing.push_back(t); - if (!processing) - { - processing = t; - processing->processing = true; - t.reset(); - } - } - } - catch (const std::exception& e) - { - // This will happen if the storage fails to initialize - // for example if one of the files has an invalid filename. - 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)) - { - m_ses.m_alerts.post_alert( - file_error_alert( - t->torrent_ptr->get_handle() - , e.what())); - } - t->torrent_ptr->abort(); - - TORRENT_ASSERT(!m_torrents.empty()); - m_torrents.pop_front(); - } - catch(...) - { -#ifndef NDEBUG - std::cerr << "error while checking resume data\n"; -#endif - mutex::scoped_lock l(m_mutex); - TORRENT_ASSERT(!m_torrents.empty()); - m_torrents.pop_front(); - TORRENT_ASSERT(false); - } - - if (!processing) continue; - - try - { - TORRENT_ASSERT(processing); - - float finished = false; - float progress = 0.f; - boost::tie(finished, progress) = processing->torrent_ptr->check_files(); - - { - mutex::scoped_lock l2(m_mutex); - - INVARIANT_CHECK; - - processing->progress = progress; - if (processing->abort) - { - TORRENT_ASSERT(!m_processing.empty()); - TORRENT_ASSERT(m_processing.front() == processing); - m_processing.pop_front(); - - // make sure the lock order is correct - l2.unlock(); - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - l2.lock(); - processing->torrent_ptr->abort(); - - processing.reset(); - if (!m_processing.empty()) - { - processing = m_processing.front(); - processing->processing = true; - } - continue; - } - } - if (finished) - { - // lock the session to add the new torrent - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - mutex::scoped_lock l2(m_mutex); - - INVARIANT_CHECK; - - TORRENT_ASSERT(!m_processing.empty()); - TORRENT_ASSERT(m_processing.front() == processing); - - // TODO: factor out the adding of torrents to the session - // and to the checker thread to avoid duplicating the - // check for abortion. - if (!m_ses.is_aborted()) - { - processing->torrent_ptr->files_checked(processing->unfinished_pieces); - m_ses.m_torrents.insert(std::make_pair( - processing->info_hash, processing->torrent_ptr)); - if (m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_checked_alert( - processing->torrent_ptr->get_handle() - , "torrent finished checking")); - } - if (processing->torrent_ptr->is_seed() - && m_ses.m_alerts.should_post(alert::info)) - { - m_ses.m_alerts.post_alert(torrent_finished_alert( - processing->torrent_ptr->get_handle() - , "torrent is complete")); - } - - peer_id id; - std::fill(id.begin(), id.end(), 0); - for (std::vector::const_iterator i = processing->peers.begin(); - i != processing->peers.end(); ++i) - { - processing->torrent_ptr->get_policy().peer_from_tracker(*i, id - , peer_info::resume_data, 0); - } - - for (std::vector::const_iterator i = processing->banned_peers.begin(); - i != processing->banned_peers.end(); ++i) - { - policy::peer* p = processing->torrent_ptr->get_policy().peer_from_tracker(*i, id - , peer_info::resume_data, 0); - if (p) p->banned = true; - } - } - else - { - processing->torrent_ptr->abort(); - } - processing.reset(); - m_processing.pop_front(); - if (!m_processing.empty()) - { - processing = m_processing.front(); - processing->processing = true; - } - } - } - catch(std::exception const& e) - { - // This will happen if the storage fails to initialize - 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)) - { - m_ses.m_alerts.post_alert( - file_error_alert( - processing->torrent_ptr->get_handle() - , e.what())); - } - - processing->torrent_ptr->abort(); - - if (!m_processing.empty() - && m_processing.front() == processing) - m_processing.pop_front(); - processing.reset(); - if (!m_processing.empty()) - { - processing = m_processing.front(); - processing->processing = true; - } - } - catch(...) - { -#ifndef NDEBUG - std::cerr << "error while checking files\n"; -#endif - mutex::scoped_lock l(m_mutex); - TORRENT_ASSERT(!m_processing.empty()); - - processing.reset(); - m_processing.pop_front(); - if (!m_processing.empty()) - { - processing = m_processing.front(); - processing->processing = true; - } - - TORRENT_ASSERT(false); - } - } - } - - aux::piece_checker_data* checker_impl::find_torrent(sha1_hash const& info_hash) - { - INVARIANT_CHECK; - for (std::deque >::iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) - { - if ((*i)->info_hash == info_hash) return i->get(); - } - for (std::deque >::iterator i - = m_processing.begin(); i != m_processing.end(); ++i) - { - if ((*i)->info_hash == info_hash) return i->get(); - } - - return 0; - } - - void checker_impl::remove_torrent(sha1_hash const& info_hash, int options) - { - INVARIANT_CHECK; - for (std::deque >::iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) - { - if ((*i)->info_hash == info_hash) - { - TORRENT_ASSERT((*i)->processing == false); - if (options & session::delete_files) - (*i)->torrent_ptr->delete_files(); - m_torrents.erase(i); - return; - } - } - for (std::deque >::iterator i - = m_processing.begin(); i != m_processing.end(); ++i) - { - if ((*i)->info_hash == info_hash) - { - TORRENT_ASSERT((*i)->processing == false); - if (options & session::delete_files) - (*i)->torrent_ptr->delete_files(); - m_processing.erase(i); - return; - } - } - - TORRENT_ASSERT(false); - } - -#ifndef NDEBUG - void checker_impl::check_invariant() const - { - for (std::deque >::const_iterator i - = m_torrents.begin(); i != m_torrents.end(); ++i) - { - TORRENT_ASSERT(*i); - TORRENT_ASSERT((*i)->torrent_ptr); - } - for (std::deque >::const_iterator i - = m_processing.begin(); i != m_processing.end(); ++i) - { - TORRENT_ASSERT(*i); - TORRENT_ASSERT((*i)->torrent_ptr); - } - } -#endif +} +namespace aux { struct seed_random_generator { @@ -546,21 +131,30 @@ namespace detail std::pair listen_port_range , fingerprint const& cl_fprint , char const* listen_interface -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , fs::path const& logpath #endif ) - : m_send_buffers(send_buffer_size) - , m_files(40) - , m_strand(m_io_service) + : +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + m_send_buffers(send_buffer_size), +#endif + m_files(40) + , m_io_service() + , m_disk_thread(m_io_service) , m_half_open(m_io_service) , m_download_channel(m_io_service, peer_connection::download_channel) +#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT + , m_upload_channel(m_io_service, peer_connection::upload_channel, true) +#else , m_upload_channel(m_io_service, peer_connection::upload_channel) +#endif , m_tracker_manager(m_settings, m_tracker_proxy) , m_listen_port_retries(listen_port_range.second - listen_port_range.first) , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) , m_abort(false) , m_max_uploads(8) + , m_allowed_upload_slots(8) , m_max_connections(200) , m_num_unchoked(0) , m_unchoke_time_scaler(0) @@ -571,14 +165,23 @@ namespace detail #ifndef TORRENT_DISABLE_DHT , m_dht_same_port(true) , m_external_udp_port(0) + , m_dht_socket(m_io_service, bind(&session_impl::on_receive_udp, this, _1, _2, _3, _4) + , m_half_open) #endif , m_timer(m_io_service) , m_next_connect_torrent(0) -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , m_logpath(logpath) #endif - , m_checker_impl(*this) +#ifndef TORRENT_DISABLE_GEO_IP + , m_asnum_db(0) + , m_country_db(0) +#endif { + m_tcp_mapping[0] = -1; + m_tcp_mapping[1] = -1; + m_udp_mapping[0] = -1; + m_udp_mapping[1] = -1; #ifdef WIN32 // windows XP has a limit on the number of // simultaneous half-open TCP connections @@ -598,7 +201,7 @@ namespace detail m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; m_bandwidth_manager[peer_connection::upload_channel] = &m_upload_channel; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING m_logger = create_log("main_session", listen_port(), false); (*m_logger) << time_now_string() << "\n"; #endif @@ -644,12 +247,119 @@ namespace detail *i = printable[rand() % (sizeof(printable)-1)]; } - m_timer.expires_from_now(seconds(1)); - m_timer.async_wait(m_strand.wrap( - bind(&session_impl::second_tick, this, _1))); + asio::error_code ec; + m_timer.expires_from_now(seconds(1), ec); + m_timer.async_wait( + bind(&session_impl::second_tick, this, _1)); m_thread.reset(new boost::thread(boost::ref(*this))); - m_checker_thread.reset(new boost::thread(boost::ref(m_checker_impl))); + } + +#ifndef TORRENT_DISABLE_GEO_IP + namespace + { + struct free_ptr + { + void* ptr_; + free_ptr(void* p): ptr_(p) {} + ~free_ptr() { free(ptr_); } + }; + } + + char const* session_impl::country_for_ip(address const& a) + { + if (!a.is_v4() || m_country_db == 0) return 0; + return GeoIP_country_code_by_ipnum(m_country_db, a.to_v4().to_ulong()); + } + + int session_impl::as_for_ip(address const& a) + { + if (!a.is_v4() || m_asnum_db == 0) return 0; + char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); + if (name == 0) return 0; + free_ptr p(name); + // GeoIP returns the name as AS??? where ? is the AS-number + return atoi(name + 2); + } + + std::string session_impl::as_name_for_ip(address const& a) + { + if (!a.is_v4() || m_asnum_db == 0) return std::string(); + char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); + if (name == 0) return std::string(); + free_ptr p(name); + char* tmp = std::strchr(name, ' '); + if (tmp == 0) return std::string(); + return tmp + 1; + } + + std::pair* session_impl::lookup_as(int as) + { + std::map::iterator i = m_as_peak.lower_bound(as); + + if (i == m_as_peak.end() || i->first != as) + { + // we don't have any data for this AS, insert a new entry + i = m_as_peak.insert(i, std::pair(as, 0)); + } + return &(*i); + } + + bool session_impl::load_asnum_db(char const* file) + { + mutex_t::scoped_lock l(m_mutex); + if (m_asnum_db) GeoIP_delete(m_asnum_db); + m_asnum_db = GeoIP_open(file, GEOIP_STANDARD); + return m_asnum_db; + } + + bool session_impl::load_country_db(char const* file) + { + mutex_t::scoped_lock l(m_mutex); + if (m_country_db) GeoIP_delete(m_country_db); + m_country_db = GeoIP_open(file, GEOIP_STANDARD); + return m_country_db; + } + +#endif + + void session_impl::load_state(entry const& ses_state) + { + if (ses_state.type() != entry::dictionary_t) return; + mutex_t::scoped_lock l(m_mutex); +#ifndef TORRENT_DISABLE_GEO_IP + entry const* as_map = ses_state.find_key("AS map"); + if (as_map && as_map->type() == entry::dictionary_t) + { + entry::dictionary_type const& as_peak = as_map->dict(); + for (entry::dictionary_type::const_iterator i = as_peak.begin() + , end(as_peak.end()); i != end; ++i) + { + int as_num = atoi(i->first.c_str()); + if (i->second.type() != entry::int_t || i->second.integer() == 0) continue; + int& peak = m_as_peak[as_num]; + if (peak < i->second.integer()) peak = i->second.integer(); + } + } +#endif + } + + entry session_impl::state() const + { + mutex_t::scoped_lock l(m_mutex); + entry ret; +#ifndef TORRENT_DISABLE_GEO_IP + entry::dictionary_type& as_map = ret["AS map"].dict(); + char buf[10]; + for (std::map::const_iterator i = m_as_peak.begin() + , end(m_as_peak.end()); i != end; ++i) + { + if (i->second == 0) continue; + sprintf(buf, "%05d", i->first); + as_map[buf] = i->second; + } +#endif + return ret; } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -681,18 +391,20 @@ namespace detail if (m_natpmp) m_natpmp->close(); #ifndef TORRENT_DISABLE_DHT if (m_dht) m_dht->stop(); + m_dht_socket.close(); #endif - m_timer.cancel(); + asio::error_code ec; + m_timer.cancel(ec); // close the listen sockets for (std::list::iterator i = m_listen_sockets.begin() , end(m_listen_sockets.end()); i != end; ++i) { - i->sock->close(); + i->sock->close(ec); } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " aborting all torrents\n"; + (*m_logger) << time_now_string() << " aborting all torrents (" << m_torrents.size() << ")\n"; #endif // abort all torrents for (torrent_map::iterator i = m_torrents.begin() @@ -731,10 +443,10 @@ namespace detail #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) boost::shared_ptr tl(new tracker_logger(*this)); m_tracker_loggers.push_back(tl); - m_tracker_manager.queue_request(m_strand, m_half_open, req, login + m_tracker_manager.queue_request(m_io_service, m_half_open, req, login , m_listen_interface.address(), tl); #else - m_tracker_manager.queue_request(m_strand, m_half_open, req, login + m_tracker_manager.queue_request(m_io_service, m_half_open, req, login , m_listen_interface.address()); #endif } @@ -752,7 +464,7 @@ namespace detail #ifndef NDEBUG int conn = m_connections.size(); #endif - (*m_connections.begin())->disconnect(); + (*m_connections.begin())->disconnect("stopping torrent"); TORRENT_ASSERT(conn == int(m_connections.size()) + 1); } @@ -763,12 +475,6 @@ namespace detail m_download_channel.close(); m_upload_channel.close(); - - mutex::scoped_lock l2(m_checker_impl.m_mutex); - // abort the checker thread - m_checker_impl.m_abort = true; - - m_io_service.stop(); } void session_impl::set_port_filter(port_filter const& f) @@ -802,10 +508,15 @@ namespace detail // less than 5 seconds unchoke interval is insane TORRENT_ASSERT(s.unchoke_interval >= 5); + if (m_settings.cache_size != s.cache_size) + m_disk_thread.set_cache_size(s.cache_size); + if (m_settings.cache_expiry != s.cache_expiry) + m_disk_thread.set_cache_size(s.cache_expiry); m_settings = s; - if (m_settings.connection_speed <= 0) m_settings.connection_speed = 200; - + if (m_settings.connection_speed <= 0) m_settings.connection_speed = 200; + m_files.resize(m_settings.file_pool_size); + if (!s.auto_upload_slots) m_allowed_upload_slots = m_max_uploads; // replace all occurances of '\n' with ' '. std::string::iterator i = m_settings.user_agent.begin(); while ((i = std::find(i, m_settings.user_agent.end(), '\n')) @@ -896,7 +607,7 @@ namespace detail return s; } - void session_impl::open_listen_port() throw() + void session_impl::open_listen_port() { // close the open listen sockets m_listen_sockets.clear(); @@ -981,15 +692,50 @@ namespace detail tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); if (!ec) { - if (m_natpmp.get()) m_natpmp->set_mappings(local.port(), 0); - if (m_upnp.get()) m_upnp->set_mappings(local.port(), 0); + if (m_natpmp.get()) + { + if (m_tcp_mapping[0] != -1) m_natpmp->delete_mapping(m_tcp_mapping[0]); + m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp + , local.port(), local.port()); + } + if (m_upnp.get()) + { + if (m_tcp_mapping[1] != -1) m_upnp->delete_mapping(m_tcp_mapping[1]); + m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp + , local.port(), local.port()); + } } } } +#ifndef TORRENT_DISABLE_DHT + + void session_impl::on_receive_udp(asio::error_code const& e + , udp::endpoint const& ep, char const* buf, int len) + { + if (e) + { + if (m_alerts.should_post(alert::info)) + { + std::string msg = "UDP socket error from '" + + boost::lexical_cast(ep) + "' " + e.message(); + m_alerts.post_alert(udp_error_alert(ep, msg)); + } + return; + } + + if (len > 20 && *buf == 'd' && m_dht) + { + // this is probably a dht message + m_dht->on_receive(ep, buf, len); + } + } + +#endif + void session_impl::async_accept(boost::shared_ptr const& listener) { - shared_ptr c(new socket_type); + shared_ptr c(new socket_type(m_io_service)); c->instantiate(m_io_service); listener->async_accept(c->get() , bind(&session_impl::on_incoming_connection, this, c @@ -997,7 +743,7 @@ namespace detail } void session_impl::on_incoming_connection(shared_ptr const& s - , weak_ptr listen_socket, asio::error_code const& e) try + , weak_ptr listen_socket, asio::error_code const& e) { boost::shared_ptr listener = listen_socket.lock(); if (!listener) return; @@ -1057,7 +803,7 @@ namespace detail } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << endp << " <== INCOMING CONNECTION\n"; + (*m_logger) << time_now_string() << " <== INCOMING CONNECTION " << endp << "\n"; #endif // local addresses do not count, since it's likely @@ -1113,66 +859,40 @@ namespace detail c->m_in_constructor = false; #endif - m_connections.insert(c); - } - catch (std::exception& exc) - { -#ifndef NDEBUG - std::string err = exc.what(); -#endif - }; - - void session_impl::connection_failed(boost::intrusive_ptr const& peer - , tcp::endpoint const& a, char const* message) -#ifndef NDEBUG - try -#endif - { - mutex_t::scoped_lock l(m_mutex); - -// too expensive -// INVARIANT_CHECK; - - connection_map::iterator p = m_connections.find(peer); - - // the connection may have been disconnected in the receive or send phase - if (p == m_connections.end()) return; - if (m_alerts.should_post(alert::debug)) + if (!c->is_disconnecting()) { - m_alerts.post_alert( - peer_error_alert( - a - , (*p)->pid() - , message)); + m_connections.insert(c); + c->start(); } - -#if defined(TORRENT_VERBOSE_LOGGING) - (*(*p)->m_logger) << "*** CONNECTION FAILED " << message << "\n"; -#endif - (*p)->set_failed(); - (*p)->disconnect(); } -#ifndef NDEBUG - catch (...) - { - TORRENT_ASSERT(false); - }; -#endif - - void session_impl::close_connection(boost::intrusive_ptr const& p) + void session_impl::close_connection(peer_connection const* p + , char const* message) { mutex_t::scoped_lock l(m_mutex); // too expensive // INVARIANT_CHECK; +#ifndef NDEBUG +// for (aux::session_impl::torrent_map::const_iterator i = m_torrents.begin() +// , end(m_torrents.end()); i != end; ++i) +// TORRENT_ASSERT(!i->second->has_peer((peer_connection*)p)); +#endif + +#if defined(TORRENT_LOGGING) + (*m_logger) << time_now_string() << " CLOSING CONNECTION " + << p->remote() << " : " << message << "\n"; +#endif + TORRENT_ASSERT(p->is_disconnecting()); - connection_map::iterator i = m_connections.find(p); - if (i != m_connections.end()) - { - if (!(*i)->is_choked()) --m_num_unchoked; - m_connections.erase(i); - } + + if (!p->is_choked()) --m_num_unchoked; +// connection_map::iterator i = std::lower_bound(m_connections.begin(), m_connections.end() +// , p, bind(&boost::intrusive_ptr::get, _1) < p); +// if (i->get() != p) i == m_connections.end(); + connection_map::iterator i = std::find_if(m_connections.begin(), m_connections.end() + , bind(&boost::intrusive_ptr::get, _1) == p); + if (i != m_connections.end()) m_connections.erase(i); } void session_impl::set_peer_id(peer_id const& id) @@ -1187,7 +907,23 @@ namespace detail m_key = key; } - void session_impl::second_tick(asio::error_code const& e) try + int session_impl::next_port() + { + std::pair const& out_ports = m_settings.outgoing_ports; + if (m_next_port < out_ports.first || m_next_port > out_ports.second) + m_next_port = out_ports.first; + + int port = m_next_port; + ++m_next_port; + if (m_next_port > out_ports.second) m_next_port = out_ports.first; +#if defined TORRENT_LOGGING + (*m_logger) << time_now_string() << " *** BINDING OUTGOING CONNECTION [ " + "port: " << port << " ]\n"; +#endif + return port; + } + + void session_impl::second_tick(asio::error_code const& e) { session_impl::mutex_t::scoped_lock l(m_mutex); @@ -1198,19 +934,20 @@ namespace detail if (e) { -#if defined(TORRENT_LOGGING) +#if defined TORRENT_LOGGING (*m_logger) << "*** SECOND TIMER FAILED " << e.message() << "\n"; #endif - abort(); + ::abort(); return; } float tick_interval = total_microseconds(time_now() - m_last_tick) / 1000000.f; m_last_tick = time_now(); - m_timer.expires_from_now(seconds(1)); - m_timer.async_wait(m_strand.wrap( - bind(&session_impl::second_tick, this, _1))); + asio::error_code ec; + m_timer.expires_from_now(seconds(1), ec); + m_timer.async_wait( + bind(&session_impl::second_tick, this, _1)); #ifdef TORRENT_STATS ++m_second_counter; @@ -1247,7 +984,72 @@ namespace detail << std::endl; #endif + // -------------------------------------------------------------- + // second_tick every torrent + // -------------------------------------------------------------- + + int congested_torrents = 0; + int uncongested_torrents = 0; + + // count the number of seeding torrents vs. downloading + // torrents we are running + int num_seeds = 0; + int num_downloads = 0; + + // count the number of peers of downloading torrents + int num_downloads_peers = 0; + + // check each torrent for tracker updates + // TODO: do this in a timer-event in each torrent instead + for (torrent_map::iterator i = m_torrents.begin(); + i != m_torrents.end();) + { + torrent& t = *i->second; + TORRENT_ASSERT(!t.is_aborted()); + if (t.bandwidth_queue_size(peer_connection::upload_channel)) + ++congested_torrents; + else + ++uncongested_torrents; + + if (t.is_finished()) + { + ++num_seeds; + } + else + { + ++num_downloads; + num_downloads_peers += t.num_peers(); + } + + if (t.should_request()) + { + tracker_request req = t.generate_tracker_request(); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; + req.key = m_key; + m_tracker_manager.queue_request(m_io_service, m_half_open, req + , t.tracker_login(), m_listen_interface.address(), i->second); + + if (m_alerts.should_post(alert::info)) + { + m_alerts.post_alert( + tracker_announce_alert( + t.get_handle(), "tracker announce")); + } + } + + t.second_tick(m_stat, tick_interval); + ++i; + } + + m_stat.second_tick(tick_interval); + + // -------------------------------------------------------------- + // connect new peers + // -------------------------------------------------------------- + // let torrents connect to peers if they want to // if there are any torrents and any free slots @@ -1256,13 +1058,17 @@ namespace detail // round robin fashion, so that every torrent is // equallt likely to connect to a peer + int free_slots = m_half_open.free_slots(); if (!m_torrents.empty() - && m_half_open.free_slots() + && free_slots > -m_half_open.limit() && num_connections() < m_max_connections) { // this is the maximum number of connections we will // attempt this tick int max_connections = m_settings.connection_speed; + int average_peers = 0; + if (num_downloads > 0) + average_peers = num_downloads_peers / num_downloads; torrent_map::iterator i = m_torrents.begin(); if (m_next_connect_torrent < int(m_torrents.size())) @@ -1276,11 +1082,27 @@ namespace detail torrent& t = *i->second; if (t.want_more_peers()) { + int connect_points = 100; + // have a bias against torrents with more peers + // than average + if (!t.is_seed() && t.num_peers() > average_peers) + connect_points /= 2; + // if this is a seed and there is a torrent that + // is downloading, lower the rate at which this + // torrent gets connections. + // dividing by num_seeds will have the effect + // that all seed will get as many connections + // together, as a single downloading torrent. + if (t.is_seed() && num_downloads > 0) + connect_points /= num_seeds + 1; + if (connect_points <= 0) connect_points = 1; + t.give_connect_points(connect_points); try { if (t.try_connect_peer()) { --max_connections; + --free_slots; steps_since_last_connect = 0; } } @@ -1302,11 +1124,11 @@ namespace detail i = m_torrents.begin(); m_next_connect_torrent = 0; } - // if we have gone one whole loop without + // if we have gone two whole loops without // handing out a single connection, break - if (steps_since_last_connect > num_torrents) break; + if (steps_since_last_connect > num_torrents * 2) break; // if there are no more free connection slots, abort - if (m_half_open.free_slots() == 0) break; + if (free_slots <= -m_half_open.limit()) break; // if we should not make any more connections // attempts this tick, abort if (max_connections == 0) break; @@ -1344,45 +1166,13 @@ namespace detail #endif c.set_failed(); - c.disconnect(); + c.disconnect("timed out"); continue; } c.keep_alive(); } - // check each torrent for tracker updates - // TODO: do this in a timer-event in each torrent instead - for (torrent_map::iterator i = m_torrents.begin(); - i != m_torrents.end();) - { - torrent& t = *i->second; - TORRENT_ASSERT(!t.is_aborted()); - if (t.should_request()) - { - tracker_request req = t.generate_tracker_request(); - req.listen_port = 0; - if (!m_listen_sockets.empty()) - req.listen_port = m_listen_sockets.front().external_port; - req.key = m_key; - m_tracker_manager.queue_request(m_strand, m_half_open, req - , t.tracker_login(), m_listen_interface.address(), i->second); - - if (m_alerts.should_post(alert::info)) - { - m_alerts.post_alert( - tracker_announce_alert( - t.get_handle(), "tracker announce")); - } - } - - // second_tick() will set the used upload quota - t.second_tick(m_stat, tick_interval); - ++i; - } - - m_stat.second_tick(tick_interval); - // -------------------------------------------------------------- // unchoke set and optimistic unchoke calculations // -------------------------------------------------------------- @@ -1421,20 +1211,39 @@ namespace detail peers.push_back(i->get()); } - // sort the peers that are eligible for unchoke by download rate and secondary + // sorts the peers that are eligible for unchoke by download rate and secondary // by total upload. The reason for this is, if all torrents are being seeded, // the download rate will be 0, and the peers we have sent the least to should // be unchoked std::sort(peers.begin(), peers.end() - , bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _1)) - < bind(&stat::total_payload_upload, bind(&peer_connection::statistics, _2))); + , bind(&peer_connection::unchoke_compare, _1, _2)); - std::stable_sort(peers.begin(), peers.end() - , bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _1)) - > bind(&stat::download_payload_rate, bind(&peer_connection::statistics, _2))); + std::for_each(m_connections.begin(), m_connections.end() + , bind(&peer_connection::reset_choke_counters, _1)); + + // auto unchoke + int upload_limit = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); + if (m_settings.auto_upload_slots && upload_limit != bandwidth_limit::inf) + { + // if our current upload rate is less than 90% of our + // limit AND most torrents are not "congested", i.e. + // they are not holding back because of a per-torrent + // limit + if (m_stat.upload_rate() < upload_limit * 0.9f + && m_allowed_upload_slots <= m_num_unchoked + 1 + && congested_torrents < uncongested_torrents) + { + ++m_allowed_upload_slots; + } + else if (m_upload_channel.queue_size() > 1 + && m_allowed_upload_slots > m_max_uploads) + { + --m_allowed_upload_slots; + } + } // reserve one upload slot for optimistic unchokes - int unchoke_set_size = m_max_uploads - 1; + int unchoke_set_size = m_allowed_upload_slots - 1; m_num_unchoked = 0; // go through all the peers and unchoke the first ones and choke @@ -1561,13 +1370,6 @@ namespace detail } } } - catch (std::exception& exc) - { -#ifndef NDEBUG - std::cerr << exc.what() << std::endl; - TORRENT_ASSERT(false); -#endif - }; // msvc 7.1 seems to require this void session_impl::operator()() { @@ -1582,10 +1384,13 @@ namespace detail do { +#ifndef BOOST_NO_EXCEPTIONS try { +#endif m_io_service.run(); TORRENT_ASSERT(m_abort == true); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { @@ -1595,32 +1400,16 @@ namespace detail #endif TORRENT_ASSERT(false); } +#endif } while (!m_abort); - ptime end = time_now() + seconds(m_settings.stop_tracker_timeout); - while (time_now() < end && !m_tracker_manager.empty()) - { - m_io_service.reset(); - m_io_service.poll(); - // sleep 200 milliseconds - boost::xtime xt; - boost::xtime_get(&xt, boost::TIME_UTC); - boost::int64_t nsec = xt.nsec + 200 * 1000000; - if (nsec > 1000000000) - { - nsec -= 1000000000; - xt.sec += 1; - } - xt.nsec = boost::xtime::xtime_nsec_t(nsec); - boost::thread::sleep(xt); - } - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " locking mutex\n"; #endif - session_impl::mutex_t::scoped_lock l(m_mutex); + session_impl::mutex_t::scoped_lock l(m_mutex); +/* #ifndef NDEBUG for (torrent_map::iterator i = m_torrents.begin(); i != m_torrents.end(); ++i) @@ -1628,7 +1417,7 @@ namespace detail TORRENT_ASSERT(i->second->num_peers() == 0); } #endif - +*/ #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " cleaning up torrents\n"; #endif @@ -1657,7 +1446,7 @@ namespace detail return boost::weak_ptr(); } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING boost::shared_ptr session_impl::create_log(std::string const& name , int instance, bool append) { @@ -1669,40 +1458,21 @@ namespace detail std::vector session_impl::get_torrents() { mutex_t::scoped_lock l(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() - , end(m_checker_impl.m_torrents.end()); i != end; ++i) - { - if ((*i)->abort) continue; - ret.push_back(torrent_handle(this, &m_checker_impl - , (*i)->info_hash)); - } - - for (std::deque >::iterator i - = m_checker_impl.m_processing.begin() - , end(m_checker_impl.m_processing.end()); i != end; ++i) - { - if ((*i)->abort) continue; - ret.push_back(torrent_handle(this, &m_checker_impl - , (*i)->info_hash)); - } for (session_impl::torrent_map::iterator i = m_torrents.begin(), end(m_torrents.end()); i != end; ++i) { if (i->second->is_aborted()) continue; - ret.push_back(torrent_handle(this, &m_checker_impl - , i->first)); + ret.push_back(torrent_handle(i->second)); } return ret; } torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) { - return torrent_handle(this, &m_checker_impl, info_hash); + return torrent_handle(find_torrent(info_hash)); } torrent_handle session_impl::add_torrent( @@ -1717,32 +1487,45 @@ namespace detail TORRENT_ASSERT(!save_path.empty()); if (ti->begin_files() == ti->end_files()) + { +#ifndef BOOST_NO_EXCEPTIONS throw std::runtime_error("no files in torrent"); +#else + return torrent_handle(); +#endif + } // lock the session and the checker thread (the order is important!) mutex_t::scoped_lock l(m_mutex); - mutex::scoped_lock l2(m_checker_impl.m_mutex); // INVARIANT_CHECK; if (is_aborted()) + { +#ifndef BOOST_NO_EXCEPTIONS throw std::runtime_error("session is closing"); +#else + return torrent_handle(); +#endif + } // is the torrent already active? if (!find_torrent(ti->info_hash()).expired()) + { +#ifndef BOOST_NO_EXCEPTIONS throw duplicate_torrent(); - - // is the torrent currently being checked? - if (m_checker_impl.find_torrent(ti->info_hash())) - throw duplicate_torrent(); +#else + return torrent_handle(); +#endif + } // create the torrent and the data associated with // the checker thread and store it before starting // the thread boost::shared_ptr torrent_ptr( - new torrent(*this, m_checker_impl, ti, save_path + new torrent(*this, ti, save_path , m_listen_interface, storage_mode, 16 * 1024 - , sc, paused)); + , sc, paused, resume_data)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1754,13 +1537,6 @@ namespace detail } #endif - boost::shared_ptr d( - new aux::piece_checker_data); - d->torrent_ptr = torrent_ptr; - d->save_path = save_path; - d->info_hash = ti->info_hash(); - d->resume_data = resume_data; - #ifndef TORRENT_DISABLE_DHT if (m_dht) { @@ -1772,13 +1548,23 @@ namespace detail } #endif - // add the torrent to the queue to be checked - m_checker_impl.m_torrents.push_back(d); - // and notify the thread that it got another - // job in its queue - m_checker_impl.m_cond.notify_one(); + m_torrents.insert(std::make_pair(ti->info_hash(), torrent_ptr)); - return torrent_handle(this, &m_checker_impl, ti->info_hash()); + return torrent_handle(torrent_ptr); + } + + void session_impl::check_torrent(boost::shared_ptr const& t) + { + if (m_queued_for_checking.empty()) t->start_checking(); + m_queued_for_checking.push_back(t); + } + + void session_impl::done_checking(boost::shared_ptr const& t) + { + TORRENT_ASSERT(m_queued_for_checking.front() == t); + m_queued_for_checking.pop_front(); + if (!m_queued_for_checking.empty()) + m_queued_for_checking.front()->start_checking(); } torrent_handle session_impl::add_torrent( @@ -1786,7 +1572,7 @@ namespace detail , sha1_hash const& info_hash , char const* name , fs::path const& save_path - , entry const& + , entry const& resume_data , storage_mode_t storage_mode , storage_constructor_type sc , bool paused @@ -1795,14 +1581,6 @@ namespace detail // TODO: support resume data in this case TORRENT_ASSERT(!save_path.empty()); - { - // lock the checker_thread - mutex::scoped_lock l(m_checker_impl.m_mutex); - - // is the torrent currently being checked? - if (m_checker_impl.find_torrent(info_hash)) - throw duplicate_torrent(); - } // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); @@ -1811,7 +1589,13 @@ namespace detail // is the torrent already active? if (!find_torrent(info_hash).expired()) + { +#ifndef BOOST_NO_EXCEPTIONS throw duplicate_torrent(); +#else + return torrent_handle(); +#endif + } // you cannot add new torrents to a session that is closing down TORRENT_ASSERT(!is_aborted()); @@ -1820,9 +1604,9 @@ namespace detail // the checker thread and store it before starting // the thread boost::shared_ptr torrent_ptr( - new torrent(*this, m_checker_impl, tracker_url, info_hash, name + new torrent(*this, tracker_url, info_hash, name , save_path, m_listen_interface, storage_mode, 16 * 1024 - , sc, paused)); + , sc, paused, resume_data)); torrent_ptr->start(); #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1834,24 +1618,28 @@ namespace detail } #endif - m_torrents.insert( - std::make_pair(info_hash, torrent_ptr)).first; + m_torrents.insert(std::make_pair(info_hash, torrent_ptr)); - return torrent_handle(this, &m_checker_impl, info_hash); + return torrent_handle(torrent_ptr); } void session_impl::remove_torrent(const torrent_handle& h, int options) { - if (h.m_ses != this) return; - TORRENT_ASSERT(h.m_chk == &m_checker_impl || h.m_chk == 0); - TORRENT_ASSERT(h.m_ses != 0); + boost::shared_ptr tptr = h.m_torrent.lock(); + if (!tptr) +#ifdef BOOST_NO_EXCEPTIONS + return; +#else + throw invalid_handle(); +#endif mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; session_impl::torrent_map::iterator i = - m_torrents.find(h.m_info_hash); + m_torrents.find(tptr->torrent_file().info_hash()); + if (i != m_torrents.end()) { torrent& t = *i->second; @@ -1872,10 +1660,10 @@ namespace detail #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) boost::shared_ptr tl(new tracker_logger(*this)); m_tracker_loggers.push_back(tl); - m_tracker_manager.queue_request(m_strand, m_half_open, req + m_tracker_manager.queue_request(m_io_service, m_half_open, req , t.tracker_login(), m_listen_interface.address(), tl); #else - m_tracker_manager.queue_request(m_strand, m_half_open, req + m_tracker_manager.queue_request(m_io_service, m_half_open, req , t.tracker_login(), m_listen_interface.address()); #endif @@ -1893,19 +1681,6 @@ namespace detail TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); return; } - - if (h.m_chk) - { - mutex::scoped_lock l(m_checker_impl.m_mutex); - - aux::piece_checker_data* d = m_checker_impl.find_torrent(h.m_info_hash); - if (d != 0) - { - if (d->processing) d->abort = true; - else m_checker_impl.remove_torrent(h.m_info_hash, options); - return; - } - } } bool session_impl::listen_on( @@ -1941,16 +1716,25 @@ namespace detail if (m_dht_same_port) m_dht_settings.service_port = new_interface.port(); // the listen interface changed, rebind the dht listen socket as well - m_dht->rebind(new_interface.address() - , m_dht_settings.service_port); + m_dht_socket.bind(m_dht_settings.service_port); if (m_natpmp.get()) - m_natpmp->set_mappings(0, m_dht_settings.service_port); + { + if (m_udp_mapping[0] != -1) m_natpmp->delete_mapping(m_udp_mapping[0]); + m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } if (m_upnp.get()) - m_upnp->set_mappings(0, m_dht_settings.service_port); + { + if (m_udp_mapping[1] != -1) m_upnp->delete_mapping(m_udp_mapping[1]); + m_udp_mapping[1] = m_upnp->add_mapping(upnp::tcp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } } #endif -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING m_logger = create_log("main_session", listen_port(), false); (*m_logger) << time_now_string() << "\n"; #endif @@ -1991,43 +1775,42 @@ namespace detail t->get_policy().peer_from_tracker(peer, peer_id(0), peer_info::lsd, 0); } - void session_impl::on_port_mapping(int tcp_port, int udp_port - , std::string const& errmsg) + void session_impl::on_port_mapping(int mapping, int port + , std::string const& errmsg, int map_transport) { #ifndef TORRENT_DISABLE_DHT - if (udp_port != 0) + if (mapping == m_udp_mapping[map_transport] && port != 0) { - m_external_udp_port = udp_port; - m_dht_settings.service_port = udp_port; + m_external_udp_port = port; + m_dht_settings.service_port = port; if (m_alerts.should_post(alert::info)) - { - std::stringstream msg; - msg << "successfully mapped UDP port " << udp_port; - m_alerts.post_alert(portmap_alert(msg.str())); - } + m_alerts.post_alert(portmap_alert(mapping, port + , map_transport, "successfully mapped UDP port")); + return; } #endif - if (tcp_port != 0) + if (mapping == m_tcp_mapping[map_transport] && port != 0) { if (!m_listen_sockets.empty()) - m_listen_sockets.front().external_port = tcp_port; + m_listen_sockets.front().external_port = port; if (m_alerts.should_post(alert::info)) - { - std::stringstream msg; - msg << "successfully mapped TCP port " << tcp_port; - m_alerts.post_alert(portmap_alert(msg.str())); - } + m_alerts.post_alert(portmap_alert(mapping, port + , map_transport, "successfully mapped TCP port")); + return; } if (!errmsg.empty()) { if (m_alerts.should_post(alert::warning)) - { - std::stringstream msg; - msg << "Error while mapping ports on NAT router: " << errmsg; - m_alerts.post_alert(portmap_error_alert(msg.str())); - } + m_alerts.post_alert(portmap_error_alert(mapping + , map_transport, errmsg)); + } + else + { + if (m_alerts.should_post(alert::warning)) + m_alerts.post_alert(portmap_alert(mapping, port + , map_transport, "successfully mapped port")); } } @@ -2039,11 +1822,14 @@ namespace detail session_status s; + s.num_peers = (int)m_connections.size(); + s.num_unchoked = m_num_unchoked; + s.allowed_upload_slots = m_allowed_upload_slots; + s.up_bandwidth_queue = m_upload_channel.queue_size(); s.down_bandwidth_queue = m_download_channel.queue_size(); s.has_incoming_connections = m_incoming_connection; - s.num_peers = (int)m_connections.size(); s.download_rate = m_stat.download_rate(); s.upload_rate = m_stat.upload_rate(); @@ -2105,13 +1891,23 @@ namespace detail m_dht_settings.service_port = m_listen_interface.port(); } m_external_udp_port = m_dht_settings.service_port; - if (m_natpmp.get()) - m_natpmp->set_mappings(0, m_dht_settings.service_port); - if (m_upnp.get()) - m_upnp->set_mappings(0, m_dht_settings.service_port); - m_dht = new dht::dht_tracker(m_io_service - , m_dht_settings, m_listen_interface.address() - , startup_state); + if (m_natpmp.get() && m_udp_mapping[0] == -1) + { + m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } + if (m_upnp.get() && m_udp_mapping[1] == -1) + { + m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } + m_dht = new dht::dht_tracker(m_dht_socket, m_dht_settings, startup_state); + if (!m_dht_socket.is_open() || m_dht_socket.local_port() != m_dht_settings.service_port) + { + m_dht_socket.bind(m_dht_settings.service_port); + } } void session_impl::stop_dht() @@ -2136,12 +1932,22 @@ namespace detail && settings.service_port != m_dht_settings.service_port && m_dht) { - m_dht->rebind(m_listen_interface.address() - , settings.service_port); + m_dht_socket.bind(settings.service_port); + if (m_natpmp.get()) - m_natpmp->set_mappings(0, m_dht_settings.service_port); + { + if (m_udp_mapping[0] != -1) m_upnp->delete_mapping(m_udp_mapping[0]); + m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } if (m_upnp.get()) - m_upnp->set_mappings(0, m_dht_settings.service_port); + { + if (m_udp_mapping[1] != -1) m_upnp->delete_mapping(m_udp_mapping[1]); + m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); + } m_external_udp_port = settings.service_port; } m_dht_settings = settings; @@ -2193,6 +1999,10 @@ namespace detail #endif abort(); +#ifndef TORRENT_DISABLE_GEO_IP + if (m_asnum_db) GeoIP_delete(m_asnum_db); + if (m_country_db) GeoIP_delete(m_country_db); +#endif #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " waiting for main thread\n"; #endif @@ -2200,31 +2010,6 @@ namespace detail TORRENT_ASSERT(m_torrents.empty()); - // it's important that the main thread is closed completely before - // the checker thread is terminated. Because all the connections - // have to be closed and removed from the torrents before they - // can be destructed. (because the weak pointers in the - // peer_connections will be invalidated when the torrents are - // destructed and then the invariant will be broken). - - { - mutex::scoped_lock l(m_checker_impl.m_mutex); - // abort the checker thread - m_checker_impl.m_abort = true; - - // abort the currently checking torrent - if (!m_checker_impl.m_torrents.empty()) - { - m_checker_impl.m_torrents.front()->abort = true; - } - m_checker_impl.m_cond.notify_one(); - } - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << time_now_string() << " waiting for checker thread\n"; -#endif - m_checker_thread->join(); - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() << " waiting for disk io thread\n"; #endif @@ -2245,7 +2030,9 @@ namespace detail INVARIANT_CHECK; if (limit <= 0) limit = (std::numeric_limits::max)(); + if (m_max_uploads == limit) return; m_max_uploads = limit; + m_allowed_upload_slots = limit; } void session_impl::set_max_connections(int limit) @@ -2345,47 +2132,61 @@ namespace detail , bind(&session_impl::on_lsd_peer, this, _1, _2)); } - void session_impl::start_natpmp() + natpmp* session_impl::start_natpmp() { mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; - if (m_natpmp) return; + if (m_natpmp) return m_natpmp.get(); m_natpmp = new natpmp(m_io_service , m_listen_interface.address() , bind(&session_impl::on_port_mapping - , this, _1, _2, _3)); + , this, _1, _2, _3, 0)); - m_natpmp->set_mappings(m_listen_interface.port(), + if (m_listen_interface.port() > 0) + { + m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp + , m_listen_interface.port(), m_listen_interface.port()); + } #ifndef TORRENT_DISABLE_DHT - m_dht ? m_dht_settings.service_port : + if (m_dht) + m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); #endif - 0); + return m_natpmp.get(); } - void session_impl::start_upnp() + upnp* session_impl::start_upnp() { mutex_t::scoped_lock l(m_mutex); INVARIANT_CHECK; - if (m_upnp) return; + if (m_upnp) return m_upnp.get(); m_upnp = new upnp(m_io_service, m_half_open , m_listen_interface.address() , m_settings.user_agent , bind(&session_impl::on_port_mapping - , this, _1, _2, _3) + , this, _1, _2, _3, 1) , m_settings.upnp_ignore_nonrouters); m_upnp->discover_device(); - m_upnp->set_mappings(m_listen_interface.port(), + if (m_listen_interface.port() > 0) + { + m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp + , m_listen_interface.port(), m_listen_interface.port()); + } #ifndef TORRENT_DISABLE_DHT - m_dht ? m_dht_settings.service_port : + if (m_dht) + m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp + , m_dht_settings.service_port + , m_dht_settings.service_port); #endif - 0); + return m_upnp.get(); } void session_impl::stop_lsd() @@ -2408,14 +2209,39 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); if (m_upnp.get()) + { m_upnp->close(); + m_udp_mapping[1] = -1; + m_tcp_mapping[1] = -1; + } m_upnp = 0; } + void session_impl::set_external_address(address const& ip) + { + TORRENT_ASSERT(ip != address()); + + if (m_external_address == ip) return; + + m_external_address = ip; + if (m_alerts.should_post(alert::info)) + { + std::stringstream msg; + msg << "external address is '"; + print_address(msg, ip) << "'"; + m_alerts.post_alert(external_ip_alert(ip, msg.str())); + } + } + void session_impl::free_disk_buffer(char* buf) { m_disk_thread.free_buffer(buf); } + + char* session_impl::allocate_disk_buffer() + { + return m_disk_thread.allocate_buffer(); + } std::pair session_impl::allocate_buffer(int size) { @@ -2425,14 +2251,18 @@ namespace detail boost::mutex::scoped_lock l(m_send_buffer_mutex); #ifdef TORRENT_STATS + TORRENT_ASSERT(m_buffer_allocations >= 0); m_buffer_allocations += num_buffers; m_buffer_usage_logger << log_time() << " protocol_buffer: " << (m_buffer_allocations * send_buffer_size) << std::endl; #endif - std::pair ret((char*)m_send_buffers.ordered_malloc(num_buffers) +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + int num_bytes = num_buffers * send_buffer_size; + return std::make_pair((char*)malloc(num_bytes), num_bytes); +#else + return std::make_pair((char*)m_send_buffers.ordered_malloc(num_buffers) , num_buffers * send_buffer_size); - if (ret.first == 0) throw std::bad_alloc(); - return ret; +#endif } void session_impl::free_buffer(char* buf, int size) @@ -2449,7 +2279,11 @@ namespace detail m_buffer_usage_logger << log_time() << " protocol_buffer: " << (m_buffer_allocations * send_buffer_size) << std::endl; #endif +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + free(buf); +#else m_send_buffers.ordered_free(buf, num_buffers); +#endif } #ifndef NDEBUG @@ -2457,6 +2291,7 @@ namespace detail { TORRENT_ASSERT(m_max_connections > 0); TORRENT_ASSERT(m_max_uploads > 0); + TORRENT_ASSERT(m_allowed_upload_slots >= m_max_uploads); int unchokes = 0; int num_optimistic = 0; for (connection_map::const_iterator i = m_connections.begin(); @@ -2492,210 +2327,5 @@ namespace detail } #endif - void piece_checker_data::parse_resume_data( - const entry& resume_data - , const torrent_info& info - , std::string& error) - { - // if we don't have any resume data, return - if (resume_data.type() == entry::undefined_t) return; - - entry rd = resume_data; - - try - { - if (rd["file-format"].string() != "libtorrent resume file") - { - error = "missing file format tag"; - return; - } - - if (rd["file-version"].integer() > 1) - { - error = "incompatible file version " - + boost::lexical_cast(rd["file-version"].integer()); - return; - } - - // verify info_hash - sha1_hash hash = rd["info-hash"].string(); - if (hash != info.info_hash()) - { - error = "mismatching info-hash: " + boost::lexical_cast(hash); - return; - } - - // the peers - - if (entry* peers_entry = rd.find_key("peers")) - { - entry::list_type& peer_list = peers_entry->list(); - - std::vector tmp_peers; - tmp_peers.reserve(peer_list.size()); - for (entry::list_type::iterator i = peer_list.begin(); - i != peer_list.end(); ++i) - { - tcp::endpoint a( - address::from_string((*i)["ip"].string()) - , (unsigned short)(*i)["port"].integer()); - tmp_peers.push_back(a); - } - - peers.swap(tmp_peers); - } - - if (entry* banned_peers_entry = rd.find_key("banned_peers")) - { - entry::list_type& peer_list = banned_peers_entry->list(); - - std::vector tmp_peers; - tmp_peers.reserve(peer_list.size()); - for (entry::list_type::iterator i = peer_list.begin(); - i != peer_list.end(); ++i) - { - tcp::endpoint a( - address::from_string((*i)["ip"].string()) - , (unsigned short)(*i)["port"].integer()); - tmp_peers.push_back(a); - } - - banned_peers.swap(tmp_peers); - } - - // read piece map - const entry::list_type& slots = rd["slots"].list(); - if ((int)slots.size() > info.num_pieces()) - { - error = "file has more slots than torrent (slots: " - + boost::lexical_cast(slots.size()) + " size: " - + boost::lexical_cast(info.num_pieces()) + " )"; - return; - } - - std::vector tmp_pieces; - tmp_pieces.reserve(slots.size()); - for (entry::list_type::const_iterator i = slots.begin(); - i != slots.end(); ++i) - { - int index = (int)i->integer(); - if (index >= info.num_pieces() || index < -2) - { - error = "too high index number in slot map (index: " - + boost::lexical_cast(index) + " size: " - + boost::lexical_cast(info.num_pieces()) + ")"; - return; - } - tmp_pieces.push_back(index); - } - - // only bother to check the partial pieces if we have the same block size - // as in the fast resume data. If the blocksize has changed, then throw - // away all partial pieces. - std::vector tmp_unfinished; - int num_blocks_per_piece = (int)rd["blocks per piece"].integer(); - if (num_blocks_per_piece == info.piece_length() / torrent_ptr->block_size()) - { - // the unfinished pieces - - entry::list_type& unfinished = rd["unfinished"].list(); - int unfinished_size = int(unfinished.size()); - block_info.resize(num_blocks_per_piece * unfinished_size); - tmp_unfinished.reserve(unfinished_size); - int index = 0; - for (entry::list_type::iterator i = unfinished.begin(); - i != unfinished.end(); ++i, ++index) - { - piece_picker::downloading_piece p; - p.info = &block_info[index * num_blocks_per_piece]; - p.index = (int)(*i)["piece"].integer(); - if (p.index < 0 || p.index >= info.num_pieces()) - { - error = "invalid piece index in unfinished piece list (index: " - + boost::lexical_cast(p.index) + " size: " - + boost::lexical_cast(info.num_pieces()) + ")"; - return; - } - - const std::string& bitmask = (*i)["bitmask"].string(); - - const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); - if ((int)bitmask.size() != num_bitmask_bytes) - { - error = "invalid size of bitmask (" + boost::lexical_cast(bitmask.size()) + ")"; - return; - } - for (int j = 0; j < num_bitmask_bytes; ++j) - { - unsigned char bits = bitmask[j]; - int num_bits = (std::min)(num_blocks_per_piece - j*8, 8); - for (int k = 0; k < num_bits; ++k) - { - const int bit = j * 8 + k; - if (bits & (1 << k)) - { - p.info[bit].state = piece_picker::block_info::state_finished; - ++p.finished; - } - } - } - - if (p.finished == 0) continue; - - std::vector::iterator slot_iter - = std::find(tmp_pieces.begin(), tmp_pieces.end(), p.index); - if (slot_iter == tmp_pieces.end()) - { - // this piece is marked as unfinished - // but doesn't have any storage - error = "piece " + boost::lexical_cast(p.index) + " is " - "marked as unfinished, but doesn't have any storage"; - return; - } - - TORRENT_ASSERT(*slot_iter == p.index); - int slot_index = static_cast(slot_iter - tmp_pieces.begin()); - const entry* ad = i->find_key("adler32"); - - if (ad && ad->type() == entry::int_t) - { - unsigned long adler - = torrent_ptr->filesystem().piece_crc( - slot_index - , torrent_ptr->block_size() - , p.info); - - // crc's didn't match, don't use the resume data - if (ad->integer() != entry::integer_type(adler)) - { - error = "checksum mismatch on piece " - + boost::lexical_cast(p.index); - return; - } - } - - tmp_unfinished.push_back(p); - } - } - - if (!torrent_ptr->verify_resume_data(rd, error)) - return; - - piece_map.swap(tmp_pieces); - unfinished_pieces.swap(tmp_unfinished); - } - catch (invalid_encoding&) - { - return; - } - catch (type_error&) - { - return; - } - catch (file_error&) - { - return; - } - } }} diff --git a/libtorrent/src/smart_ban.cpp b/libtorrent/src/smart_ban.cpp new file mode 100644 index 000000000..d0c753907 --- /dev/null +++ b/libtorrent/src/smart_ban.cpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +namespace libtorrent { namespace +{ + + struct smart_ban_plugin : torrent_plugin, boost::enable_shared_from_this + { + smart_ban_plugin(torrent& t) + : m_torrent(t) + , m_salt(rand()) + { + } + + void on_piece_pass(int p) + { +#ifdef TORRENT_LOGGING + (*m_torrent.session().m_logger) << time_now_string() << " PIECE PASS [ p: " << p + << " | block_crc_size: " << m_block_crc.size() << " ]\n"; +#endif + // has this piece failed earlier? If it has, go through the + // CRCs from the time it failed and ban the peers that + // sent bad blocks + std::map::iterator i = m_block_crc.lower_bound(piece_block(p, 0)); + if (i == m_block_crc.end() || i->first.piece_index != p) return; + + int size = m_torrent.torrent_file().piece_size(p); + peer_request r = {p, 0, (std::min)(16*1024, size)}; + piece_block pb(p, 0); + while (size > 0) + { + if (i->first.block_index == pb.block_index) + { + m_torrent.filesystem().async_read(r, bind(&smart_ban_plugin::on_read_ok_block + , shared_from_this(), *i, _1, _2)); + m_block_crc.erase(i++); + } + else + { + TORRENT_ASSERT(i->first.block_index > pb.block_index); + } + + if (i == m_block_crc.end() || i->first.piece_index != p) + break; + + r.start += 16*1024; + size -= 16*1024; + r.length = (std::min)(16*1024, size); + ++pb.block_index; + } + +#ifndef NDEBUG + // make sure we actually removed all the entries for piece 'p' + i = m_block_crc.lower_bound(piece_block(p, 0)); + TORRENT_ASSERT(i == m_block_crc.end() || i->first.piece_index != p); +#endif + + if (m_torrent.is_seed()) + { + std::map().swap(m_block_crc); + return; + } + } + + void on_piece_failed(int p) + { + // The piece failed the hash check. Record + // the CRC and origin peer of every block + + std::vector downloaders; + m_torrent.picker().get_downloaders(downloaders, p); + + int size = m_torrent.torrent_file().piece_size(p); + peer_request r = {p, 0, (std::min)(16*1024, size)}; + piece_block pb(p, 0); + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + if (*i != 0) + { + m_torrent.filesystem().async_read(r, bind(&smart_ban_plugin::on_read_failed_block + , shared_from_this(), pb, (policy::peer*)*i, _1, _2)); + } + + r.start += 16*1024; + size -= 16*1024; + r.length = (std::min)(16*1024, size); + ++pb.block_index; + } + TORRENT_ASSERT(size <= 0); + } + + private: + + // this entry ties a specific block CRC to + // a peer. + struct block_entry + { + policy::peer* peer; + unsigned long crc; + }; + + void on_read_failed_block(piece_block b, policy::peer* p, int ret, disk_io_job const& j) + { + TORRENT_ASSERT(p); + // ignore read errors + if (ret != j.buffer_size) return; + + adler32_crc crc; + crc.update(j.buffer, j.buffer_size); + crc.update((char const*)&m_salt, sizeof(m_salt)); + + block_entry e = {p, crc.final()}; + + // since this callback is called directory from the disk io + // thread, the session mutex is not locked when we get here + aux::session_impl::mutex_t::scoped_lock l(m_torrent.session().m_mutex); + + std::map::iterator i = m_block_crc.lower_bound(b); + if (i != m_block_crc.end() && i->first == b && i->second.peer == p) + { + // this peer has sent us this block before + if (i->second.crc != e.crc) + { + // this time the crc of the block is different + // from the first time it sent it + // at least one of them must be bad + + if (p == 0) return; + if (!m_torrent.get_policy().has_peer(p)) return; + +#ifdef TORRENT_LOGGING + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + (*m_torrent.session().m_logger) << time_now_string() << " BANNING PEER [ p: " << b.piece_index + << " | b: " << b.block_index + << " | c: " << client + << " | crc1: " << i->second.crc + << " | crc2: " << e.crc + << " | ip: " << p->ip << " ]\n"; +#endif + p->banned = true; + if (p->connection) p->connection->disconnect("banning peer for sending bad data"); + } + // we already have this exact entry in the map + // we don't have to insert it + return; + } + + m_block_crc.insert(i, std::make_pair(b, e)); + +#ifdef TORRENT_LOGGING + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + (*m_torrent.session().m_logger) << time_now_string() << " STORE BLOCK CRC [ p: " << b.piece_index + << " | b: " << b.block_index + << " | c: " << client + << " | crc: " << e.crc + << " | ip: " << p->ip << " ]\n"; +#endif + } + + void on_read_ok_block(std::pair b, int ret, disk_io_job const& j) + { + // since this callback is called directory from the disk io + // thread, the session mutex is not locked when we get here + aux::session_impl::mutex_t::scoped_lock l(m_torrent.session().m_mutex); + + // ignore read errors + if (ret != j.buffer_size) return; + + adler32_crc crc; + crc.update(j.buffer, j.buffer_size); + crc.update((char const*)&m_salt, sizeof(m_salt)); + unsigned long ok_crc = crc.final(); + + if (b.second.crc == ok_crc) return; + + policy::peer* p = b.second.peer; + + if (p == 0) return; + if (!m_torrent.get_policy().has_peer(p)) return; + +#ifdef TORRENT_LOGGING + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + (*m_torrent.session().m_logger) << time_now_string() << " BANNING PEER [ p: " << b.first.piece_index + << " | b: " << b.first.block_index + << " | c: " << client + << " | ok_crc: " << ok_crc + << " | bad_crc: " << b.second.crc + << " | ip: " << p->ip << " ]\n"; +#endif + p->banned = true; + if (p->connection) p->connection->disconnect("banning peer for sending bad data"); + } + + torrent& m_torrent; + + // This table maps a piece_block (piece and block index + // pair) to a peer and the block CRC. The CRC is calculated + // from the data in the block + the salt + std::map m_block_crc; + + // This salt is a random value used to calculate the block CRCs + // Since the CRC function that is used is not a one way function + // the salt is required to avoid attacks where bad data is sent + // that is forged to match the CRC of the good data. + int m_salt; + }; + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_smart_ban_plugin(torrent* t, void*) + { + return boost::shared_ptr(new smart_ban_plugin(*t)); + } + +} + + diff --git a/libtorrent/src/socks5_stream.cpp b/libtorrent/src/socks5_stream.cpp index f0c41c02b..62d1faf22 100644 --- a/libtorrent/src/socks5_stream.cpp +++ b/libtorrent/src/socks5_stream.cpp @@ -220,8 +220,7 @@ namespace libtorrent write_uint8(1, p); // CONNECT command write_uint8(0, p); // reserved write_uint8(m_remote_endpoint.address().is_v4()?1:4, p); // address type - write_address(m_remote_endpoint.address(), p); - write_uint16(m_remote_endpoint.port(), p); + write_endpoint(m_remote_endpoint, p); TORRENT_ASSERT(p - &m_buffer[0] == int(m_buffer.size())); asio::async_write(m_sock, asio::buffer(m_buffer) diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 062902a63..51fab7e12 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -67,6 +67,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/file_pool.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/disk_buffer_holder.hpp" #ifndef NDEBUG #include @@ -259,23 +260,23 @@ namespace libtorrent { size_type size = 0; std::time_t time = 0; - try - { -#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 - fs::path f = p / i->path; - size = file_size_win(f); - time = last_write_time_win(f); -#elif defined(_WIN32) && defined(UNICODE) - fs::wpath f = safe_convert((p / i->path).string()); - size = file_size(f); - time = last_write_time(f); +#if defined(_WIN32) && defined(UNICODE) + fs::wpath f = safe_convert((p / i->path).string()); #else - fs::path f = p / i->path; + fs::path f = p / i->path; +#endif +#ifndef BOOST_NO_EXCEPTIONS + try +#else + if (exists(f)) +#endif + { size = file_size(f); time = last_write_time(f); -#endif } +#ifndef BOOST_NO_EXCEPTIONS catch (std::exception&) {} +#endif sizes.push_back(std::make_pair(size, time)); } return sizes; @@ -308,23 +309,24 @@ namespace libtorrent { size_type size = 0; std::time_t time = 0; - try - { -#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 - fs::path f = p / i->path; - size = file_size_win(f); - time = last_write_time_win(f); -#elif defined(_WIN32) && defined(UNICODE) - fs::wpath f = safe_convert((p / i->path).string()); - size = file_size(f); - time = last_write_time(f); + +#if defined(_WIN32) && defined(UNICODE) + fs::wpath f = safe_convert((p / i->path).string()); #else - fs::path f = p / i->path; + fs::path f = p / i->path; +#endif +#ifndef BOOST_NO_EXCEPTIONS + try +#else + if (exists(f)) +#endif + { size = file_size(f); time = last_write_time(f); -#endif } +#ifndef BOOST_NO_EXCEPTIONS catch (std::exception&) {} +#endif if ((compact_mode && size != s->first) || (!compact_mode && size < s->first)) { @@ -361,20 +363,20 @@ namespace libtorrent TORRENT_ASSERT(m_save_path.is_complete()); } - void release_files(); - void delete_files(); - void initialize(bool allocate_files); + bool release_files(); + bool delete_files(); + bool initialize(bool allocate_files); bool move_storage(fs::path save_path); - size_type read(char* buf, int slot, int offset, int size); - void write(const char* buf, int slot, int offset, int size); - void move_slot(int src_slot, int dst_slot); - void swap_slots(int slot1, int slot2); - void swap_slots3(int slot1, int slot2, int slot3); - bool verify_resume_data(entry& rd, std::string& error); - void write_resume_data(entry& rd) const; + int read(char* buf, int slot, int offset, int size); + int write(const char* buf, int slot, int offset, int size); + bool move_slot(int src_slot, int dst_slot); + bool swap_slots(int slot1, int slot2); + bool swap_slots3(int slot1, int slot2, int slot3); + bool verify_resume_data(entry const& rd, std::string& error); + bool write_resume_data(entry& rd) const; sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size); - size_type read_impl(char* buf, int slot, int offset, int size, bool fill_zero); + int read_impl(char* buf, int slot, int offset, int size, bool fill_zero); ~storage() { m_files.release(this); } @@ -397,7 +399,7 @@ namespace libtorrent hasher whole; int slot_size1 = piece_size; m_scratch_buffer.resize(slot_size1); - read_impl(&m_scratch_buffer[0], slot, 0, slot_size1, true); + int read_result = read_impl(&m_scratch_buffer[0], slot, 0, slot_size1, true); if (ph.offset > 0) partial.update(&m_scratch_buffer[0], ph.offset); whole.update(&m_scratch_buffer[0], slot_size1); @@ -420,7 +422,7 @@ namespace libtorrent #endif } - void storage::initialize(bool allocate_files) + bool storage::initialize(bool allocate_files) { // first, create all missing directories fs::path last_path; @@ -452,37 +454,63 @@ namespace libtorrent // the directory exists. if (file_iter->size == 0) { +#ifndef BOOST_NO_EXCEPTIONS try { - file(m_save_path / file_iter->path, file::out); - } catch (std::exception&) {} +#endif + file(m_save_path / file_iter->path, file::out); +#ifndef BOOST_NO_EXCEPTIONS + } + catch (std::exception& e) + { + set_error((m_save_path / file_iter->path).string(), e.what()); + return true; + } +#endif continue; } +#ifndef BOOST_NO_EXCEPTIONS try { - if (allocate_files) - { - m_files.open_file(this, m_save_path / file_iter->path, file::in | file::out) - ->set_size(file_iter->size); - } - } catch (std::exception&) {} +#endif + if (allocate_files) + { + std::string error; + boost::shared_ptr f = m_files.open_file(this + , m_save_path / file_iter->path, file::in | file::out + , error); + if (f && f->error().empty()) + f->set_size(file_iter->size); + } +#ifndef BOOST_NO_EXCEPTIONS + } + catch (std::exception& e) + { + set_error((m_save_path / file_iter->path).string(), e.what()); + return true; + } +#endif } // close files that were opened in write mode m_files.release(this); + return false; } - void storage::release_files() + bool storage::release_files() { m_files.release(this); buffer().swap(m_scratch_buffer); + return false; } - void storage::delete_files() + bool storage::delete_files() { // make sure we don't have the files open m_files.release(this); buffer().swap(m_scratch_buffer); + int result = 0; std::string error; + std::string error_file; // delete the files from disk std::set directories; @@ -500,7 +528,11 @@ namespace libtorrent bp = bp.branch_path(); } if (std::remove(p.c_str()) != 0 && errno != ENOENT) + { error = std::strerror(errno); + error_file = p; + result = errno; + } } // remove the directories. Reverse order to delete @@ -510,18 +542,28 @@ namespace libtorrent , end(directories.rend()); i != end; ++i) { if (std::remove(i->c_str()) != 0 && errno != ENOENT) + { error = std::strerror(errno); + error_file = *i; + result = errno; + } } - if (!error.empty()) throw std::runtime_error(error); + if (!error.empty()) + { + m_error.swap(error); + m_error_file.swap(error_file); + } + return result != 0; } - void storage::write_resume_data(entry& rd) const + bool storage::write_resume_data(entry& rd) const { + TORRENT_ASSERT(rd.type() == entry::dictionary_t); + std::vector > file_sizes = get_filesizes(*m_info, m_save_path); - rd["file sizes"] = entry::list_type(); entry::list_type& fl = rd["file sizes"].list(); for (std::vector >::iterator i = file_sizes.begin(), end(file_sizes.end()); i != end; ++i) @@ -531,19 +573,37 @@ namespace libtorrent p.push_back(entry(i->second)); fl.push_back(entry(p)); } + return false; } - bool storage::verify_resume_data(entry& rd, std::string& error) + bool storage::verify_resume_data(entry const& rd, std::string& error) { - std::vector > file_sizes; - entry::list_type& l = rd["file sizes"].list(); + if (rd.type() != entry::dictionary_t) + { + error = "invalid fastresume file (not a dictionary)"; + return true; + } - for (entry::list_type::iterator i = l.begin(); + std::vector > file_sizes; + entry const* file_sizes_ent = rd.find_key("file sizes"); + if (file_sizes_ent == 0 || file_sizes_ent->type() != entry::list_t) + { + error = "missing or invalid 'file sizes' entry in resume data"; + return false; + } + + entry::list_type const& l = file_sizes_ent->list(); + + for (entry::list_type::const_iterator i = l.begin(); i != l.end(); ++i) { - file_sizes.push_back(std::make_pair( - i->list().front().integer() - , std::time_t(i->list().back().integer()))); + if (i->type() != entry::list_t) break; + entry::list_type const& pair = i->list(); + if (pair.size() != 2 || pair.front().type() != entry::int_t + || pair.back().type() != entry::int_t) + break; + file_sizes.push_back(std::pair( + pair.front().integer(), std::time_t(pair.back().integer()))); } if (file_sizes.empty()) @@ -552,7 +612,14 @@ namespace libtorrent return false; } - entry::list_type& slots = rd["slots"].list(); + entry const* slots_ent = rd.find_key("slots"); + if (slots_ent == 0 || slots_ent->type() != entry::list_t) + { + error = "missing or invalid 'slots' entry in resume data"; + return false; + } + + entry::list_type const& slots = slots_ent->list(); bool seed = int(slots.size()) == m_info->num_pieces() && std::find_if(slots.begin(), slots.end() , boost::bind(std::less() @@ -560,11 +627,9 @@ namespace libtorrent &entry::integer, _1), 0)) == slots.end(); bool full_allocation_mode = false; - try - { - full_allocation_mode = rd["allocation"].string() == "full"; - } - catch (std::exception&) {} + entry const* allocation_mode = rd.find_key("allocation"); + if (allocation_mode && allocation_mode->type() == entry::string_t) + full_allocation_mode = allocation_mode->string() == "full"; if (seed) { @@ -639,8 +704,10 @@ namespace libtorrent new_path = save_path / m_info->name(); #endif +#ifndef BOOST_NO_EXCEPTIONS try { +#endif #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 rename_win(old_path, new_path); rename(old_path, new_path); @@ -649,9 +716,11 @@ namespace libtorrent #endif m_save_path = save_path; return true; +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception&) {} return false; +#endif } #ifndef NDEBUG @@ -684,28 +753,31 @@ namespace libtorrent */ #endif - void storage::move_slot(int src_slot, int dst_slot) + bool storage::move_slot(int src_slot, int dst_slot) { int piece_size = m_info->piece_size(dst_slot); m_scratch_buffer.resize(piece_size); - read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); - write(&m_scratch_buffer[0], dst_slot, 0, piece_size); + int ret1 = read_impl(&m_scratch_buffer[0], src_slot, 0, piece_size, true); + int ret2 = write(&m_scratch_buffer[0], dst_slot, 0, piece_size); + return ret1 != piece_size || ret2 != piece_size; } - void storage::swap_slots(int slot1, int slot2) + bool storage::swap_slots(int slot1, int slot2) { // the size of the target slot is the size of the piece int piece_size = m_info->piece_length(); int piece1_size = m_info->piece_size(slot2); int piece2_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); - read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); - read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); - write(&m_scratch_buffer[0], slot2, 0, piece1_size); - write(&m_scratch_buffer[piece_size], slot1, 0, piece2_size); + int ret1 = read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); + int ret2 = read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); + int ret3 = write(&m_scratch_buffer[0], slot2, 0, piece1_size); + int ret4 = write(&m_scratch_buffer[piece_size], slot1, 0, piece2_size); + return ret1 != piece1_size || ret2 != piece2_size + || ret3 != piece1_size || ret4 != piece2_size; } - void storage::swap_slots3(int slot1, int slot2, int slot3) + bool storage::swap_slots3(int slot1, int slot2, int slot3) { // the size of the target slot is the size of the piece int piece_size = m_info->piece_length(); @@ -713,15 +785,18 @@ namespace libtorrent int piece2_size = m_info->piece_size(slot3); int piece3_size = m_info->piece_size(slot1); m_scratch_buffer.resize(piece_size * 2); - read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); - read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); - write(&m_scratch_buffer[0], slot2, 0, piece1_size); - read_impl(&m_scratch_buffer[0], slot3, 0, piece3_size, true); - write(&m_scratch_buffer[piece_size], slot3, 0, piece2_size); - write(&m_scratch_buffer[0], slot1, 0, piece3_size); + int ret1 = read_impl(&m_scratch_buffer[0], slot1, 0, piece1_size, true); + int ret2 = read_impl(&m_scratch_buffer[piece_size], slot2, 0, piece2_size, true); + int ret3 = write(&m_scratch_buffer[0], slot2, 0, piece1_size); + int ret4 = read_impl(&m_scratch_buffer[0], slot3, 0, piece3_size, true); + int ret5 = write(&m_scratch_buffer[piece_size], slot3, 0, piece2_size); + int ret6 = write(&m_scratch_buffer[0], slot1, 0, piece3_size); + return ret1 != piece1_size || ret2 != piece2_size + || ret3 != piece1_size || ret4 != piece3_size + || ret5 != piece2_size || ret6 != piece3_size; } - size_type storage::read( + int storage::read( char* buf , int slot , int offset @@ -730,7 +805,7 @@ namespace libtorrent return read_impl(buf, slot, offset, size, false); } - size_type storage::read_impl( + int storage::read_impl( char* buf , int slot , int offset @@ -766,11 +841,21 @@ namespace libtorrent } int buf_pos = 0; + std::string error; boost::shared_ptr in(m_files.open_file( - this, m_save_path / file_iter->path, file::in)); - + this, m_save_path / file_iter->path, file::in + , error)); + if (!in) + { + set_error((m_save_path / file_iter->path).string(), error); + return -1; + } + if (!in->error().empty()) + { + set_error((m_save_path / file_iter->path).string(), in->error()); + return -1; + } TORRENT_ASSERT(file_offset < file_iter->size); - TORRENT_ASSERT(slices[0].offset == file_offset + file_iter->file_base); size_type new_pos = in->seek(file_offset + file_iter->file_base); @@ -778,7 +863,10 @@ namespace libtorrent { // the file was not big enough if (!fill_zero) - throw file_error("slot has no storage"); + { + set_error((m_save_path / file_iter->path).string(), "seek failed"); + return -1; + } std::memset(buf + buf_pos, 0, size - buf_pos); return size; } @@ -825,7 +913,10 @@ namespace libtorrent // the file was not big enough if (actual_read > 0) buf_pos += actual_read; if (!fill_zero) - throw file_error("slot has no storage"); + { + set_error((m_save_path / file_iter->path).string(), "read failed"); + return -1; + } std::memset(buf + buf_pos, 0, size - buf_pos); return size; } @@ -847,16 +938,36 @@ namespace libtorrent fs::path path = m_save_path / file_iter->path; file_offset = 0; + std::string error; in = m_files.open_file( - this, path, file::in); - in->seek(file_iter->file_base); + this, path, file::in, error); + if (!in) + { + set_error(path.string(), error); + return -1; + } + if (!in->error().empty()) + { + set_error((m_save_path / file_iter->path).string(), in->error()); + return -1; + } + size_type pos = in->seek(file_iter->file_base); + if (pos != file_iter->file_base) + { + if (!fill_zero) + { + set_error((m_save_path / file_iter->path).string(), "seek failed"); + return -1; + } + std::memset(buf + buf_pos, 0, size - buf_pos); + return size; + } } } return result; } - // throws file_error if it fails to write - void storage::write( + int storage::write( const char* buf , int slot , int offset @@ -891,9 +1002,20 @@ namespace libtorrent } fs::path p(m_save_path / file_iter->path); + std::string error; boost::shared_ptr out = m_files.open_file( - this, p, file::out | file::in); + this, p, file::out | file::in, error); + if (!out) + { + set_error(p.string(), error); + return -1; + } + if (!out->error().empty()) + { + set_error(p.string(), out->error()); + return -1; + } TORRENT_ASSERT(file_offset < file_iter->size); TORRENT_ASSERT(slices[0].offset == file_offset + file_iter->file_base); @@ -901,9 +1023,8 @@ namespace libtorrent if (pos != file_offset + file_iter->file_base) { - std::stringstream s; - s << "no storage for slot " << slot; - throw file_error(s.str()); + set_error((m_save_path / file_iter->path).string(), "seek failed"); + return -1; } int left_to_write = size; @@ -940,9 +1061,8 @@ namespace libtorrent if (written != write_bytes) { - std::stringstream s; - s << "no storage for slot " << slot; - throw file_error(s.str()); + set_error((m_save_path / file_iter->path).string(), "write failed"); + return -1; } left_to_write -= write_bytes; @@ -962,12 +1082,31 @@ namespace libtorrent TORRENT_ASSERT(file_iter != m_info->end_files(true)); fs::path p = m_save_path / file_iter->path; file_offset = 0; + std::string error; out = m_files.open_file( - this, p, file::out | file::in); + this, p, file::out | file::in, error); - out->seek(file_iter->file_base); + if (!out) + { + set_error(p.string(), error); + return -1; + } + if (!out->error().empty()) + { + set_error(p.string(), out->error()); + return -1; + } + + size_type pos = out->seek(file_iter->file_base); + + if (pos != file_iter->file_base) + { + set_error((m_save_path / file_iter->path).string(), "seek failed"); + return -1; + } } } + return size; } storage_interface* default_storage_constructor(boost::intrusive_ptr ti @@ -984,9 +1123,10 @@ namespace libtorrent , fs::path const& save_path , file_pool& fp , disk_io_thread& io - , storage_constructor_type sc) + , storage_constructor_type sc + , storage_mode_t sm) : m_storage(sc(ti, save_path, fp)) - , m_storage_mode(storage_mode_sparse) + , m_storage_mode(sm) , m_info(ti) , m_save_path(complete(save_path)) , m_state(state_none) @@ -997,31 +1137,19 @@ namespace libtorrent , m_io_thread(io) , m_torrent(torrent) { -#ifndef NDEBUG - m_resume_data_verified = false; -#endif } piece_manager::~piece_manager() { } - void piece_manager::write_resume_data(entry& rd) const + void piece_manager::async_save_resume_data( + boost::function const& handler) { - m_storage->write_resume_data(rd); - } - - bool piece_manager::verify_resume_data(entry& rd, std::string& error) - { -#ifndef NDEBUG - m_resume_data_verified = true; -#endif - return m_storage->verify_resume_data(rd, error); - } - - void piece_manager::free_buffer(char* buf) - { - m_io_thread.free_buffer(buf); + disk_io_job j; + j.storage = this; + j.action = disk_io_job::save_resume_data; + m_io_thread.add_job(j, handler); } void piece_manager::async_release_files( @@ -1052,10 +1180,29 @@ namespace libtorrent m_io_thread.add_job(j, handler); } + void piece_manager::async_check_fastresume(entry const* resume_data + , boost::function const& handler) + { + TORRENT_ASSERT(resume_data != 0); + disk_io_job j; + j.storage = this; + j.action = disk_io_job::check_fastresume; + j.buffer = (char*)resume_data; + m_io_thread.add_job(j, handler); + } + + void piece_manager::async_check_files( + boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::check_files; + m_io_thread.add_job(j, handler); + } + void piece_manager::async_read( peer_request const& r , boost::function const& handler - , char* buffer , int priority) { disk_io_job j; @@ -1064,20 +1211,26 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; - j.buffer = buffer; + j.buffer = 0; j.priority = priority; // if a buffer is not specified, only one block can be read // since that is the size of the pool allocator's buffers - TORRENT_ASSERT(r.length <= 16 * 1024 || buffer != 0); + TORRENT_ASSERT(r.length <= 16 * 1024); m_io_thread.add_job(j, handler); +#ifndef NDEBUG + boost::recursive_mutex::scoped_lock l(m_mutex); + TORRENT_ASSERT(slot_for(r.piece) >= 0); +#endif } void piece_manager::async_write( peer_request const& r - , char const* buffer + , disk_buffer_holder& buffer , boost::function const& handler) { TORRENT_ASSERT(r.length <= 16 * 1024); + // the buffer needs to be allocated through the io_thread + TORRENT_ASSERT(m_io_thread.is_disk_buffer(buffer.buffer())); disk_io_job j; j.storage = this; @@ -1085,10 +1238,9 @@ namespace libtorrent j.piece = r.piece; j.offset = r.start; j.buffer_size = r.length; - j.buffer = m_io_thread.allocate_buffer(); - if (j.buffer == 0) throw file_error("out of memory"); - std::memcpy(j.buffer, buffer, j.buffer_size); + j.buffer = buffer.buffer(); m_io_thread.add_job(j, handler); + buffer.release(); } void piece_manager::async_hash(int piece @@ -1134,17 +1286,18 @@ namespace libtorrent return false; } - void piece_manager::export_piece_map( - std::vector& p, std::vector const& have) const + void piece_manager::write_resume_data(entry& rd) const { boost::recursive_mutex::scoped_lock lock(m_mutex); INVARIANT_CHECK; + m_storage->write_resume_data(rd); + if (m_storage_mode == storage_mode_compact) { - p.clear(); - p.reserve(m_info->num_pieces()); + entry::list_type& slots = rd["slots"].list(); + slots.clear(); std::vector::const_reverse_iterator last; for (last = m_slot_to_piece.rbegin(); last != m_slot_to_piece.rend(); ++last) @@ -1156,23 +1309,13 @@ namespace libtorrent m_slot_to_piece.begin(); i != last.base(); ++i) { - p.push_back((*i >= 0) ? *i : unassigned); - } - } - else - { - p.reserve(m_info->num_pieces()); - for (int i = 0; i < m_info->num_pieces(); ++i) - { - p.push_back(have[i] ? i : unassigned); + slots.push_back((*i >= 0) ? *i : unassigned); } } } void piece_manager::mark_failed(int piece_index) { - boost::recursive_mutex::scoped_lock lock(m_mutex); - INVARIANT_CHECK; if (m_storage_mode != storage_mode_compact) return; @@ -1186,49 +1329,7 @@ namespace libtorrent m_free_slots.push_back(slot_index); } - unsigned long piece_manager::piece_crc( - int slot_index - , int block_size - , piece_picker::block_info const* bi) - try - { - TORRENT_ASSERT(slot_index >= 0); - TORRENT_ASSERT(slot_index < m_info->num_pieces()); - TORRENT_ASSERT(block_size > 0); - - adler32_crc crc; - std::vector buf(block_size); - int num_blocks = static_cast(m_info->piece_size(slot_index)) / block_size; - int last_block_size = static_cast(m_info->piece_size(slot_index)) % block_size; - if (last_block_size == 0) last_block_size = block_size; - - for (int i = 0; i < num_blocks-1; ++i) - { - if (bi[i].state != piece_picker::block_info::state_finished) continue; - m_storage->read( - &buf[0] - , slot_index - , i * block_size - , block_size); - crc.update(&buf[0], block_size); - } - if (num_blocks > 0 && bi[num_blocks - 1].state == piece_picker::block_info::state_finished) - { - m_storage->read( - &buf[0] - , slot_index - , block_size * (num_blocks - 1) - , last_block_size); - crc.update(&buf[0], last_block_size); - } - return crc.final(); - } - catch (std::exception&) - { - return 0; - } - - size_type piece_manager::read_impl( + int piece_manager::read_impl( char* buf , int piece_index , int offset @@ -1241,7 +1342,7 @@ namespace libtorrent return m_storage->read(buf, slot, offset, size); } - void piece_manager::write_impl( + int piece_manager::write_impl( const char* buf , int piece_index , int offset @@ -1252,44 +1353,86 @@ namespace libtorrent TORRENT_ASSERT(size > 0); TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); + int slot = allocate_slot_for_piece(piece_index); + int ret = m_storage->write(buf, slot, offset, size); + // only save the partial hash if the write succeeds + if (ret != size) return ret; + +// std::ofstream out("partial_hash.log", std::ios::app); + if (offset == 0) { partial_hash& ph = m_piece_hasher[piece_index]; TORRENT_ASSERT(ph.offset == 0); ph.offset = size; ph.h.update(buf, size); +/* + out << time_now_string() << " NEW [" + " s: " << this + << " p: " << piece_index + << " off: " << offset + << " size: " << size + << " entries: " << m_piece_hasher.size() + << " ]" << std::endl; +*/ } else { std::map::iterator i = m_piece_hasher.find(piece_index); if (i != m_piece_hasher.end()) { +#ifndef NDEBUG TORRENT_ASSERT(i->second.offset > 0); - TORRENT_ASSERT(offset >= i->second.offset); + int hash_offset = i->second.offset; + TORRENT_ASSERT(offset >= hash_offset); +#endif if (offset == i->second.offset) { +/* + out << time_now_string() << " UPDATING [" + " s: " << this + << " p: " << piece_index + << " off: " << offset + << " size: " << size + << " entries: " << m_piece_hasher.size() + << " ]" << std::endl; +*/ i->second.offset += size; i->second.h.update(buf, size); } +/* else + { + out << time_now_string() << " SKIPPING (out of order) [" + " s: " << this + << " p: " << piece_index + << " off: " << offset + << " size: " << size + << " entries: " << m_piece_hasher.size() + << " ]" << std::endl; + } +*/ } +/* else + { + out << time_now_string() << " SKIPPING (no entry) [" + " s: " << this + << " p: " << piece_index + << " off: " << offset + << " size: " << size + << " entries: " << m_piece_hasher.size() + << " ]" << std::endl; } +*/ } - int slot = allocate_slot_for_piece(piece_index); - m_storage->write(buf, slot, offset, size); + return ret; } int piece_manager::identify_data( const std::vector& piece_data - , int current_slot - , std::vector& have_pieces - , int& num_pieces - , const std::multimap& hash_to_piece - , boost::recursive_mutex& mutex) + , int current_slot) { // INVARIANT_CHECK; - TORRENT_ASSERT((int)have_pieces.size() == m_info->num_pieces()); - const int piece_size = static_cast(m_info->piece_length()); const int last_piece_size = static_cast(m_info->piece_size( m_info->num_pieces() - 1)); @@ -1319,8 +1462,8 @@ namespace libtorrent map_iter end2; // makes the lookups for the small digest and the large digest - boost::tie(begin1, end1) = hash_to_piece.equal_range(small_hash); - boost::tie(begin2, end2) = hash_to_piece.equal_range(large_hash); + boost::tie(begin1, end1) = m_hash_to_piece.equal_range(small_hash); + boost::tie(begin2, end2) = m_hash_to_piece.equal_range(large_hash); // copy all potential piece indices into this vector std::vector matching_pieces; @@ -1346,15 +1489,11 @@ namespace libtorrent // we will assume that the piece is in the right place const int piece_index = current_slot; - // lock because we're writing to have_pieces - boost::recursive_mutex::scoped_lock l(mutex); - - if (have_pieces[piece_index]) + int other_slot = m_piece_to_slot[piece_index]; + if (other_slot >= 0) { // we have already found a piece with // this index. - int other_slot = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(other_slot >= 0); // take one of the other matching pieces // that hasn't already been assigned @@ -1362,18 +1501,15 @@ namespace libtorrent for (std::vector::iterator i = matching_pieces.begin(); i != matching_pieces.end(); ++i) { - if (have_pieces[*i] || *i == piece_index) continue; + if (m_piece_to_slot[*i] >= 0 || *i == piece_index) continue; other_piece = *i; break; } if (other_piece >= 0) { // replace the old slot with 'other_piece' - TORRENT_ASSERT(have_pieces[other_piece] == false); - have_pieces[other_piece] = true; m_slot_to_piece[other_slot] = other_piece; m_piece_to_slot[other_piece] = other_slot; - ++num_pieces; } else { @@ -1389,19 +1525,9 @@ namespace libtorrent TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot); TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); m_piece_to_slot[piece_index] = has_no_slot; -#ifndef NDEBUG - // to make the assert happy, a few lines down - have_pieces[piece_index] = false; -#endif - } - else - { - ++num_pieces; } - TORRENT_ASSERT(have_pieces[piece_index] == false); TORRENT_ASSERT(m_piece_to_slot[piece_index] == has_no_slot); - have_pieces[piece_index] = true; return piece_index; } @@ -1412,21 +1538,14 @@ namespace libtorrent for (std::vector::iterator i = matching_pieces.begin(); i != matching_pieces.end(); ++i) { - if (have_pieces[*i]) continue; + if (m_piece_to_slot[*i] >= 0) continue; free_piece = *i; break; } if (free_piece >= 0) { - // lock because we're writing to have_pieces - boost::recursive_mutex::scoped_lock l(mutex); - - TORRENT_ASSERT(have_pieces[free_piece] == false); TORRENT_ASSERT(m_piece_to_slot[free_piece] == has_no_slot); - have_pieces[free_piece] = true; - ++num_pieces; - return free_piece; } else @@ -1436,15 +1555,99 @@ namespace libtorrent } } + int piece_manager::check_no_fastresume(std::string& error) + { + torrent_info::file_iterator i = m_info->begin_files(true); + torrent_info::file_iterator end = m_info->end_files(true); + + for (; i != end; ++i) + { + bool file_exists = false; + fs::path f = m_save_path / i->path; +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif +#if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400 + file_exists = exists_win(f); +#elif defined(_WIN32) && defined(UNICODE) + fs::wpath wf = safe_convert(f.string()); + file_exists = exists(wf); +#else + file_exists = exists(f); +#endif +#ifndef BOOST_NO_EXCEPTIONS + } + catch (std::exception& e) + { + error = f.string(); + error += ": "; + error += e.what(); + return fatal_disk_error; + } +#endif + if (file_exists && i->size > 0) + { + m_state = state_full_check; + m_piece_to_slot.clear(); + m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); + m_slot_to_piece.clear(); + m_slot_to_piece.resize(m_info->num_pieces(), unallocated); + if (m_storage_mode == storage_mode_compact) + { + m_unallocated_slots.clear(); + m_free_slots.clear(); + } + return need_full_check; + } + } + + if (m_storage_mode == storage_mode_compact) + { + // in compact mode without checking, we need to + // populate the unallocated list + TORRENT_ASSERT(m_unallocated_slots.empty()); + for (int i = 0, end(m_info->num_pieces()); i < end; ++i) + m_unallocated_slots.push_back(i); + m_piece_to_slot.clear(); + m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); + m_slot_to_piece.clear(); + m_slot_to_piece.resize(m_info->num_pieces(), unallocated); + } + + return check_init_storage(error); + } + + int piece_manager::check_init_storage(std::string& error) + { + if (m_storage->initialize(m_storage_mode == storage_mode_allocate)) + { + error = m_storage->error(); + m_storage->clear_error(); + return fatal_disk_error; + } + m_state = state_finished; + buffer().swap(m_scratch_buffer); + buffer().swap(m_scratch_buffer2); + if (m_storage_mode != storage_mode_compact) + { + // if no piece is out of place + // since we're in full allocation mode, we can + // forget the piece allocation tables + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + std::vector().swap(m_free_slots); + std::vector().swap(m_unallocated_slots); + } + return no_error; + } + // check if the fastresume data is up to date // if it is, use it and return true. If it // isn't return false and the full check // will be run - bool piece_manager::check_fastresume( - aux::piece_checker_data& data - , std::vector& pieces - , int& num_pieces, storage_mode_t storage_mode - , std::string& error_msg) + int piece_manager::check_fastresume( + entry const& rd, std::string& error) { boost::recursive_mutex::scoped_lock lock(m_mutex); @@ -1452,131 +1655,195 @@ namespace libtorrent TORRENT_ASSERT(m_info->piece_length() > 0); - m_storage_mode = storage_mode; + // if we don't have any resume data, return + if (rd.type() == entry::undefined_t) return check_no_fastresume(error); - // This will corrupt the storage - // use while debugging to find - // states that cannot be scanned - // by check_pieces. -// m_storage->shuffle(); + if (rd.type() != entry::dictionary_t) + { + error = "invalid fastresume data (not a bencoded dictionary)"; + return check_no_fastresume(error); + } - m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); - m_slot_to_piece.resize(m_info->num_pieces(), unallocated); - TORRENT_ASSERT(m_free_slots.empty()); - TORRENT_ASSERT(m_unallocated_slots.empty()); + entry const* file_format = rd.find_key("file-format"); + + if (file_format == 0 || file_format->type() != entry::string_t) + { + error = "missing file format tag"; + return check_no_fastresume(error); + } + + if (file_format->string() != "libtorrent resume file") + { + error = "invalid file format tag"; + return check_no_fastresume(error); + } + + entry const* info_hash = rd.find_key("info-hash"); + if (info_hash == 0 || info_hash->type() != entry::string_t) + { + error = "missing info-hash"; + return check_no_fastresume(error); + } + + if (sha1_hash(info_hash->string()) != m_info->info_hash()) + { + error = "mismatching info-hash"; + return check_no_fastresume(error); + } + + int block_size = (std::min)(16 * 1024, m_info->piece_length()); + entry const* blocks_per_piece_ent = rd.find_key("blocks per piece"); + if (blocks_per_piece_ent != 0 + && blocks_per_piece_ent->type() == entry::int_t + && blocks_per_piece_ent->integer() != m_info->piece_length() / block_size) + { + error = "invalid 'blocks per piece' entry"; + return check_no_fastresume(error); + } + + storage_mode_t storage_mode = storage_mode_compact; + entry const* allocation = rd.find_key("allocation"); + if (allocation != 0 + && allocation->type() == entry::string_t + && allocation->string() != "compact") + storage_mode = storage_mode_sparse; // assume no piece is out of place (i.e. in a slot // other than the one it should be in) bool out_of_place = false; - - 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 - if (!data.piece_map.empty() - && int(data.piece_map.size()) <= m_info->num_pieces()) + // if we don't have a piece map, we need the slots + // if we're in compact mode, we also need the slots map + if (storage_mode == storage_mode_compact || rd.find_key("pieces") == 0) { - TORRENT_ASSERT(m_resume_data_verified); - for (int i = 0; i < (int)data.piece_map.size(); ++i) + // read slots map + entry const* slots = rd.find_key("slots"); + if (slots == 0 || slots->type() != entry::list_t) { - m_slot_to_piece[i] = data.piece_map[i]; - if (data.piece_map[i] >= 0) - { - if (data.piece_map[i] != i) out_of_place = true; - m_piece_to_slot[data.piece_map[i]] = i; - int found_piece = data.piece_map[i]; - - // if the piece is not in the unfinished list - // we have all of it - if (std::find_if( - data.unfinished_pieces.begin() - , data.unfinished_pieces.end() - , piece_picker::has_index(found_piece)) - == data.unfinished_pieces.end()) - { - ++num_pieces; - pieces[found_piece] = true; - } - } - else if (data.piece_map[i] == unassigned) - { - if (m_storage_mode == storage_mode_compact) - m_free_slots.push_back(i); - } - else - { - TORRENT_ASSERT(data.piece_map[i] == unallocated); - if (m_storage_mode == storage_mode_compact) - m_unallocated_slots.push_back(i); - } + error = "missing slot list"; + return check_no_fastresume(error); } - if (m_storage_mode == storage_mode_compact) + if ((int)slots->list().size() > m_info->num_pieces()) { - m_unallocated_slots.reserve(int(m_info->num_pieces() - data.piece_map.size())); - for (int i = (int)data.piece_map.size(); i < (int)m_info->num_pieces(); ++i) + error = "file has more slots than torrent (slots: " + + boost::lexical_cast(slots->list().size()) + " size: " + + boost::lexical_cast(m_info->num_pieces()) + " )"; + return check_no_fastresume(error); + } + + if (storage_mode == storage_mode_compact) + { + int num_pieces = int(m_info->num_pieces()); + m_slot_to_piece.resize(num_pieces, unallocated); + m_piece_to_slot.resize(num_pieces, has_no_slot); + int slot = 0; + for (entry::list_type::const_iterator i = slots->list().begin(); + i != slots->list().end(); ++i, ++slot) { - m_unallocated_slots.push_back(i); - } - if (m_unallocated_slots.empty()) - { - switch_to_full_mode(); + if (i->type() != entry::int_t) + { + error = "invalid entry type in slot list"; + return check_no_fastresume(error); + } + + int index = int(i->integer()); + if (index >= num_pieces || index < -2) + { + error = "too high index number in slot map (index: " + + boost::lexical_cast(index) + " size: " + + boost::lexical_cast(num_pieces) + ")"; + return check_no_fastresume(error); + } + if (index >= 0) + { + m_slot_to_piece[slot] = index; + m_piece_to_slot[index] = slot; + if (slot != index) out_of_place = true; + } + else if (index == unassigned) + { + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(slot); + } + else + { + TORRENT_ASSERT(index == unallocated); + if (m_storage_mode == storage_mode_compact) + m_unallocated_slots.push_back(slot); + } } } else { - if (!out_of_place) + int slot = 0; + for (entry::list_type::const_iterator i = slots->list().begin(); + i != slots->list().end(); ++i, ++slot) { - // if no piece is out of place - // since we're in full allocation mode, we can - // forget the piece allocation tables + if (i->type() != entry::int_t) + { + error = "invalid entry type in slot list"; + return check_no_fastresume(error); + } - std::vector().swap(m_piece_to_slot); - std::vector().swap(m_slot_to_piece); - m_state = state_create_files; - return false; + int index = int(i->integer()); + if (index != slot && index >= 0) + { + error = "invalid slot index"; + return check_no_fastresume(error); + } } - else + } + + if (!m_storage->verify_resume_data(rd, error)) + return check_no_fastresume(error); + + // This will corrupt the storage + // use while debugging to find + // states that cannot be scanned + // by check_pieces. + // m_storage->shuffle(); + + if (m_storage_mode == storage_mode_compact) + { + if (m_unallocated_slots.empty()) switch_to_full_mode(); + } + else + { + TORRENT_ASSERT(m_free_slots.empty()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + + if (out_of_place) { // in this case we're in full allocation mode, but // we're resuming a compact allocated storage m_state = state_expand_pieces; m_current_slot = 0; - error_msg = "pieces needs to be reordered"; - return false; + error = "pieces needs to be reordered"; + return need_full_check; } } - m_state = state_create_files; - return false; } - m_state = state_full_check; - return false; + return check_init_storage(error); } /* state chart: - check_fastresume() - - | | - | v + check_fastresume() ----------+ + | + | | | + | v v | +------------+ +---------------+ | | full_check |-->| expand_pieses | | +------------+ +---------------+ | | | | v | | +--------------+ | - +->| create_files | <------+ + +->| finished | <------+ +--------------+ - | - v - +----------+ - | finished | - +----------+ */ @@ -1586,22 +1853,12 @@ namespace libtorrent // the second return value is the progress the // file check is at. 0 is nothing done, and 1 // is finished - std::pair piece_manager::check_files( - std::vector& pieces, int& num_pieces, boost::recursive_mutex& mutex) + int piece_manager::check_files(int& current_slot, int& have_piece, std::string& error) { -#ifndef NDEBUG - boost::recursive_mutex::scoped_lock l_(mutex); - TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - l_.unlock(); -#endif - - if (m_state == state_create_files) - { - m_storage->initialize(m_storage_mode == storage_mode_allocate); - m_state = state_finished; - return std::make_pair(true, 1.f); - } + TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_info->num_pieces()); + current_slot = m_current_slot; + have_piece = -1; if (m_state == state_expand_pieces) { INVARIANT_CHECK; @@ -1617,21 +1874,34 @@ namespace libtorrent if (m_scratch_buffer2.empty()) m_scratch_buffer2.resize(m_info->piece_length()); - m_storage->read(&m_scratch_buffer2[0], piece, 0, m_info->piece_size(other_piece)); + int piece_size = m_info->piece_size(other_piece); + if (m_storage->read(&m_scratch_buffer2[0], piece, 0, piece_size) + != piece_size) + { + error = m_storage->error(); + m_storage->clear_error(); + return fatal_disk_error; + } m_scratch_piece = other_piece; m_piece_to_slot[other_piece] = unassigned; } // the slot where this piece belongs is // free. Just move the piece there. - m_storage->write(&m_scratch_buffer[0], piece, 0, m_info->piece_size(piece)); + int piece_size = m_info->piece_size(piece); + if (m_storage->write(&m_scratch_buffer[0], piece, 0, piece_size) != piece_size) + { + error = m_storage->error(); + m_storage->clear_error(); + return fatal_disk_error; + } m_piece_to_slot[piece] = piece; m_slot_to_piece[piece] = piece; if (other_piece >= 0) m_scratch_buffer.swap(m_scratch_buffer2); - return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + return need_full_check; } while (m_current_slot < m_info->num_pieces() @@ -1643,17 +1913,11 @@ namespace libtorrent if (m_current_slot == m_info->num_pieces()) { - m_state = state_create_files; - buffer().swap(m_scratch_buffer); - buffer().swap(m_scratch_buffer2); - if (m_storage_mode != storage_mode_compact) - { - std::vector().swap(m_piece_to_slot); - std::vector().swap(m_slot_to_piece); - } - return std::make_pair(false, 1.f); + return check_init_storage(error); } + TORRENT_ASSERT(m_current_slot < m_info->num_pieces()); + int piece = m_slot_to_piece[m_current_slot]; TORRENT_ASSERT(piece >= 0); int other_piece = m_slot_to_piece[piece]; @@ -1665,7 +1929,13 @@ namespace libtorrent if (m_scratch_buffer.empty()) m_scratch_buffer.resize(m_info->piece_length()); - m_storage->read(&m_scratch_buffer[0], piece, 0, m_info->piece_size(other_piece)); + int piece_size = m_info->piece_size(other_piece); + if (m_storage->read(&m_scratch_buffer[0], piece, 0, piece_size) != piece_size) + { + error = m_storage->error(); + m_storage->clear_error(); + return fatal_disk_error; + } m_scratch_piece = other_piece; m_piece_to_slot[other_piece] = unassigned; } @@ -1677,245 +1947,22 @@ namespace libtorrent m_slot_to_piece[m_current_slot] = unassigned; m_slot_to_piece[piece] = piece; - return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + return need_full_check; } TORRENT_ASSERT(m_state == state_full_check); - // ------------------------ - // DO THE FULL CHECK - // ------------------------ + bool skip = check_one_piece(have_piece); + TORRENT_ASSERT(m_current_slot <= m_info->num_pieces()); - try + if (skip) { - // initialization for the full check - if (m_hash_to_piece.empty()) - { - for (int i = 0; i < m_info->num_pieces(); ++i) - { - m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); - } - boost::recursive_mutex::scoped_lock l(mutex); - std::fill(pieces.begin(), pieces.end(), false); - num_pieces = 0; - } - - m_piece_data.resize(int(m_info->piece_length())); - int piece_size = int(m_info->piece_size(m_current_slot)); - int num_read = int(m_storage->read(&m_piece_data[0] - , m_current_slot, 0, piece_size)); - - // if the file is incomplete, skip the rest of it - if (num_read != piece_size) - throw file_error(""); - - int piece_index = identify_data(m_piece_data, m_current_slot - , pieces, num_pieces, m_hash_to_piece, mutex); - - if (piece_index != m_current_slot - && piece_index >= 0) - m_out_of_place = true; - - TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); - - const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; - const bool other_should_move = m_piece_to_slot[m_current_slot] != has_no_slot; - - // check if this piece should be swapped with any other slot - // this section will ensure that the storage is correctly sorted - // libtorrent will never leave the storage in a state that - // requires this sorting, but other clients may. - - // example of worst case: - // | m_current_slot = 5 - // V - // +---+- - - +---+- - - +---+- - - // | x | | 5 | | 3 | <- piece data in slots - // +---+- - - +---+- - - +---+- - - // 3 y 5 <- slot index - - // in this example, the data in the m_current_slot (5) - // is piece 3. It has to be moved into slot 3. The data - // in slot y (piece 5) should be moved into the m_current_slot. - // and the data in slot 3 (piece x) should be moved to slot y. - - // there are three possible cases. - // 1. There's another piece that should be placed into this slot - // 2. This piece should be placed into another slot. - // 3. There's another piece that should be placed into this slot - // and this piece should be placed into another slot - - // swap piece_index with this slot - - // case 1 - if (this_should_move && !other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - - const int other_slot = piece_index; - TORRENT_ASSERT(other_slot >= 0); - int other_piece = m_slot_to_piece[other_slot]; - - m_slot_to_piece[other_slot] = piece_index; - m_slot_to_piece[m_current_slot] = other_piece; - m_piece_to_slot[piece_index] = piece_index; - if (other_piece >= 0) m_piece_to_slot[other_piece] = m_current_slot; - - if (other_piece == unassigned) - { - std::vector::iterator i = - std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); - TORRENT_ASSERT(i != m_free_slots.end()); - if (m_storage_mode == storage_mode_compact) - { - m_free_slots.erase(i); - m_free_slots.push_back(m_current_slot); - } - } - - if (other_piece >= 0) - m_storage->swap_slots(other_slot, m_current_slot); - else - m_storage->move_slot(m_current_slot, other_slot); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - // case 2 - else if (!this_should_move && other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - - const int other_piece = m_current_slot; - const int other_slot = m_piece_to_slot[other_piece]; - TORRENT_ASSERT(other_slot >= 0); - - m_slot_to_piece[m_current_slot] = other_piece; - m_slot_to_piece[other_slot] = piece_index; - m_piece_to_slot[other_piece] = m_current_slot; - - if (piece_index == unassigned - && m_storage_mode == storage_mode_compact) - m_free_slots.push_back(other_slot); - - if (piece_index >= 0) - { - m_piece_to_slot[piece_index] = other_slot; - m_storage->swap_slots(other_slot, m_current_slot); - } - else - { - m_storage->move_slot(other_slot, m_current_slot); - } - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - else if (this_should_move && other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - TORRENT_ASSERT(piece_index >= 0); - - const int piece1 = m_slot_to_piece[piece_index]; - const int piece2 = m_current_slot; - const int slot1 = piece_index; - const int slot2 = m_piece_to_slot[piece2]; - - TORRENT_ASSERT(slot1 >= 0); - TORRENT_ASSERT(slot2 >= 0); - TORRENT_ASSERT(piece2 >= 0); - - if (slot1 == slot2) - { - // this means there are only two pieces involved in the swap - TORRENT_ASSERT(piece1 >= 0); - - // movement diagram: - // +-------------------------------+ - // | | - // +--> slot1 --> m_current_slot --+ - - m_slot_to_piece[slot1] = piece_index; - m_slot_to_piece[m_current_slot] = piece1; - - m_piece_to_slot[piece_index] = slot1; - m_piece_to_slot[piece1] = m_current_slot; - - TORRENT_ASSERT(piece1 == m_current_slot); - TORRENT_ASSERT(piece_index == slot1); - - m_storage->swap_slots(m_current_slot, slot1); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - else - { - TORRENT_ASSERT(slot1 != slot2); - TORRENT_ASSERT(piece1 != piece2); - - // movement diagram: - // +-----------------------------------------+ - // | | - // +--> slot1 --> slot2 --> m_current_slot --+ - - m_slot_to_piece[slot1] = piece_index; - m_slot_to_piece[slot2] = piece1; - m_slot_to_piece[m_current_slot] = piece2; - - m_piece_to_slot[piece_index] = slot1; - m_piece_to_slot[m_current_slot] = piece2; - - if (piece1 == unassigned) - { - std::vector::iterator i = - std::find(m_free_slots.begin(), m_free_slots.end(), slot1); - TORRENT_ASSERT(i != m_free_slots.end()); - if (m_storage_mode == storage_mode_compact) - { - m_free_slots.erase(i); - m_free_slots.push_back(slot2); - } - } - - if (piece1 >= 0) - { - m_piece_to_slot[piece1] = slot2; - m_storage->swap_slots3(m_current_slot, slot1, slot2); - } - else - { - m_storage->move_slot(m_current_slot, slot1); - m_storage->move_slot(slot2, m_current_slot); - } - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - } - else - { - TORRENT_ASSERT(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unallocated); - TORRENT_ASSERT(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); - - // the slot was identified as piece 'piece_index' - if (piece_index != unassigned) - m_piece_to_slot[piece_index] = m_current_slot; - else if (m_storage_mode == storage_mode_compact) - m_free_slots.push_back(m_current_slot); - - m_slot_to_piece[m_current_slot] = piece_index; - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - } - catch (file_error&) - { - // find the file that failed, and skip all the blocks in that file + clear_error(); + // skip means that the piece we checked failed to be read from disk + // completely. We should skip all pieces belonging to that file. + // find the file that failed, and skip all the pieces in that file size_type file_offset = 0; - size_type current_offset = m_current_slot * m_info->piece_length(); + size_type current_offset = size_type(m_current_slot) * m_info->piece_length(); for (torrent_info::file_iterator i = m_info->begin_files(true); i != m_info->end_files(true); ++i) { @@ -1925,8 +1972,8 @@ namespace libtorrent TORRENT_ASSERT(file_offset > current_offset); int skip_blocks = static_cast( - (file_offset - current_offset + m_info->piece_length() - 1) - / m_info->piece_length()); + (file_offset - current_offset + m_info->piece_length() - 1) + / m_info->piece_length()); if (m_storage_mode == storage_mode_compact) { @@ -1939,8 +1986,11 @@ namespace libtorrent // current slot will increase by one at the end of the for-loop too m_current_slot += skip_blocks - 1; + TORRENT_ASSERT(m_current_slot <= m_info->num_pieces()); } + ++m_current_slot; + current_slot = m_current_slot; if (m_current_slot >= m_info->num_pieces()) { @@ -1960,8 +2010,7 @@ namespace libtorrent std::vector().swap(m_piece_to_slot); std::vector().swap(m_slot_to_piece); - m_state = state_create_files; - return std::make_pair(false, 1.f); + return check_init_storage(error); } else { @@ -1969,25 +2018,257 @@ namespace libtorrent // we're resuming a compact allocated storage m_state = state_expand_pieces; m_current_slot = 0; - return std::make_pair(false, 0.f); + current_slot = m_current_slot; + return need_full_check; } } else if (m_unallocated_slots.empty()) { switch_to_full_mode(); } - m_state = state_create_files; + return check_init_storage(error); + } + return need_full_check; + } -#ifndef NDEBUG - boost::recursive_mutex::scoped_lock l(mutex); - TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); -#endif - return std::make_pair(false, 1.f); + bool piece_manager::check_one_piece(int& have_piece) + { + // ------------------------ + // DO THE FULL CHECK + // ------------------------ + + TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_info->num_pieces()); + TORRENT_ASSERT(int(m_slot_to_piece.size()) == m_info->num_pieces()); + TORRENT_ASSERT(have_piece == -1); + + // initialization for the full check + if (m_hash_to_piece.empty()) + { + for (int i = 0; i < m_info->num_pieces(); ++i) + m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); } - TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); + m_piece_data.resize(int(m_info->piece_length())); + int piece_size = m_info->piece_size(m_current_slot); + int num_read = m_storage->read(&m_piece_data[0] + , m_current_slot, 0, piece_size); - return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + // if the file is incomplete, skip the rest of it + if (num_read != piece_size) + return true; + + int piece_index = identify_data(m_piece_data, m_current_slot); + + if (piece_index >= 0) have_piece = piece_index; + + if (piece_index != m_current_slot + && piece_index >= 0) + m_out_of_place = true; + + TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); + + const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; + const bool other_should_move = m_piece_to_slot[m_current_slot] != has_no_slot; + + // check if this piece should be swapped with any other slot + // this section will ensure that the storage is correctly sorted + // libtorrent will never leave the storage in a state that + // requires this sorting, but other clients may. + + // example of worst case: + // | m_current_slot = 5 + // V + // +---+- - - +---+- - - +---+- - + // | x | | 5 | | 3 | <- piece data in slots + // +---+- - - +---+- - - +---+- - + // 3 y 5 <- slot index + + // in this example, the data in the m_current_slot (5) + // is piece 3. It has to be moved into slot 3. The data + // in slot y (piece 5) should be moved into the m_current_slot. + // and the data in slot 3 (piece x) should be moved to slot y. + + // there are three possible cases. + // 1. There's another piece that should be placed into this slot + // 2. This piece should be placed into another slot. + // 3. There's another piece that should be placed into this slot + // and this piece should be placed into another slot + + // swap piece_index with this slot + + // case 1 + if (this_should_move && !other_should_move) + { + TORRENT_ASSERT(piece_index != m_current_slot); + + const int other_slot = piece_index; + TORRENT_ASSERT(other_slot >= 0); + int other_piece = m_slot_to_piece[other_slot]; + + m_slot_to_piece[other_slot] = piece_index; + m_slot_to_piece[m_current_slot] = other_piece; + m_piece_to_slot[piece_index] = piece_index; + if (other_piece >= 0) m_piece_to_slot[other_piece] = m_current_slot; + + if (other_piece == unassigned) + { + std::vector::iterator i = + std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); + TORRENT_ASSERT(i != m_free_slots.end()); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(m_current_slot); + } + } + + bool ret = false; + if (other_piece >= 0) + ret |= m_storage->swap_slots(other_slot, m_current_slot); + else + ret |= m_storage->move_slot(m_current_slot, other_slot); + + if (ret) return true; + + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + // case 2 + else if (!this_should_move && other_should_move) + { + TORRENT_ASSERT(piece_index != m_current_slot); + + const int other_piece = m_current_slot; + const int other_slot = m_piece_to_slot[other_piece]; + TORRENT_ASSERT(other_slot >= 0); + + m_slot_to_piece[m_current_slot] = other_piece; + m_slot_to_piece[other_slot] = piece_index; + m_piece_to_slot[other_piece] = m_current_slot; + + if (piece_index == unassigned + && m_storage_mode == storage_mode_compact) + m_free_slots.push_back(other_slot); + + bool ret = false; + if (piece_index >= 0) + { + m_piece_to_slot[piece_index] = other_slot; + ret |= m_storage->swap_slots(other_slot, m_current_slot); + } + else + { + ret |= m_storage->move_slot(other_slot, m_current_slot); + + } + if (ret) return true; + + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + else if (this_should_move && other_should_move) + { + TORRENT_ASSERT(piece_index != m_current_slot); + TORRENT_ASSERT(piece_index >= 0); + + const int piece1 = m_slot_to_piece[piece_index]; + const int piece2 = m_current_slot; + const int slot1 = piece_index; + const int slot2 = m_piece_to_slot[piece2]; + + TORRENT_ASSERT(slot1 >= 0); + TORRENT_ASSERT(slot2 >= 0); + TORRENT_ASSERT(piece2 >= 0); + + if (slot1 == slot2) + { + // this means there are only two pieces involved in the swap + TORRENT_ASSERT(piece1 >= 0); + + // movement diagram: + // +-------------------------------+ + // | | + // +--> slot1 --> m_current_slot --+ + + m_slot_to_piece[slot1] = piece_index; + m_slot_to_piece[m_current_slot] = piece1; + + m_piece_to_slot[piece_index] = slot1; + m_piece_to_slot[piece1] = m_current_slot; + + TORRENT_ASSERT(piece1 == m_current_slot); + TORRENT_ASSERT(piece_index == slot1); + + m_storage->swap_slots(m_current_slot, slot1); + + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + else + { + TORRENT_ASSERT(slot1 != slot2); + TORRENT_ASSERT(piece1 != piece2); + + // movement diagram: + // +-----------------------------------------+ + // | | + // +--> slot1 --> slot2 --> m_current_slot --+ + + m_slot_to_piece[slot1] = piece_index; + m_slot_to_piece[slot2] = piece1; + m_slot_to_piece[m_current_slot] = piece2; + + m_piece_to_slot[piece_index] = slot1; + m_piece_to_slot[m_current_slot] = piece2; + + if (piece1 == unassigned) + { + std::vector::iterator i = + std::find(m_free_slots.begin(), m_free_slots.end(), slot1); + TORRENT_ASSERT(i != m_free_slots.end()); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(slot2); + } + } + + bool ret = false; + if (piece1 >= 0) + { + m_piece_to_slot[piece1] = slot2; + ret |= m_storage->swap_slots3(m_current_slot, slot1, slot2); + } + else + { + ret |= m_storage->move_slot(m_current_slot, slot1); + ret |= m_storage->move_slot(slot2, m_current_slot); + } + + if (ret) return true; + + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + } + else + { + TORRENT_ASSERT(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unallocated); + TORRENT_ASSERT(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); + + // the slot was identified as piece 'piece_index' + if (piece_index != unassigned) + m_piece_to_slot[piece_index] = m_current_slot; + else if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(m_current_slot); + + m_slot_to_piece[m_current_slot] = piece_index; + + TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned + || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); + } + return false; } void piece_manager::switch_to_full_mode() @@ -2010,7 +2291,7 @@ namespace libtorrent if (m_storage_mode != storage_mode_compact) return piece_index; -// INVARIANT_CHECK; + INVARIANT_CHECK; TORRENT_ASSERT(piece_index >= 0); TORRENT_ASSERT(piece_index < (int)m_piece_to_slot.size()); @@ -2112,10 +2393,8 @@ namespace libtorrent TORRENT_ASSERT(slot_index >= 0); TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); - if (m_unallocated_slots.empty()) - { + if (m_free_slots.empty() && m_unallocated_slots.empty()) switch_to_full_mode(); - } return slot_index; } @@ -2125,7 +2404,7 @@ namespace libtorrent boost::recursive_mutex::scoped_lock lock(m_mutex); TORRENT_ASSERT(num_slots > 0); -// INVARIANT_CHECK; + INVARIANT_CHECK; TORRENT_ASSERT(!m_unallocated_slots.empty()); TORRENT_ASSERT(m_storage_mode == storage_mode_compact); @@ -2180,7 +2459,11 @@ namespace libtorrent { boost::recursive_mutex::scoped_lock lock(m_mutex); - if (m_unallocated_slots.empty() && m_state == state_finished) + TORRENT_ASSERT(m_current_slot <= m_info->num_pieces()); + + if (m_unallocated_slots.empty() + && m_free_slots.empty() + && m_state == state_finished) { TORRENT_ASSERT(m_storage_mode != storage_mode_compact); } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index 26bfedd22..8afc9e990 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -150,14 +150,14 @@ namespace libtorrent torrent::torrent( session_impl& ses - , aux::checker_impl& checker , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface , storage_mode_t storage_mode , int block_size , storage_constructor_type sc - , bool paused) + , bool paused + , entry const& resume_data) : m_torrent_file(tf) , m_abort(false) , m_paused(paused) @@ -179,7 +179,6 @@ namespace libtorrent , m_last_dht_announce(time_now() - minutes(15)) #endif , m_ses(ses) - , m_checker(checker) , m_picker(0) , m_trackers(m_torrent_file->trackers()) , m_last_working_tracker(-1) @@ -187,7 +186,7 @@ namespace libtorrent , m_failed_trackers(0) , m_time_scaler(0) , m_num_pieces(0) - , m_sequenced_download_threshold(0) + , m_sequential_download(false) , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) @@ -195,6 +194,9 @@ namespace libtorrent , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) , m_storage_mode(storage_mode) + , m_state(torrent_status::queued_for_checking) + , m_progress(0.f) + , m_resume_data(resume_data) , m_default_block_size(block_size) , m_connections_initialized(true) , m_settings(ses.settings()) @@ -202,6 +204,7 @@ namespace libtorrent , m_max_uploads((std::numeric_limits::max)()) , m_num_uploads(0) , m_max_connections((std::numeric_limits::max)()) + , m_deficit_counter(0) , m_policy(this) { #ifndef NDEBUG @@ -211,7 +214,6 @@ namespace libtorrent torrent::torrent( session_impl& ses - , aux::checker_impl& checker , char const* tracker_url , sha1_hash const& info_hash , char const* name @@ -220,7 +222,8 @@ namespace libtorrent , storage_mode_t storage_mode , int block_size , storage_constructor_type sc - , bool paused) + , bool paused + , entry const& resume_data) : m_torrent_file(new torrent_info(info_hash)) , m_abort(false) , m_paused(paused) @@ -242,14 +245,13 @@ namespace libtorrent , m_last_dht_announce(time_now() - minutes(15)) #endif , m_ses(ses) - , m_checker(checker) , m_picker(0) , m_last_working_tracker(-1) , m_currently_trying_tracker(0) , m_failed_trackers(0) , m_time_scaler(0) , m_num_pieces(0) - , m_sequenced_download_threshold(0) + , m_sequential_download(false) , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) @@ -257,6 +259,9 @@ namespace libtorrent , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) , m_storage_mode(storage_mode) + , m_state(torrent_status::queued_for_checking) + , m_progress(0.f) + , m_resume_data(resume_data) , m_default_block_size(block_size) , m_connections_initialized(false) , m_settings(ses.settings()) @@ -264,6 +269,7 @@ namespace libtorrent , m_max_uploads((std::numeric_limits::max)()) , m_num_uploads(0) , m_max_connections((std::numeric_limits::max)()) + , m_deficit_counter(0) , m_policy(this) { #ifndef NDEBUG @@ -285,9 +291,10 @@ namespace libtorrent boost::weak_ptr self(shared_from_this()); if (m_torrent_file->is_valid()) init(); if (m_abort) return; - m_announce_timer.expires_from_now(seconds(1)); - m_announce_timer.async_wait(m_ses.m_strand.wrap( - bind(&torrent::on_announce_disp, self, _1))); + asio::error_code ec; + m_announce_timer.expires_from_now(seconds(1), ec); + m_announce_timer.async_wait( + bind(&torrent::on_announce_disp, self, _1)); } #ifndef TORRENT_DISABLE_DHT @@ -321,7 +328,7 @@ namespace libtorrent INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { @@ -399,7 +406,8 @@ namespace libtorrent // the shared_from_this() will create an intentional // cycle of ownership, se the hpp file for description. m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file - , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor); + , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor + , m_storage_mode); m_storage = m_owning_storage.get(); m_block_size = calculate_block_size(*m_torrent_file, m_default_block_size); m_picker.reset(new piece_picker( @@ -409,6 +417,232 @@ namespace libtorrent 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())); + + m_state = torrent_status::queued_for_checking; + + m_storage->async_check_fastresume(&m_resume_data + , bind(&torrent::on_resume_data_checked + , shared_from_this(), _1, _2)); + } + + void torrent::on_resume_data_checked(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (ret == piece_manager::fatal_disk_error) + { + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert(file_error_alert(j.error_file, get_handle(), j.str)); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << ": fatal disk error [" + " error: " << j.str << + " torrent: " << torrent_file().name() << + " ]\n"; +#endif + } + std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); + m_num_pieces = 0; + pause(); + return; + } + + // parse out "peers" from the resume data and add them to the peer list + entry const* peers_entry = m_resume_data.find_key("peers"); + if (peers_entry && peers_entry->type() == entry::list_t) + { + peer_id id; + std::fill(id.begin(), id.end(), 0); + entry::list_type const& peer_list = peers_entry->list(); + + for (entry::list_type::const_iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + if (i->type() != entry::dictionary_t) continue; + entry const* ip = i->find_key("ip"); + entry const* port = i->find_key("port"); + if (ip == 0 || port == 0 + || ip->type() != entry::string_t + || port->type() != entry::int_t) + continue; + tcp::endpoint a( + address::from_string(ip->string()) + , (unsigned short)port->integer()); + m_policy.peer_from_tracker(a, id, peer_info::resume_data, 0); + } + } + + // parse out "banned_peers" and add them as banned + entry const* banned_peers_entry = m_resume_data.find_key("banned_peers"); + if (banned_peers_entry != 0 && banned_peers_entry->type() == entry::list_t) + { + peer_id id; + std::fill(id.begin(), id.end(), 0); + entry::list_type const& peer_list = banned_peers_entry->list(); + + for (entry::list_type::const_iterator i = peer_list.begin(); + i != peer_list.end(); ++i) + { + if (i->type() != entry::dictionary_t) continue; + entry const* ip = i->find_key("ip"); + entry const* port = i->find_key("port"); + if (ip == 0 || port == 0 + || ip->type() != entry::string_t + || port->type() != entry::int_t) + continue; + tcp::endpoint a( + address::from_string(ip->string()) + , (unsigned short)port->integer()); + policy::peer* p = m_policy.peer_from_tracker(a, id, peer_info::resume_data, 0); + if (p) p->banned = true; + } + } + + bool fastresume_rejected = !j.str.empty(); + + if (fastresume_rejected && m_ses.m_alerts.should_post(alert::warning)) + { + m_ses.m_alerts.post_alert(fastresume_rejected_alert(get_handle(), j.str)); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << "fastresume data for " + << torrent_file().name() << " rejected: " + << j.str << "\n"; +#endif + } + + if (ret == 0) + { + // there are either no files for this torrent + // or the resume_data was accepted + + m_num_pieces = 0; + std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); + if (!fastresume_rejected) + { + TORRENT_ASSERT(m_resume_data.type() == entry::dictionary_t); + + // parse have bitmask + entry const* pieces = m_resume_data.find_key("pieces"); + if (pieces && pieces->type() == entry::string_t + && pieces->string().length() == m_torrent_file->num_pieces()) + { + std::string const& pieces_str = pieces->string(); + for (int i = 0, end(pieces_str.size()); i < end; ++i) + { + bool have = pieces_str[i] & 1; + m_have_pieces[i] = have; + m_num_pieces += have; + } + } + + // parse unfinished pieces + int num_blocks_per_piece = + static_cast(torrent_file().piece_length()) / block_size(); + + entry const* unfinished_ent = m_resume_data.find_key("unfinished"); + if (unfinished_ent != 0 && unfinished_ent->type() == entry::list_t) + { + entry::list_type const& unfinished = unfinished_ent->list(); + int index = 0; + for (entry::list_type::const_iterator i = unfinished.begin(); + i != unfinished.end(); ++i, ++index) + { + if (i->type() != entry::dictionary_t) continue; + entry const* piece = i->find_key("piece"); + if (piece == 0 || piece->type() != entry::int_t) continue; + int piece_index = int(piece->integer()); + if (piece_index < 0 || piece_index >= torrent_file().num_pieces()) + continue; + + if (m_have_pieces[piece_index]) + { + m_have_pieces[piece_index] = false; + --m_num_pieces; + } + + entry const* bitmask_ent = i->find_key("bitmask"); + if (bitmask_ent == 0 || bitmask_ent->type() != entry::string_t) break; + std::string const& bitmask = bitmask_ent->string(); + + const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); + if ((int)bitmask.size() != num_bitmask_bytes) continue; + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char bits = bitmask[j]; + int num_bits = (std::min)(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < num_bits; ++k) + { + const int bit = j * 8 + k; + if (bits & (1 << k)) + { + m_picker->mark_as_finished(piece_block(piece_index, bit), 0); + if (m_picker->is_piece_finished(piece_index)) + async_verify_piece(piece_index, bind(&torrent::piece_finished + , shared_from_this(), piece_index, _1)); + } + } + } + } + } + } + + files_checked(); + } + else + { + // either the fastresume data was rejected or there are + // some files + m_ses.check_torrent(shared_from_this()); + } + } + + void torrent::start_checking() + { + m_state = torrent_status::checking_files; + + m_storage->async_check_files(bind( + &torrent::on_piece_checked + , shared_from_this(), _1, _2)); + } + + void torrent::on_piece_checked(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (ret == piece_manager::fatal_disk_error) + { + if (m_ses.m_alerts.should_post(alert::fatal)) + { + m_ses.m_alerts.post_alert(file_error_alert(j.error_file, get_handle(), j.str)); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << ": fatal disk error [" + " error: " << j.str << + " torrent: " << torrent_file().name() << + " ]\n"; +#endif + } + std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); + m_num_pieces = 0; + pause(); + m_ses.done_checking(shared_from_this()); + return; + } + + m_progress = j.piece / float(torrent_file().num_pieces()); + + if (j.offset >= 0 && !m_have_pieces[j.offset]) + { + m_have_pieces[j.offset] = true; + ++m_num_pieces; + } + + // we're not done checking yet + // this handler will be called repeatedly until + // we're done, or encounter a failure + if (ret == piece_manager::need_full_check) return; + + m_ses.done_checking(shared_from_this()); + files_checked(); } void torrent::use_interface(const char* net_interface) @@ -428,20 +662,18 @@ namespace libtorrent } void torrent::on_announce() -#ifndef NDEBUG - try -#endif { if (m_abort) return; boost::weak_ptr self(shared_from_this()); + asio::error_code ec; if (!m_torrent_file->priv()) { // announce on local network every 5 minutes - m_announce_timer.expires_from_now(minutes(5)); - m_announce_timer.async_wait(m_ses.m_strand.wrap( - bind(&torrent::on_announce_disp, self, _1))); + m_announce_timer.expires_from_now(minutes(5), ec); + m_announce_timer.async_wait( + bind(&torrent::on_announce_disp, self, _1)); // announce with the local discovery service if (!m_paused) @@ -449,9 +681,9 @@ namespace libtorrent } else { - m_announce_timer.expires_from_now(minutes(15)); - m_announce_timer.async_wait(m_ses.m_strand.wrap( - bind(&torrent::on_announce_disp, self, _1))); + m_announce_timer.expires_from_now(minutes(15), ec); + m_announce_timer.async_wait( + bind(&torrent::on_announce_disp, self, _1)); } #ifndef TORRENT_DISABLE_DHT @@ -463,17 +695,10 @@ namespace libtorrent m_last_dht_announce = now; m_ses.m_dht->announce(m_torrent_file->info_hash() , m_ses.m_listen_sockets.front().external_port - , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); + , bind(&torrent::on_dht_announce_response_disp, self, _1)); } #endif } -#ifndef NDEBUG - catch (std::exception& e) - { - std::cerr << e.what() << std::endl; - TORRENT_ASSERT(false); - }; -#endif #ifndef TORRENT_DISABLE_DHT @@ -512,7 +737,7 @@ namespace libtorrent req.info_hash = m_torrent_file->info_hash(); req.kind = tracker_request::scrape_request; req.url = m_trackers[m_currently_trying_tracker].url; - m_ses.m_tracker_manager.queue_request(m_ses.m_strand, m_ses.m_half_open, req + m_ses.m_tracker_manager.queue_request(m_ses.m_io_service, m_ses.m_half_open, req , tracker_login(), m_ses.m_listen_interface.address(), shared_from_this()); } @@ -520,7 +745,7 @@ namespace libtorrent // tracker request bool torrent::should_request() { - INVARIANT_CHECK; +// INVARIANT_CHECK; if (m_trackers.empty()) return false; @@ -569,13 +794,17 @@ namespace libtorrent , std::vector& peer_list , int interval , int complete - , int incomplete) + , int incomplete + , address const& external_ip) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; TORRENT_ASSERT(r.kind == tracker_request::announce_request); + if (external_ip != address()) + m_ses.set_external_address(external_ip); + m_failed_trackers = 0; // announce intervals less than 5 minutes // are insane. @@ -594,7 +823,7 @@ namespace libtorrent // connect to random peers from the list std::random_shuffle(peer_list.begin(), peer_list.end()); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING std::stringstream s; s << "TRACKER RESPONSE:\n" "interval: " << m_duration << "\n" @@ -607,6 +836,7 @@ namespace libtorrent if (!i->pid.is_all_zeros()) s << " " << i->pid << " " << identify_client(i->pid); s << "\n"; } + s << "external ip: " << external_ip << "\n"; debug_log(s.str()); #endif // for each of the peers we got from the tracker @@ -617,35 +847,36 @@ namespace libtorrent if (i->pid == m_ses.get_peer_id()) continue; - try - { - tcp::endpoint a(address::from_string(i->ip), i->port); + asio::error_code ec; + tcp::endpoint a(address::from_string(i->ip, ec), i->port); + if (ec) + { + // assume this is because we got a hostname instead of + // an ip address from the tracker + + tcp::resolver::query q(i->ip, boost::lexical_cast(i->port)); + m_host_resolver.async_resolve(q, + bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid)); + } + else + { if (m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("blocked ip from tracker: " + i->ip); #endif if (m_ses.m_alerts.should_post(alert::info)) - { + { m_ses.m_alerts.post_alert(peer_blocked_alert(a.address() , "peer from tracker blocked by IP filter")); } continue; } - + m_policy.peer_from_tracker(a, i->pid, peer_info::tracker, 0); } - catch (std::exception&) - { - // assume this is because we got a hostname instead of - // an ip address from the tracker - - tcp::resolver::query q(i->ip, boost::lexical_cast(i->port)); - m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( - bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid))); - } } if (m_ses.m_alerts.should_post(alert::info)) @@ -659,7 +890,7 @@ namespace libtorrent } void torrent::on_peer_name_lookup(asio::error_code const& e, tcp::resolver::iterator host - , peer_id pid) try + , peer_id pid) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -670,7 +901,7 @@ namespace libtorrent if (m_ses.m_ip_filter.access(host->endpoint().address()) & ip_filter::blocked) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("blocked ip from tracker: " + host->endpoint().address().to_string()); #endif if (m_ses.m_alerts.should_post(alert::info)) @@ -684,8 +915,6 @@ namespace libtorrent m_policy.peer_from_tracker(*host, pid, peer_info::tracker, 0); } - catch (std::exception&) - {}; size_type torrent::bytes_left() const { @@ -908,7 +1137,11 @@ namespace libtorrent return make_tuple(total_done, wanted_done); } - void torrent::piece_finished(int index, bool passed_hash_check) + // passed_hash_check + // 0: success, piece passed check + // -1: disk failure + // -2: piece failed check + void torrent::piece_finished(int index, int passed_hash_check) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -921,7 +1154,7 @@ namespace libtorrent bool was_finished = m_picker->num_filtered() + num_pieces() == torrent_file().num_pieces(); - if (passed_hash_check) + if (passed_hash_check == 0) { if (m_ses.m_alerts.should_post(alert::debug)) { @@ -943,57 +1176,26 @@ namespace libtorrent // i.e. all the pieces we're interested in have // been downloaded. Release the files (they will open // in read only mode if needed) - try { finished(); } - catch (std::exception& e) - { -#ifndef NDEBUG - std::cerr << e.what() << std::endl; - TORRENT_ASSERT(false); -#endif - } + finished(); } } - else + else if (passed_hash_check == -2) { piece_failed(index); } - -#ifndef NDEBUG - try + else { -#endif - - m_policy.piece_finished(index, passed_hash_check); - -#ifndef NDEBUG + TORRENT_ASSERT(passed_hash_check == -1); + m_picker->restore_piece(index); } - catch (std::exception const& e) - { - std::cerr << e.what() << std::endl; - TORRENT_ASSERT(false); - } -#endif -#ifndef NDEBUG - try - { -#endif + m_policy.piece_finished(index, passed_hash_check == 0); if (!was_seed && is_seed()) { - TORRENT_ASSERT(passed_hash_check); + TORRENT_ASSERT(passed_hash_check == 0); completed(); } - -#ifndef NDEBUG - } - catch (std::exception const& e) - { - std::cerr << e.what() << std::endl; - TORRENT_ASSERT(false); - } -#endif - } void torrent::piece_failed(int index) @@ -1029,11 +1231,29 @@ namespace libtorrent std::set peers; std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); +#ifndef NDEBUG + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + policy::peer* p = (policy::peer*)*i; + if (p && p->connection) + { + p->connection->piece_failed = true; + } + } +#endif + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { (*i)->on_piece_failed(index); } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (*i)->on_piece_failed(index); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif @@ -1065,11 +1285,15 @@ namespace libtorrent if (p->connection) { -#if defined(TORRENT_VERBOSE_LOGGING) +#ifdef TORRENT_LOGGING + (*m_ses.m_logger) << time_now_string() << " *** BANNING PEER [ " << p->ip + << " ] 'too many corrupt pieces'\n"; +#endif +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*p->connection->m_logger) << "*** BANNING PEER [ " << p->ip << " ] 'too many corrupt pieces'\n"; #endif - p->connection->disconnect(); + p->connection->disconnect("too many corrupt pieces, banning peer"); } } } @@ -1085,9 +1309,20 @@ namespace libtorrent // that has sent the least number of pieces m_picker->restore_piece(index); TORRENT_ASSERT(m_storage); - m_storage->mark_failed(index); TORRENT_ASSERT(m_have_pieces[index] == false); + +#ifndef NDEBUG + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + policy::peer* p = (policy::peer*)*i; + if (p && p->connection) + { + p->connection->piece_failed = false; + } + } +#endif } void torrent::abort() @@ -1102,7 +1337,7 @@ namespace libtorrent // disconnect all peers and close all // files belonging to the torrents -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { @@ -1116,7 +1351,8 @@ namespace libtorrent bind(&torrent::on_files_released, shared_from_this(), _1, _2)); m_owning_storage = 0; - m_announce_timer.cancel(); + asio::error_code ec; + m_announce_timer.cancel(ec); m_host_resolver.cancel(); } @@ -1149,6 +1385,27 @@ namespace libtorrent */ } + void torrent::on_save_resume_data(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + char const* msg; + if (j.resume_data) + { + write_resume_data(*j.resume_data); + msg = "resume data generated"; + } + else + { + msg = j.str.c_str(); + } + alerts().post_alert(save_resume_data_alert(j.resume_data + , get_handle(), msg)); + } + } + void torrent::on_torrent_paused(int ret, disk_io_job const& j) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -1183,7 +1440,7 @@ namespace libtorrent m_picker->we_have(index); for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) - try { (*i)->announce_piece(index); } catch (std::exception&) {} + (*i)->announce_piece(index); for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) @@ -1201,7 +1458,13 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { (*i)->on_piece_pass(index); } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (*i)->on_piece_pass(index); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif if (is_seed()) @@ -1244,8 +1507,10 @@ namespace libtorrent TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + bool was_finished = is_finished(); bool filter_updated = m_picker->set_piece_priority(index, priority); - if (filter_updated) update_peer_interest(); + TORRENT_ASSERT(m_num_pieces >= m_picker->num_have_filtered()); + if (filter_updated) update_peer_interest(was_finished); } int torrent::piece_priority(int index) const @@ -1275,14 +1540,16 @@ namespace libtorrent int index = 0; bool filter_updated = false; + bool was_finished = is_finished(); for (std::vector::const_iterator i = pieces.begin() , end(pieces.end()); i != end; ++i, ++index) { TORRENT_ASSERT(*i >= 0); TORRENT_ASSERT(*i <= 7); filter_updated |= m_picker->set_piece_priority(index, *i); + TORRENT_ASSERT(m_num_pieces >= m_picker->num_have_filtered()); } - if (filter_updated) update_peer_interest(); + if (filter_updated) update_peer_interest(was_finished); } void torrent::piece_priorities(std::vector& pieces) const @@ -1325,6 +1592,8 @@ namespace libtorrent if (m_torrent_file->num_pieces() == 0) return; + bool was_finished = is_finished(); + int piece_length = m_torrent_file->piece_length(); // initialize the piece priorities to 0, then only allow // setting higher priorities @@ -1348,14 +1617,24 @@ namespace libtorrent , bind(&set_if_greater, _1, files[i])); } prioritize_pieces(pieces); - update_peer_interest(); + update_peer_interest(was_finished); } + // this is called when piece priorities have been updated // updates the interested flag in peers - void torrent::update_peer_interest() + void torrent::update_peer_interest(bool was_finished) { for (peer_iterator i = begin(); i != end(); ++i) (*i)->update_interest(); + + // if we used to be finished, but we aren't anymore + // we may need to connect to peers again + if (!is_finished() && was_finished) + m_policy.recalculate_connect_candidates(); + + // the torrent just became finished + if (is_finished() && !was_finished) + finished(); } void torrent::filter_piece(int index, bool filter) @@ -1370,8 +1649,9 @@ namespace libtorrent TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + bool was_finished = is_finished(); m_picker->set_piece_priority(index, filter ? 1 : 0); - update_peer_interest(); + update_peer_interest(was_finished); } void torrent::filter_pieces(std::vector const& bitmask) @@ -1384,6 +1664,7 @@ namespace libtorrent TORRENT_ASSERT(m_picker.get()); + bool was_finished = is_finished(); int index = 0; for (std::vector::const_iterator i = bitmask.begin() , end(bitmask.end()); i != end; ++i, ++index) @@ -1394,7 +1675,7 @@ namespace libtorrent else m_picker->set_piece_priority(index, 1); } - update_peer_interest(); + update_peer_interest(was_finished); } bool torrent::is_piece_filtered(int index) const @@ -1542,7 +1823,7 @@ namespace libtorrent } } - void torrent::remove_peer(peer_connection* p) try + void torrent::remove_peer(peer_connection* p) { // INVARIANT_CHECK; @@ -1569,16 +1850,12 @@ namespace libtorrent } else { - // if we're a seed, we don't keep track of piece availability - if (!is_seed()) + if (m_picker.get()) { const std::vector& pieces = p->get_bitfield(); - - for (std::vector::const_iterator i = pieces.begin(); - i != pieces.end(); ++i) - { - if (*i) peer_lost(static_cast(i - pieces.begin())); - } + TORRENT_ASSERT(std::count(pieces.begin(), pieces.end(), true) + < int(pieces.size())); + m_picker->dec_refcount(pieces); } } } @@ -1603,19 +1880,12 @@ namespace libtorrent } } } - catch (std::exception& e) - { -#ifndef NDEBUG - std::string err = e.what(); -#endif - TORRENT_ASSERT(false); - }; void torrent::connect_to_url_seed(std::string const& url) { INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING (*m_ses.m_logger) << time_now_string() << " resolving web seed: " << url << "\n"; #endif @@ -1675,27 +1945,27 @@ namespace libtorrent // use proxy tcp::resolver::query q(ps.hostname , boost::lexical_cast(ps.port)); - m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( - bind(&torrent::on_proxy_name_lookup, shared_from_this(), _1, _2, url))); + m_host_resolver.async_resolve(q, + bind(&torrent::on_proxy_name_lookup, shared_from_this(), _1, _2, url)); } else { tcp::resolver::query q(hostname, boost::lexical_cast(port)); - m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( + m_host_resolver.async_resolve(q, bind(&torrent::on_name_lookup, shared_from_this(), _1, _2, url - , tcp::endpoint()))); + , tcp::endpoint())); } } void torrent::on_proxy_name_lookup(asio::error_code const& e, tcp::resolver::iterator host - , std::string url) try + , std::string url) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING (*m_ses.m_logger) << time_now_string() << " completed resolve proxy hostname for: " << url << "\n"; #endif @@ -1736,22 +2006,18 @@ namespace libtorrent } tcp::resolver::query q(hostname, boost::lexical_cast(port)); - m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( - bind(&torrent::on_name_lookup, shared_from_this(), _1, _2, url, a))); + m_host_resolver.async_resolve(q, + bind(&torrent::on_name_lookup, shared_from_this(), _1, _2, url, a)); } - catch (std::exception&) - { - TORRENT_ASSERT(false); - }; void torrent::on_name_lookup(asio::error_code const& e, tcp::resolver::iterator host - , std::string url, tcp::endpoint proxy) try + , std::string url, tcp::endpoint proxy) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING (*m_ses.m_logger) << time_now_string() << " completed resolve: " << url << "\n"; #endif @@ -1767,7 +2033,7 @@ namespace libtorrent m_ses.m_alerts.post_alert( url_seed_alert(get_handle(), url, msg.str())); } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING (*m_ses.m_logger) << " ** HOSTNAME LOOKUP FAILED!**: " << url << "\n"; #endif @@ -1791,7 +2057,8 @@ namespace libtorrent return; } - boost::shared_ptr s(new socket_type); + boost::shared_ptr s(new (std::nothrow) socket_type(m_ses.m_io_service)); + if (!s) return; bool ret = instantiate_connection(m_ses.m_io_service, m_ses.web_seed_proxy(), *s); TORRENT_ASSERT(ret); @@ -1803,8 +2070,15 @@ namespace libtorrent // the proxy, without requiring CONNECT support s->get().set_no_connect(true); } - boost::intrusive_ptr c(new web_peer_connection( + + std::pair const& out_ports = m_settings.outgoing_ports; + asio::error_code ec; + if (out_ports.first > 0 && out_ports.second >= out_ports.first) + s->bind(tcp::endpoint(address(), m_ses.next_port()), ec); + + boost::intrusive_ptr c(new (std::nothrow) web_peer_connection( m_ses, shared_from_this(), s, a, url, 0)); + if (!c) return; #ifndef NDEBUG c->m_in_constructor = false; @@ -1819,37 +2093,36 @@ namespace libtorrent } #endif + // add the newly connected peer to this torrent's peer list + m_connections.insert(boost::get_pointer(c)); + m_ses.m_connections.insert(c); + +#ifndef BOOST_NO_EXCEPTIONS try { +#endif // add the newly connected peer to this torrent's peer list m_connections.insert(boost::get_pointer(c)); m_ses.m_connections.insert(c); + c->start(); m_ses.m_half_open.enqueue( bind(&peer_connection::connect, c, _1) , bind(&peer_connection::timed_out, c) , seconds(settings().peer_connect_timeout)); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING (*m_ses.m_logger) << " ** HOSTNAME LOOKUP FAILED!**: " << e.what() << "\n"; #endif // 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(c, a, e.what()); - c->disconnect(); + c->disconnect(e.what()); } - } - catch (std::exception& exc) - { -#ifndef NDEBUG - std::cerr << exc.what() << std::endl; #endif - TORRENT_ASSERT(false); - }; + } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES namespace @@ -1872,8 +2145,8 @@ namespace libtorrent m_resolving_country = true; asio::ip::address_v4 reversed(swap_bytes(p->remote().address().to_v4().to_ulong())); tcp::resolver::query q(reversed.to_string() + ".zz.countries.nerd.dk", "0"); - m_host_resolver.async_resolve(q, m_ses.m_strand.wrap( - bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p))); + m_host_resolver.async_resolve(q, + bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p)); } namespace @@ -1973,7 +2246,7 @@ namespace libtorrent { // unknown country! p->set_country("!!"); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING (*m_ses.m_logger) << "IP " << p->remote().address() << " was mapped to unknown country: " << country << "\n"; #endif return; @@ -1984,6 +2257,128 @@ namespace libtorrent } #endif + void torrent::write_resume_data(entry& ret) const + { + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + + ret["allocation"] = m_storage_mode == storage_mode_sparse?"sparse" + :m_storage_mode == storage_mode_allocate?"full":"compact"; + + const sha1_hash& info_hash = torrent_file().info_hash(); + ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); + + // blocks per piece + int num_blocks_per_piece = + static_cast(torrent_file().piece_length()) / block_size(); + ret["blocks per piece"] = num_blocks_per_piece; + + // if this torrent is a seed, we won't have a piece picker + // and there will be no half-finished pieces. + if (!is_seed()) + { + const std::vector& q + = m_picker->get_download_queue(); + + // unfinished pieces + ret["unfinished"] = entry::list_type(); + entry::list_type& up = ret["unfinished"].list(); + + // info for each unfinished piece + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + if (i->finished == 0) continue; + + entry piece_struct(entry::dictionary_t); + + // the unfinished piece's index + piece_struct["piece"] = i->index; + + std::string bitmask; + const int num_bitmask_bytes + = (std::max)(num_blocks_per_piece / 8, 1); + + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char v = 0; + int bits = (std::min)(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < bits; ++k) + v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) + ? (1 << k) : 0; + bitmask.insert(bitmask.end(), v); + TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); + } + piece_struct["bitmask"] = bitmask; + // push the struct onto the unfinished-piece list + up.push_back(piece_struct); + } + } + + // write have bitmask + entry::string_type& pieces = ret["pieces"].string(); + pieces.resize(m_torrent_file->num_pieces()); + for (int i = 0, end(pieces.size()); i < end; ++i) + pieces[i] = m_have_pieces[i] ? 1 : 0; + + // write local peers + + entry::list_type& peer_list = ret["peers"].list(); + entry::list_type& banned_peer_list = ret["banned_peers"].list(); + + int max_failcount = m_ses.m_settings.max_failcount; + + for (policy::const_iterator i = m_policy.begin_peer() + , end(m_policy.end_peer()); i != end; ++i) + { + asio::error_code ec; + if (i->second.banned) + { + tcp::endpoint ip = i->second.ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; + peer["port"] = ip.port(); + banned_peer_list.push_back(peer); + continue; + } + // we cannot save remote connection + // since we don't know their listen port + // unless they gave us their listen port + // through the extension handshake + // so, if the peer is not connectable (i.e. we + // don't know its listen port) or if it has + // been banned, don't save it. + if (i->second.type == policy::peer::not_connectable) continue; + + // don't save peers that doesn't work + if (i->second.failcount >= max_failcount) continue; + + tcp::endpoint ip = i->second.ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; + peer["port"] = ip.port(); + peer_list.push_back(peer); + } + } + + void torrent::get_full_peer_list(std::vector& v) const + { + v.clear(); + v.reserve(m_policy.num_peers()); + for (policy::const_iterator i = m_policy.begin_peer(); + i != m_policy.end_peer(); ++i) + { + peer_list_entry e; + e.ip = i->second.ip; + e.flags = i->second.banned ? peer_list_entry::banned : 0; + e.failcount = i->second.failcount; + e.source = i->second.source; + v.push_back(e); + } + } + void torrent::get_peer_info(std::vector& v) { v.clear(); @@ -2084,6 +2479,7 @@ namespace libtorrent TORRENT_ASSERT(peerinfo); TORRENT_ASSERT(peerinfo->connection == 0); + peerinfo->connected = time_now(); #ifndef NDEBUG // this asserts that we don't have duplicates in the policy's peer list @@ -2099,10 +2495,14 @@ namespace libtorrent tcp::endpoint const& a(peerinfo->ip); TORRENT_ASSERT((m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) == 0); - boost::shared_ptr s(new socket_type); + boost::shared_ptr s(new socket_type(m_ses.m_io_service)); bool ret = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy(), *s); TORRENT_ASSERT(ret); + std::pair const& out_ports = m_ses.settings().outgoing_ports; + asio::error_code ec; + if (out_ports.first > 0 && out_ports.second >= out_ports.first) + s->bind(tcp::endpoint(address(), m_ses.next_port()), ec); boost::intrusive_ptr c(new bt_peer_connection( m_ses, shared_from_this(), s, a, peerinfo)); @@ -2111,100 +2511,115 @@ namespace libtorrent c->m_in_constructor = false; #endif - try - { + c->add_stat(peerinfo->prev_amount_download, peerinfo->prev_amount_upload); + peerinfo->prev_amount_download = 0; + peerinfo->prev_amount_upload = 0; + #ifndef TORRENT_DISABLE_EXTENSIONS - for (extension_list_t::iterator i = m_extensions.begin() - , end(m_extensions.end()); i != end; ++i) - { + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif boost::shared_ptr pp((*i)->new_connection(c.get())); if (pp) c->add_extension(pp); - } +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif + } #endif - // add the newly connected peer to this torrent's peer list - m_connections.insert(boost::get_pointer(c)); - m_ses.m_connections.insert(c); + // add the newly connected peer to this torrent's peer list + m_connections.insert(boost::get_pointer(c)); + m_ses.m_connections.insert(c); + c->start(); - int timeout = settings().peer_connect_timeout; - if (peerinfo) timeout += 3 * peerinfo->failcount; + int timeout = settings().peer_connect_timeout; + if (peerinfo) timeout += 3 * peerinfo->failcount; +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif m_ses.m_half_open.enqueue( bind(&peer_connection::connect, c, _1) , bind(&peer_connection::timed_out, c) , seconds(timeout)); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { std::set::iterator i = m_connections.find(boost::get_pointer(c)); if (i != m_connections.end()) m_connections.erase(i); - m_ses.connection_failed(c, a, e.what()); - c->disconnect(); + c->disconnect(e.what()); return false; } +#endif peerinfo->connection = c.get(); return true; } - void torrent::set_metadata(entry const& metadata) + bool torrent::set_metadata(entry const& metadata, std::string& error) { INVARIANT_CHECK; TORRENT_ASSERT(!m_torrent_file->is_valid()); - m_torrent_file->parse_info_section(metadata); - - init(); - - boost::mutex::scoped_lock(m_checker.m_mutex); - - boost::shared_ptr d( - new aux::piece_checker_data); - d->torrent_ptr = shared_from_this(); - d->save_path = m_save_path; - d->info_hash = m_torrent_file->info_hash(); - // add the torrent to the queue to be checked - m_checker.m_torrents.push_back(d); - typedef session_impl::torrent_map torrent_map; - torrent_map::iterator i = m_ses.m_torrents.find( - m_torrent_file->info_hash()); - TORRENT_ASSERT(i != m_ses.m_torrents.end()); - m_ses.m_torrents.erase(i); - // and notify the thread that it got another - // job in its queue - m_checker.m_cond.notify_one(); + if (!m_torrent_file->parse_info_section(metadata, error)) + { + // parse failed + return false; + } if (m_ses.m_alerts.should_post(alert::info)) { m_ses.m_alerts.post_alert(metadata_received_alert( get_handle(), "metadata successfully received from swarm")); } + + init(); + + return true; } - void torrent::attach_peer(peer_connection* p) + bool torrent::attach_peer(peer_connection* p) { // INVARIANT_CHECK; TORRENT_ASSERT(p != 0); TORRENT_ASSERT(!p->is_local()); + if ((m_state == torrent_status::queued_for_checking + || m_state == torrent_status::checking_files) + && valid_metadata()) + { + p->disconnect("torrent is not ready to accept peers"); + return false; + } + if (m_ses.m_connections.find(p) == m_ses.m_connections.end()) { - throw protocol_error("peer is not properly constructed"); + p->disconnect("peer is not properly constructed"); + return false; } if (m_ses.is_aborted()) { - throw protocol_error("session is closing"); + p->disconnect("session is closing"); + return false; } if (int(m_connections.size()) >= m_max_connections) { - throw protocol_error("reached connection limit"); + p->disconnect("reached connection limit"); + return false; } +#ifndef BOOST_NO_EXCEPTIONS try { +#endif #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2213,26 +2628,40 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif - m_policy.new_connection(*p); + if (!m_policy.new_connection(*p)) + return false; +#ifndef BOOST_NO_EXCEPTIONS } - catch (std::exception&) + catch (std::exception& e) { - throw; +#if defined TORRENT_LOGGING + (*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION " + << p->remote() << " policy::new_connection threw: " << e.what() << "\n"; +#endif + p->disconnect(e.what()); + return false; } +#endif TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); peer_iterator ci = m_connections.insert(p).first; - TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint()); - #ifndef NDEBUG + asio::error_code ec; + TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint(ec) || ec); +#endif + +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS m_policy.check_invariant(); #endif + return true; } bool torrent::want_more_peers() const { return int(m_connections.size()) < m_max_connections - && m_ses.m_half_open.free_slots() - && !m_paused; + && !m_paused + && m_state != torrent_status::checking_files + && m_state != torrent_status::queued_for_checking + && m_policy.num_connect_candidates() > 0; } void torrent::disconnect_all() @@ -2246,7 +2675,7 @@ namespace libtorrent peer_connection* p = *m_connections.begin(); TORRENT_ASSERT(p->associated_torrent().lock().get() == this); -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING if (m_abort) (*p->m_logger) << "*** CLOSING CONNECTION 'aborting'\n"; else @@ -2255,11 +2684,10 @@ namespace libtorrent #ifndef NDEBUG std::size_t size = m_connections.size(); #endif - if (p->is_disconnecting()) m_connections.erase(m_connections.begin()); else - p->disconnect(); + p->disconnect(m_abort?"stopping torrent":"pausing torrent"); TORRENT_ASSERT(m_connections.size() <= size); } } @@ -2269,13 +2697,21 @@ namespace libtorrent return m_bandwidth_limit[channel].throttle(); } + int torrent::bandwidth_queue_size(int channel) const + { + return (int)m_bandwidth_queue[channel].size(); + } + void torrent::request_bandwidth(int channel , boost::intrusive_ptr const& p - , int priority) + , int max_block_size, int priority) { + TORRENT_ASSERT(max_block_size > 0); TORRENT_ASSERT(m_bandwidth_limit[channel].throttle() > 0); TORRENT_ASSERT(p->max_assignable_bandwidth(channel) > 0); - int block_size = m_bandwidth_limit[channel].throttle() / 10; + TORRENT_ASSERT(p->m_channel_state[channel] == peer_info::bw_torrent); + int block_size = (std::min)(m_bandwidth_limit[channel].throttle() / 10 + , max_block_size); if (block_size <= 0) block_size = 1; if (m_bandwidth_limit[channel].max_assignable() > 0) @@ -2301,6 +2737,8 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + INVARIANT_CHECK; + TORRENT_ASSERT(amount > 0); m_bandwidth_limit[channel].expire(amount); queue_t tmp; @@ -2327,6 +2765,8 @@ namespace libtorrent , int block_size , int priority) { + TORRENT_ASSERT(p->m_channel_state[channel] == peer_info::bw_torrent); + p->m_channel_state[channel] = peer_info::bw_global; m_ses.m_bandwidth_manager[channel]->request_bandwidth(p , block_size, priority); m_bandwidth_limit[channel].assign(block_size); @@ -2355,6 +2795,8 @@ namespace libtorrent , "torrent has finished downloading")); } + m_state = torrent_status::finished; + // disconnect all seeds // TODO: should disconnect all peers that have the pieces we have // not just seeds @@ -2366,14 +2808,14 @@ namespace libtorrent TORRENT_ASSERT(p->associated_torrent().lock().get() == this); if (p->is_seed()) { -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*p->m_logger) << "*** SEED, CLOSING CONNECTION\n"; #endif seeds.push_back(p); } } std::for_each(seeds.begin(), seeds.end() - , bind(&peer_connection::disconnect, _1)); + , bind(&peer_connection::disconnect, _1, "torrent finished, disconnecting seed")); TORRENT_ASSERT(m_storage); // we need to keep the object alive during this operation @@ -2389,6 +2831,7 @@ namespace libtorrent // make the next tracker request // be a completed-event m_event = tracker_request::completed; + m_state = torrent_status::seeding; force_tracker_request(); } @@ -2440,7 +2883,7 @@ namespace libtorrent boost::weak_ptr self(shared_from_this()); m_ses.m_dht->announce(m_torrent_file->info_hash() , m_ses.m_listen_sockets.front().external_port - , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); + , bind(&torrent::on_dht_announce_response_disp, self, _1)); } #endif @@ -2453,117 +2896,39 @@ namespace libtorrent } - bool torrent::check_fastresume(aux::piece_checker_data& data) - { - INVARIANT_CHECK; - - TORRENT_ASSERT(valid_metadata()); - bool done = true; - try - { - std::string error_msg; - TORRENT_ASSERT(m_storage); - TORRENT_ASSERT(m_owning_storage.get()); - done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces - , m_storage_mode, error_msg); - - if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) - { - m_ses.m_alerts.post_alert(fastresume_rejected_alert( - get_handle(), error_msg)); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_ses.m_logger) << "fastresume data for " - << torrent_file().name() << " rejected: " - << error_msg << "\n"; -#endif - } - } - catch (std::exception& e) - { - // probably means file permission failure or invalid filename - std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); - m_num_pieces = 0; - - if (m_ses.m_alerts.should_post(alert::fatal)) - { - m_ses.m_alerts.post_alert( - file_error_alert( - get_handle() - , e.what())); - } - pause(); - } - return done; - } - - std::pair torrent::check_files() - { - TORRENT_ASSERT(m_torrent_file->is_valid()); - INVARIANT_CHECK; - - TORRENT_ASSERT(m_owning_storage.get()); - - std::pair progress(true, 1.f); - try - { - TORRENT_ASSERT(m_storage); - progress = m_storage->check_files(m_have_pieces, m_num_pieces - , m_ses.m_mutex); - } - catch (std::exception& e) - { - // probably means file permission failure or invalid filename - std::fill(m_have_pieces.begin(), m_have_pieces.end(), false); - m_num_pieces = 0; - - if (m_ses.m_alerts.should_post(alert::fatal)) - { - m_ses.m_alerts.post_alert( - file_error_alert( - get_handle() - , e.what())); - } - pause(); - } - - return progress; - } - - void torrent::files_checked(std::vector const& - unfinished_pieces) + void torrent::files_checked() { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); TORRENT_ASSERT(m_torrent_file->is_valid()); INVARIANT_CHECK; + m_state = torrent_status::connecting_to_tracker; + if (!is_seed()) { - // this is filled in with pieces that needs to be checked - // against its hashes. - std::vector verify_pieces; - m_picker->files_checked(m_have_pieces, unfinished_pieces, verify_pieces); - if (m_sequenced_download_threshold > 0) - picker().set_sequenced_download_threshold(m_sequenced_download_threshold); - while (!verify_pieces.empty()) - { - int piece = verify_pieces.back(); - verify_pieces.pop_back(); - async_verify_piece(piece, bind(&torrent::piece_finished - , shared_from_this(), piece, _1)); - } + m_picker->init(m_have_pieces); + if (m_sequential_download) + picker().sequential_download(m_sequential_download); } #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { (*i)->on_files_checked(); } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (*i)->on_files_checked(); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif if (is_seed()) { + m_state = torrent_status::seeding; m_picker.reset(); if (m_ses.settings().free_torrent_hashes) m_torrent_file->seed_free(); @@ -2577,20 +2942,21 @@ namespace libtorrent for (torrent::peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end;) { + boost::intrusive_ptr pc = *i; + ++i; +#ifndef BOOST_NO_EXCEPTIONS try { - (*i)->on_metadata(); - (*i)->init(); - ++i; +#endif + pc->on_metadata(); + pc->init(); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { - // the connection failed, close it - torrent::peer_iterator j = i; - ++j; - m_ses.connection_failed(*i, (*i)->remote(), e.what()); - i = j; + pc->disconnect(e.what()); } +#endif } } #ifndef NDEBUG @@ -2605,10 +2971,7 @@ namespace libtorrent fs::path torrent::save_path() const { - if (m_owning_storage.get()) - return m_owning_storage->save_path(); - else - return m_save_path; + return m_save_path; } void torrent::move_storage(fs::path const& save_path) @@ -2643,9 +3006,9 @@ namespace libtorrent } - torrent_handle torrent::get_handle() const + torrent_handle torrent::get_handle() { - return torrent_handle(&m_ses, &m_checker, m_torrent_file->info_hash()); + return torrent_handle(shared_from_this()); } session_settings const& torrent::settings() const @@ -2658,6 +3021,9 @@ namespace libtorrent { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + TORRENT_ASSERT(m_resume_data.type() == entry::dictionary_t + || m_resume_data.type() == entry::undefined_t); + TORRENT_ASSERT(m_bandwidth_queue[0].size() <= m_connections.size()); TORRENT_ASSERT(m_bandwidth_queue[1].size() <= m_connections.size()); @@ -2675,6 +3041,8 @@ namespace libtorrent std::map num_requests; for (const_peer_iterator i = begin(); i != end(); ++i) { + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); peer_connection const& p = *(*i); for (std::deque::const_iterator i = p.request_queue().begin() , end(p.request_queue().end()); i != end; ++i) @@ -2697,6 +3065,7 @@ namespace libtorrent if (!m_picker->is_downloaded(i->first)) TORRENT_ASSERT(m_picker->num_peers(i->first) == i->second); } + TORRENT_ASSERT(m_num_pieces >= m_picker->num_have_filtered()); } if (valid_metadata()) @@ -2747,13 +3116,6 @@ namespace libtorrent complete = false; break; } - if (complete && m_files_checked) - { - disk_io_job ret = m_ses.m_disk_thread.find_job( - m_owning_storage, -1, i->index); - TORRENT_ASSERT(ret.action == disk_io_job::hash || ret.action == disk_io_job::write); - TORRENT_ASSERT(ret.piece == i->index); - } } } @@ -2766,15 +3128,15 @@ namespace libtorrent } #endif - void torrent::set_sequenced_download_threshold(int threshold) + void torrent::set_sequential_download(bool sd) { if (has_picker()) { - picker().set_sequenced_download_threshold(threshold); + picker().sequential_download(sd); } else { - m_sequenced_download_threshold = threshold; + m_sequential_download = sd; } } @@ -2843,7 +3205,7 @@ namespace libtorrent void torrent::delete_files() { -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { @@ -2866,6 +3228,27 @@ namespace libtorrent } } + // this is an async operation triggered by the client + void torrent::save_resume_data() + { + INVARIANT_CHECK; + + if (m_owning_storage.get()) + { + TORRENT_ASSERT(m_storage); + m_storage->async_save_resume_data( + bind(&torrent::on_save_resume_data, shared_from_this(), _1, _2)); + } + else + { + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(save_resume_data_alert(boost::shared_ptr() + , get_handle(), "save resume data failed, torrent is being destructed")); + } + } + } + void torrent::pause() { INVARIANT_CHECK; @@ -2876,11 +3259,17 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { if ((*i)->on_pause()) return; } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + if ((*i)->on_pause()) return; +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif -#if defined(TORRENT_VERBOSE_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) { @@ -2920,7 +3309,13 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { if ((*i)->on_resume()) return; } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + if ((*i)->on_resume()) return; +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif @@ -2942,7 +3337,13 @@ namespace libtorrent for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) { - try { (*i)->tick(); } catch (std::exception&) {} +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (*i)->tick(); +#ifndef BOOST_NO_EXCEPTIONS + } catch (std::exception&) {} +#endif } #endif @@ -3005,19 +3406,22 @@ namespace libtorrent m_stat += p->statistics(); // updates the peer connection's ul/dl bandwidth // resource requests +#ifndef BOOST_NO_EXCEPTIONS try { +#endif p->second_tick(tick_interval); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { - (void)e; -#ifdef TORRENT_VERBOSE_LOGGING +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING (*p->m_logger) << "**ERROR**: " << e.what() << "\n"; #endif p->set_failed(); - p->disconnect(); + p->disconnect(e.what()); } +#endif } accumulator += m_stat; m_stat.second_tick(tick_interval); @@ -3039,10 +3443,21 @@ namespace libtorrent bool torrent::try_connect_peer() { TORRENT_ASSERT(want_more_peers()); - return m_policy.connect_one_peer(); + if (m_deficit_counter < 100) return false; + m_deficit_counter -= 100; + bool ret = m_policy.connect_one_peer(); + return ret; } - void torrent::async_verify_piece(int piece_index, boost::function const& f) + void torrent::give_connect_points(int points) + { + TORRENT_ASSERT(points <= 100); + TORRENT_ASSERT(points > 0); + TORRENT_ASSERT(want_more_peers()); + m_deficit_counter += points; + } + + void torrent::async_verify_piece(int piece_index, boost::function const& f) { // INVARIANT_CHECK; @@ -3051,20 +3466,43 @@ namespace libtorrent TORRENT_ASSERT(piece_index >= 0); TORRENT_ASSERT(piece_index < m_torrent_file->num_pieces()); TORRENT_ASSERT(piece_index < (int)m_have_pieces.size()); +#ifndef NDEBUG + if (m_picker) + { + int blocks_in_piece = m_picker->blocks_in_piece(piece_index); + for (int i = 0; i < blocks_in_piece; ++i) + { + TORRENT_ASSERT(m_picker->num_peers(piece_block(piece_index, i)) == 0); + } + } +#endif m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified , shared_from_this(), _1, _2, f)); -#ifndef NDEBUG +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS check_invariant(); #endif } void torrent::on_piece_verified(int ret, disk_io_job const& j - , boost::function f) + , boost::function f) { - sha1_hash h(j.str); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - f(m_torrent_file->hash_for_piece(j.piece) == h); + + // return value: + // 0: success, piece passed hash check + // -1: disk failure + // -2: hash check failed + + if (ret == -1) + { + if (alerts().should_post(alert::fatal)) + { + alerts().post_alert(file_error_alert(j.error_file, get_handle(), j.str)); + } + pause(); + } + f(ret); } const tcp::endpoint& torrent::current_tracker() const @@ -3072,9 +3510,6 @@ namespace libtorrent return m_tracker_address; } - bool torrent::is_allocating() const - { return m_owning_storage.get() && m_owning_storage->is_allocating(); } - void torrent::file_progress(std::vector& fp) const { TORRENT_ASSERT(valid_metadata()); @@ -3128,9 +3563,9 @@ namespace libtorrent st.num_peers = (int)std::count_if(m_connections.begin(), m_connections.end() , !boost::bind(&peer_connection::is_connecting, _1)); - st.list_peers = std::distance(m_policy.begin_peer(), m_policy.end_peer()); - st.list_seeds = (int)std::count_if(m_policy.begin_peer(), m_policy.end_peer() - , boost::bind(&policy::peer::seed, bind(&policy::iterator::value_type::second, _1))); + st.list_peers = m_policy.num_peers(); + st.list_seeds = m_policy.num_seeds(); + st.connect_candidates = m_policy.num_connect_candidates(); st.storage_mode = m_storage_mode; @@ -3179,6 +3614,8 @@ namespace libtorrent st.connections_limit = m_max_connections; // if we don't have any metadata, stop here + st.state = m_state; + if (!valid_metadata()) { if (m_got_tracker_response == false) @@ -3186,14 +3623,8 @@ namespace libtorrent else st.state = torrent_status::downloading_metadata; -// TODO: add a progress member to the torrent that will be used in this case -// and that may be set by a plugin -// if (m_metadata_size == 0) st.progress = 0.f; -// else st.progress = (std::min)(1.f, m_metadata_progress / (float)m_metadata_size); - st.progress = 0.f; - + st.progress = m_progress; st.block_size = 0; - return st; } @@ -3220,31 +3651,14 @@ namespace libtorrent TORRENT_ASSERT(st.total_wanted >= st.total_wanted_done); - if (st.total_wanted == 0) st.progress = 1.f; + if (m_state == torrent_status::checking_files) + st.progress = m_progress; + else if (st.total_wanted == 0) st.progress = 1.f; else st.progress = st.total_wanted_done / 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; - } - else if (is_seed()) - { - TORRENT_ASSERT(st.total_done == m_torrent_file->total_size()); - st.state = torrent_status::seeding; - } - else if (st.total_wanted_done == st.total_wanted) - { - st.state = torrent_status::finished; - } - else - { - st.state = torrent_status::downloading; - } - st.num_seeds = num_seeds(); if (m_picker.get()) st.distributed_copies = m_picker->distributed_copies(); @@ -3268,7 +3682,7 @@ namespace libtorrent INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("*** tracker timed out"); #endif @@ -3301,7 +3715,7 @@ namespace libtorrent INVARIANT_CHECK; -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log(std::string("*** tracker error: ") + str); #endif if (m_ses.m_alerts.should_post(alert::warning)) @@ -3324,7 +3738,7 @@ namespace libtorrent } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING void torrent::debug_log(const std::string& line) { (*m_ses.m_logger) << time_now_string() << " " << line << "\n"; diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index 4635f4411..ad827412d 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -81,59 +81,41 @@ using libtorrent::aux::session_impl; #ifdef BOOST_NO_EXCEPTIONS #define TORRENT_FORWARD(call) \ - if (m_ses == 0) return; \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) return; \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ t->call #define TORRENT_FORWARD_RETURN(call, def) \ - if (m_ses == 0) return def; \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) return def; \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return def; \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ return t->call #define TORRENT_FORWARD_RETURN2(call, def) \ - if (m_ses == 0) return def; \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) return def; \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return def; \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ t->call #else #define TORRENT_FORWARD(call) \ - if (m_ses == 0) throw_invalid_handle(); \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) throw_invalid_handle(); \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) throw_invalid_handle(); \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ t->call #define TORRENT_FORWARD_RETURN(call, def) \ - if (m_ses == 0) throw_invalid_handle(); \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) return def; \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) throw_invalid_handle(); \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ return t->call #define TORRENT_FORWARD_RETURN2(call, def) \ - if (m_ses == 0) throw_invalid_handle(); \ - TORRENT_ASSERT(m_chk); \ - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); \ - mutex::scoped_lock l2(m_chk->m_mutex); \ - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); \ - if (t == 0) return def; \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) throw_invalid_handle(); \ + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); \ t->call #endif @@ -150,30 +132,22 @@ namespace libtorrent throw invalid_handle(); } #endif - - torrent* find_torrent( - session_impl* ses - , aux::checker_impl* chk - , sha1_hash const& hash) - { - aux::piece_checker_data* d = chk->find_torrent(hash); - if (d != 0) return d->torrent_ptr.get(); - - boost::shared_ptr t = ses->find_torrent(hash).lock(); - if (t) return t.get(); - return 0; - } } #ifndef NDEBUG void torrent_handle::check_invariant() const - { - TORRENT_ASSERT((m_ses == 0 && m_chk == 0) || (m_ses != 0 && m_chk != 0)); - } + {} #endif + sha1_hash torrent_handle::info_hash() const + { + INVARIANT_CHECK; + const static sha1_hash empty; + TORRENT_FORWARD_RETURN(torrent_file().info_hash(), empty); + } + void torrent_handle::set_max_uploads(int max_uploads) const { INVARIANT_CHECK; @@ -279,6 +253,12 @@ namespace libtorrent TORRENT_FORWARD(pause()); } + void torrent_handle::save_resume_data() const + { + INVARIANT_CHECK; + TORRENT_FORWARD(save_resume_data()); + } + void torrent_handle::resume() const { INVARIANT_CHECK; @@ -301,50 +281,13 @@ namespace libtorrent torrent_status torrent_handle::status() const { INVARIANT_CHECK; - - if (m_ses == 0) -#ifdef BOOST_NO_EXCEPTIONS - return torrent_status(); -#else - throw_invalid_handle(); -#endif - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d != 0) - { - torrent_status st = d->torrent_ptr->status(); - - if (d->processing) - { - if (d->torrent_ptr->is_allocating()) - st.state = torrent_status::allocating; - else - st.state = torrent_status::checking_files; - } - else - st.state = torrent_status::queued_for_checking; - st.progress = d->progress; - st.paused = d->torrent_ptr->is_paused(); - return st; - } - - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); - if (t) return t->status(); - -#ifndef BOOST_NO_EXCEPTIONS - throw_invalid_handle(); -#endif - return torrent_status(); + TORRENT_FORWARD_RETURN(status(), torrent_status()); } - void torrent_handle::set_sequenced_download_threshold(int threshold) const + void torrent_handle::set_sequential_download(bool sd) const { INVARIANT_CHECK; - TORRENT_FORWARD(set_sequenced_download_threshold(threshold)); + TORRENT_FORWARD(set_sequential_download(sd)); } std::string torrent_handle::name() const @@ -466,15 +409,16 @@ namespace libtorrent INVARIANT_CHECK; #ifdef BOOST_NO_EXCEPTIONS const static torrent_info empty; - if (m_ses == 0) return empty; -#else - if (m_ses == 0) throw_invalid_handle(); #endif - TORRENT_ASSERT(m_chk); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); - if (t == 0 || !t->valid_metadata()) + boost::shared_ptr t = m_torrent.lock(); + if (!t) +#ifdef BOOST_NO_EXCEPTIONS + return empty; +#else + throw_invalid_handle(); +#endif + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); + if (!t->valid_metadata()) #ifdef BOOST_NO_EXCEPTIONS return empty; #else @@ -486,158 +430,15 @@ namespace libtorrent bool torrent_handle::is_valid() const { INVARIANT_CHECK; - if (m_ses == 0) return false; - TORRENT_ASSERT(m_chk); - session_impl::mutex_t::scoped_lock l1(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); - return t != 0; + return !m_torrent.expired(); } entry torrent_handle::write_resume_data() const { INVARIANT_CHECK; - if (m_ses == 0) -#ifdef BOOST_NO_EXCEPTIONS - return entry(); -#else - throw_invalid_handle(); -#endif - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - mutex::scoped_lock l2(m_chk->m_mutex); - - torrent* t = find_torrent(m_ses, m_chk, m_info_hash); - if (!t || !t->valid_metadata()) -#ifdef BOOST_NO_EXCEPTIONS - return entry(); -#else - throw_invalid_handle(); -#endif - - std::vector have_pieces = t->pieces(); - entry ret(entry::dictionary_t); - - ret["file-format"] = "libtorrent resume file"; - ret["file-version"] = 1; - - ret["allocation"] = t->filesystem().compact_allocation()?"compact":"full"; - - const sha1_hash& info_hash = t->torrent_file().info_hash(); - ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); - - // blocks per piece - int num_blocks_per_piece = - static_cast(t->torrent_file().piece_length()) / t->block_size(); - ret["blocks per piece"] = num_blocks_per_piece; - - // if this torrent is a seed, we won't have a piece picker - // and there will be no half-finished pieces. - if (!t->is_seed()) - { - const piece_picker& p = t->picker(); - - const std::vector& q - = p.get_download_queue(); - - // unfinished pieces - ret["unfinished"] = entry::list_type(); - entry::list_type& up = ret["unfinished"].list(); - - // info for each unfinished piece - for (std::vector::const_iterator i - = q.begin(); i != q.end(); ++i) - { - if (i->finished == 0) continue; - - entry piece_struct(entry::dictionary_t); - - // the unfinished piece's index - piece_struct["piece"] = i->index; - - have_pieces[i->index] = true; - - std::string bitmask; - const int num_bitmask_bytes - = (std::max)(num_blocks_per_piece / 8, 1); - - for (int j = 0; j < num_bitmask_bytes; ++j) - { - unsigned char v = 0; - int bits = (std::min)(num_blocks_per_piece - j*8, 8); - for (int k = 0; k < bits; ++k) - v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) - ? (1 << k) : 0; - bitmask.insert(bitmask.end(), v); - TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); - } - piece_struct["bitmask"] = bitmask; -/* - TORRENT_ASSERT(t->filesystem().slot_for(i->index) >= 0); - unsigned long adler - = t->filesystem().piece_crc( - t->filesystem().slot_for(i->index) - , t->block_size() - , i->info); - - piece_struct["adler32"] = adler; -*/ - // push the struct onto the unfinished-piece list - up.push_back(piece_struct); - } - } - - std::vector piece_index; - t->filesystem().export_piece_map(piece_index, have_pieces); - entry::list_type& slots = ret["slots"].list(); - std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); - - // write local peers - - entry::list_type& peer_list = ret["peers"].list(); - entry::list_type& banned_peer_list = ret["banned_peers"].list(); - - policy& pol = t->get_policy(); - - int max_failcount = t->settings().max_failcount; - - for (policy::iterator i = pol.begin_peer() - , end(pol.end_peer()); i != end; ++i) - { - asio::error_code ec; - if (i->second.banned) - { - tcp::endpoint ip = i->second.ip; - entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(ec); - if (ec) continue; - peer["port"] = ip.port(); - banned_peer_list.push_back(peer); - continue; - } - // we cannot save remote connection - // since we don't know their listen port - // unless they gave us their listen port - // through the extension handshake - // so, if the peer is not connectable (i.e. we - // don't know its listen port) or if it has - // been banned, don't save it. - if (i->second.type == policy::peer::not_connectable) continue; - - // don't save peers that doesn't work - if (i->second.failcount >= max_failcount) continue; - - tcp::endpoint ip = i->second.ip; - entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(ec); - if (ec) continue; - peer["port"] = ip.port(); - peer_list.push_back(peer); - } - + TORRENT_FORWARD(write_resume_data(ret)); t->filesystem().write_resume_data(ret); return ret; @@ -654,35 +455,15 @@ namespace libtorrent { INVARIANT_CHECK; - if (m_ses == 0) + boost::shared_ptr t = m_torrent.lock(); + if (!t) #ifdef BOOST_NO_EXCEPTIONS return; #else throw_invalid_handle(); #endif - TORRENT_ASSERT(m_chk); - - session_impl::mutex_t::scoped_lock l(m_ses->m_mutex); - boost::shared_ptr t = m_ses->find_torrent(m_info_hash).lock(); + session_impl::mutex_t::scoped_lock l(t->session().m_mutex); - if (!t) - { - // the torrent is being checked. Add the peer to its - // peer list. The entries in there will be connected - // once the checking is complete. - mutex::scoped_lock l2(m_chk->m_mutex); - - aux::piece_checker_data* d = m_chk->find_torrent(m_info_hash); - if (d == 0) -#ifdef BOOST_NO_EXCEPTIONS - return; -#else - throw_invalid_handle(); -#endif - d->peers.push_back(adr); - return; - } - peer_id id; std::fill(id.begin(), id.end(), 0); t->get_policy().peer_from_tracker(adr, id, source, 0); @@ -731,6 +512,12 @@ namespace libtorrent } #endif + void torrent_handle::get_full_peer_list(std::vector& v) const + { + INVARIANT_CHECK; + TORRENT_FORWARD(get_full_peer_list(v)); + } + void torrent_handle::get_peer_info(std::vector& v) const { INVARIANT_CHECK; diff --git a/libtorrent/src/torrent_info.cpp b/libtorrent/src/torrent_info.cpp index cc199c069..f1d3e50b2 100755 --- a/libtorrent/src/torrent_info.cpp +++ b/libtorrent/src/torrent_info.cpp @@ -58,7 +58,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/hasher.hpp" #include "libtorrent/entry.hpp" -namespace pt = boost::posix_time; namespace gr = boost::gregorian; using namespace libtorrent; @@ -160,10 +159,13 @@ namespace } } - void extract_single_file(const entry& dict, file_entry& target + bool extract_single_file(entry const& dict, file_entry& target , std::string const& root_dir) { - target.size = dict["length"].integer(); + entry const* length = dict.find_key("length"); + if (length == 0 || length->type() != entry::int_t) + return false; + target.size = length->integer(); target.path = root_dir; target.file_base = 0; @@ -172,38 +174,46 @@ namespace // likely to be correctly encoded const entry::list_type* list = 0; - if (entry const* p = dict.find_key("path.utf-8")) + entry const* p8 = dict.find_key("path.utf-8"); + if (p8 != 0 && p8->type() == entry::list_t) { - list = &p->list(); + list = &p8->list(); } else { - list = &dict["path"].list(); + entry const* p = dict.find_key("path"); + if (p == 0 || p->type() != entry::list_t) + return false; + list = &p->list(); } for (entry::list_type::const_iterator i = list->begin(); i != list->end(); ++i) { + if (i->type() != entry::string_t) + return false; if (i->string() != "..") target.path /= i->string(); } verify_encoding(target); - if (target.path.is_complete()) throw std::runtime_error("torrent contains " - "a file with an absolute path: '" - + target.path.native_file_string() + "'"); + if (target.path.is_complete()) + return false; + return true; } - void extract_files(const entry::list_type& list, std::vector& target + bool extract_files(const entry::list_type& list, std::vector& target , std::string const& root_dir) { size_type offset = 0; for (entry::list_type::const_iterator i = list.begin(); i != list.end(); ++i) { target.push_back(file_entry()); - extract_single_file(*i, target.back(), root_dir); + if (!extract_single_file(*i, target.back(), root_dir)) + return false; target.back().offset = offset; offset += target.back().size; } + return true; } /* void remove_dir(fs::path& p) @@ -231,14 +241,13 @@ namespace libtorrent , m_half_metadata(false) #endif { - try - { - read_torrent_info(torrent_file); - } - catch(type_error&) - { + std::string error; +#ifndef BOOST_NO_EXCEPTIONS + if (!read_torrent_info(torrent_file, error)) throw invalid_torrent_file(); - } +#else + read_torrent_info(torrent_file, error); +#endif } // constructor used for creating new torrents @@ -330,8 +339,14 @@ namespace libtorrent } } - void torrent_info::parse_info_section(entry const& info) + bool torrent_info::parse_info_section(entry const& info, std::string& error) { + if (info.type() != entry::dictionary_t) + { + error = "'info' entry is not a dictionary"; + return false; + } + // encode the info-field in order to calculate it's sha1-hash std::vector buf; bencode(std::back_inserter(buf), info); @@ -340,49 +355,82 @@ namespace libtorrent m_info_hash = h.final(); // extract piece length - m_piece_length = int(info["piece length"].integer()); - if (m_piece_length <= 0) throw std::runtime_error("invalid torrent. piece length <= 0"); + entry const* piece_length = info.find_key("piece length"); + if (piece_length == 0 || piece_length->type() != entry::int_t) + { + error = "invalid or missing 'piece length' entry in torrent file"; + return false; + } + m_piece_length = int(piece_length->integer()); + if (m_piece_length <= 0) + { + error = "invalid torrent. piece length <= 0"; + return false; + } // extract file name (or the directory name if it's a multifile libtorrent) - if (entry const* e = info.find_key("name.utf-8")) + entry const* e = info.find_key("name.utf-8"); + if (e && e->type() == entry::string_t) { m_name = e->string(); } else - { m_name = info["name"].string(); } + { + entry const* e = info.find_key("name"); + if (e == 0 || e->type() != entry::string_t) + { + error = "invalid name in torrent file"; + return false; + } + m_name = e->string(); + } fs::path tmp = m_name; - if (tmp.is_complete()) - { - m_name = tmp.leaf(); - } - else if (tmp.has_branch_path()) - { - fs::path p; - for (fs::path::iterator i = tmp.begin() - , end(tmp.end()); i != end; ++i) - { - if (*i == "." || *i == "..") continue; - p /= *i; - } - m_name = p.string(); - } - if (m_name == ".." || m_name == ".") - throw std::runtime_error("invalid 'name' of torrent (possible exploit attempt)"); + if (tmp.is_complete()) + { + m_name = tmp.leaf(); + } + else if (tmp.has_branch_path()) + { + fs::path p; + for (fs::path::iterator i = tmp.begin() + , end(tmp.end()); i != end; ++i) + { + if (*i == "." || *i == "..") continue; + p /= *i; + } + m_name = p.string(); + } + if (m_name == ".." || m_name == ".") + { + + error = "invalid 'name' of torrent (possible exploit attempt)"; + return false; + } // extract file list entry const* i = info.find_key("files"); if (i == 0) { + entry const* length = info.find_key("length"); + if (length == 0 || length->type() != entry::int_t) + { + error = "invalid length of torrent"; + return false; + } // if there's no list of files, there has to be a length // field. file_entry e; e.path = m_name; e.offset = 0; - e.size = info["length"].integer(); + e.size = length->integer(); m_files.push_back(e); } else { - extract_files(i->list(), m_files, m_name); + if (!extract_files(i->list(), m_files, m_name)) + { + error = "failed to parse files from torrent file"; + return false; + } m_multifile = true; } @@ -395,12 +443,22 @@ namespace libtorrent // we want this division to round upwards, that's why we have the // extra addition + entry const* pieces_ent = info.find_key("pieces"); + if (pieces_ent == 0 || pieces_ent->type() != entry::string_t) + { + error = "invalid or missing 'pieces' entry in torrent file"; + return false; + } + m_num_pieces = static_cast((m_total_size + m_piece_length - 1) / m_piece_length); m_piece_hash.resize(m_num_pieces); - const std::string& hash_string = info["pieces"].string(); + const std::string& hash_string = pieces_ent->string(); if ((int)hash_string.length() != m_num_pieces * 20) - throw invalid_torrent_file(); + { + error = "incorrect number of piece hashes in torrent file"; + return false; + } for (int i = 0; i < m_num_pieces; ++i) std::copy( @@ -436,34 +494,37 @@ namespace libtorrent TORRENT_ASSERT(hasher(&info_section_buf[0], info_section_buf.size()).final() == m_info_hash); #endif + return true; } // extracts information from a libtorrent file and fills in the structures in // the torrent object - void torrent_info::read_torrent_info(const entry& torrent_file) + bool torrent_info::read_torrent_info(const entry& torrent_file, std::string& error) { + if (torrent_file.type() != entry::dictionary_t) + { + error = "torrent file is not a dictionary"; + return false; + } + // extract the url of the tracker - if (entry const* i = torrent_file.find_key("announce-list")) + entry const* i = torrent_file.find_key("announce-list"); + if (i && i->type() == entry::list_t) { const entry::list_type& l = i->list(); for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j) { + if (j->type() != entry::list_t) break; const entry::list_type& ll = j->list(); for (entry::list_type::const_iterator k = ll.begin(); k != ll.end(); ++k) { + if (k->type() != entry::string_t) break; announce_entry e(k->string()); e.tier = (int)std::distance(l.begin(), j); m_urls.push_back(e); } } - if (m_urls.size() == 0) - { - // the announce-list is empty - // fall back to look for announce - m_urls.push_back(announce_entry( - torrent_file["announce"].string())); - } // shuffle each tier std::vector::iterator start = m_urls.begin(); std::vector::iterator stop; @@ -479,14 +540,17 @@ namespace libtorrent } std::random_shuffle(start, stop); } - else if (entry const* i = torrent_file.find_key("announce")) + + entry const* announce = torrent_file.find_key("announce"); + if (m_urls.empty() && announce && announce->type() == entry::string_t) { - m_urls.push_back(announce_entry(i->string())); + m_urls.push_back(announce_entry(announce->string())); } - if (entry const* i = torrent_file.find_key("nodes")) + entry const* nodes = torrent_file.find_key("nodes"); + if (nodes && nodes->type() == entry::list_t) { - entry::list_type const& list = i->list(); + entry::list_type const& list = nodes->list(); for (entry::list_type::const_iterator i(list.begin()) , end(list.end()); i != end; ++i) { @@ -494,41 +558,40 @@ namespace libtorrent entry::list_type const& l = i->list(); entry::list_type::const_iterator iter = l.begin(); if (l.size() < 1) continue; + if (iter->type() != entry::string_t) continue; std::string const& hostname = iter->string(); ++iter; int port = 6881; + if (iter->type() != entry::int_t) continue; if (l.end() != iter) port = int(iter->integer()); m_nodes.push_back(std::make_pair(hostname, port)); } } // extract creation date - try + entry const* creation_date = torrent_file.find_key("creation date"); + if (creation_date && creation_date->type() == entry::int_t) { m_creation_date = pt::ptime(gr::date(1970, gr::Jan, 1)) - + pt::seconds(long(torrent_file["creation date"].integer())); + + pt::seconds(long(creation_date->integer())); } - catch (type_error) {} // if there are any url-seeds, extract them - try + entry const* url_seeds = torrent_file.find_key("url-list"); + if (url_seeds && url_seeds->type() == entry::string_t) { - 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 && 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(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()); - } + if (i->type() != entry::string_t) continue; + m_url_seeds.push_back(i->string()); } } - catch (type_error&) {} // extract comment if (entry const* e = torrent_file.find_key("comment.utf-8")) @@ -541,7 +604,13 @@ namespace libtorrent else if (entry const* e = torrent_file.find_key("created by")) { m_created_by = e->string(); } - parse_info_section(torrent_file["info"]); + entry const* info = torrent_file.find_key("info"); + if (info == 0 || info->type() != entry::dictionary_t) + { + error = "missing or invalid 'info' section in torrent file"; + return false; + } + return parse_info_section(*info, error); } boost::optional @@ -822,8 +891,8 @@ namespace libtorrent TORRENT_ASSERT(index >= 0 && index < num_pieces()); if (index == num_pieces()-1) { - size_type size = total_size() - - (num_pieces() - 1) * piece_length(); + int size = int(total_size() + - (num_pieces() - 1) * piece_length()); TORRENT_ASSERT(size > 0); TORRENT_ASSERT(size <= piece_length()); return int(size); @@ -934,3 +1003,4 @@ namespace libtorrent } } + diff --git a/libtorrent/src/tracker_manager.cpp b/libtorrent/src/tracker_manager.cpp index eb1f4cfd3..9dda723bb 100755 --- a/libtorrent/src/tracker_manager.cpp +++ b/libtorrent/src/tracker_manager.cpp @@ -38,8 +38,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include "zlib.h" - #include #include "libtorrent/tracker_manager.hpp" @@ -63,237 +61,14 @@ namespace http_buffer_size = 2048 }; - - enum - { - FTEXT = 0x01, - FHCRC = 0x02, - FEXTRA = 0x04, - FNAME = 0x08, - FCOMMENT = 0x10, - FRESERVED = 0xe0, - - GZIP_MAGIC0 = 0x1f, - GZIP_MAGIC1 = 0x8b - }; - } namespace libtorrent { - // returns -1 if gzip header is invalid or the header size in bytes - int gzip_header(const char* buf, int size) - { - TORRENT_ASSERT(buf != 0); - TORRENT_ASSERT(size > 0); - - const unsigned char* buffer = reinterpret_cast(buf); - const int total_size = size; - - // The zip header cannot be shorter than 10 bytes - if (size < 10) return -1; - - // check the magic header of gzip - if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; - - int method = buffer[2]; - int flags = buffer[3]; - - // check for reserved flag and make sure it's compressed with the correct metod - if (method != Z_DEFLATED || (flags & FRESERVED) != 0) return -1; - - // skip time, xflags, OS code - size -= 10; - buffer += 10; - - if (flags & FEXTRA) - { - int extra_len; - - if (size < 2) return -1; - - extra_len = (buffer[1] << 8) | buffer[0]; - - if (size < (extra_len+2)) return -1; - size -= (extra_len + 2); - buffer += (extra_len + 2); - } - - if (flags & FNAME) - { - while (size && *buffer) - { - --size; - ++buffer; - } - if (!size || *buffer) return -1; - - --size; - ++buffer; - } - - if (flags & FCOMMENT) - { - while (size && *buffer) - { - --size; - ++buffer; - } - if (!size || *buffer) return -1; - - --size; - ++buffer; - } - - if (flags & FHCRC) - { - if (size < 2) return -1; - - size -= 2; - buffer += 2; - } - - return total_size - size; - } - - bool inflate_gzip( - std::vector& buffer - , tracker_request const& req - , request_callback* requester - , int maximum_tracker_response_length) - { - TORRENT_ASSERT(maximum_tracker_response_length > 0); - - int header_len = gzip_header(&buffer[0], (int)buffer.size()); - if (header_len < 0) - { - requester->tracker_request_error(req, 200, "invalid gzip header in tracker response"); - return true; - } - - // start off wth one kilobyte and grow - // if needed - std::vector inflate_buffer(1024); - - // initialize the zlib-stream - z_stream str; - - // subtract 8 from the end of the buffer since that's CRC32 and input size - // and those belong to the gzip file - str.avail_in = (int)buffer.size() - header_len - 8; - str.next_in = reinterpret_cast(&buffer[header_len]); - str.next_out = reinterpret_cast(&inflate_buffer[0]); - str.avail_out = (int)inflate_buffer.size(); - str.zalloc = Z_NULL; - str.zfree = Z_NULL; - str.opaque = 0; - // -15 is really important. It will make inflate() not look for a zlib header - // and just deflate the buffer - if (inflateInit2(&str, -15) != Z_OK) - { - requester->tracker_request_error(req, 200, "gzip out of memory"); - return true; - } - - // inflate and grow inflate_buffer as needed - int ret = inflate(&str, Z_SYNC_FLUSH); - while (ret == Z_OK) - { - if (str.avail_out == 0) - { - if (inflate_buffer.size() >= (unsigned)maximum_tracker_response_length) - { - inflateEnd(&str); - requester->tracker_request_error(req, 200 - , "tracker response too large"); - return true; - } - int new_size = (int)inflate_buffer.size() * 2; - if (new_size > maximum_tracker_response_length) new_size = maximum_tracker_response_length; - int old_size = (int)inflate_buffer.size(); - - inflate_buffer.resize(new_size); - str.next_out = reinterpret_cast(&inflate_buffer[old_size]); - str.avail_out = new_size - old_size; - } - - ret = inflate(&str, Z_SYNC_FLUSH); - } - - inflate_buffer.resize(inflate_buffer.size() - str.avail_out); - inflateEnd(&str); - - if (ret != Z_STREAM_END) - { - requester->tracker_request_error(req, 200, "gzip error"); - return true; - } - - // commit the resulting buffer - std::swap(buffer, inflate_buffer); - return false; - } - - std::string base64encode(const std::string& s) - { - static const char base64_table[] = - { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' - }; - - unsigned char inbuf[3]; - unsigned char outbuf[4]; - - std::string ret; - for (std::string::const_iterator i = s.begin(); i != s.end();) - { - // available input is 1,2 or 3 bytes - // since we read 3 bytes at a time at most - int available_input = (std::min)(3, (int)std::distance(i, s.end())); - - // clear input buffer - std::fill(inbuf, inbuf+3, 0); - - // read a chunk of input into inbuf - for (int j = 0; j < available_input; ++j) - { - inbuf[j] = *i; - ++i; - } - - // encode inbuf to outbuf - outbuf[0] = (inbuf[0] & 0xfc) >> 2; - outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf [1] & 0xf0) >> 4); - outbuf[2] = ((inbuf[1] & 0x0f) << 2) | ((inbuf [2] & 0xc0) >> 6); - outbuf[3] = inbuf[2] & 0x3f; - - // write output - for (int j = 0; j < available_input+1; ++j) - { - ret += base64_table[outbuf[j]]; - } - - // write pad - for (int j = 0; j < 3 - available_input; ++j) - { - ret += '='; - } - } - return ret; - } - - timeout_handler::timeout_handler(asio::strand& str) - : m_strand(str) - , m_start_time(time_now()) + timeout_handler::timeout_handler(io_service& ios) + : m_start_time(time_now()) , m_read_time(time_now()) - , m_timeout(str.io_service()) + , m_timeout(ios) , m_completion_timeout(0) , m_read_timeout(0) , m_abort(false) @@ -309,9 +84,10 @@ namespace libtorrent int timeout = (std::min)( m_read_timeout, (std::min)(m_completion_timeout, m_read_timeout)); - m_timeout.expires_at(m_read_time + seconds(timeout)); - m_timeout.async_wait(m_strand.wrap(bind( - &timeout_handler::timeout_callback, self(), _1))); + asio::error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait(bind( + &timeout_handler::timeout_callback, self(), _1)); } void timeout_handler::restart_read_timeout() @@ -323,10 +99,11 @@ namespace libtorrent { m_abort = true; m_completion_timeout = 0; - m_timeout.cancel(); + asio::error_code ec; + m_timeout.cancel(ec); } - void timeout_handler::timeout_callback(asio::error_code const& error) try + void timeout_handler::timeout_callback(asio::error_code const& error) { if (error) return; if (m_completion_timeout == 0) return; @@ -348,22 +125,19 @@ namespace libtorrent int timeout = (std::min)( m_read_timeout, (std::min)(m_completion_timeout, m_read_timeout)); - m_timeout.expires_at(m_read_time + seconds(timeout)); - m_timeout.async_wait(m_strand.wrap( - bind(&timeout_handler::timeout_callback, self(), _1))); - } - catch (std::exception&) - { - TORRENT_ASSERT(false); + asio::error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait( + bind(&timeout_handler::timeout_callback, self(), _1)); } tracker_connection::tracker_connection( tracker_manager& man , tracker_request const& req - , asio::strand& str + , io_service& ios , address bind_interface_ , boost::weak_ptr r) - : timeout_handler(str) + : timeout_handler(ios) , m_requester(r) , m_bind_interface(bind_interface_) , m_man(man) @@ -412,9 +186,13 @@ namespace libtorrent { std::string hostname; // hostname only std::string auth; // user:pass - std::string protocol; // should be http + std::string protocol; // http or https for instance int port = 80; + std::string::iterator at; + std::string::iterator colon; + std::string::iterator port_pos; + // PARSE URL std::string::iterator start = url.begin(); // remove white spaces in front of the url @@ -424,18 +202,20 @@ namespace libtorrent = std::find(url.begin(), url.end(), ':'); protocol.assign(start, end); - if (end == url.end()) throw std::runtime_error("invalid url"); + if (protocol == "https") port = 443; + + if (end == url.end()) goto exit; ++end; - if (end == url.end()) throw std::runtime_error("invalid url"); - if (*end != '/') throw std::runtime_error("invalid url"); + if (end == url.end()) goto exit; + if (*end != '/') goto exit; ++end; - if (end == url.end()) throw std::runtime_error("invalid url"); - if (*end != '/') throw std::runtime_error("invalid url"); + if (end == url.end()) goto exit; + if (*end != '/') goto exit; ++end; start = end; - std::string::iterator at = std::find(start, url.end(), '@'); - std::string::iterator colon = std::find(start, url.end(), ':'); + at = std::find(start, url.end(), '@'); + colon = std::find(start, url.end(), ':'); end = std::find(start, url.end(), '/'); if (at != url.end() @@ -448,13 +228,11 @@ namespace libtorrent ++start; } - std::string::iterator port_pos; - // this is for IPv6 addresses if (start != url.end() && *start == '[') { port_pos = std::find(start, url.end(), ']'); - if (port_pos == url.end()) throw std::runtime_error("invalid hostname syntax"); + if (port_pos == url.end()) goto exit; port_pos = std::find(port_pos, url.end(), ':'); } else @@ -466,15 +244,7 @@ namespace libtorrent { 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 = atoi(std::string(port_pos, end).c_str()); } else { @@ -482,12 +252,13 @@ namespace libtorrent } start = end; +exit: return make_tuple(protocol, auth, hostname, port , std::string(start, url.end())); } void tracker_manager::queue_request( - asio::strand& str + io_service& ios , connection_queue& cc , tracker_request req , std::string const& auth @@ -503,63 +274,38 @@ namespace libtorrent if (m_abort && req.event != tracker_request::stopped) return; - try + std::string protocol = req.url.substr(0, req.url.find(':')); + + boost::intrusive_ptr con; + +#ifdef TORRENT_USE_OPENSSL + if (protocol == "http" || protocol == "https") +#else + if (protocol == "http") +#endif { - std::string protocol; - std::string hostname; - int port; - std::string request_string; - - using boost::tuples::ignore; - // TODO: should auth be used here? - boost::tie(protocol, ignore, hostname, port, request_string) - = parse_url_components(req.url); - - boost::intrusive_ptr con; - - if (protocol == "http") - { - con = new http_tracker_connection( - str - , cc - , *this - , req - , hostname - , port - , request_string - , bind_infc - , c - , m_settings - , m_proxy - , auth); - } - else if (protocol == "udp") - { - con = new udp_tracker_connection( - str - , *this - , req - , hostname - , port - , bind_infc - , c - , m_settings); - } - else - { - throw std::runtime_error("unkown protocol in tracker url"); - } - - m_connections.push_back(con); - - boost::shared_ptr cb = con->requester(); - if (cb) cb->m_manager = this; + con = new http_tracker_connection( + ios, cc, *this, req, bind_infc, c + , m_settings, m_proxy, auth); } - catch (std::exception& e) + else if (protocol == "udp") + { + con = new udp_tracker_connection( + ios, cc, *this, req, bind_infc + , c, m_settings, m_proxy); + } + else { if (boost::shared_ptr r = c.lock()) - r->tracker_request_error(req, -1, e.what()); + r->tracker_request_error(req, -1, "unknown protocol in tracker url: " + + req.url); + return; } + + m_connections.push_back(con); + + boost::shared_ptr cb = con->requester(); + if (cb) cb->m_manager = this; } void tracker_manager::abort_all_requests() @@ -589,6 +335,11 @@ namespace libtorrent } // close will remove the entry from m_connections // so no need to pop + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + boost::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: " + req.url); +#endif c->close(); } diff --git a/libtorrent/src/udp_socket.cpp b/libtorrent/src/udp_socket.cpp new file mode 100644 index 000000000..6f64997a9 --- /dev/null +++ b/libtorrent/src/udp_socket.cpp @@ -0,0 +1,435 @@ +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/connection_queue.hpp" +#include +#include +#include +#include +#include + +using namespace libtorrent; + +udp_socket::udp_socket(asio::io_service& ios, udp_socket::callback_t const& c + , connection_queue& cc) + : m_callback(c) + , m_ipv4_sock(ios) + , m_ipv6_sock(ios) + , m_bind_port(0) + , m_socks5_sock(ios) + , m_connection_ticket(-1) + , m_cc(cc) + , m_resolver(ios) + , m_tunnel_packets(false) +{ +} + +void udp_socket::send(udp::endpoint const& ep, char const* p, int len, asio::error_code& ec) +{ + if (m_tunnel_packets) + { + // send udp packets through SOCKS5 server + wrap(ep, p, len, ec); + return; + } + + if (ep.address().is_v4() && m_ipv4_sock.is_open()) + m_ipv4_sock.send_to(asio::buffer(p, len), ep, 0, ec); + else + m_ipv6_sock.send_to(asio::buffer(p, len), ep, 0, ec); +} + +void udp_socket::on_read(udp::socket* s, asio::error_code const& e, std::size_t bytes_transferred) +{ + if (!m_callback) return; + + if (e) + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + if (s == &m_ipv4_sock) + m_callback(e, m_v4_ep, 0, 0); + else + m_callback(e, m_v6_ep, 0, 0); +#ifndef BOOST_NO_EXCEPTIONS + } catch(std::exception&) {} +#endif + + // don't stop listening on recoverable errors + if (e != asio::error::host_unreachable + && e != asio::error::fault + && e != asio::error::connection_reset + && e != asio::error::connection_refused + && e != asio::error::connection_aborted + && e != asio::error::message_size) + return; + + if (s == &m_ipv4_sock) + s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf)) + , m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2)); + else + s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf)) + , m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2)); + + return; + } + + if (s == &m_ipv4_sock) + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + + if (m_tunnel_packets && m_v4_ep == m_proxy_addr) + unwrap(e, m_v4_buf, bytes_transferred); + else + m_callback(e, m_v4_ep, m_v4_buf, bytes_transferred); + +#ifndef BOOST_NO_EXCEPTIONS + } catch(std::exception&) {} +#endif + s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf)) + , m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2)); + } + else + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + + if (m_tunnel_packets && m_v6_ep == m_proxy_addr) + unwrap(e, m_v6_buf, bytes_transferred); + else + m_callback(e, m_v6_ep, m_v6_buf, bytes_transferred); + +#ifndef BOOST_NO_EXCEPTIONS + } catch(std::exception&) {} +#endif + s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf)) + , m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2)); + } +} + +void udp_socket::wrap(udp::endpoint const& ep, char const* p, int len, asio::error_code& ec) +{ + using namespace libtorrent::detail; + + char header[20]; + char* h = header; + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(ep.address().is_v4()?1:4, h); // atyp + write_address(ep.address(), h); + write_uint16(ep.port(), h); + + boost::array iovec; + iovec[0] = asio::const_buffer(header, h - header); + iovec[1] = asio::const_buffer(p, len); + + if (m_proxy_addr.address().is_v4() && m_ipv4_sock.is_open()) + m_ipv4_sock.send_to(iovec, m_proxy_addr, 0, ec); + else + m_ipv6_sock.send_to(iovec, m_proxy_addr, 0, ec); +} + +// unwrap the UDP packet from the SOCKS5 header +void udp_socket::unwrap(asio::error_code const& e, char const* buf, int size) +{ + using namespace libtorrent::detail; + + // the minimum socks5 header size + if (size <= 10) return; + + char const* p = buf; + p += 2; // reserved + int frag = read_uint8(p); + // fragmentation is not supported + if (frag != 0) return; + + udp::endpoint sender; + + int atyp = read_uint8(p); + if (atyp == 1) + { + // IPv4 + sender = read_v4_endpoint(p); + } + else if (atyp == 4) + { + // IPv6 + sender = read_v6_endpoint(p); + } + else + { + // domain name not supported + return; + } + + m_callback(e, sender, p, size - (p - buf)); +} + +void udp_socket::close() +{ + asio::error_code ec; + m_ipv4_sock.close(ec); + m_ipv6_sock.close(ec); + m_socks5_sock.close(ec); + m_callback.clear(); + if (m_connection_ticket >= 0) + { + m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + } +} + +void udp_socket::bind(udp::endpoint const& ep, asio::error_code& ec) +{ + if (m_ipv4_sock.is_open()) m_ipv4_sock.close(ec); + if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec); + + if (ep.address().is_v4()) + { + m_ipv4_sock.open(udp::v4(), ec); + if (ec) return; + m_ipv4_sock.bind(ep, ec); + if (ec) return; + m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf)) + , m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2)); + } + else + { + m_ipv6_sock.set_option(v6only(true), ec); + if (ec) return; + m_ipv6_sock.bind(ep, ec); + if (ec) return; + m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf)) + , m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2)); + } + m_bind_port = ep.port(); +} + +void udp_socket::bind(int port) +{ + asio::error_code ec; + + if (m_ipv4_sock.is_open()) m_ipv4_sock.close(ec); + if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec); + + m_ipv4_sock.open(udp::v4(), ec); + if (!ec) + { + m_ipv4_sock.bind(udp::endpoint(address_v4::any(), port), ec); + m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf)) + , m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2)); + } + m_ipv6_sock.open(udp::v6(), ec); + if (!ec) + { + m_ipv6_sock.set_option(v6only(true), ec); + m_ipv6_sock.bind(udp::endpoint(address_v6::any(), port), ec); + m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf)) + , m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2)); + } + m_bind_port = port; +} + +void udp_socket::set_proxy_settings(proxy_settings const& ps) +{ + asio::error_code ec; + m_socks5_sock.close(ec); + m_tunnel_packets = false; + + m_proxy_settings = ps; + + if (ps.type == proxy_settings::socks5 + || ps.type == proxy_settings::socks5_pw) + { + // connect to socks5 server and open up the UDP tunnel + tcp::resolver::query q(ps.hostname + , boost::lexical_cast(ps.port)); + m_resolver.async_resolve(q, boost::bind( + &udp_socket::on_name_lookup, this, _1, _2)); + } +} + +void udp_socket::on_name_lookup(asio::error_code const& e, tcp::resolver::iterator i) +{ + if (e) return; + m_proxy_addr.address(i->endpoint().address()); + m_proxy_addr.port(i->endpoint().port()); + m_cc.enqueue(boost::bind(&udp_socket::on_connect, this, _1) + , boost::bind(&udp_socket::on_timeout, this), seconds(10)); +} + +void udp_socket::on_timeout() +{ + asio::error_code ec; + m_socks5_sock.close(ec); + m_connection_ticket = -1; +} + +void udp_socket::on_connect(int ticket) +{ + m_connection_ticket = ticket; + asio::error_code ec; + m_socks5_sock.open(m_proxy_addr.address().is_v4()?tcp::v4():tcp::v6(), ec); + m_socks5_sock.async_connect(tcp::endpoint(m_proxy_addr.address(), m_proxy_addr.port()) + , boost::bind(&udp_socket::on_connected, this, _1)); +} + +void udp_socket::on_connected(asio::error_code const& e) +{ + m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + if (e) return; + + using namespace libtorrent::detail; + + // send SOCKS5 authentication methods + char* p = &m_tmp_buf[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_proxy_settings.username.empty() + || m_proxy_settings.type == proxy_settings::socks5) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::handshake1, this, _1)); +} + +void udp_socket::handshake1(asio::error_code const& e) +{ + if (e) return; + + asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 2) + , boost::bind(&udp_socket::handshake2, this, _1)); +} + +void udp_socket::handshake2(asio::error_code const& e) +{ + if (e) return; + + using namespace libtorrent::detail; + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < 5) return; + + if (method == 0) + { + socks_forward_udp(); + } + else if (method == 2) + { + if (m_proxy_settings.username.empty()) + { + asio::error_code ec; + m_socks5_sock.close(ec); + return; + } + + // start sub-negotiation + char* p = &m_tmp_buf[0]; + write_uint8(1, p); + write_uint8(m_proxy_settings.username.size(), p); + write_string(m_proxy_settings.username, p); + write_uint8(m_proxy_settings.password.size(), p); + write_string(m_proxy_settings.password, p); + asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::handshake3, this, _1)); + } + else + { + asio::error_code ec; + m_socks5_sock.close(ec); + return; + } +} + +void udp_socket::handshake3(asio::error_code const& e) +{ + if (e) return; + + asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 2) + , boost::bind(&udp_socket::handshake4, this, _1)); +} + +void udp_socket::handshake4(asio::error_code const& e) +{ + if (e) return; + + using namespace libtorrent::detail; + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) return; + if (status != 0) return; + + socks_forward_udp(); +} + +void udp_socket::socks_forward_udp() +{ + using namespace libtorrent::detail; + + // send SOCKS5 UDP command + char* p = &m_tmp_buf[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(3, p); // UDP ASSOCIATE command + write_uint8(0, p); // reserved + write_uint8(0, p); // ATYP IPv4 + write_uint32(0, p); // IP any + write_uint16(m_bind_port, p); + + asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::connect1, this, _1)); +} + +void udp_socket::connect1(asio::error_code const& e) +{ + if (e) return; + + asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 10) + , boost::bind(&udp_socket::connect2, this, _1)); +} + +void udp_socket::connect2(asio::error_code const& e) +{ + if (e) return; + + using namespace libtorrent::detail; + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); // VERSION + int status = read_uint8(p); // STATUS + read_uint8(p); // RESERVED + int atyp = read_uint8(p); // address type + + if (version != 5) return; + if (status != 0) return; + + if (atyp == 1) + { + m_proxy_addr.address(address_v4(read_uint32(p))); + m_proxy_addr.port(read_uint16(p)); + } + else + { + // in this case we need to read more data from the socket + TORRENT_ASSERT(false && "not implemented yet!"); + } + + m_tunnel_packets = true; +} + diff --git a/libtorrent/src/udp_tracker_connection.cpp b/libtorrent/src/udp_tracker_connection.cpp index 0ad38ef86..7347dbe4b 100755 --- a/libtorrent/src/udp_tracker_connection.cpp +++ b/libtorrent/src/udp_tracker_connection.cpp @@ -62,8 +62,7 @@ namespace udp_connection_retries = 4, udp_announce_retries = 15, udp_connect_timeout = 15, - udp_announce_timeout = 10, - udp_buffer_size = 2048 + udp_announce_timeout = 10 }; } @@ -74,36 +73,48 @@ namespace libtorrent { udp_tracker_connection::udp_tracker_connection( - asio::strand& str + io_service& ios + , connection_queue& cc , tracker_manager& man , tracker_request const& req - , std::string const& hostname - , unsigned short port , address bind_infc , boost::weak_ptr c - , session_settings const& stn) - : tracker_connection(man, req, str, bind_infc, c) + , session_settings const& stn + , proxy_settings const& proxy) + : tracker_connection(man, req, ios, bind_infc, c) , m_man(man) - , m_strand(str) - , m_name_lookup(m_strand.io_service()) - , m_socket(m_strand.io_service()) + , m_name_lookup(ios) + , m_socket(ios, boost::bind(&udp_tracker_connection::on_receive, this, _1, _2, _3, _4), cc) , m_transaction_id(0) , m_connection_id(0) , m_settings(stn) , m_attempts(0) + , m_state(action_error) { + m_socket.set_proxy_settings(proxy); + + std::string hostname; + int port; + + using boost::tuples::ignore; + boost::tie(ignore, ignore, hostname, port, ignore) = parse_url_components(req.url); + udp::resolver::query q(hostname, boost::lexical_cast(port)); m_name_lookup.async_resolve(q - , m_strand.wrap(boost::bind( - &udp_tracker_connection::name_lookup, self(), _1, _2))); + , boost::bind( + &udp_tracker_connection::name_lookup, self(), _1, _2)); set_timeout(req.event == tracker_request::stopped ? m_settings.stop_tracker_timeout : m_settings.tracker_completion_timeout , m_settings.tracker_receive_timeout); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log(("*** UDP_TRACKER [ initiating name lookup: " + hostname + " ]").c_str()); +#endif } void udp_tracker_connection::name_lookup(asio::error_code const& error - , udp::resolver::iterator i) try + , udp::resolver::iterator i) { if (error == asio::error::operation_aborted) return; if (error || i == udp::resolver::iterator()) @@ -113,8 +124,8 @@ namespace libtorrent } boost::shared_ptr cb = requester(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - if (cb) cb->debug_log("udp tracker name lookup successful"); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER [ name lookup successful ]"); #endif restart_read_timeout(); @@ -145,20 +156,23 @@ namespace libtorrent if (cb) cb->m_tracker_address = tcp::endpoint(target_address.address(), target_address.port()); m_target = target_address; - m_socket.open(target_address.protocol()); - m_socket.bind(udp::endpoint(bind_interface(), 0)); - m_socket.connect(target_address); + asio::error_code ec; + m_socket.bind(udp::endpoint(bind_interface(), 0), ec); + if (ec) + { + fail(-1, ec.message().c_str()); + return; + } send_udp_connect(); } - catch (std::exception& e) - { - fail(-1, e.what()); - }; void udp_tracker_connection::on_timeout() { - asio::error_code ec; - m_socket.close(ec); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** UDP_TRACKER [ timed out ]"); +#endif + m_socket.close(); m_name_lookup.cancel(); fail_timeout(); } @@ -166,14 +180,110 @@ namespace libtorrent void udp_tracker_connection::close() { asio::error_code ec; - m_socket.close(ec); + m_socket.close(); m_name_lookup.cancel(); tracker_connection::close(); } + void udp_tracker_connection::on_receive(asio::error_code const& e + , udp::endpoint const& ep, char const* buf, int size) + { + // ignore resposes before we've sent any requests + if (m_state == action_error) return; + + if (!m_socket.is_open()) return; // the operation was aborted + + // ignore packet not sent from the tracker + if (m_target != ep) return; + + if (e) fail(-1, e.message().c_str()); + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + boost::shared_ptr cb = requester(); + if (cb) + { + std::stringstream msg; + msg << "<== UDP_TRACKER_PACKET [ size: " << size << " ]"; + cb->debug_log(msg.str()); + } +#endif + + // ignore packets smaller than 8 bytes + if (size < 8) return; + + restart_read_timeout(); + + const char* ptr = buf; + int action = detail::read_int32(ptr); + int transaction = detail::read_int32(ptr); + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + if (cb) + { + std::stringstream msg; + msg << "*** UDP_TRACKER_PACKET [ acton: " << action << " ]"; + cb->debug_log(msg.str()); + } +#endif + + // ignore packets with incorrect transaction id + if (m_transaction_id != transaction) return; + + if (action == action_error) + { + fail(-1, std::string(ptr, size - 8).c_str()); + return; + } + + // ignore packets that's not a response to our message + if (action != m_state) return; + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + if (cb) + { + std::stringstream msg; + msg << "*** UDP_TRACKER_RESPONSE [ cid: " << m_connection_id << " ]"; + cb->debug_log(msg.str()); + } +#endif + + switch (m_state) + { + case action_connect: + on_connect_response(buf, size); + break; + case action_announce: + on_announce_response(buf, size); + break; + case action_scrape: + on_scrape_response(buf, size); + break; + default: break; + } + } + + void udp_tracker_connection::on_connect_response(char const* buf, int size) + { + // ignore packets smaller than 16 bytes + if (size < 16) return; + + restart_read_timeout(); + buf += 8; // skip header + + // reset transaction + m_transaction_id = 0; + m_attempts = 0; + m_connection_id = detail::read_int64(buf); + + if (tracker_req().kind == tracker_request::announce_request) + send_udp_announce(); + else if (tracker_req().kind == tracker_request::scrape_request) + send_udp_scrape(); + } + void udp_tracker_connection::send_udp_connect() { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING boost::shared_ptr cb = requester(); if (cb) { @@ -183,170 +293,27 @@ namespace libtorrent #endif if (!m_socket.is_open()) return; // the operation was aborted - char send_buf[16]; - char* ptr = send_buf; + char buf[16]; + char* ptr = 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); + detail::write_uint32(0x27101980, ptr); // connection_id + detail::write_int32(action_connect, ptr); // action (connect) + detail::write_int32(m_transaction_id, ptr); // transaction_id + TORRENT_ASSERT(ptr - buf == sizeof(buf)); - m_socket.send(asio::buffer((void*)send_buf, 16), 0); + asio::error_code ec; + m_socket.send(m_target, buf, 16, ec); + m_state = action_connect; ++m_attempts; - m_buffer.resize(udp_buffer_size); - m_socket.async_receive_from(asio::buffer(m_buffer), m_sender - , boost::bind(&udp_tracker_connection::connect_response, self(), _1, _2)); - } - - void udp_tracker_connection::connect_response(asio::error_code const& error - , std::size_t bytes_transferred) try - { - if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; // the operation was aborted - if (error) + if (ec) { - fail(-1, error.message().c_str()); + fail(-1, ec.message().c_str()); return; } - - if (m_target != m_sender) - { - // this packet was not received from the tracker - m_socket.async_receive_from(asio::buffer(m_buffer), 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) - boost::shared_ptr cb = requester(); - if (cb) - { - cb->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()); - } - - void udp_tracker_connection::send_udp_announce() - { - if (m_transaction_id == 0) - m_transaction_id = rand() ^ (rand() << 16); - - if (!m_socket.is_open()) return; // the operation was aborted - - 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(action_announce, out); - // transaction_id - detail::write_int32(m_transaction_id, out); - // info_hash - std::copy(req.info_hash.begin(), req.info_hash.end(), out); - // peer_id - std::copy(req.pid.begin(), req.pid.end(), out); - // downloaded - detail::write_int64(req.downloaded, out); - // left - detail::write_int64(req.left, out); - // uploaded - detail::write_int64(req.uploaded, out); - // event - detail::write_int32(req.event, out); - // ip address - if (m_settings.announce_ip != address() && m_settings.announce_ip.is_v4()) - detail::write_uint32(m_settings.announce_ip.to_v4().to_ulong(), out); - else - detail::write_int32(0, out); - // key - detail::write_int32(req.key, out); - // num_want - detail::write_int32(req.num_want, out); - // port - detail::write_uint16(req.listen_port, out); - // extensions - detail::write_uint16(0, out); - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - boost::shared_ptr cb = requester(); - if (cb) - { - cb->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), m_sender - , bind(&udp_tracker_connection::announce_response, self(), _1, _2)); } void udp_tracker_connection::send_udp_scrape() @@ -356,97 +323,48 @@ namespace libtorrent if (!m_socket.is_open()) return; // the operation was aborted - std::vector buf; - std::back_insert_iterator > out(buf); + char buf[8 + 4 + 4 + 20]; + char* out = buf; - // connection_id - detail::write_int64(m_connection_id, out); - // action (scrape) - detail::write_int32(action_scrape, out); - // transaction_id - detail::write_int32(m_transaction_id, out); + detail::write_int64(m_connection_id, out); // connection_id + detail::write_int32(action_scrape, out); // action (scrape) + detail::write_int32(m_transaction_id, out); // transaction_id // info_hash std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end(), out); + out += 20; + TORRENT_ASSERT(out - buf == sizeof(buf)); - m_socket.send(asio::buffer(&buf[0], buf.size()), 0); + asio::error_code ec; + m_socket.send(m_target, buf, sizeof(buf), ec); + m_state = action_scrape; ++m_attempts; - - m_socket.async_receive_from(asio::buffer(m_buffer), m_sender - , bind(&udp_tracker_connection::scrape_response, self(), _1, _2)); + if (ec) + { + fail(-1, ec.message().c_str()); + return; + } } - void udp_tracker_connection::announce_response(asio::error_code const& error - , std::size_t bytes_transferred) try + void udp_tracker_connection::on_announce_response(char const* buf, int size) { - if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; // the operation was aborted - if (error) - { - fail(-1, error.message().c_str()); - return; - } - - if (m_target != m_sender) - { - // this packet was not received from the tracker - m_socket.async_receive_from(asio::buffer(m_buffer), 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; - } + if (size < 20) 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) - { - fail(-1, "incorrect transaction id"); - return; - } - - if (action == action_error) - { - fail(-1, std::string(buf, bytes_transferred - 8).c_str()); - return; - } - - if (action != action_announce) - { - fail(-1, "invalid action in announce response"); - return; - } - - if (bytes_transferred < 20) - { - fail(-1, "got a message with size < 20"); - return; - } + buf += 8; // skip header + restart_read_timeout(); int interval = detail::read_int32(buf); int incomplete = detail::read_int32(buf); int complete = detail::read_int32(buf); - int num_peers = (bytes_transferred - 20) / 6; - if ((bytes_transferred - 20) % 6 != 0) + int num_peers = (size - 20) / 6; + if ((size - 20) % 6 != 0) { fail(-1, "invalid udp tracker response length"); return; } boost::shared_ptr cb = requester(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING if (cb) { cb->debug_log("<== UDP_TRACKER_ANNOUNCE_RESPONSE"); @@ -462,6 +380,7 @@ namespace libtorrent std::vector peer_list; for (int i = 0; i < num_peers; ++i) { + // TODO: don't use a string here peer_entry e; std::stringstream s; s << (int)detail::read_uint8(buf) << "."; @@ -475,50 +394,17 @@ namespace libtorrent } cb->tracker_response(tracker_req(), peer_list, interval - , complete, incomplete); + , complete, incomplete, address()); m_man.remove_request(this); close(); - return; } - catch (std::exception& e) + + void udp_tracker_connection::on_scrape_response(char const* buf, int size) { - fail(-1, e.what()); - }; // msvc 7.1 seems to require this - - void udp_tracker_connection::scrape_response(asio::error_code const& error - , std::size_t bytes_transferred) try - { - if (error == asio::error::operation_aborted) return; - if (!m_socket.is_open()) return; // the operation was aborted - if (error) - { - fail(-1, error.message().c_str()); - return; - } - - if (m_target != m_sender) - { - // this packet was not received from the tracker - m_socket.async_receive_from(asio::buffer(m_buffer), 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; - } + buf += 8; // skip header restart_read_timeout(); - char* buf = &m_buffer[0]; int action = detail::read_int32(buf); int transaction = detail::read_int32(buf); @@ -530,7 +416,7 @@ namespace libtorrent if (action == action_error) { - fail(-1, std::string(buf, bytes_transferred - 8).c_str()); + fail(-1, std::string(buf, size - 8).c_str()); return; } @@ -540,7 +426,7 @@ namespace libtorrent return; } - if (bytes_transferred < 20) + if (size < 20) { fail(-1, "got a message with size < 20"); return; @@ -553,7 +439,6 @@ namespace libtorrent boost::shared_ptr cb = requester(); if (!cb) { - m_man.remove_request(this); close(); return; } @@ -564,9 +449,60 @@ namespace libtorrent m_man.remove_request(this); close(); } - catch (std::exception& e) + + void udp_tracker_connection::send_udp_announce() { - fail(-1, e.what()); + if (m_transaction_id == 0) + m_transaction_id = rand() ^ (rand() << 16); + + if (!m_socket.is_open()) return; // the operation was aborted + + char buf[8 + 4 + 4 + 20 + 20 + 8 + 8 + 8 + 4 + 4 + 4 + 4 + 2 + 2]; + char* out = buf; + + tracker_request const& req = tracker_req(); + + detail::write_int64(m_connection_id, out); // connection_id + detail::write_int32(action_announce, out); // action (announce) + detail::write_int32(m_transaction_id, out); // transaction_id + std::copy(req.info_hash.begin(), req.info_hash.end(), out); // info_hash + out += 20; + std::copy(req.pid.begin(), req.pid.end(), out); // peer_id + out += 20; + detail::write_int64(req.downloaded, out); // downloaded + detail::write_int64(req.left, out); // left + detail::write_int64(req.uploaded, out); // uploaded + detail::write_int32(req.event, out); // event + // ip address + if (m_settings.announce_ip != address() && m_settings.announce_ip.is_v4()) + detail::write_uint32(m_settings.announce_ip.to_v4().to_ulong(), out); + else + detail::write_int32(0, out); + detail::write_int32(req.key, out); // key + detail::write_int32(req.num_want, out); // num_want + detail::write_uint16(req.listen_port, out); // port + detail::write_uint16(0, out); // extensions + + TORRENT_ASSERT(out - buf == sizeof(buf)); + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + boost::shared_ptr cb = requester(); + if (cb) + { + cb->debug_log("==> UDP_TRACKER_ANNOUNCE [" + + lexical_cast(req.info_hash) + "]"); + } +#endif + + asio::error_code ec; + m_socket.send(m_target, buf, sizeof(buf), ec); + m_state = action_announce; + ++m_attempts; + if (ec) + { + fail(-1, ec.message().c_str()); + return; + } } } diff --git a/libtorrent/src/upnp.cpp b/libtorrent/src/upnp.cpp index 24cb7769b..ab77a999f 100644 --- a/libtorrent/src/upnp.cpp +++ b/libtorrent/src/upnp.cpp @@ -63,15 +63,12 @@ namespace libtorrent upnp::upnp(io_service& ios, connection_queue& cc , address const& listen_interface, std::string const& user_agent , portmap_callback_t const& cb, bool ignore_nonrouters) - : m_udp_local_port(0) - , m_tcp_local_port(0) - , m_user_agent(user_agent) + : m_user_agent(user_agent) , m_callback(cb) , m_retry_count(0) , m_io_service(ios) - , m_strand(ios) , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) - , m_strand.wrap(bind(&upnp::on_reply, self(), _1, _2, _3)), false) + , bind(&upnp::on_reply, self(), _1, _2, _3), false) , m_broadcast_timer(ios) , m_refresh_timer(ios) , m_disabled(false) @@ -89,7 +86,14 @@ upnp::~upnp() { } -void upnp::discover_device() try +void upnp::discover_device() +{ + mutex_t::scoped_lock l(m_mutex); + + discover_device_impl(); +} + +void upnp::discover_device_impl() { const char msearch[] = "M-SEARCH * HTTP/1.1\r\n" @@ -113,73 +117,114 @@ void upnp::discover_device() try << " ==> Broadcast FAILED: " << ec.message() << std::endl << "aborting" << std::endl; #endif - disable(); + disable(ec.message().c_str()); return; } ++m_retry_count; - m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count)); - m_broadcast_timer.async_wait(m_strand.wrap(bind(&upnp::resend_request - , self(), _1))); + m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); + m_broadcast_timer.async_wait(bind(&upnp::resend_request + , self(), _1)); #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " ==> Broadcasting search for rootdevice" << std::endl; #endif } -catch (std::exception&) -{ - disable(); -}; -void upnp::set_mappings(int tcp, int udp) +// returns a reference to a mapping or -1 on failure +int upnp::add_mapping(upnp::protocol_type p, int external_port, int local_port) { + mutex_t::scoped_lock l(m_mutex); + #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " *** set mappings " << tcp << " " << udp; + << " *** add mapping [ proto: " << (p == tcp?"tcp":"udp") + << " ext_port: " << external_port + << " local_port :" << local_port << " ]"; if (m_disabled) m_log << " DISABLED"; m_log << std::endl; #endif + if (m_disabled) return -1; - if (m_disabled) return; - if (udp != 0) m_udp_local_port = udp; - if (tcp != 0) m_tcp_local_port = tcp; + std::vector::iterator i = std::find_if( + m_mappings.begin(), m_mappings.end() + , boost::bind(&global_mapping_t::protocol, _1) == int(none)); + + if (i == m_mappings.end()) + { + m_mappings.push_back(global_mapping_t()); + i = m_mappings.end() - 1; + } + + i->protocol = p; + i->external_port = external_port; + i->local_port = local_port; + + int mapping_index = i - m_mappings.begin(); for (std::set::iterator i = m_devices.begin() , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); TORRENT_ASSERT(d.magic == 1337); - if (d.mapping[0].local_port != m_tcp_local_port) - { - if (d.mapping[0].external_port == 0) - d.mapping[0].external_port = m_tcp_local_port; - d.mapping[0].local_port = m_tcp_local_port; - d.mapping[0].need_update = true; - } - if (d.mapping[1].local_port != m_udp_local_port) - { - if (d.mapping[1].external_port == 0) - d.mapping[1].external_port = m_udp_local_port; - d.mapping[1].local_port = m_udp_local_port; - d.mapping[1].need_update = true; - } - if (d.service_namespace - && (d.mapping[0].need_update || d.mapping[1].need_update)) - map_port(d, 0); + + if (int(d.mapping.size()) <= mapping_index) + d.mapping.resize(mapping_index + 1); + mapping_t& m = d.mapping[mapping_index]; + + m.action = mapping_t::action_add; + m.protocol = p; + m.external_port = external_port; + m.local_port = local_port; + + if (d.service_namespace) update_map(d, mapping_index); + } + + return mapping_index; +} + +void upnp::delete_mapping(int mapping) +{ + mutex_t::scoped_lock l(m_mutex); + + if (mapping <= int(m_mappings.size())) return; + + global_mapping_t& m = m_mappings[mapping]; + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " *** delete mapping [ proto: " << (m.protocol == tcp?"tcp":"udp") + << " ext_port:" << m.external_port + << " local_port:" << m.local_port << " ]"; + m_log << std::endl; +#endif + + if (m.protocol == none) return; + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + + TORRENT_ASSERT(mapping < int(d.mapping.size())); + d.mapping[mapping].action = mapping_t::action_delete; + + if (d.service_namespace) update_map(d, mapping); } } void upnp::resend_request(asio::error_code const& e) -#ifndef NDEBUG -try -#endif { if (e) return; + + mutex_t::scoped_lock l(m_mutex); + if (m_retry_count < 9 && (m_devices.empty() || m_retry_count < 4)) { - discover_device(); + discover_device_impl(); return; } @@ -190,7 +235,7 @@ try << " *** Got no response in 9 retries. Giving up, " "disabling UPnP." << std::endl; #endif - disable(); + disable("no UPnP router found"); return; } @@ -210,9 +255,9 @@ try << " ==> connecting to " << d.url << std::endl; #endif d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 - , boost::ref(d))))); - d.upnp_connection->get(d.url); + , m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2 + , boost::ref(d)))); + d.upnp_connection->get(d.url, seconds(30), 1); } catch (std::exception& e) { @@ -227,19 +272,12 @@ try } } } -#ifndef NDEBUG -catch (std::exception&) -{ - TORRENT_ASSERT(false); -}; -#endif void upnp::on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) -#ifndef NDEBUG -try -#endif { + mutex_t::scoped_lock l(m_mutex); + using namespace libtorrent::detail; // parse out the url for the device @@ -296,17 +334,14 @@ try } http_parser p; - try + bool error = false; + p.incoming(buffer::const_interval(buffer + , buffer + bytes_transferred), error); + if (error) { - p.incoming(buffer::const_interval(buffer - , buffer + bytes_transferred)); - } - catch (std::exception& e) - { - (void)e; #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== (" << from << ") Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl; + m_log << time_now_string() << " <== (" << from << ") Rootdevice " + "responded with incorrect HTTP packet. Ignoring device" << std::endl; #endif return; } @@ -399,25 +434,16 @@ try return; } - if (m_tcp_local_port != 0) + TORRENT_ASSERT(d.mapping.empty()); + for (std::vector::iterator j = m_mappings.begin() + , end(m_mappings.end()); j != end; ++j) { - d.mapping[0].need_update = true; - d.mapping[0].local_port = m_tcp_local_port; - if (d.mapping[0].external_port == 0) - d.mapping[0].external_port = d.mapping[0].local_port; -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl; -#endif - } - if (m_udp_local_port != 0) - { - d.mapping[1].need_update = true; - d.mapping[1].local_port = m_udp_local_port; - if (d.mapping[1].external_port == 0) - d.mapping[1].external_port = d.mapping[1].local_port; -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl; -#endif + mapping_t m; + m.action = mapping_t::action_add; + m.local_port = j->local_port; + m.external_port = j->external_port; + m.protocol = j->protocol; + d.mapping.push_back(m); } boost::tie(i, boost::tuples::ignore) = m_devices.insert(d); } @@ -427,7 +453,8 @@ try // just to make sure we find all devices if (m_retry_count >= 4 && !m_devices.empty()) { - m_broadcast_timer.cancel(); + asio::error_code ec; + m_broadcast_timer.cancel(ec); for (std::set::iterator i = m_devices.begin() , end(m_devices.end()); i != end; ++i) @@ -438,16 +465,19 @@ try // ask for it rootdevice& d = const_cast(*i); TORRENT_ASSERT(d.magic == 1337); +#ifndef BOOST_NO_EXCEPTIONS try { +#endif #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " ==> connecting to " << d.url << std::endl; #endif d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, self(), _1, _2 - , boost::ref(d))))); - d.upnp_connection->get(d.url); + , m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2 + , boost::ref(d)))); + d.upnp_connection->get(d.url, seconds(30), 1); +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { @@ -459,16 +489,11 @@ try #endif d.disabled = true; } +#endif } } } } -#ifndef NDEBUG -catch (std::exception&) -{ - TORRENT_ASSERT(false); -}; -#endif void upnp::post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action) @@ -495,6 +520,8 @@ void upnp::post(upnp::rootdevice const& d, std::string const& soap void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(d.magic == 1337); if (!d.upnp_connection) @@ -516,11 +543,12 @@ void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" ""; + asio::error_code ec; soap << "" "" << d.mapping[i].external_port << "" "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" "" << d.mapping[i].local_port << "" - "" << c.socket().local_endpoint().address().to_string() << "" + "" << c.socket().local_endpoint(ec).address() << "" "1" "" << m_user_agent << "" "" << d.lease_duration << ""; @@ -529,22 +557,45 @@ void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) post(d, soap.str(), soap_action); } -void upnp::map_port(rootdevice& d, int i) +void upnp::next(rootdevice& d, int i) +{ + if (i < num_mappings() - 1) + { + update_map(d, i + 1); + } + else + { + std::vector::iterator i + = std::find_if(d.mapping.begin(), d.mapping.end() + , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none)); + if (i == d.mapping.end()) return; + + update_map(d, i - d.mapping.begin()); + } +} + +void upnp::update_map(rootdevice& d, int i) { TORRENT_ASSERT(d.magic == 1337); + TORRENT_ASSERT(i < int(d.mapping.size())); + TORRENT_ASSERT(d.mapping.size() == m_mappings.size()); + if (d.upnp_connection) return; - if (!d.mapping[i].need_update) + mapping_t& m = d.mapping[i]; + + if (m.action == mapping_t::action_none + || m.protocol == none) { #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() << " *** mapping (" << i - << ") does not need update, skipping" << std::endl; + if (m.protocol != none) + m_log << time_now_string() << " *** mapping (" << i + << ") does not need update, skipping" << std::endl; #endif - if (i < num_mappings - 1) - map_port(d, i + 1); + next(d, i); return; } - d.mapping[i].need_update = false; + TORRENT_ASSERT(!d.upnp_connection); TORRENT_ASSERT(d.service_namespace); @@ -552,17 +603,40 @@ void upnp::map_port(rootdevice& d, int i) m_log << time_now_string() << " ==> connecting to " << d.hostname << std::endl; #endif - d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, self(), _1, _2 - , boost::ref(d), i)), true - , bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); + if (m.action == mapping_t::action_add) + { + if (m.failcount > 5) + { + // giving up + next(d, i); + return; + } - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); + d.upnp_connection.reset(new http_connection(m_io_service + , m_cc, bind(&upnp::on_upnp_map_response, self(), _1, _2 + , boost::ref(d), i), true + , bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); + + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10), 1); + } + else if (m.action == mapping_t::action_delete) + { + d.upnp_connection.reset(new http_connection(m_io_service + , m_cc, bind(&upnp::on_upnp_unmap_response, self(), _1, _2 + , boost::ref(d), i), true + , bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10), 1); + } + + m.action = mapping_t::action_none; } void upnp::delete_port_mapping(rootdevice& d, int i) { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(d.magic == 1337); if (!d.upnp_connection) @@ -592,31 +666,6 @@ void upnp::delete_port_mapping(rootdevice& d, int i) post(d, soap.str(), soap_action); } -// requires the mutex to be locked -void upnp::unmap_port(rootdevice& d, int i) -{ - TORRENT_ASSERT(d.magic == 1337); - if (d.mapping[i].external_port == 0 - || d.disabled) - { - if (i < num_mappings - 1) - { - unmap_port(d, i + 1); - } - return; - } -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> connecting to " << d.hostname << std::endl; -#endif - d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, self(), _1, _2 - , boost::ref(d), i)), true - , bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); -} - namespace { struct parse_state @@ -633,6 +682,7 @@ namespace std::string top_tag; std::string control_url; char const* service_type; + std::string model; }; void find_control_url(int type, char const* string, parse_state& state) @@ -646,6 +696,10 @@ namespace { state.top_tag = string; } + else if (!strcmp(string, "modelName")) + { + state.top_tag = string; + } } else if (type == xml_end_tag) { @@ -669,14 +723,20 @@ namespace state.control_url = string; if (state.found_service) state.exit = true; } + else if (state.top_tag == "modelName") + { + state.model = string; + } } } } void upnp::on_upnp_xml(asio::error_code const& e - , libtorrent::http_parser const& p, rootdevice& d) try + , libtorrent::http_parser const& p, rootdevice& d) { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { @@ -722,6 +782,7 @@ void upnp::on_upnp_xml(asio::error_code const& e if (s.found_service) { d.service_namespace = s.service_type; + if (!s.model.empty()) m_model = s.model; } else { @@ -733,6 +794,7 @@ void upnp::on_upnp_xml(asio::error_code const& e if (s.found_service) { d.service_namespace = s.service_type; + if (!s.model.empty()) m_model = s.model; } else { @@ -754,19 +816,26 @@ void upnp::on_upnp_xml(asio::error_code const& e d.control_url = s.control_url; - map_port(d, 0); + update_map(d, 0); } -catch (std::exception&) -{ - disable(); -}; -void upnp::disable() +void upnp::disable(char const* msg) { m_disabled = true; + + // kill all mappings + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none) continue; + i->protocol = none; + m_callback(i - m_mappings.begin(), 0, msg); + } + m_devices.clear(); - m_broadcast_timer.cancel(); - m_refresh_timer.cancel(); + asio::error_code ec; + m_broadcast_timer.cancel(ec); + m_refresh_timer.cancel(ec); m_socket.close(); } @@ -823,8 +892,10 @@ namespace } void upnp::on_upnp_map_response(asio::error_code const& e - , libtorrent::http_parser const& p, rootdevice& d, int mapping) try + , libtorrent::http_parser const& p, rootdevice& d, int mapping) { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { @@ -867,7 +938,7 @@ void upnp::on_upnp_map_response(asio::error_code const& e m_log << time_now_string() << " <== error while adding portmap: incomplete http message" << std::endl; #endif - d.disabled = true; + next(d, mapping); return; } @@ -886,37 +957,44 @@ void upnp::on_upnp_map_response(asio::error_code const& e } #endif + mapping_t& m = d.mapping[mapping]; + if (s.error_code == 725) { // only permanent leases supported d.lease_duration = 0; - d.mapping[mapping].need_update = true; - map_port(d, mapping); + m.action = mapping_t::action_add; + ++m.failcount; + update_map(d, mapping); return; } - else if (s.error_code == 718) + else if (s.error_code == 718 || s.error_code == 727) { - // conflict in mapping, try next external port - ++d.mapping[mapping].external_port; - d.mapping[mapping].need_update = true; - map_port(d, mapping); + if (m.external_port != 0) + { + // conflict in mapping, set port to wildcard + // and let the router decide + m.external_port = 0; + m.action = mapping_t::action_add; + ++m.failcount; + update_map(d, mapping); + return; + } + return_error(mapping, s.error_code); + } + else if (s.error_code == 716) + { + // The external port cannot be wildcarder + // pick a random port + m.external_port = 40000 + (rand() % 10000); + m.action = mapping_t::action_add; + ++m.failcount; + update_map(d, mapping); return; } else if (s.error_code != -1) { - int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); - error_code_t* end = error_codes + num_errors; - error_code_t tmp = {s.error_code, 0}; - error_code_t* e = std::lower_bound(error_codes, end, tmp - , bind(&error_code_t::code, _1) < bind(&error_code_t::code, _2)); - std::string error_string = "UPnP mapping error "; - error_string += boost::lexical_cast(s.error_code); - if (e != end && e->code == s.error_code) - { - error_string += ": "; - error_string += e->msg; - } - m_callback(0, 0, error_string); + return_error(mapping, s.error_code); } #ifdef TORRENT_UPNP_LOGGING @@ -927,50 +1005,52 @@ void upnp::on_upnp_map_response(asio::error_code const& e if (s.error_code == -1) { - int tcp = 0; - int udp = 0; - - if (mapping == 0) - tcp = d.mapping[mapping].external_port; - else - udp = d.mapping[mapping].external_port; - - m_callback(tcp, udp, ""); + m_callback(mapping, m.external_port, ""); if (d.lease_duration > 0) { - d.mapping[mapping].expires = time_now() + m.expires = time_now() + seconds(int(d.lease_duration * 0.75f)); ptime next_expire = m_refresh_timer.expires_at(); if (next_expire < time_now() - || next_expire > d.mapping[mapping].expires) + || next_expire > m.expires) { - m_refresh_timer.expires_at(d.mapping[mapping].expires); - m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, self(), _1))); + asio::error_code ec; + m_refresh_timer.expires_at(m.expires, ec); + m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1)); } } else { - d.mapping[mapping].expires = max_time(); + m.expires = max_time(); } + m.failcount = 0; } - for (int i = 0; i < num_mappings; ++i) - { - if (d.mapping[i].need_update) - { - map_port(d, i); - return; - } - } + next(d, mapping); } -catch (std::exception&) + +void upnp::return_error(int mapping, int code) { - disable(); -}; + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {code, 0}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , bind(&error_code_t::code, _1) < bind(&error_code_t::code, _2)); + std::string error_string = "UPnP mapping error "; + error_string += boost::lexical_cast(code); + if (e != end && e->code == code) + { + error_string += ": "; + error_string += e->msg; + } + m_callback(mapping, 0, error_string); +} void upnp::on_upnp_unmap_response(asio::error_code const& e - , libtorrent::http_parser const& p, rootdevice& d, int mapping) try + , libtorrent::http_parser const& p, rootdevice& d, int mapping) { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(d.magic == 1337); if (d.upnp_connection) { @@ -984,58 +1064,50 @@ void upnp::on_upnp_unmap_response(asio::error_code const& e m_log << time_now_string() << " <== error while deleting portmap: " << e.message() << std::endl; #endif - } - - if (!p.header_finished()) + } else if (!p.header_finished()) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " <== error while deleting portmap: incomplete http message" << std::endl; #endif - return; } - - if (p.status_code() != 200) + else if (p.status_code() != 200) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() << " <== error while deleting portmap: " << p.message() << std::endl; #endif - d.disabled = true; - return; } + else + { #ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end) - << std::endl; + m_log << time_now_string() + << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end) + << std::endl; #endif - - // ignore errors and continue with the next mapping for this device - if (mapping < num_mappings - 1) - { - unmap_port(d, mapping + 1); - return; } -} -catch (std::exception&) -{ - disable(); -}; -void upnp::on_expire(asio::error_code const& e) try + d.mapping[mapping].protocol = none; + + next(d, mapping); +} + +void upnp::on_expire(asio::error_code const& e) { if (e) return; ptime now = time_now(); ptime next_expire = max_time(); + mutex_t::scoped_lock l(m_mutex); + for (std::set::iterator i = m_devices.begin() , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); TORRENT_ASSERT(d.magic == 1337); - for (int m = 0; m < num_mappings; ++m) + for (int m = 0; m < num_mappings(); ++m) { if (d.mapping[m].expires != max_time()) continue; @@ -1043,7 +1115,7 @@ void upnp::on_expire(asio::error_code const& e) try if (d.mapping[m].expires < now) { d.mapping[m].expires = max_time(); - map_port(d, m); + update_map(d, m); } else if (d.mapping[m].expires < next_expire) { @@ -1053,35 +1125,41 @@ void upnp::on_expire(asio::error_code const& e) try } if (next_expire != max_time()) { - m_refresh_timer.expires_at(next_expire); - m_refresh_timer.async_wait(m_strand.wrap(bind(&upnp::on_expire, self(), _1))); + asio::error_code ec; + m_refresh_timer.expires_at(next_expire, ec); + m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1)); } } -catch (std::exception&) -{ - disable(); -}; void upnp::close() { - m_refresh_timer.cancel(); - m_broadcast_timer.cancel(); + mutex_t::scoped_lock l(m_mutex); + + asio::error_code ec; + m_refresh_timer.cancel(ec); + m_broadcast_timer.cancel(ec); m_closing = true; m_socket.close(); - if (m_disabled) - { - m_devices.clear(); - return; - } - for (std::set::iterator i = m_devices.begin() , end(m_devices.end()); i != end; ++i) { rootdevice& d = const_cast(*i); TORRENT_ASSERT(d.magic == 1337); if (d.control_url.empty()) continue; - unmap_port(d, 0); + for (std::vector::iterator j = d.mapping.begin() + , end(d.mapping.end()); j != end; ++j) + { + if (j->protocol == none) continue; + if (j->action == mapping_t::action_add) + { + j->action = mapping_t::action_none; + continue; + } + j->action = mapping_t::action_delete; + m_mappings[j - d.mapping.begin()].protocol = none; + } + update_map(d, 0); } } diff --git a/libtorrent/src/ut_metadata.cpp b/libtorrent/src/ut_metadata.cpp new file mode 100644 index 000000000..4cefcdc7c --- /dev/null +++ b/libtorrent/src/ut_metadata.cpp @@ -0,0 +1,452 @@ +/* + +Copyright (c) 2007, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/pch.hpp" + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/alert_types.hpp" +#ifdef TORRENT_STATS +#include "libtorrent/aux_/session_impl.hpp" +#endif + +namespace libtorrent { namespace +{ + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + void nop(char*) {} + + struct ut_metadata_plugin : torrent_plugin + { + ut_metadata_plugin(torrent& t) + : m_torrent(t) + , m_metadata_progress(0) + , m_metadata_size(0) + { + } + + virtual void on_files_checked() + { + // if the torrent is a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + virtual boost::shared_ptr new_connection( + peer_connection* pc); + + std::vector const& metadata() const + { + TORRENT_ASSERT(m_torrent.valid_metadata()); + + if (m_metadata.empty()) + { + bencode(std::back_inserter(m_metadata) + , m_torrent.torrent_file().create_info_metadata()); + + TORRENT_ASSERT(hasher(&m_metadata[0], m_metadata.size()).final() + == m_torrent.torrent_file().info_hash()); + } + TORRENT_ASSERT(!m_metadata.empty()); + return m_metadata; + } + + bool received_metadata(char const* buf, int size, int piece, int total_size) + { + if (m_torrent.valid_metadata()) return false; + + if (m_metadata.empty()) + { + // verify the total_size + if (total_size <= 0 || total_size > 500 * 1024) return false; + + m_metadata.resize(total_size); + m_requested_metadata.resize(div_round_up(total_size, 16 * 1024), 0); + } + + if (piece < 0 || piece >= int(m_requested_metadata.size())) + return false; + + TORRENT_ASSERT(piece * 16 * 1024 + size <= int(m_metadata.size())); + std::memcpy(&m_metadata[piece * 16 * 1024], buf, size); + // mark this piece has 'have' + m_requested_metadata[piece] = (std::numeric_limits::max)(); + + bool have_all = std::count(m_requested_metadata.begin() + , m_requested_metadata.end(), (std::numeric_limits::max)()) + == int(m_requested_metadata.size()); + + if (!have_all) return false; + + hasher h; + h.update(&m_metadata[0], (int)m_metadata.size()); + sha1_hash info_hash = h.final(); + + if (info_hash != m_torrent.torrent_file().info_hash()) + { + std::fill(m_requested_metadata.begin(), m_requested_metadata.end(), 0); + + if (m_torrent.alerts().should_post(alert::info)) + { + m_torrent.alerts().post_alert(metadata_failed_alert( + m_torrent.get_handle(), "invalid metadata received from swarm")); + } + + return false; + } + + entry metadata = bdecode(m_metadata.begin(), m_metadata.end()); + std::string error; + if (!m_torrent.set_metadata(metadata, error)) + { + // this means the metadata is correct, since we + // verified it against the info-hash, but we + // failed to parse it. Pause the torrent + // TODO: Post an alert! + m_torrent.pause(); + return false; + } + + // clear the storage for the bitfield + std::vector().swap(m_requested_metadata); + + return true; + } + + // returns a piece of the metadata that + // we should request. + int metadata_request(); + + // this is called from the peer_connection for + // each piece of metadata it receives + void metadata_progress(int total_size, int received) + { + m_metadata_progress += received; + m_metadata_size = total_size; + } + + void on_piece_pass(int) + { + // if we became a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + void metadata_size(int size) + { + if (m_metadata_size > 0 || size <= 0 || size > 500 * 1024) return; + m_metadata_size = size; + m_metadata.resize(size); + m_requested_metadata.resize(div_round_up(size, 16 * 1024), 0); + } + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable std::vector m_metadata; + + int m_metadata_progress; + int m_metadata_size; + + // this vector keeps track of how many times each meatdata + // block has been requested + // std::numeric_limits::max() means we have the piece + std::vector m_requested_metadata; + }; + + + struct ut_metadata_peer_plugin : peer_plugin + { + ut_metadata_peer_plugin(torrent& t, bt_peer_connection& pc + , ut_metadata_plugin& tp) + : m_message_index(0) + , m_no_metadata(min_time()) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + {} + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) + { + entry& messages = h["m"]; + messages["ut_metadata"] = 15; + if (m_torrent.valid_metadata()) + h["metadata_size"] = m_tp.metadata().size(); + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(entry const& h) + { + entry const* metadata_size = h.find_key("metadata_size"); + if (metadata_size && metadata_size->type() == entry::int_t) + m_tp.metadata_size(metadata_size->integer()); + + entry const* messages = h.find_key("m"); + if (!messages || messages->type() != entry::dictionary_t) return false; + + entry const* index = messages->find_key("ut_metadata"); + if (!index || index->type() != entry::int_t) return false; + m_message_index = int(index->integer()); + return true; + } + + void write_metadata_packet(int type, int piece) + { + TORRENT_ASSERT(type >= 0 && type <= 2); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_pc.m_logger) << time_now_string() << " ==> UT_METADATA [ " + "type: " << type << " | piece: " << piece << " ]\n"; +#endif + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + entry e; + e["msg_type"] = type; + e["piece"] = piece; + + char const* metadata = 0; + int metadata_piece_size = 0; + + if (type == 1) + { + TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); + e["total_size"] = m_tp.metadata().size(); + int offset = piece * 16 * 1024; + metadata = &m_tp.metadata()[0] + offset; + metadata_piece_size = (std::min)( + int(m_tp.metadata().size() - offset), 16 * 1024); + TORRENT_ASSERT(metadata_piece_size > 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.metadata().size())); + } + + char msg[200]; + char* header = msg; + char* p = &msg[6]; + int len = bencode(p, e); + int total_size = 2 + len + metadata_piece_size; + namespace io = detail; + io::write_uint32(total_size, header); + io::write_uint8(bt_peer_connection::msg_extended, header); + io::write_uint8(m_message_index, header); + + m_pc.send_buffer(msg, len + 6); + if (metadata_piece_size) m_pc.append_send_buffer( + (char*)metadata, metadata_piece_size, &nop); + } + + virtual bool on_extended(int length + , int extended_msg, buffer::const_interval body) + { + if (extended_msg != 15) return false; + if (m_message_index == 0) return false; + + if (length > 17 * 1024) + { + m_pc.disconnect("ut_metadata message larger than 17 kB"); + return true; + } + + if (!m_pc.packet_finished()) return true; + + int len; + entry msg = bdecode(body.begin, body.end, len); + + int type = msg["msg_type"].integer(); + int piece = msg["piece"].integer(); + +#ifdef TORRENT_VERBOSE_LOGGING + (*m_pc.m_logger) << time_now_string() << " <== UT_METADATA [ " + "type: " << type << " | piece: " << piece << " ]\n"; +#endif + + switch (type) + { + case 0: // request + { + if (!m_torrent.valid_metadata()) + { + write_metadata_packet(2, piece); + return true; + } + // TODO: put the request on the queue in some cases + write_metadata_packet(1, piece); + } + break; + case 1: // data + { + std::vector::iterator i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + + // unwanted piece? + if (i == m_sent_requests.end()) return true; + + m_sent_requests.erase(i); + entry const* total_size = msg.find_key("total_size"); + m_tp.received_metadata(body.begin + len, body.left() - len, piece + , (total_size && total_size->type() == entry::int_t) ? total_size->integer() : 0); + } + break; + case 2: // have no data + { + m_no_metadata = time_now(); + std::vector::iterator i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + // unwanted piece? + if (i == m_sent_requests.end()) return true; + m_sent_requests.erase(i); + } + break; + default: + { + std::stringstream msg; + msg << "unknown ut_metadata extension message: " << type; + m_pc.disconnect(msg.str().c_str()); + } + } + return true; + } + + virtual void tick() + { + // if we don't have any metadata, and this peer + // supports the request metadata extension + // and we aren't currently waiting for a request + // reply. Then, send a request for some metadata. + if (!m_torrent.valid_metadata() + && m_message_index != 0 + && m_sent_requests.size() < 2 + && has_metadata()) + { + int piece = m_tp.metadata_request(); + m_sent_requests.push_back(piece); + write_metadata_packet(0, piece); + } + } + + bool has_metadata() const + { + return time_now() - m_no_metadata > minutes(1); + } + + private: + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // this is set to the current time each time we get a + // "I don't have metadata" message. + ptime m_no_metadata; + + // request queues + std::vector m_sent_requests; + std::vector m_incoming_requests; + + torrent& m_torrent; + bt_peer_connection& m_pc; + ut_metadata_plugin& m_tp; + }; + + boost::shared_ptr ut_metadata_plugin::new_connection( + peer_connection* pc) + { + bt_peer_connection* c = dynamic_cast(pc); + if (!c) return boost::shared_ptr(); + return boost::shared_ptr(new ut_metadata_peer_plugin(m_torrent, *c, *this)); + } + + int ut_metadata_plugin::metadata_request() + { + std::vector::iterator i = std::min_element( + m_requested_metadata.begin(), m_requested_metadata.end()); + + if (m_requested_metadata.empty()) + { + // if we don't know how many pieces there are + // just ask for piece 0 + m_requested_metadata.resize(1, 1); + return 0; + } + + int piece = i - m_requested_metadata.begin(); + m_requested_metadata[piece] = piece; + return piece; + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_ut_metadata_plugin(torrent* t, void*) + { + return boost::shared_ptr(new ut_metadata_plugin(*t)); + } + +} + + diff --git a/libtorrent/src/ut_pex.cpp b/libtorrent/src/ut_pex.cpp index 18c37cda4..d27186a1b 100644 --- a/libtorrent/src/ut_pex.cpp +++ b/libtorrent/src/ut_pex.cpp @@ -113,7 +113,7 @@ namespace libtorrent { namespace for (torrent::peer_iterator i = m_torrent.begin() , end(m_torrent.end()); i != end; ++i) { - peer_connection* peer = *i; + peer_connection* peer = *i; if (!send_peer(*peer)) continue; tcp::endpoint const& remote = peer->remote(); @@ -198,18 +198,15 @@ namespace libtorrent { namespace virtual bool on_extension_handshake(entry const& h) { - entry const& messages = h["m"]; + m_message_index = 0; + entry const* messages = h.find_key("m"); + if (!messages || messages->type() != entry::dictionary_t) return false; - if (entry const* index = messages.find_key(extension_name)) - { - m_message_index = int(index->integer()); - return true; - } - else - { - m_message_index = 0; - return false; - } + entry const* index = messages->find_key(extension_name); + if (!index || index->type() != entry::int_t) return false; + + m_message_index = int(index->integer()); + return true; } virtual bool on_extended(int length, int msg, buffer::const_interval body) @@ -218,15 +215,22 @@ namespace libtorrent { namespace if (m_message_index == 0) return false; if (length > 500 * 1024) - throw protocol_error("uT peer exchange message larger than 500 kB"); + { + m_pc.disconnect("peer exchange message larger than 500 kB"); + return true; + } if (body.left() < length) return true; - try + entry pex_msg = bdecode(body.begin, body.end); + + entry const* p = pex_msg.find_key("added"); + entry const* pf = pex_msg.find_key("added.f"); + + if (p != 0 && pf != 0 && p->type() == entry::string_t && pf->type() == entry::string_t) { - entry pex_msg = bdecode(body.begin, body.end); - std::string const& peers = pex_msg["added"].string(); - std::string const& peer_flags = pex_msg["added.f"].string(); + std::string const& peers = p->string(); + std::string const& peer_flags = pf->string(); int num_peers = peers.length() / 6; char const* in = peers.c_str(); @@ -243,32 +247,30 @@ namespace libtorrent { namespace char flags = detail::read_uint8(fin); p.peer_from_tracker(adr, pid, peer_info::pex, flags); } - - if (entry const* p6 = pex_msg.find_key("added6")) - { - std::string const& peers6 = p6->string(); - std::string const& peer6_flags = pex_msg["added6.f"].string(); - - int num_peers = peers6.length() / 18; - char const* in = peers6.c_str(); - char const* fin = peer6_flags.c_str(); - - if (int(peer6_flags.size()) != num_peers) - return true; - - peer_id pid(0); - policy& p = m_torrent.get_policy(); - for (int i = 0; i < num_peers; ++i) - { - tcp::endpoint adr = detail::read_v6_endpoint(in); - char flags = detail::read_uint8(fin); - p.peer_from_tracker(adr, pid, peer_info::pex, flags); - } - } } - catch (std::exception&) + + entry const* p6 = pex_msg.find_key("added6"); + entry const* p6f = pex_msg.find_key("added6.f"); + if (p6 && p6f && p6->type() == entry::string_t && p6f->type() == entry::string_t) { - throw protocol_error("invalid uT peer exchange message"); + std::string const& peers6 = p6->string(); + std::string const& peer6_flags = p6f->string(); + + int num_peers = peers6.length() / 18; + char const* in = peers6.c_str(); + char const* fin = peer6_flags.c_str(); + + if (int(peer6_flags.size()) != num_peers) + return true; + + peer_id pid(0); + policy& p = m_torrent.get_policy(); + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v6_endpoint(in); + char flags = detail::read_uint8(fin); + p.peer_from_tracker(adr, pid, peer_info::pex, flags); + } } return true; } diff --git a/libtorrent/src/web_peer_connection.cpp b/libtorrent/src/web_peer_connection.cpp index 3e390ac4f..04ee0a410 100755 --- a/libtorrent/src/web_peer_connection.cpp +++ b/libtorrent/src/web_peer_connection.cpp @@ -315,7 +315,6 @@ namespace libtorrent } } - // throws exception when the client should be disconnected void web_peer_connection::on_receive(asio::error_code const& error , std::size_t bytes_transferred) { @@ -344,9 +343,16 @@ namespace libtorrent bool header_finished = m_parser.header_finished(); if (!header_finished) { - boost::tie(payload, protocol) = m_parser.incoming(recv_buffer); + bool error = false; + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, error); m_statistics.received_bytes(payload, protocol); + if (error) + { + disconnect("failed to parse HTTP response"); + return; + } + TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); TORRENT_ASSERT(recv_buffer.left() <= packet_size()); @@ -374,7 +380,8 @@ namespace libtorrent m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), url() , error_msg)); } - throw std::runtime_error(error_msg); + disconnect(error_msg.c_str()); + return; } if (!m_parser.header_finished()) break; @@ -399,7 +406,8 @@ namespace libtorrent { // we should not try this server again. t->remove_url_seed(m_url); - throw std::runtime_error("got HTTP redirection status without location header"); + disconnect("got HTTP redirection status without location header"); + return; } bool single_file_request = false; @@ -419,14 +427,20 @@ namespace libtorrent if (i == std::string::npos) { t->remove_url_seed(m_url); - throw std::runtime_error("got invalid HTTP redirection location (\"" + location + "\") " - "expected it to end with: " + path); + std::stringstream msg; + msg << "got invalid HTTP redirection location (\"" << location << "\") " + "expected it to end with: " << path; + disconnect(msg.str().c_str()); + return; } location.resize(i); } t->add_url_seed(location); t->remove_url_seed(m_url); - throw std::runtime_error("redirecting to " + location); + std::stringstream msg; + msg << "redirecting to \"" << location << "\""; + disconnect(msg.str().c_str()); + return; } std::string const& server_version = m_parser.header("server"); @@ -459,7 +473,10 @@ namespace libtorrent { // 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()); + std::stringstream msg; + msg << "invalid range in HTTP response: " << range_str.str(); + disconnect(msg.str().c_str()); + return; } // the http range is inclusive range_end++; @@ -472,7 +489,8 @@ namespace libtorrent { // we should not try this server again. t->remove_url_seed(m_url); - throw std::runtime_error("no content-length in HTTP response"); + disconnect("no content-length in HTTP response"); + return; } } @@ -482,7 +500,10 @@ namespace libtorrent torrent_info const& info = t->torrent_file(); if (m_requests.empty() || m_file_requests.empty()) - throw std::runtime_error("unexpected HTTP response"); + { + disconnect("unexpected HTTP response"); + return; + } int file_index = m_file_requests.front(); peer_request in_range = info.map_file(file_index, range_start @@ -513,7 +534,8 @@ namespace libtorrent { // this means the end of the incoming request ends _before_ the // first expected byte (fs + m_piece.size()) - throw std::runtime_error("invalid range in HTTP response"); + disconnect("invalid range in HTTP response"); + return; } // if the request is contained in the range (i.e. the entire request diff --git a/setup.py b/setup.py index 52608bdde..f33ba58b2 100644 --- a/setup.py +++ b/setup.py @@ -153,12 +153,14 @@ else: 'boost_date_time', 'boost_thread', 'boost_python', + 'boost_iostreams', 'pthread', 'ssl', 'z' ] _sources = glob.glob("./libtorrent/src/*.cpp") + \ + glob.glob("./libtorrent/src/*.c") + \ glob.glob("./libtorrent/src/kademlia/*.cpp") + \ glob.glob("./libtorrent/bindings/python/src/*.cpp")