From 8dc1d552c0bab7b72371c3a1529e365410c7548c Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Mon, 17 May 2010 12:03:46 +0200 Subject: add rtorrent-extended Signed-off-by: Florian Pritz --- libtorrent-extended/PKGBUILD | 54 + libtorrent-extended/bad_peer_handling.patch | 67 + libtorrent-extended/dht_pex_static_map.patch | 2353 +++++++++++++++++++++++ libtorrent-extended/ipv6.patch | 1235 ++++++++++++ libtorrent-extended/magnet_uri.patch | 1652 ++++++++++++++++ libtorrent-extended/object_sstr.patch | 233 +++ rtorrent-extended/PKGBUILD | 122 ++ rtorrent-extended/bad_peer_handling.patch | 219 +++ rtorrent-extended/canvas_color.patch | 282 +++ rtorrent-extended/dht_pex_static_map.patch | 12 + rtorrent-extended/ip_filter_no_boost_fast.patch | 1083 +++++++++++ rtorrent-extended/ipv6.patch | 572 ++++++ rtorrent-extended/karabaja_mod.patch | 438 +++++ rtorrent-extended/magnet_uri.patch | 228 +++ rtorrent-extended/rtorrent-extended.install | 9 + rtorrent-extended/trackerinfo.patch | 21 + rtorrent-extended/vi_kb_akston.patch | 176 ++ rtorrent-extended/vi_kb_tjwoosta.patch | 175 ++ 18 files changed, 8931 insertions(+) create mode 100644 libtorrent-extended/PKGBUILD create mode 100644 libtorrent-extended/bad_peer_handling.patch create mode 100644 libtorrent-extended/dht_pex_static_map.patch create mode 100644 libtorrent-extended/ipv6.patch create mode 100644 libtorrent-extended/magnet_uri.patch create mode 100644 libtorrent-extended/object_sstr.patch create mode 100644 rtorrent-extended/PKGBUILD create mode 100644 rtorrent-extended/bad_peer_handling.patch create mode 100644 rtorrent-extended/canvas_color.patch create mode 100644 rtorrent-extended/dht_pex_static_map.patch create mode 100644 rtorrent-extended/ip_filter_no_boost_fast.patch create mode 100644 rtorrent-extended/ipv6.patch create mode 100644 rtorrent-extended/karabaja_mod.patch create mode 100644 rtorrent-extended/magnet_uri.patch create mode 100644 rtorrent-extended/rtorrent-extended.install create mode 100644 rtorrent-extended/trackerinfo.patch create mode 100644 rtorrent-extended/vi_kb_akston.patch create mode 100644 rtorrent-extended/vi_kb_tjwoosta.patch diff --git a/libtorrent-extended/PKGBUILD b/libtorrent-extended/PKGBUILD new file mode 100644 index 0000000..eef3cf9 --- /dev/null +++ b/libtorrent-extended/PKGBUILD @@ -0,0 +1,54 @@ +# Maintainer: Lucky +# Contributor: Daenyth +# Contributor: Jeff Mickey +# Contributor: sh__ + +pkgname=libtorrent-extended +_pkgname=libtorrent +pkgver=0.12.6 +pkgrel=3 +pkgdesc="BitTorrent library written in C++ with magnet link, IPv6 and bad peer patch" +url="http://libtorrent.rakshasa.no" +arch=('i686' 'x86_64') +license=('GPL') +depends=('libsigc++2.0' 'openssl') +conflicts=('libtorrent') +provides=('libtorrent') +options=('!libtool') +source=(http://libtorrent.rakshasa.no/downloads/${_pkgname}-${pkgver}.tar.gz + # support for magnet links + # http://libtorrent.rakshasa.no/ticket/955 (deleted) + # http://libtorrent.rakshasa.no/ticket/2100 + dht_pex_static_map.patch + magnet_uri.patch + object_sstr.patch + # support for IPv6 + # http://libtorrent.rakshasa.no/ticket/1111 + ipv6.patch + # support for bad peers (kick/ban) + # http://ovh.ttdpatch.net/~jdrexler/rt/experimental/bad_peer_handling.diff + # https://calomel.org/rtorrent_mods.html + bad_peer_handling.patch) +md5sums=('037499ed708aaf72988cee60e5a8d96b' + 'c8c538e26e77a0cf5951c47b86252922' + 'b1d9701ca0b8c26291c467e20d6e3eba' + '0eb6044530c1bb5bb21d5d539481c23d' + '90c5e5a374ee29737a0ad0a93c912e6a' + 'b01ebff539102c07a0192e179c3170b1') + +build() { + cd "${srcdir}/${_pkgname}-${pkgver}" + + patch -p1 < ${srcdir}/dht_pex_static_map.patch + patch -p1 < ${srcdir}/magnet_uri.patch + patch -p1 < ${srcdir}/object_sstr.patch + patch -p1 < ${srcdir}/ipv6.patch + patch -p1 < ${srcdir}/bad_peer_handling.patch + + ./autogen.sh + CXXFLAGS="${CXXFLAGS} -fno-strict-aliasing" \ + ./configure --prefix=/usr --enable-ipv6 --disable-debug || return 1 + make || return 1 + make DESTDIR="${pkgdir}" install +} +# vim:set ts=2 sw=2 et: diff --git a/libtorrent-extended/bad_peer_handling.patch b/libtorrent-extended/bad_peer_handling.patch new file mode 100644 index 0000000..26d9515 --- /dev/null +++ b/libtorrent-extended/bad_peer_handling.patch @@ -0,0 +1,67 @@ +diff --git a/src/download/choke_manager.cc b/src/download/choke_manager.cc +index 4915a96..efda46c 100644 +--- a/src/download/choke_manager.cc ++++ b/src/download/choke_manager.cc +@@ -193,7 +193,8 @@ ChokeManager::set_snubbed(PeerConnectionBase* pc, ChokeManagerNode* base) { + choke_manager_erase(&m_queued, pc); + } + +- base->set_queued(false); ++ //breaks unsnubbing, ticket #989: ++ //base->set_queued(false); + } + + void +diff --git a/src/torrent/peer/peer.cc b/src/torrent/peer/peer.cc +index 3f29f82..a15a7b2 100644 +--- a/src/torrent/peer/peer.cc ++++ b/src/torrent/peer/peer.cc +@@ -63,7 +63,8 @@ bool Peer::is_down_interested() const { return c_ptr()->is_up_interested(); + + bool Peer::is_snubbed() const { return c_ptr()->is_up_snubbed(); } + void Peer::set_snubbed(bool v) { m_ptr()->set_upload_snubbed(v); } +-void Peer::set_banned() { m_peerInfo->set_failed_counter(64); } ++void Peer::set_banned() { m_peerInfo->set_banned(); } ++void Peer::set_unbanned() { m_peerInfo->set_unbanned(); } + + const Rate* Peer::down_rate() const { return c_ptr()->c_peer_chunks()->download_throttle()->rate(); } + const Rate* Peer::up_rate() const { return c_ptr()->c_peer_chunks()->upload_throttle()->rate(); } +diff --git a/src/torrent/peer/peer.h b/src/torrent/peer/peer.h +index 70169ad..6d9b4f9 100644 +--- a/src/torrent/peer/peer.h ++++ b/src/torrent/peer/peer.h +@@ -69,7 +69,10 @@ public: + + bool is_snubbed() const; + void set_snubbed(bool v); ++ ++ bool is_banned() const { return peer_info()->is_banned(); } + void set_banned(); ++ void set_unbanned(); + + const HashString& id() const { return peer_info()->id(); } + const char* options() const { return peer_info()->options(); } +diff --git a/src/torrent/peer/peer_info.h b/src/torrent/peer/peer_info.h +index fe80027..9b17e61 100644 +--- a/src/torrent/peer/peer_info.h ++++ b/src/torrent/peer/peer_info.h +@@ -51,6 +51,8 @@ public: + friend class PeerList; + friend class ProtocolExtension; + ++ static const unsigned int banned_mask = 4096; ++ + static const int flag_connected = (1 << 0); + static const int flag_incoming = (1 << 1); + static const int flag_handshake = (1 << 2); +@@ -79,6 +81,10 @@ public: + uint32_t failed_counter() const { return m_failedCounter; } + void set_failed_counter(uint32_t c) { m_failedCounter = c; } + ++ bool is_banned() const { return m_failedCounter & banned_mask; } ++ void set_banned() { m_failedCounter |= banned_mask; } ++ void set_unbanned() { m_failedCounter &= ~banned_mask; } ++ + uint32_t transfer_counter() const { return m_transferCounter; } + void set_transfer_counter(uint32_t c) { m_transferCounter = c; } + diff --git a/libtorrent-extended/dht_pex_static_map.patch b/libtorrent-extended/dht_pex_static_map.patch new file mode 100644 index 0000000..1e767e0 --- /dev/null +++ b/libtorrent-extended/dht_pex_static_map.patch @@ -0,0 +1,2353 @@ +diff --git a/src/dht/dht_bucket.cc b/src/dht/dht_bucket.cc +index 04aa475..819f4aa 100644 +--- a/src/dht/dht_bucket.cc ++++ b/src/dht/dht_bucket.cc +@@ -52,6 +52,8 @@ DhtBucket::DhtBucket(const HashString& begin, const HashString& end) : + m_good(0), + m_bad(0), + ++ m_fullCacheLength(0), ++ + m_begin(begin), + m_end(end) { + +@@ -67,6 +69,8 @@ DhtBucket::add_node(DhtNode* n) { + m_good++; + else if (n->is_bad()) + m_bad++; ++ ++ m_fullCacheLength = 0; + } + + void +@@ -81,6 +85,8 @@ DhtBucket::remove_node(DhtNode* n) { + m_good--; + else if (n->is_bad()) + m_bad--; ++ ++ m_fullCacheLength = 0; + } + + void +@@ -92,9 +98,11 @@ DhtBucket::count() { + // Called every 15 minutes for housekeeping. + void + DhtBucket::update() { +- // For now we only update the counts after some nodes have become bad +- // due to prolonged inactivity. + count(); ++ ++ // In case adjacent buckets whose nodes we borrowed have changed, ++ // we force an update of the cache. ++ m_fullCacheLength = 0; + } + + DhtBucket::iterator +@@ -188,4 +196,23 @@ DhtBucket::split(const HashString& id) { + return other; + } + ++void ++DhtBucket::build_full_cache() { ++ DhtBucketChain chain(this); ++ ++ char* pos = m_fullCache; ++ do { ++ for (const_iterator itr = chain.bucket()->begin(); itr != chain.bucket()->end() && pos < m_fullCache + sizeof(m_fullCache); ++itr) { ++ if (!(*itr)->is_bad()) { ++ pos = (*itr)->store_compact(pos); ++ ++ if (pos > m_fullCache + sizeof(m_fullCache)) ++ throw internal_error("DhtRouter::store_closest_nodes wrote past buffer end."); ++ } ++ } ++ } while (pos < m_fullCache + sizeof(m_fullCache) && chain.next() != NULL); ++ ++ m_fullCacheLength = pos - m_fullCache; ++} ++ + } +diff --git a/src/dht/dht_bucket.h b/src/dht/dht_bucket.h +index 97622a3..bcdfd67 100644 +--- a/src/dht/dht_bucket.h ++++ b/src/dht/dht_bucket.h +@@ -111,6 +111,10 @@ public: + DhtBucket* parent() const { return m_parent; } + DhtBucket* child() const { return m_child; } + ++ // Return a full bucket's worth of compact node data. If this bucket is not ++ // full, it uses nodes from the child/parent buckets until we have enough. ++ SimpleString full_bucket(); ++ + // Called by the DhtNode on its bucket to update good/bad node counts. + void node_now_good(bool was_bad); + void node_now_bad(bool was_good); +@@ -118,6 +122,8 @@ public: + private: + void count(); + ++ void build_full_cache(); ++ + DhtBucket* m_parent; + DhtBucket* m_child; + +@@ -126,11 +132,15 @@ private: + unsigned int m_good; + unsigned int m_bad; + ++ size_t m_fullCacheLength; ++ + // These are 40 bytes together, so might as well put them last. + // m_end is const because it is used as key for the DhtRouter routing table + // map, which would be inconsistent if m_end were changed carelessly. + HashString m_begin; + const HashString m_end; ++ ++ char m_fullCache[num_nodes * 26]; + }; + + // Helper class to recursively follow a chain of buckets. It first recurses +@@ -160,6 +170,13 @@ DhtBucket::node_now_bad(bool was_good) { + m_bad++; + } + ++inline SimpleString ++DhtBucket::full_bucket() { ++ if (!m_fullCacheLength) ++ build_full_cache(); ++ return SimpleString(m_fullCache, m_fullCacheLength); ++} ++ + inline const DhtBucket* + DhtBucketChain::next() { + // m_restart is clear when we're done recursing into the children and +diff --git a/src/dht/dht_router.cc b/src/dht/dht_router.cc +index b1c95c3..ff38b8c 100644 +--- a/src/dht/dht_router.cc ++++ b/src/dht/dht_router.cc +@@ -329,24 +329,6 @@ DhtRouter::node_invalid(const HashString& id) { + delete_node(m_nodes.find(&node->id())); + } + +-char* +-DhtRouter::store_closest_nodes(const HashString& id, char* buffer, char* bufferEnd) { +- DhtBucketChain chain(find_bucket(id)->second); +- +- do { +- for (DhtBucket::const_iterator itr = chain.bucket()->begin(); itr != chain.bucket()->end() && buffer != bufferEnd; ++itr) { +- if (!(*itr)->is_bad()) { +- buffer = (*itr)->store_compact(buffer); +- +- if (buffer > bufferEnd) +- throw internal_error("DhtRouter::store_closest_nodes wrote past buffer end."); +- } +- } +- } while (buffer != bufferEnd && chain.next() != NULL); +- +- return buffer; +-} +- + Object* + DhtRouter::store_cache(Object* container) const { + container->insert_key("self_id", str()); +@@ -470,7 +452,7 @@ DhtRouter::receive_timeout() { + for (DhtBucketList::const_iterator itr = m_routingTable.begin(); itr != m_routingTable.end(); ++itr) { + itr->second->update(); + +- if (!itr->second->is_full() || itr->second->age() > timeout_bucket_bootstrap) ++ if (!itr->second->is_full() || itr->second == bucket() || itr->second->age() > timeout_bucket_bootstrap) + bootstrap_bucket(itr->second); + } + +@@ -505,15 +487,13 @@ DhtRouter::generate_token(const rak::socket_address* sa, int token, char buffer[ + return buffer; + } + +-std::string +-DhtRouter::make_token(const rak::socket_address* sa) { +- char token[20]; +- +- return std::string(generate_token(sa, m_curToken, token), size_token); ++SimpleString ++DhtRouter::make_token(const rak::socket_address* sa, char* buffer) { ++ return SimpleString(generate_token(sa, m_curToken, buffer), size_token); + } + + bool +-DhtRouter::token_valid(const std::string& token, const rak::socket_address* sa) { ++DhtRouter::token_valid(SimpleString token, const rak::socket_address* sa) { + if (token.length() != size_token) + return false; + +@@ -521,12 +501,12 @@ DhtRouter::token_valid(const std::string& token, const rak::socket_address* sa) + char reference[20]; + + // First try current token. +- if (std::memcmp(generate_token(sa, m_curToken, reference), token.c_str(), size_token) == 0) ++ if (token == SimpleString(generate_token(sa, m_curToken, reference), size_token)) + return true; + + // If token recently changed, some clients may be using the older one. + // That way a token is valid for 15-30 minutes, instead of 0-15. +- return std::memcmp(generate_token(sa, m_prevToken, reference), token.c_str(), size_token) == 0; ++ return token == SimpleString(generate_token(sa, m_prevToken, reference), size_token); + } + + DhtNode* +diff --git a/src/dht/dht_router.h b/src/dht/dht_router.h +index f2b673f..816747f 100644 +--- a/src/dht/dht_router.h ++++ b/src/dht/dht_router.h +@@ -115,14 +115,14 @@ public: + + // Store compact node information (26 bytes) for nodes closest to the + // given ID in the given buffer, return new buffer end. +- char* store_closest_nodes(const HashString& id, char* buffer, char* bufferEnd); ++ SimpleString get_closest_nodes(const HashString& id) { return find_bucket(id)->second->full_bucket(); } + + // Store DHT cache in the given container. + Object* store_cache(Object* container) const; + + // Create and verify a token. Tokens are valid between 15-30 minutes from creation. +- std::string make_token(const rak::socket_address* sa); +- bool token_valid(const std::string& token, const rak::socket_address* sa); ++ SimpleString make_token(const rak::socket_address* sa, char* buffer); ++ bool token_valid(SimpleString token, const rak::socket_address* sa); + + DhtManager::statistics_type get_statistics() const; + void reset_statistics() { m_server.reset_statistics(); } +@@ -147,6 +147,8 @@ private: + bool add_node_to_bucket(DhtNode* node); + void delete_node(const DhtNodeList::accessor& itr); + ++ void store_closest_nodes(const HashString& id, DhtBucket* bucket); ++ + DhtBucketList::iterator split_bucket(const DhtBucketList::iterator& itr, DhtNode* node); + + void bootstrap(); +diff --git a/src/dht/dht_server.cc b/src/dht/dht_server.cc +index 1f2234b..256b92b 100644 +--- a/src/dht/dht_server.cc ++++ b/src/dht/dht_server.cc +@@ -38,13 +38,14 @@ + #include "globals.h" + + #include +-#include ++#include + + #include "torrent/exceptions.h" + #include "torrent/connection_manager.h" + #include "torrent/object.h" + #include "torrent/object_stream.h" + #include "torrent/poll.h" ++#include "torrent/static_map.h" + #include "torrent/throttle.h" + #include "tracker/tracker_dht.h" + +@@ -63,6 +64,34 @@ const char* DhtServer::queries[] = { + "announce_peer", + }; + ++// List of all possible keys we need/support in a DHT message. ++// Unsupported keys we receive are dropped (ignored) while decoding. ++// See torrent/static_map.h for how this works. ++DhtMessage::mapping_type dht_key_names[DhtMessage::length] = { ++ { key_a_id, "a::id" }, ++ { key_a_infoHash, "a::info_hash" }, ++ { key_a_port, "a::port", }, ++ { key_a_target, "a::target" }, ++ { key_a_token, "a::token" }, ++ ++ { key_e_0, "e[0]" }, ++ { key_e_1, "e[1]" }, ++ ++ { key_q, "q" }, ++ ++ { key_r_id, "r::id" }, ++ { key_r_nodes, "r::nodes" }, ++ { key_r_token, "r::token" }, ++ { key_r_values, "r::values[]" }, ++ ++ { key_t, "t::" }, ++ { key_v, "v" }, ++ { key_y, "y" }, ++}; ++ ++template<> ++const DhtMessage::key_map_init DhtMessage::base_type::keyMap(dht_key_names); ++ + // Error in DHT protocol, avoids std::string ctor from communication_error + class dht_error : public network_error { + public: +@@ -238,54 +267,51 @@ DhtServer::update() { + } + + void +-DhtServer::process_query(const Object& transactionId, const HashString& id, const rak::socket_address* sa, Object& request) { ++DhtServer::process_query(const HashString& id, const rak::socket_address* sa, const DhtMessage& msg) { + m_queriesReceived++; + m_networkUp = true; + +- std::string& query = request.get_key_string("q"); +- +- Object& arg = request.get_key("a"); ++ SimpleString query = msg[key_q].as_sstring(); + + // Construct reply. +- Object reply = Object::create_map(); ++ DhtMessage reply; + + if (query == "find_node") +- create_find_node_response(arg, reply); ++ create_find_node_response(msg, reply); + + else if (query == "get_peers") +- create_get_peers_response(arg, sa, reply); ++ create_get_peers_response(msg, sa, reply); + + else if (query == "announce_peer") +- create_announce_peer_response(arg, sa, reply); ++ create_announce_peer_response(msg, sa, reply); + + else if (query != "ping") + throw dht_error(dht_error_bad_method, "Unknown query type."); + + m_router->node_queried(id, sa); +- create_response(transactionId, sa, reply); ++ create_response(msg, sa, reply); + } + + void +-DhtServer::create_find_node_response(const Object& arg, Object& reply) { +- const std::string& target = arg.get_key_string("target"); ++DhtServer::create_find_node_response(const DhtMessage& req, DhtMessage& reply) { ++ SimpleString target = req[key_a_target].as_sstring(); + + if (target.length() < HashString::size_data) + throw dht_error(dht_error_protocol, "target string too short"); + +- char compact[sizeof(compact_node_info) * DhtBucket::num_nodes]; +- char* end = m_router->store_closest_nodes(*HashString::cast_from(target), compact, compact + sizeof(compact)); +- +- if (end == compact) ++ SimpleString nodes = m_router->get_closest_nodes(*HashString::cast_from(target)); ++ if (nodes.empty()) + throw dht_error(dht_error_generic, "No nodes"); + +- reply.insert_key("nodes", std::string(compact, end)); ++ reply[key_r_nodes] = nodes; + } + + void +-DhtServer::create_get_peers_response(const Object& arg, const rak::socket_address* sa, Object& reply) { +- reply.insert_key("token", m_router->make_token(sa)); ++DhtServer::create_get_peers_response(const DhtMessage& req, const rak::socket_address* sa, DhtMessage& reply) { ++ reply[key_r_token] = m_router->make_token(sa, reply.data_end); ++ reply.data_end += reply[key_r_token].as_sstring().length(); + +- const std::string& info_hash_str = arg.get_key_string("info_hash"); ++ SimpleString info_hash_str = req[key_a_infoHash].as_sstring(); + + if (info_hash_str.length() < HashString::size_data) + throw dht_error(dht_error_protocol, "info hash too short"); +@@ -296,35 +322,34 @@ DhtServer::create_get_peers_response(const Object& arg, const rak::socket_addres + + // If we're not tracking or have no peers, send closest nodes. + if (!tracker || tracker->empty()) { +- char compact[sizeof(compact_node_info) * DhtBucket::num_nodes]; +- char* end = m_router->store_closest_nodes(*info_hash, compact, compact + sizeof(compact)); +- +- if (end == compact) ++ SimpleString nodes = m_router->get_closest_nodes(*info_hash); ++ if (nodes.empty()) + throw dht_error(dht_error_generic, "No peers nor nodes"); + +- reply.insert_key("nodes", std::string(compact, end)); ++ reply[key_r_nodes] = nodes; + + } else { +- reply.insert_key("values", Object::create_list()).as_list().swap(tracker->get_peers().as_list()); ++ reply[key_r_values] = tracker->get_peers(); + } + } + + void +-DhtServer::create_announce_peer_response(const Object& arg, const rak::socket_address* sa, Object& reply) { +- const std::string& info_hash = arg.get_key_string("info_hash"); ++DhtServer::create_announce_peer_response(const DhtMessage& req, const rak::socket_address* sa, DhtMessage& reply) { ++ SimpleString info_hash = req[key_a_infoHash].as_sstring(); + + if (info_hash.length() < HashString::size_data) + throw dht_error(dht_error_protocol, "info hash too short"); + +- if (!m_router->token_valid(arg.get_key_string("token"), sa)) ++ if (!m_router->token_valid(req[key_a_token].as_sstring(), sa)) + throw dht_error(dht_error_protocol, "Token invalid."); + + DhtTracker* tracker = m_router->get_tracker(*HashString::cast_from(info_hash), true); +- tracker->add_peer(sa->sa_inet()->address_n(), arg.get_key_value("port")); ++ tracker->add_peer(sa->sa_inet()->address_n(), req[key_a_port].as_value()); + } + + void +-DhtServer::process_response(int transactionId, const HashString& id, const rak::socket_address* sa, Object& request) { ++DhtServer::process_response(const HashString& id, const rak::socket_address* sa, const DhtMessage& response) { ++ int transactionId = (unsigned char)response[key_t].as_sstring()[2]; + transaction_itr itr = m_transactions.find(DhtTransaction::key(sa, transactionId)); + + // Response to a transaction we don't have in our table. At this point it's +@@ -351,11 +376,9 @@ DhtServer::process_response(int transactionId, const HashString& id, const rak:: + if ((id != transaction->id() && transaction->id() != m_router->zero_id)) + return; + +- const Object& response = request.get_key("r"); +- + switch (transaction->type()) { + case DhtTransaction::DHT_FIND_NODE: +- parse_find_node_reply(transaction->as_find_node(), response.get_key_string("nodes")); ++ parse_find_node_reply(transaction->as_find_node(), response[key_r_nodes].as_sstring()); + break; + + case DhtTransaction::DHT_GET_PEERS: +@@ -381,7 +404,8 @@ DhtServer::process_response(int transactionId, const HashString& id, const rak:: + } + + void +-DhtServer::process_error(int transactionId, const rak::socket_address* sa, Object& request) { ++DhtServer::process_error(const rak::socket_address* sa, const DhtMessage& error) { ++ int transactionId = (unsigned char)error[key_t].as_sstring()[2]; + transaction_itr itr = m_transactions.find(DhtTransaction::key(sa, transactionId)); + + if (itr == m_transactions.end()) +@@ -399,7 +423,7 @@ DhtServer::process_error(int transactionId, const rak::socket_address* sa, Objec + } + + void +-DhtServer::parse_find_node_reply(DhtTransactionSearch* transaction, const std::string& nodes) { ++DhtServer::parse_find_node_reply(DhtTransactionSearch* transaction, SimpleString nodes) { + transaction->complete(true); + + if (sizeof(const compact_node_info) != 26) +@@ -421,16 +445,16 @@ DhtServer::parse_find_node_reply(DhtTransactionSearch* transaction, const std::s + } + + void +-DhtServer::parse_get_peers_reply(DhtTransactionGetPeers* transaction, const Object& response) { ++DhtServer::parse_get_peers_reply(DhtTransactionGetPeers* transaction, const DhtMessage& response) { + DhtAnnounce* announce = static_cast(transaction->as_search()->search()); + + transaction->complete(true); + +- if (response.has_key_list("values")) +- announce->receive_peers(response.get_key("values")); ++ if (response[key_r_values].is_sstring()) ++ announce->receive_peers(response[key_r_values].as_sstring()); + +- if (response.has_key_string("token")) +- add_transaction(new DhtTransactionAnnouncePeer(transaction->id(), transaction->address(), announce->target(), response.get_key_string("token")), packet_prio_low); ++ if (response[key_r_token].is_sstring()) ++ add_transaction(new DhtTransactionAnnouncePeer(transaction->id(), transaction->address(), announce->target(), response[key_r_token].as_sstring()), packet_prio_low); + + announce->update_status(); + } +@@ -490,17 +514,19 @@ DhtServer::create_query(transaction_itr itr, int tID, const rak::socket_address* + if (itr->second->id() == m_router->id()) + throw internal_error("DhtServer::create_query trying to send to itself."); + +- Object query = Object::create_map(); ++ DhtMessage query; + +- DhtTransaction* transaction = itr->second; +- char trans_id = tID; +- query.insert_key("t", std::string(&trans_id, 1)); +- query.insert_key("y", "q"); +- query.insert_key("q", queries[transaction->type()]); +- query.insert_key("v", PEER_VERSION); ++ // Transaction ID is a bencode string. ++ query[key_t] = SimpleString(query.data_end, 3); ++ *query.data_end++ = '1'; ++ *query.data_end++ = ':'; ++ *query.data_end++ = tID; + +- Object& q = query.insert_key("a", Object::create_map()); +- q.insert_key("id", m_router->str()); ++ DhtTransaction* transaction = itr->second; ++ query[key_y] = SimpleString("q", 1); ++ query[key_q] = SimpleString(queries[transaction->type()]); ++ query[key_v] = SimpleString(PEER_VERSION, 4); ++ query[key_a_id] = m_router->s_str(); + + switch (transaction->type()) { + case DhtTransaction::DHT_PING: +@@ -508,17 +534,17 @@ DhtServer::create_query(transaction_itr itr, int tID, const rak::socket_address* + break; + + case DhtTransaction::DHT_FIND_NODE: +- q.insert_key("target", transaction->as_find_node()->search()->target().str()); ++ query[key_a_target] = transaction->as_find_node()->search()->target().s_str(); + break; + + case DhtTransaction::DHT_GET_PEERS: +- q.insert_key("info_hash", transaction->as_get_peers()->search()->target().str()); ++ query[key_a_infoHash] = transaction->as_get_peers()->search()->target().s_str(); + break; + + case DhtTransaction::DHT_ANNOUNCE_PEER: +- q.insert_key("info_hash", transaction->as_announce_peer()->info_hash().str()); +- q.insert_key("token", transaction->as_announce_peer()->token()); +- q.insert_key("port", manager->connection_manager()->listen_port()); ++ query[key_a_infoHash] = transaction->as_announce_peer()->info_hash().s_str(); ++ query[key_a_token] = transaction->as_announce_peer()->token(); ++ query[key_a_port] = manager->connection_manager()->listen_port(); + break; + } + +@@ -530,31 +556,26 @@ DhtServer::create_query(transaction_itr itr, int tID, const rak::socket_address* + } + + void +-DhtServer::create_response(const Object& transactionId, const rak::socket_address* sa, Object& r) { +- Object reply = Object::create_map(); +- r.insert_key("id", m_router->str()); +- +- reply.insert_key("t", transactionId); +- reply.insert_key("y", "r"); +- reply.insert_key("r", r); +- reply.insert_key("v", PEER_VERSION); ++DhtServer::create_response(const DhtMessage& req, const rak::socket_address* sa, DhtMessage& reply) { ++ reply[key_r_id] = m_router->s_str(); ++ reply[key_t] = req[key_t]; ++ reply[key_y] = SimpleString("r", 1); ++ reply[key_v] = SimpleString(PEER_VERSION, 4); + + add_packet(new DhtTransactionPacket(sa, reply), packet_prio_reply); + } + + void +-DhtServer::create_error(const Object* transactionId, const rak::socket_address* sa, int num, const std::string& msg) { +- Object error = Object::create_map(); ++DhtServer::create_error(const DhtMessage& req, const rak::socket_address* sa, int num, const char* msg) { ++ DhtMessage error; + +- if (transactionId != NULL) +- error.insert_key("t", *transactionId); ++ if (req[key_t].is_sstring()) ++ error[key_t] = req[key_t]; + +- error.insert_key("y", "e"); +- error.insert_key("v", PEER_VERSION); +- +- Object& e = error.insert_key("e", Object::create_list()); +- e.insert_back(num); +- e.insert_back(msg); ++ error[key_y] = SimpleString("e", 1); ++ error[key_v] = SimpleString(PEER_VERSION, 4); ++ error[key_e_0] = num; ++ error[key_e_1] = SimpleString(msg); + + add_packet(new DhtTransactionPacket(sa, error), packet_prio_reply); + } +@@ -656,15 +677,12 @@ DhtServer::clear_transactions() { + void + DhtServer::event_read() { + uint32_t total = 0; +- std::istringstream sstream; +- +- sstream.imbue(std::locale::classic()); + + while (true) { + Object request; + rak::socket_address sa; + int type = '?'; +- const Object* transactionId = NULL; ++ DhtMessage message; + const HashString* nodeId = NULL; + + try { +@@ -675,31 +693,32 @@ DhtServer::event_read() { + break; + + total += read; +- sstream.str(std::string(buffer, read)); +- +- sstream >> request; + + // If it's not a valid bencode dictionary at all, it's probably not a DHT + // packet at all, so we don't throw an error to prevent bounce loops. +- if (sstream.fail() || !request.is_map()) ++ try { ++ staticMap_read_bencode(buffer, buffer + read, message); ++ } catch (bencode_error& e) { + continue; ++ } + +- if (!request.has_key("t")) ++ if (!message[key_t].is_sstring()) + throw dht_error(dht_error_protocol, "No transaction ID"); + +- transactionId = &request.get_key("t"); +- +- if (!request.has_key_string("y")) ++ if (!message[key_y].is_sstring()) + throw dht_error(dht_error_protocol, "No message type"); + +- if (request.get_key_string("y").length() != 1) ++ if (message[key_y].as_sstring().length() != 1) + throw dht_error(dht_error_bad_method, "Unsupported message type"); + +- type = request.get_key_string("y")[0]; ++ type = message[key_y].as_sstring()[0]; + + // Queries and replies have node ID in different dictionaries. + if (type == 'r' || type == 'q') { +- const std::string& nodeIdStr = request.get_key(type == 'q' ? "a" : "r").get_key_string("id"); ++ if (!message[type == 'q' ? key_a_id : key_r_id].is_sstring()) ++ throw dht_error(dht_error_protocol, "Invalid `id' value"); ++ ++ SimpleString nodeIdStr = message[type == 'q' ? key_a_id : key_r_id].as_sstring(); + + if (nodeIdStr.length() < HashString::size_data) + throw dht_error(dht_error_protocol, "`id' value too short"); +@@ -709,7 +728,8 @@ DhtServer::event_read() { + + // Sanity check the returned transaction ID. + if ((type == 'r' || type == 'e') && +- (!transactionId->is_string() || transactionId->as_string().length() != 1)) ++ (!message[key_t].is_sstring() || message[key_t].as_sstring().length() != 3 ++ || message[key_t].as_sstring()[0] != '1' || message[key_t].as_sstring()[1] != ':')) + throw dht_error(dht_error_protocol, "Invalid transaction ID type/length."); + + // Stupid broken implementations. +@@ -718,15 +738,15 @@ DhtServer::event_read() { + + switch (type) { + case 'q': +- process_query(*transactionId, *nodeId, &sa, request); ++ process_query(*nodeId, &sa, message); + break; + + case 'r': +- process_response(((unsigned char*)transactionId->as_string().c_str())[0], *nodeId, &sa, request); ++ process_response(*nodeId, &sa, message); + break; + + case 'e': +- process_error(((unsigned char*)transactionId->as_string().c_str())[0], &sa, request); ++ process_error(&sa, message); + break; + + default: +@@ -737,16 +757,19 @@ DhtServer::event_read() { + // so that if it repeatedly sends malformed replies we will drop it instead of propagating it + // to other nodes. + } catch (bencode_error& e) { +- if ((type == 'r' || type == 'e') && nodeId != NULL) ++ if ((type == 'r' || type == 'e') && nodeId != NULL) { + m_router->node_inactive(*nodeId, &sa); +- else +- create_error(transactionId, &sa, dht_error_protocol, std::string("Malformed packet: ") + e.what()); ++ } else { ++ snprintf(message.data_end, message.data + message.data_size - message.data_end - 1, "Malformed packet: %s", e.what()); ++ message.data[message.data_size - 1] = 0; ++ create_error(message, &sa, dht_error_protocol, message.data_end); ++ } + + } catch (dht_error& e) { + if ((type == 'r' || type == 'e') && nodeId != NULL) + m_router->node_inactive(*nodeId, &sa); + else +- create_error(transactionId, &sa, e.code(), e.what()); ++ create_error(message, &sa, e.code(), e.what()); + + } catch (network_error& e) { + +diff --git a/src/dht/dht_server.h b/src/dht/dht_server.h +index 1855b73..1f55f15 100644 +--- a/src/dht/dht_server.h ++++ b/src/dht/dht_server.h +@@ -46,6 +46,7 @@ + #include "net/throttle_node.h" + #include "download/download_info.h" // for SocketAddressCompact + #include "torrent/hash_string.h" ++#include "torrent/simple_string.h" + + #include "dht_transaction.h" + +@@ -56,6 +57,7 @@ class DhtNode; + class DhtRouter; + + class DownloadInfo; ++class DhtMessage; + class TrackerDht; + + // UDP server that handles the DHT node communications. +@@ -134,23 +136,23 @@ private: + + void start_write(); + +- void process_query(const Object& transaction, const HashString& id, const rak::socket_address* sa, Object& req); +- void process_response(int transaction, const HashString& id, const rak::socket_address* sa, Object& req); +- void process_error(int transaction, const rak::socket_address* sa, Object& req); ++ void process_query(const HashString& id, const rak::socket_address* sa, const DhtMessage& req); ++ void process_response(const HashString& id, const rak::socket_address* sa, const DhtMessage& req); ++ void process_error(const rak::socket_address* sa, const DhtMessage& error); + +- void parse_find_node_reply(DhtTransactionSearch* t, const std::string& nodes); +- void parse_get_peers_reply(DhtTransactionGetPeers* t, const Object& res); ++ void parse_find_node_reply(DhtTransactionSearch* t, SimpleString res); ++ void parse_get_peers_reply(DhtTransactionGetPeers* t, const DhtMessage& res); + + void find_node_next(DhtTransactionSearch* t); + + void add_packet(DhtTransactionPacket* packet, int priority); + void create_query(transaction_itr itr, int tID, const rak::socket_address* sa, int priority); +- void create_response(const Object& transactionID, const rak::socket_address* sa, Object& r); +- void create_error(const Object* transactionID, const rak::socket_address* sa, int num, const std::string& msg); ++ void create_response(const DhtMessage& req, const rak::socket_address* sa, DhtMessage& reply); ++ void create_error(const DhtMessage& req, const rak::socket_address* sa, int num, const char* msg); + +- void create_find_node_response(const Object& arg, Object& reply); +- void create_get_peers_response(const Object& arg, const rak::socket_address* sa, Object& reply); +- void create_announce_peer_response(const Object& arg, const rak::socket_address* sa, Object& reply); ++ void create_find_node_response(const DhtMessage& arg, DhtMessage& reply); ++ void create_get_peers_response(const DhtMessage& arg, const rak::socket_address* sa, DhtMessage& reply); ++ void create_announce_peer_response(const DhtMessage& arg, const rak::socket_address* sa, DhtMessage& reply); + + int add_transaction(DhtTransaction* t, int priority); + +diff --git a/src/dht/dht_tracker.cc b/src/dht/dht_tracker.cc +index 416dbf3..6e1afe9 100644 +--- a/src/dht/dht_tracker.cc ++++ b/src/dht/dht_tracker.cc +@@ -54,8 +54,8 @@ DhtTracker::add_peer(uint32_t addr, uint16_t port) { + + // Check if peer exists. If not, find oldest peer. + for (unsigned int i = 0; i < size(); i++) { +- if (m_peers[i].addr == compact.addr) { +- m_peers[i].port = compact.port; ++ if (m_peers[i].peer.addr == compact.addr) { ++ m_peers[i].peer.port = compact.port; + m_lastSeen[i] = cachedTime.seconds(); + return; + +@@ -77,10 +77,13 @@ DhtTracker::add_peer(uint32_t addr, uint16_t port) { + } + } + +-// Return compact info (6 bytes) for up to 30 peers, returning different +-// peers for each call if there are more. +-Object ++// Return compact info as bencoded string (8 bytes per peer) for up to 30 peers, ++// returning different peers for each call if there are more. ++SimpleString + DhtTracker::get_peers(unsigned int maxPeers) { ++ if (sizeof(BencodeAddress) != 8) ++ throw internal_error("DhtTracker::BencodeAddress is packed incorrectly."); ++ + PeerList::iterator first = m_peers.begin(); + PeerList::iterator last = m_peers.end(); + +@@ -94,11 +97,7 @@ DhtTracker::get_peers(unsigned int maxPeers) { + last = first + maxPeers; + } + +- Object peers = Object::create_list(); +- for (; first != last; ++first) +- peers.insert_back(std::string(first->c_str(), sizeof(*first))); +- +- return peers; ++ return SimpleString(first->bencode(), last->bencode() - first->bencode()); + } + + // Remove old announces. +@@ -107,9 +106,9 @@ DhtTracker::prune(uint32_t maxAge) { + uint32_t minSeen = cachedTime.seconds() - maxAge; + + for (unsigned int i = 0; i < m_lastSeen.size(); i++) +- if (m_lastSeen[i] < minSeen) m_peers[i].port = 0; ++ if (m_lastSeen[i] < minSeen) m_peers[i].peer.port = 0; + +- m_peers.erase(std::remove_if(m_peers.begin(), m_peers.end(), rak::on(rak::mem_ref(&SocketAddressCompact::port), std::bind2nd(std::equal_to(), 0))), m_peers.end()); ++ m_peers.erase(std::remove_if(m_peers.begin(), m_peers.end(), std::mem_fun_ref(&BencodeAddress::empty)), m_peers.end()); + m_lastSeen.erase(std::remove_if(m_lastSeen.begin(), m_lastSeen.end(), std::bind2nd(std::less(), minSeen)), m_lastSeen.end()); + + if (m_peers.size() != m_lastSeen.size()) +diff --git a/src/dht/dht_tracker.h b/src/dht/dht_tracker.h +index 8515dd0..53fd1e3 100644 +--- a/src/dht/dht_tracker.h ++++ b/src/dht/dht_tracker.h +@@ -43,6 +43,7 @@ + #include + + #include "download/download_info.h" // for SocketAddressCompact ++#include "torrent/simple_string.h" + + namespace torrent { + +@@ -65,14 +66,26 @@ public: + size_t size() const { return m_peers.size(); } + + void add_peer(uint32_t addr, uint16_t port); +- Object get_peers(unsigned int maxPeers = max_peers); ++ SimpleString get_peers(unsigned int maxPeers = max_peers); + + // Remove old announces from the tracker that have not reannounced for + // more than the given number of seconds. + void prune(uint32_t maxAge); + + private: +- typedef std::vector PeerList; ++ // We need to store the address as a bencoded string. ++ struct BencodeAddress { ++ char header[2]; ++ SocketAddressCompact peer; ++ ++ BencodeAddress(const SocketAddressCompact& p) : peer(p) { header[0] = '6'; header[1] = ':'; } ++ ++ const char* bencode() const { return header; } ++ ++ bool empty() const { return !peer.port; } ++ } __attribute__ ((packed)); ++ ++ typedef std::vector PeerList; + + PeerList m_peers; + std::vector m_lastSeen; +diff --git a/src/dht/dht_transaction.cc b/src/dht/dht_transaction.cc +index 2a6a8a6..0b3cfd0 100644 +--- a/src/dht/dht_transaction.cc ++++ b/src/dht/dht_transaction.cc +@@ -123,7 +123,7 @@ DhtSearch::trim(bool final) { + // We keep: + // - the max_contacts=18 closest good or unknown nodes and all nodes closer + // than them (to see if further searches find closer ones) +- // - for announces, also the 8 closest good nodes (i.e. nodes that have ++ // - for announces, also the 3 closest good nodes (i.e. nodes that have + // replied) to have at least that many for the actual announce + // - any node that currently has transactions pending + // +@@ -136,7 +136,7 @@ DhtSearch::trim(bool final) { + // node is new and unknown otherwise + + int needClosest = final ? 0 : max_contacts; +- int needGood = is_announce() ? DhtBucket::num_nodes : 0; ++ int needGood = is_announce() ? max_announce : 0; + + // We're done if we can't find any more nodes to contact. + m_next = end(); +@@ -252,7 +252,7 @@ DhtAnnounce::start_announce() { + } + + void +-DhtAnnounce::receive_peers(const Object& peers) { ++DhtAnnounce::receive_peers(SimpleString peers) { + m_tracker->receive_peers(peers); + } + +@@ -262,9 +262,9 @@ DhtAnnounce::update_status() { + } + + void +-DhtTransactionPacket::build_buffer(const Object& data) { ++DhtTransactionPacket::build_buffer(const DhtMessage& msg) { + char buffer[1500]; // If the message would exceed an Ethernet frame, something went very wrong. +- object_buffer_t result = object_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), &data); ++ object_buffer_t result = staticMap_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), msg); + + m_length = result.second - buffer; + m_data = new char[m_length]; +@@ -277,7 +277,6 @@ DhtTransaction::DhtTransaction(int quick_timeout, int timeout, const HashString& + m_sa(*sa), + m_timeout(cachedTime.seconds() + timeout), + m_quickTimeout(cachedTime.seconds() + quick_timeout), +- m_retry(3), + m_packet(NULL) { + + } +diff --git a/src/dht/dht_transaction.h b/src/dht/dht_transaction.h +index 194316d..43b42ab 100644 +--- a/src/dht/dht_transaction.h ++++ b/src/dht/dht_transaction.h +@@ -43,6 +43,7 @@ + + #include "dht/dht_node.h" + #include "torrent/hash_string.h" ++#include "torrent/static_map.h" + + namespace torrent { + +@@ -93,6 +94,9 @@ public: + // Number of closest potential contact nodes to keep. + static const unsigned int max_contacts = 18; + ++ // Number of closest nodes we actually announce to. ++ static const unsigned int max_announce = 3; ++ + DhtSearch(const HashString& target, const DhtBucket& contacts); + virtual ~DhtSearch(); + +@@ -178,22 +182,66 @@ public: + // counts announces instead. + const_accessor start_announce(); + +- void receive_peers(const Object& peer_list); ++ void receive_peers(SimpleString peers); + void update_status(); + + private: + TrackerDht* m_tracker; + }; + ++// Possible bencode keys in a DHT message. ++enum dht_keys { ++ key_a_id, ++ key_a_infoHash, ++ key_a_port, ++ key_a_target, ++ key_a_token, ++ ++ key_e_0, ++ key_e_1, ++ ++ key_q, ++ ++ key_r_id, ++ key_r_nodes, ++ key_r_token, ++ key_r_values, ++ ++ key_t, ++ key_v, ++ key_y, ++ ++ key_LAST, ++}; ++ ++class DhtMessage : public StaticMap { ++public: ++ typedef StaticMap base_type; ++ typedef StaticMapKeys::mapping_type mapping_type; ++ ++ DhtMessage() : data_end(data) {}; ++ ++ // Must be big enough to hold one of the possible variable-sized reply data. ++ // Currently either: ++ // - error message (size doesn't really matter, it'll be truncated at worst) ++ // - announce token (8 bytes, needs 20 bytes buffer to build) ++ // Never more than one of the above. ++ // And additionally for queries we send: ++ // - transaction ID (3 bytes) ++ static const size_t data_size = 64; ++ char data[data_size]; ++ char* data_end; ++}; ++ + // Class holding transaction data to be transmitted. + class DhtTransactionPacket { + public: + // transaction packet +- DhtTransactionPacket(const rak::socket_address* s, const Object& d, unsigned int id, DhtTransaction* t) ++ DhtTransactionPacket(const rak::socket_address* s, const DhtMessage& d, unsigned int id, DhtTransaction* t) + : m_sa(*s), m_id(id), m_transaction(t) { build_buffer(d); }; + + // non-transaction packet +- DhtTransactionPacket(const rak::socket_address* s, const Object& d) ++ DhtTransactionPacket(const rak::socket_address* s, const DhtMessage& d) + : m_sa(*s), m_id(-cachedTime.seconds()), m_transaction(NULL) { build_buffer(d); }; + + ~DhtTransactionPacket() { delete[] m_data; } +@@ -214,7 +262,7 @@ public: + DhtTransaction* transaction() { return m_transaction; } + + private: +- void build_buffer(const Object& data); ++ void build_buffer(const DhtMessage& data); + + rak::socket_address m_sa; + char* m_data; +@@ -255,9 +303,6 @@ public: + int quick_timeout() { return m_quickTimeout; } + bool has_quick_timeout() { return m_hasQuickTimeout; } + +- int dec_retry() { return m_retry--; } +- int retry() { return m_retry; } +- + DhtTransactionPacket* packet() { return m_packet; } + void set_packet(DhtTransactionPacket* p) { m_packet = p; } + +@@ -282,7 +327,6 @@ private: + rak::socket_address m_sa; + int m_timeout; + int m_quickTimeout; +- int m_retry; + DhtTransactionPacket* m_packet; + }; + +@@ -337,7 +381,7 @@ public: + + class DhtTransactionAnnouncePeer : public DhtTransaction { + public: +- DhtTransactionAnnouncePeer(const HashString& id, const rak::socket_address* sa, const HashString& infoHash, const std::string& token) ++ DhtTransactionAnnouncePeer(const HashString& id, const rak::socket_address* sa, const HashString& infoHash, SimpleString token) + : DhtTransaction(-1, 30, id, sa), + m_infoHash(infoHash), + m_token(token) { } +@@ -345,11 +389,11 @@ public: + virtual transaction_type type() { return DHT_ANNOUNCE_PEER; } + + const HashString& info_hash() { return m_infoHash; } +- const std::string& token() { return m_token; } ++ SimpleString token() { return m_token; } + + private: + HashString m_infoHash; +- std::string m_token; ++ SimpleString m_token; + }; + + inline bool +diff --git a/src/download/download_constructor.cc b/src/download/download_constructor.cc +index fc2a272..f37f848 100644 +--- a/src/download/download_constructor.cc ++++ b/src/download/download_constructor.cc +@@ -36,6 +36,7 @@ + + #include "config.h" + ++#include + #include + #include + #include +diff --git a/src/net/address_list.cc b/src/net/address_list.cc +index 2fc3992..e5cf3cb 100644 +--- a/src/net/address_list.cc ++++ b/src/net/address_list.cc +@@ -70,7 +70,7 @@ AddressList::parse_address_normal(const Object::list_type& b) { + } + + void +-AddressList::parse_address_compact(const std::string& s) { ++AddressList::parse_address_compact(SimpleString s) { + if (sizeof(const SocketAddressCompact) != 6) + throw internal_error("ConnectionList::AddressList::parse_address_compact(...) bad struct size."); + +@@ -79,4 +79,18 @@ AddressList::parse_address_compact(const std::string& s) { + std::back_inserter(*this)); + } + ++void ++AddressList::parse_address_bencode(SimpleString s) { ++ if (sizeof(const SocketAddressCompact) != 6) ++ throw internal_error("AddressList::parse_address_bencode(...) bad struct size."); ++ ++ while (s.length() >= 2 + sizeof(SocketAddressCompact)) { ++ if (s[0] != '6' || s[1] != ':') ++ break; ++ ++ insert(end(), *reinterpret_cast(s.c_str() + 2)); ++ s = SimpleString(s.c_str() + 2 + sizeof(SocketAddressCompact), s.length() - 2 - sizeof(SocketAddressCompact)); ++ } ++} ++ + } +diff --git a/src/net/address_list.h b/src/net/address_list.h +index e4d2009..10dbac4 100644 +--- a/src/net/address_list.h ++++ b/src/net/address_list.h +@@ -42,6 +42,7 @@ + #include + + #include ++#include + + namespace torrent { + +@@ -49,7 +50,8 @@ class AddressList : public std::list { + public: + // Parse normal or compact list of addresses and add to AddressList + void parse_address_normal(const Object::list_type& b); +- void parse_address_compact(const std::string& s); ++ void parse_address_compact(SimpleString s); ++ void parse_address_bencode(SimpleString s); + + private: + static rak::socket_address parse_address(const Object& b); +diff --git a/src/protocol/extensions.cc b/src/protocol/extensions.cc +index f3464af..7cbf6e3 100644 +--- a/src/protocol/extensions.cc ++++ b/src/protocol/extensions.cc +@@ -37,7 +37,7 @@ + #include "config.h" + + #include +-#include ++#include + + #include + +@@ -49,18 +49,62 @@ + #include "torrent/object_stream.h" + #include "torrent/peer/connection_list.h" + #include "torrent/peer/peer_info.h" +-#include "tracker/tracker_http.h" ++#include "torrent/static_map.h" + #include "manager.h" + + #include "extensions.h" + + namespace torrent { + +-const char* ProtocolExtension::message_keys[] = { +- "HANDSHAKE", +- "ut_pex", ++enum ext_handshake_keys { ++ key_e, ++ key_m_utPex, ++ key_p, ++ key_reqq, ++ key_v, ++ key_handshake_LAST + }; + ++enum ext_pex_keys { ++ key_pex_added, ++ key_pex_LAST ++}; ++ ++class ExtHandshakeMessage : public StaticMap { ++public: ++ typedef StaticMap base_type; ++ typedef StaticMapKeys::mapping_type mapping_type; ++}; ++ ++class ExtPEXMessage : public StaticMap { ++public: ++ typedef StaticMap base_type; ++ typedef StaticMapKeys::mapping_type mapping_type; ++}; ++ ++ExtHandshakeMessage::mapping_type ext_handshake_key_names[ExtHandshakeMessage::length] = { ++ { key_e, "e" }, ++ { key_m_utPex, "m::ut_pex" }, ++ { key_p, "p" }, ++ { key_reqq, "reqq" }, ++ { key_v, "v" }, ++}; ++ ++ExtPEXMessage::mapping_type ext_pex_key_names[ExtPEXMessage::length] = { ++ { key_pex_added, "added" }, ++}; ++ ++ext_handshake_keys message_keys[ProtocolExtension::FIRST_INVALID] = { ++ key_handshake_LAST, // Handshake, not actually used. ++ key_m_utPex, ++}; ++ ++template<> ++const ExtHandshakeMessage::key_map_init ExtHandshakeMessage::base_type::keyMap(ext_handshake_key_names); ++ ++template<> ++const ExtPEXMessage::key_map_init ExtPEXMessage::base_type::keyMap(ext_pex_key_names); ++ + void + ProtocolExtension::cleanup() { + // if (is_default()) +@@ -105,23 +149,21 @@ ProtocolExtension::unset_local_enabled(int t) { + + DataBuffer + ProtocolExtension::generate_handshake_message() { +- Object map = Object::create_map(); +- Object message = Object::create_map(); +- +- map.insert_key(message_keys[UT_PEX], is_local_enabled(UT_PEX) ? 1 : 0); ++ ExtHandshakeMessage message; + + // Add "e" key if encryption is enabled, set it to 1 if we require + // encryption for incoming connections, or 0 otherwise. + if ((manager->connection_manager()->encryption_options() & ConnectionManager::encryption_allow_incoming) != 0) +- message.insert_key("e", (manager->connection_manager()->encryption_options() & ConnectionManager::encryption_require) != 0); ++ message[key_e] = (manager->connection_manager()->encryption_options() & ConnectionManager::encryption_require) != 0; ++ ++ message[key_p] = manager->connection_manager()->listen_port(); ++ message[key_v] = SimpleString("libTorrent " VERSION); ++ message[key_reqq] = 2048; // maximum request queue size + +- message.insert_key("m", map); +- message.insert_key("p", manager->connection_manager()->listen_port()); +- message.insert_key("v", "libTorrent " VERSION); +- message.insert_key("reqq", 2048); // maximum request queue size ++ message[key_m_utPex] = is_local_enabled(UT_PEX) ? UT_PEX : 0; + + char buffer[1024]; +- object_buffer_t result = object_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), &message); ++ object_buffer_t result = staticMap_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), message); + + int length = result.second - buffer; + char* copy = new char[length]; +@@ -130,21 +172,30 @@ ProtocolExtension::generate_handshake_message() { + return DataBuffer(copy, copy + length); + } + +-DataBuffer +-ProtocolExtension::generate_toggle_message(ProtocolExtension::MessageType t, bool on) { +- // TODO: Check if we're accepting this message type? ++inline DataBuffer ++ProtocolExtension::build_bencode(size_t maxLength, const char* format, ...) { ++ char* b = new char[maxLength]; + +- // Manually create bencoded map { "m" => { message_keys[t] => on ? t : 0 } } +- char* b = new char[32]; +- unsigned int length = snprintf(b, 32, "d1:md%zu:%si%deee", strlen(message_keys[t]), message_keys[t], on ? t : 0); ++ va_list args; ++ va_start(args, format); ++ unsigned int length = vsnprintf(b, maxLength, format, args); ++ va_end(args); + +- if (length > 32) +- throw internal_error("ProtocolExtension::toggle_message wrote past buffer."); ++ if (length > maxLength) ++ throw internal_error("ProtocolExtension::build_bencode wrote past buffer."); + + return DataBuffer(b, b + length); + } + + DataBuffer ++ProtocolExtension::generate_toggle_message(MessageType t, bool on) { ++ // TODO: Check if we're accepting this message type? ++ ++ // Manually create bencoded map { "m" => { message_keys[t] => on ? t : 0 } } ++ return build_bencode(32, "d1:md%zu:%si%deee", strlen(ext_handshake_key_names[message_keys[t]].key) - 3, ext_handshake_key_names[message_keys[t]].key + 3, on ? t : 0); ++} ++ ++DataBuffer + ProtocolExtension::generate_ut_pex_message(const PEXList& added, const PEXList& removed) { + if (added.empty() && removed.empty()) + return DataBuffer(); +@@ -195,37 +246,30 @@ ProtocolExtension::read_start(int type, uint32_t length, bool skip) { + + void + ProtocolExtension::read_done() { +- if (m_readType == SKIP_EXTENSION) { +- delete [] m_read; +- m_read = NULL; +- return; +- } ++ try { ++ switch(m_readType) { ++ case SKIP_EXTENSION: ++ break; + +- std::stringstream s(std::string(m_read, m_readPos)); +- s.imbue(std::locale::classic()); ++ case HANDSHAKE: ++ parse_handshake(); ++ break; + +- delete [] m_read; +- m_read = NULL; +- +- Object message; +- s >> message; ++ case UT_PEX: ++ parse_ut_pex(); ++ break; + +- if (s.fail() || !message.is_map()) +- throw communication_error("Invalid extension message."); +- +- switch(m_readType) { +- case HANDSHAKE: +- parse_handshake(message); +- break; +- +- case UT_PEX: +- parse_ut_pex(message); +- break; ++ default: ++ throw internal_error("ProtocolExtension::read_done called with invalid extension type."); ++ } + +- default: +- throw internal_error("ProtocolExtension::down_extension_finished called with invalid extension type."); ++ } catch (bencode_error& e) { ++ // Ignore malformed messages. + } + ++ delete [] m_read; ++ m_read = NULL; ++ + m_readType = FIRST_INVALID; + m_flags |= flag_received_ext; + } +@@ -242,24 +286,22 @@ ProtocolExtension::peer_toggle_remote(int type, bool active) { + } + + void +-ProtocolExtension::parse_handshake(const Object& message) { +- if (message.has_key_map("m")) { +- const Object& idMap = message.get_key("m"); ++ProtocolExtension::parse_handshake() { ++ ExtHandshakeMessage message; ++ staticMap_read_bencode(m_read, m_readPos, message); + +- for (int t = HANDSHAKE + 1; t < FIRST_INVALID; t++) { +- if (!idMap.has_key_value(message_keys[t])) +- continue; ++ for (int t = HANDSHAKE + 1; t < FIRST_INVALID; t++) { ++ if (!message[message_keys[t]].is_value()) ++ continue; + +- uint8_t id = idMap.get_key_value(message_keys[t]); ++ uint8_t id = message[message_keys[t]].as_value(); + +- set_remote_supported(t); ++ set_remote_supported(t); + +- if (id != m_idMap[t - 1]) { +- peer_toggle_remote(t, id != 0); +- +- m_idMap[t - 1] = id; +- } ++ if (id != m_idMap[t - 1]) { ++ peer_toggle_remote(t, id != 0); + ++ m_idMap[t - 1] = id; + } + } + +@@ -271,29 +313,32 @@ ProtocolExtension::parse_handshake(const Object& message) { + unset_local_enabled(t); + } + +- if (message.has_key_value("p")) { +- uint16_t port = message.get_key_value("p"); ++ if (message[key_p].is_value()) { ++ uint16_t port = message[key_p].as_value(); + + if (port > 0) + m_peerInfo->set_listen_port(port); + } + +- if (message.has_key_value("reqq")) +- m_maxQueueLength = message.get_key_value("reqq"); ++ if (message[key_reqq].is_value()) ++ m_maxQueueLength = message[key_reqq].as_value(); + + m_flags &= ~flag_initial_handshake; + } + + void +-ProtocolExtension::parse_ut_pex(const Object& message) { ++ProtocolExtension::parse_ut_pex() { + // Ignore message if we're still in the handshake (no connection + // yet), or no peers are present. + ++ ExtPEXMessage message; ++ staticMap_read_bencode(m_read, m_readPos, message); ++ + // TODO: Check if pex is enabled? +- if (!message.has_key_string("added")) ++ if (!message[key_pex_added].is_sstring()) + return; + +- const std::string& peers = message.get_key_string("added"); ++ SimpleString peers = message[key_pex_added].as_sstring(); + if (peers.empty()) + return; + +diff --git a/src/protocol/extensions.h b/src/protocol/extensions.h +index 1c370fc..96ed652 100644 +--- a/src/protocol/extensions.h ++++ b/src/protocol/extensions.h +@@ -46,6 +46,13 @@ + #include "download/download_info.h" + #include "net/data_buffer.h" + ++// Not really important, so no need to make this a configure check. ++#ifdef __GNUC__ ++#define ATTRIBUTE_PRINTF(num) __attribute__ ((format (printf, num, num+1))) ++#else ++#define ATTRIBUTE_PRINTF(num) ++#endif ++ + namespace torrent { + + class ProtocolExtension { +@@ -71,8 +78,6 @@ public: + static const int flag_local_enabled_base = 1<<8; + static const int flag_remote_supported_base = 1<<16; + +- static const char* message_keys[FIRST_INVALID]; +- + // Number of extensions we support, not counting handshake. + static const int extension_count = FIRST_INVALID - HANDSHAKE - 1; + +@@ -128,8 +133,10 @@ public: + void reset() { std::memset(&m_idMap, 0, sizeof(m_idMap)); } + + private: +- void parse_handshake(const Object& message); +- void parse_ut_pex(const Object& message); ++ void parse_handshake(); ++ void parse_ut_pex(); ++ ++ static DataBuffer build_bencode(size_t maxLength, const char* format, ...) ATTRIBUTE_PRINTF(2); + + void peer_toggle_remote(int type, bool active); + +diff --git a/src/torrent/Makefile.am b/src/torrent/Makefile.am +index bec124d..820ce52 100644 +--- a/src/torrent/Makefile.am ++++ b/src/torrent/Makefile.am +@@ -41,6 +41,9 @@ libsub_torrent_la_SOURCES = \ + rate.h \ + resume.cc \ + resume.h \ ++ simple_string.h \ ++ static_map.cc \ ++ static_map.h \ + throttle.cc \ + throttle.h \ + torrent.cc \ +@@ -74,6 +77,8 @@ libtorrentinclude_HEADERS = \ + poll_select.h \ + rate.h \ + resume.h \ ++ simple_string.h \ ++ static_map.h \ + throttle.h \ + torrent.h \ + tracker.h \ +diff --git a/src/torrent/hash_string.h b/src/torrent/hash_string.h +index f62d450..14623f7 100644 +--- a/src/torrent/hash_string.h ++++ b/src/torrent/hash_string.h +@@ -44,6 +44,7 @@ + #include + #include + #include ++#include + + namespace torrent { + +@@ -85,6 +86,8 @@ public: + + std::string str() const { return std::string(m_data, size_data); } + ++ SimpleString s_str() const { return SimpleString(m_data, size_data); } ++ + void clear(int v = 0) { std::memset(data(), v, size()); } + + void assign(const value_type* src) { std::memcpy(data(), src, size()); } +@@ -96,6 +99,7 @@ public: + // size_data. + static const HashString* cast_from(const char* src) { return (const HashString*)src; } + static const HashString* cast_from(const std::string& src) { return (const HashString*)src.c_str(); } ++ static const HashString* cast_from(const SimpleString& src){ return (const HashString*)src.c_str(); } + + static HashString* cast_from(char* src) { return (HashString*)src; } + +diff --git a/src/torrent/object.cc b/src/torrent/object.cc +index 2b1cf41..3a0bcae 100644 +--- a/src/torrent/object.cc ++++ b/src/torrent/object.cc +@@ -195,6 +195,7 @@ Object::operator = (const Object& src) { + case TYPE_STRING: m_string = new string_type(*src.m_string); break; + case TYPE_LIST: m_list = new list_type(*src.m_list); break; + case TYPE_MAP: m_map = new map_type(*src.m_map); break; ++ case TYPE_SSTRING:m_sstring = src.m_sstring; break; + } + + return *this; +diff --git a/src/torrent/object.h b/src/torrent/object.h +index 7ad040b..6cc4e4a 100644 +--- a/src/torrent/object.h ++++ b/src/torrent/object.h +@@ -42,6 +42,7 @@ + #include + #include + #include ++#include + + namespace torrent { + +@@ -82,13 +83,16 @@ public: + TYPE_VALUE, + TYPE_STRING, + TYPE_LIST, +- TYPE_MAP ++ TYPE_MAP, ++ TYPE_SSTRING, // Only used in StaticMap. + }; + + Object() : m_flags(TYPE_NONE) {} + Object(const value_type v) : m_flags(TYPE_VALUE), m_value(v) {} + Object(const char* s) : m_flags(TYPE_STRING), m_string(new string_type(s)) {} + Object(const string_type& s) : m_flags(TYPE_STRING), m_string(new string_type(s)) {} ++ Object(const char* s, size_t l) : m_flags(TYPE_SSTRING), m_sstring(SimpleString(s, l)) {} ++ Object(SimpleString s) : m_flags(TYPE_SSTRING), m_sstring(s) {} + Object(const Object& b); + + ~Object() { clear(); } +@@ -96,6 +100,7 @@ public: + // Move this out of the class namespace, call them create_object_. + static Object create_value() { return Object(value_type()); } + static Object create_string() { return Object(string_type()); } ++ static Object create_sstring(){ return Object(SimpleString()); } + static Object create_list() { Object tmp; tmp.m_flags = TYPE_LIST; tmp.m_list = new list_type(); return tmp; } + static Object create_map() { Object tmp; tmp.m_flags = TYPE_MAP; tmp.m_map = new map_type(); return tmp; } + +@@ -120,6 +125,7 @@ public: + bool is_string() const { return type() == TYPE_STRING; } + bool is_list() const { return type() == TYPE_LIST; } + bool is_map() const { return type() == TYPE_MAP; } ++ bool is_sstring() const { return type() == TYPE_SSTRING; } + + value_type& as_value() { check_throw(TYPE_VALUE); return m_value; } + const value_type& as_value() const { check_throw(TYPE_VALUE); return m_value; } +@@ -133,6 +139,9 @@ public: + map_type& as_map() { check_throw(TYPE_MAP); return *m_map; } + const map_type& as_map() const { check_throw(TYPE_MAP); return *m_map; } + ++ SimpleStringBase& as_sstring() { check_throw(TYPE_SSTRING); return m_sstring; } ++ SimpleString as_sstring() const { check_throw(TYPE_SSTRING); return m_sstring; } ++ + bool has_key(const key_type& k) const { check_throw(TYPE_MAP); return m_map->find(k) != m_map->end(); } + bool has_key_value(const key_type& k) const { check_throw(TYPE_MAP); return check(m_map->find(k), TYPE_VALUE); } + bool has_key_string(const key_type& k) const { check_throw(TYPE_MAP); return check(m_map->find(k), TYPE_STRING); } +@@ -200,6 +209,7 @@ public: + string_type* m_string; + list_type* m_list; + map_type* m_map; ++ SimpleStringBase m_sstring; + }; + }; + +@@ -211,6 +221,7 @@ Object::Object(const Object& b) : m_flags(b.type()) { + case TYPE_STRING: m_string = new string_type(*b.m_string); break; + case TYPE_LIST: m_list = new list_type(*b.m_list); break; + case TYPE_MAP: m_map = new map_type(*b.m_map); break; ++ case TYPE_SSTRING:m_sstring = b.m_sstring; break; + } + } + +@@ -222,6 +233,7 @@ Object::clear() { + case TYPE_STRING: delete m_string; break; + case TYPE_LIST: delete m_list; break; + case TYPE_MAP: delete m_map; break; ++ case TYPE_SSTRING:break; + } + + // Only clear type? +diff --git a/src/torrent/object_stream.cc b/src/torrent/object_stream.cc +index 18eb849..73c816b 100644 +--- a/src/torrent/object_stream.cc ++++ b/src/torrent/object_stream.cc +@@ -38,12 +38,14 @@ + + #include + #include ++#include + #include + + #include "utils/sha1.h" + + #include "object.h" + #include "object_stream.h" ++#include "static_map.h" + + namespace torrent { + +@@ -63,6 +65,18 @@ object_read_string(std::istream* input, std::string& str) { + return !input->fail(); + } + ++Object ++object_get_sstring(const char** buffer) { ++ /*const*/ char* next; ++ size_t length = strtoumax(*buffer, &next, 10); ++ ++ if (next == *buffer || *next != ':') ++ return Object(); ++ ++ *buffer = next + 1 + length; ++ return Object(next + 1, length); ++} ++ + // Could consider making this non-recursive, but they seldomly are + // deep enough to make that worth-while. + void +@@ -159,6 +173,133 @@ object_read_bencode(std::istream* input, Object* object, uint32_t depth) { + object->clear(); + } + ++const char* ++staticMap_read_bencode_c(const char* buffer, const char* bufferEnd, uint32_t depth, Object* values, const StaticMapKeys& keys, bool discard) { ++ if (buffer >= bufferEnd) ++ return bufferEnd; ++ ++ // Undecoded bencode object. ++ if (!discard && keys.type() == StaticMapKeys::TYPE_BENCODE) { ++ const char* begin = buffer; ++ buffer = staticMap_read_bencode_c(buffer, bufferEnd, ++depth, values, keys, true); ++ values[keys.index_begin()] = SimpleString(begin, buffer - begin); ++ return buffer; ++ } ++ ++ if (!discard && keys.type() == StaticMapKeys::TYPE_BENCODE_LIST && *buffer != 'l') ++ discard = true; ++ ++ switch (*buffer) { ++ case 'i': { ++ char* next; ++ intmax_t value = strtoimax(++buffer, &next, 10); ++ ++ if (next == buffer || next > bufferEnd || *next != 'e') ++ break; ++ ++ if (!discard && keys.type() == StaticMapKeys::TYPE_VALUE) ++ values[keys.index_begin()] = (int64_t)value; ++ ++ return next + 1; ++ } ++ ++ case 'l': { ++ ++buffer; ++ if (++depth >= 1024) ++ break; ++ ++ // Want undecoded bencode list: find end of list. ++ if (!discard && keys.type() == StaticMapKeys::TYPE_BENCODE_LIST) { ++ const char* end = buffer; ++ while (end < bufferEnd && *end != 'e') ++ end = staticMap_read_bencode_c(end, bufferEnd, depth, values, keys, true); ++ ++ values[keys.index_begin()] = SimpleString(buffer, end - buffer); ++ return ++end; ++ } ++ ++ StaticMapKeys::const_iterator itr = keys.begin(); ++ while (buffer != bufferEnd) { ++ if (*buffer == 'e') ++ return ++buffer; ++ ++ discard |= itr == keys.end(); ++ buffer = staticMap_read_bencode_c(buffer, bufferEnd, depth, values, discard ? keys : *itr, discard); ++ ++ if (itr != keys.end()) ++ ++itr; ++ } ++ ++ break; ++ } ++ ++ case 'd': { ++ ++buffer; ++ if (++depth >= 1024) ++ break; ++ ++ StaticMapKeys::const_iterator itr = keys.begin(); ++ SimpleString last; ++ bool discardThis = discard; ++ ++ while (buffer != bufferEnd) { ++ if (*buffer == 'e') ++ return ++buffer; ++ ++ Object keyObj = object_get_sstring(&buffer); ++ if (!keyObj.is_sstring()) ++ break; ++ ++ SimpleString key = keyObj.as_sstring(); ++ if (key.end() >= bufferEnd) ++ break; ++ ++ if (key < last) { ++ itr = keys.begin(); ++ discardThis = discard; ++ } ++ ++ discardThis |= itr == keys.end(); ++ int cmp = discardThis ? -1 : key.cmp(itr->key()); ++ while (cmp > 0) { ++ if (++itr == keys.end()) { ++ cmp = -1; ++ discardThis = true; ++ break; ++ } ++ ++ cmp = key.cmp(itr->key()); ++ } ++ ++ buffer = staticMap_read_bencode_c(buffer, bufferEnd, depth, values, cmp ? keys : *itr, cmp); ++ ++ last = key; ++ } ++ ++ break; ++ } ++ ++ default: ++ if (*buffer < '0' || *buffer > '9') ++ break; ++ ++ Object strObj = object_get_sstring(&buffer); ++ if (!strObj.is_sstring()) ++ break; ++ ++ SimpleString str = strObj.as_sstring(); ++ if (str.end() >= bufferEnd) ++ break; ++ ++ if (!discard && keys.type() == StaticMapKeys::TYPE_VALUE) ++ values[keys.index_begin()] = str; ++ ++ return str.end(); ++ } ++ ++ throw bencode_error("Invalid bencode data."); ++} ++ + void + object_write_bencode(std::ostream* output, const Object* object) { + char buffer[1024]; +@@ -267,6 +408,7 @@ void + object_write_bencode_c_object(object_write_data_t* output, const Object* object) { + switch (object->type()) { + case Object::TYPE_NONE: ++ case Object::TYPE_SSTRING: + break; + + case Object::TYPE_VALUE: +@@ -306,6 +448,86 @@ object_write_bencode_c_object(object_write_data_t* output, const Object* object) + } + } + ++void ++staticMap_write_bencode_c_values(object_write_data_t* output, const Object* values, const StaticMapKeys& keys) { ++ if (keys.type() == StaticMapKeys::TYPE_LIST) { ++ size_t indexEnd = keys.index_begin(); ++ while (indexEnd < keys.index_end() && values[indexEnd].type() != Object::TYPE_NONE) ++ indexEnd++; ++ ++ // Empty list? Drop it. Sparse lists are not possible so only check first element. ++ if (indexEnd == keys.index_begin()) ++ return; ++ ++ object_write_bencode_c_char(output, 'l'); ++ StaticMapKeys::const_iterator itr = keys.begin(); ++ size_t index = keys.index_begin(); ++ while (index < indexEnd) { ++ staticMap_write_bencode_c_values(output, values, *itr); ++ index = itr->index_end(); ++ if (++itr == keys.end() && index != indexEnd) ++ throw internal_error("staticMap_write_bencode_c_values reached end of list before end of index list."); ++ } ++ object_write_bencode_c_char(output, 'e'); ++ ++ } else if (keys.type() == StaticMapKeys::TYPE_DICT) { ++ // Find next non-empty entry. ++ size_t next = keys.index_begin(); ++ while (values[next].type() == Object::TYPE_NONE) ++ if (++next == keys.index_end()) ++ return; ++ ++ object_write_bencode_c_char(output, 'd'); ++ StaticMapKeys::const_iterator itr = keys.begin(); ++ while (next < keys.index_end()) { ++ while (itr->index_end() <= next) ++ if (++itr == keys.end()) ++ throw internal_error("staticMap_write_bencode_c_values reached end of keys before end of index list."); ++ ++ object_write_bencode_c_value(output, itr->key().size()); ++ object_write_bencode_c_char(output, ':'); ++ object_write_bencode_c_string(output, itr->key().c_str(), itr->key().size()); ++ ++ staticMap_write_bencode_c_values(output, values, *itr); ++ ++ next = itr->index_end(); ++ while (next < keys.index_end() && values[next].type() == Object::TYPE_NONE) ++ ++next; ++ } ++ object_write_bencode_c_char(output, 'e'); ++ ++ // Undecoded bencode value. ++ } else if (keys.type() == StaticMapKeys::TYPE_BENCODE) { ++ SimpleString value = values[keys.index_begin()].as_sstring(); ++ object_write_bencode_c_string(output, value.c_str(), value.size()); ++ ++ } else if (keys.type() == StaticMapKeys::TYPE_BENCODE_LIST) { ++ SimpleString value = values[keys.index_begin()].as_sstring(); ++ object_write_bencode_c_char(output, 'l'); ++ object_write_bencode_c_string(output, value.c_str(), value.size()); ++ object_write_bencode_c_char(output, 'e'); ++ ++ } else if (keys.type() != StaticMapKeys::TYPE_VALUE) { ++ throw internal_error("staticMap_write_bencode_c_values received key keys with invalid values type."); ++ ++ } else if (values[keys.index_begin()].type() == Object::TYPE_NONE) { ++ ++ } else if (values[keys.index_begin()].type() == Object::TYPE_VALUE) { ++ object_write_bencode_c_char(output, 'i'); ++ object_write_bencode_c_value(output, values[keys.index_begin()].as_value()); ++ object_write_bencode_c_char(output, 'e'); ++ ++ } else if (values[keys.index_begin()].type() == Object::TYPE_SSTRING) { ++ SimpleString value = values[keys.index_begin()].as_sstring(); ++ object_write_bencode_c_value(output, value.size()); ++ object_write_bencode_c_char(output, ':'); ++ object_write_bencode_c_string(output, value.c_str(), value.size()); ++ ++ } else { ++ throw internal_error("staticMap_write_bencode_c_values received key keys with invalid values type."); ++ } ++} ++ + object_buffer_t + object_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* object) { + object_write_data_t output; +@@ -327,6 +549,32 @@ object_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buf + } + + object_buffer_t ++staticMap_write_bencode_c_wrap(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* values, const StaticMapKeys& map) { ++ object_write_data_t output; ++ output.writeFunc = writeFunc; ++ output.data = data; ++ output.buffer = buffer; ++ output.pos = buffer.first; ++ ++ staticMap_write_bencode_c_values(&output, values, map); ++#ifdef USE_EXTRA_DEBUG ++ std::istringstream sstream; ++ sstream.imbue(std::locale::classic()); ++ sstream.str(std::string(output.buffer.first, output.pos)); ++ Object request; ++ sstream >> request; ++ if (sstream.fail()) ++ throw internal_error("staticMap_write_bencode_c_wrap failed to create valid bencode format."); ++#endif ++ ++ // Don't flush the buffer. ++ if (output.pos == output.buffer.first) ++ return output.buffer; ++ ++ return output.writeFunc(output.data, object_buffer_t(output.buffer.first, output.pos)); ++} ++ ++object_buffer_t + object_write_to_buffer(void* data, object_buffer_t buffer) { + if (buffer.first == buffer.second) + throw internal_error("object_write_to_buffer(...) buffer overflow."); +diff --git a/src/torrent/object_stream.h b/src/torrent/object_stream.h +index 41cf82a..b399bf7 100644 +--- a/src/torrent/object_stream.h ++++ b/src/torrent/object_stream.h +@@ -43,6 +43,10 @@ + + namespace torrent { + ++template ++class StaticMap; ++class StaticMapKeys; ++ + std::string object_sha1(const Object* object) LIBTORRENT_EXPORT; + + // Assumes the stream's locale has been set to POSIX or C. Max depth +@@ -53,6 +57,18 @@ void object_read_bencode(std::istream* input, Object* object, uint32_t depth = 0 + // Assumes the stream's locale has been set to POSIX or C. + void object_write_bencode(std::ostream* output, const Object* object) LIBTORRENT_EXPORT; + ++// Convert buffer to static key map. Inlined because we don't want ++// a separate wrapper function for each template argument. ++template ++inline const char* ++staticMap_read_bencode(const char* buffer, const char* bufferEnd, StaticMap& map) { ++ return staticMap_read_bencode_c(buffer, bufferEnd, 0, map.values(), map.map(), false); ++}; ++ ++// Internal use only. ++const char* ++staticMap_read_bencode_c(const char* buffer, const char* bufferEnd, uint32_t depth, Object* values, const StaticMapKeys& keys, bool discard); ++ + std::istream& operator >> (std::istream& input, Object& object) LIBTORRENT_EXPORT; + std::ostream& operator << (std::ostream& output, const Object& object) LIBTORRENT_EXPORT; + +@@ -62,6 +78,15 @@ typedef object_buffer_t (*object_write_t)(void* data, object_buffer_t buffer); + + object_buffer_t object_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* object) LIBTORRENT_EXPORT; + ++template ++inline object_buffer_t ++staticMap_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const StaticMap& object) { ++ return staticMap_write_bencode_c_wrap(writeFunc, data, buffer, object.values(), object.map()); ++} ++ ++// Internal use only. ++object_buffer_t staticMap_write_bencode_c_wrap(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* values, const StaticMapKeys& keys) LIBTORRENT_EXPORT; ++ + // To char buffer. 'data' is NULL. + object_buffer_t object_write_to_buffer(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; + object_buffer_t object_write_to_sha1(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; +diff --git a/src/torrent/simple_string.h b/src/torrent/simple_string.h +new file mode 100644 +index 0000000..8eaf3b7 +--- /dev/null ++++ b/src/torrent/simple_string.h +@@ -0,0 +1,129 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2008, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++// A simple string with no constructors (i.e. POD) plus a derived ++// class with constructors and conversion operators. In most cases, ++// SimpleString is the class to use, except in unions where it needs ++// to be SimpleStringBase. ++// ++// For efficient conversion from C string literals, depends on the ++// compiler optimizing strlen("literal") to an integer literal. ++// Then a comparison with either a C string literal or a SimpleString ++// literal is a memcmp call plus (if equal) a comparison of the lengths. ++ ++#ifndef LIBTORRENT_SIMPLE_STRING_H ++#define LIBTORRENT_SIMPLE_STRING_H ++ ++#include ++#include ++#include ++#include ++ ++namespace torrent { ++ ++// Simple string base class (POD). ++struct LIBTORRENT_EXPORT SimpleStringBase { ++ int cmp(const SimpleStringBase& other) const; ++ ++ char operator [] (size_t index) const { return m_data[index]; } ++ ++ const char* begin() const { return m_data; } ++ const char* end() const { return m_data + m_length; } ++ ++ // NOTE: Unlike std::string, SimpleString's c_str() is NOT guaranteed to be zero-terminated! ++ const char* c_str() const { return m_data; } ++ const char* data() const { return m_data; } ++ ++ bool empty() const { return !m_length; } ++ size_t length() const { return m_length; } ++ size_t size() const { return m_length; } ++ ++ std::string str() const { return std::string(m_data, m_length); } ++ std::string substr(size_t pos = 0, size_t n = npos) const { return std::string(m_data + pos, std::min(m_length - pos, n)); } ++ ++ // Allocates a copy of the string and returns it. ++ SimpleStringBase copy() const; ++ ++ static const size_t npos = static_cast(-1); ++ ++protected: ++ const char* m_data; ++ size_t m_length; ++}; ++ ++// Conversion helper class, we don't want constructors ++// in the base class to be able to put it in a union. ++struct LIBTORRENT_EXPORT SimpleString : public SimpleStringBase { ++ typedef SimpleStringBase base_type; ++ ++ SimpleString() { m_data = ""; m_length = 0; } ++ SimpleString(const base_type& s) { m_data = s.c_str(); m_length = s.length(); } ++ SimpleString(const std::string& s) { m_data = s.c_str(); m_length = s.length(); } ++ SimpleString(const char* s) { m_data = s; m_length = strlen(s); } ++ SimpleString(const char* s, size_t l) { m_data = s; m_length = l; } ++}; ++ ++inline int ++SimpleStringBase::cmp(const SimpleStringBase& other) const { ++ int cmp = memcmp(m_data, other.m_data, std::min(m_length, other.m_length)); ++ return cmp ? cmp : m_length - other.m_length; ++} ++ ++inline SimpleStringBase ++SimpleStringBase::copy() const { ++ char* data = new char[m_length + 1]; ++ memcpy(data, m_data, m_length); ++ data[m_length] = 0; ++ return SimpleString(data, m_length); ++} ++ ++inline bool operator == (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) == 0; } ++inline bool operator != (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) != 0; } ++inline bool operator <= (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) <= 0; } ++inline bool operator < (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) < 0; } ++inline bool operator >= (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) >= 0; } ++inline bool operator > (const SimpleStringBase& one, const SimpleStringBase& other) { return one.cmp(other) > 0; } ++ ++inline bool operator == (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) == 0; } ++inline bool operator != (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) != 0; } ++inline bool operator <= (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) <= 0; } ++inline bool operator < (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) < 0; } ++inline bool operator >= (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) >= 0; } ++inline bool operator > (const SimpleStringBase& one, const char* other) { return one.cmp(SimpleString(other)) > 0; } ++ ++} ++ ++#endif +diff --git a/src/torrent/static_map.cc b/src/torrent/static_map.cc +new file mode 100644 +index 0000000..b71f257 +--- /dev/null ++++ b/src/torrent/static_map.cc +@@ -0,0 +1,123 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2008, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++#include "config.h" ++ ++#include "static_map.h" ++ ++namespace torrent { ++ ++inline int ++StaticMapKeys::check_key_order(SimpleString key) { ++ int cmp = empty() ? -1 : back().key().cmp(key); ++ if (cmp > 0) { ++ if (type() == TYPE_LIST) ++ cmp = -1; // List order is given by indices, not alphabetically. ++ else ++ throw internal_error("StaticMapKeys::StaticMapKeys() called with unsorted keys."); ++ } ++ ++ return cmp; ++} ++ ++StaticMapKeys::StaticMapKeys(const mapping_type* key_list, size_t length) ++ : m_key(SimpleString("root", 4)), ++ m_indexBegin(0), ++ m_indexEnd(0), ++ m_type(key_list[0].key[0] == '[' ? TYPE_LIST : TYPE_DICT) { ++ ++ for (size_t index = 0; index < length; index++, key_list++) { ++ if (key_list->index != index) ++ throw internal_error("StaticMapKeys::StaticMapKeys() used with list not in index order."); ++ ++ StaticMapKeys* curMap = this; ++ const char* key = key_list->key; ++ while (key != NULL && *key) { ++ curMap->set_end(index + 1); ++ ++ const char* sep = key + 1 + strcspn(key + 1, ":["); ++ SimpleString keyStr(key, sep - key); ++ ++ // New key, in correct order? Or same key as before? ++ int cmp = curMap->check_key_order(keyStr); ++ ++ if (sep[0] == 0) { ++ curMap->insert(curMap->end(), StaticMapKeys(keyStr, TYPE_VALUE, index, index + 1)); ++ break; ++ ++ } else if (sep[0] == '[' && sep[1] == ']' && sep[2] == 0) { ++ curMap->insert(curMap->end(), StaticMapKeys(keyStr, TYPE_BENCODE_LIST, index, index + 1)); ++ break; ++ ++ } else if (sep[0] == ':' && sep[1] == ':' && sep[2] == 0) { ++ curMap->insert(curMap->end(), StaticMapKeys(keyStr, TYPE_BENCODE, index, index + 1)); ++ break; ++ } ++ ++ if (sep[0] == ':' && sep[1] == ':') { ++ if (cmp < 0) ++ curMap->insert(curMap->end(), StaticMapKeys(keyStr, TYPE_DICT, index, index + 1)); ++ else if (curMap->back().type() != TYPE_DICT) ++ throw internal_error("StaticMapKeys::StaticMapKeys() called with a mixed dictionary/list entry."); ++ ++ curMap = &curMap->back(); ++ key = sep + 2; ++ ++ } else if (sep[0] == '[' && sep[1] >= '0' && sep[1] <= '9') { ++ key = sep++; ++ while (*sep >= '0' && *sep <= '9') ++ ++sep; ++ if (*sep != ']') ++ throw internal_error("StaticMapKeys::StaticMapKeys() called with invalid list index."); ++ ++ if (cmp < 0) ++ curMap->insert(curMap->end(), StaticMapKeys(keyStr, TYPE_LIST, index, index + 1)); ++ else if (curMap->back().type() != TYPE_LIST) ++ throw internal_error("StaticMapKeys::StaticMapKeys() called with a mixed dictionary/list entry."); ++ ++ curMap = &curMap->back(); ++ ++ } else { ++ throw internal_error("StaticMapKeys::StaticMapKeys() called with unsupported key type."); ++ } ++ } ++ } ++ ++ if (index_end() != length) ++ throw internal_error("StaticMapKeys::StaticMapKeys() is missing values."); ++} ++ ++} +diff --git a/src/torrent/static_map.h b/src/torrent/static_map.h +new file mode 100644 +index 0000000..d862f16 +--- /dev/null ++++ b/src/torrent/static_map.h +@@ -0,0 +1,158 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2008, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++#ifndef LIBTORRENT_STATIC_MAP_H ++#define LIBTORRENT_STATIC_MAP_H ++ ++#include ++#include ++#include ++#include ++ ++// StaticMap: holds a pre-defined subset of possible bencode keys and stores ++// their values in a flat array for fast decoding, key access and encoding. ++// Makes no copies, so the underlying data buffer must outlive the map object. ++ ++// With this, the complexity for bencoding and bdecoding a StaticMap object ++// is O(n). The access to any of the pre-defined keys is O(1). Access to ++// other keys is not supported, they are dropped while bdecoding. Decoded ++// Object types are either VALUE, SSTRING or NONE (if key was not present). ++ ++// To use, define an enum of all required keys, and use this type along with ++// the number of possible keys in the StaticMap template arguments. Define ++// the enum -> key string as array of StaticMapKeys::mapping_type. Define ++// the static keyMap variable, most simply by defining base_type in your ++// derived map class, like this: ++// template<> const Derived::key_map_init Derived::base_type::keyMap(key_list); ++ ++// The argument of the constructor of this static keyMap object is a list ++// of mapping_type entries. For efficiency, they must be ordered in ++// increasing number of the index, and increasing alphabetical order ++// (or more specifically, the bencode order) at the same time. In other words, ++// the original enum must also be in alphabetical order of the keys the enum ++// values refer to. ++ ++// Format of the key specifications ("..." may contain any number of further keys): ++// "foo::..." makes foo a bencode dictionary ++// "foo[0]..." makes foo a bencode list ++// "foo::" makes foo an undecoded bencode value (may contain arbitrary bencode data) ++// "foo[]" makes foo an undecoded list of bencode values (like the above but adding the 'l' and 'e' indicators) ++// "foo" makes foo an integer or string value (automatic) ++// ++// Examples: ++// "baz" refers to a single value for key "baz" ++// "foo::a[0]::bar" refers to a single value for key "bar" in the dictionary at index 0 of the list for key "a" in dictionary "foo" ++// "foo::a[1]" refers to a single value at index 1 of the list for key "a" in the dictionary "foo" ++// "zoo::" refers to a bdecoded value for key "zoo" ++// ++// If the four values are 4, 5, "6" and 7, this would be bencoded as d3:bazi4e3:food1:ald3:bari5ee1:6ee3:zooi7ee ++// ++// Note that sparse lists are not possible, you must explicitly specify all needed entries starting from index 0, ++// and when bencoding, the first unset value terminates the list. ++ ++namespace torrent { ++ ++// Hierarchical structure mapping bencode keys to flat array indices. ++class LIBTORRENT_EXPORT StaticMapKeys : public std::vector { ++public: ++ typedef std::vector base_type; ++ ++ struct mapping_type { ++ size_t index; ++ const char* key; ++ }; ++ ++ enum value_type { ++ TYPE_VALUE, ++ TYPE_LIST, ++ TYPE_DICT, ++ TYPE_BENCODE, ++ TYPE_BENCODE_LIST, ++ }; ++ ++ StaticMapKeys(const mapping_type* key_list, size_t length); ++ ++ void set_end(size_t end) { m_indexEnd = end; } ++ ++ size_t index_begin() const { return m_indexBegin; } ++ size_t index_end() const { return m_indexEnd; } ++ ++ value_type type() const { return m_type; } ++ ++ SimpleString key() const { return m_key; } ++ ++private: ++ StaticMapKeys(SimpleString key, value_type type, size_t begin, size_t end) ++ : m_key(key), m_indexBegin(begin), m_indexEnd(end), m_type(type) {} ++ ++ int check_key_order(SimpleString key); ++ ++ SimpleString m_key; ++ size_t m_indexBegin; ++ size_t m_indexEnd; ++ value_type m_type; ++}; ++ ++template ++class LIBTORRENT_EXPORT StaticMap { ++public: ++ typedef Object& value_type; ++ typedef tmpl_key_type key_type; ++ typedef StaticMapKeys key_map_type; ++ typedef Object list_type[tmpl_length]; ++ ++ Object& operator [] (key_type key) { return m_values[key]; } ++ const Object& operator [] (key_type key) const { return m_values[key]; } ++ ++ const key_map_type& map() const { return keyMap; } ++ ++ list_type& values() { return m_values; } ++ const list_type& values() const { return m_values; } ++ ++ static const size_t length = tmpl_length; ++ ++private: ++ struct key_map_init : public key_map_type { ++ key_map_init(key_map_type::mapping_type* key_list) : key_map_type(key_list, tmpl_length) {}; ++ }; ++ static const key_map_init keyMap; ++ ++ list_type m_values; ++}; ++ ++} ++ ++#endif +diff --git a/src/tracker/tracker_dht.cc b/src/tracker/tracker_dht.cc +index c63ce58..309fcf2 100644 +--- a/src/tracker/tracker_dht.cc ++++ b/src/tracker/tracker_dht.cc +@@ -115,13 +115,11 @@ TrackerDht::type() const { + } + + void +-TrackerDht::receive_peers(const Object& peer_list) { ++TrackerDht::receive_peers(SimpleString peers) { + if (!is_busy()) + throw internal_error("TrackerDht::receive_peers called while not busy."); + +- Object::list_type peers = peer_list.as_list(); +- for (Object::list_type::const_iterator itr = peers.begin(); itr != peers.end(); ++itr) +- m_peers.parse_address_compact(itr->as_string()); ++ m_peers.parse_address_bencode(peers); + } + + void +diff --git a/src/tracker/tracker_dht.h b/src/tracker/tracker_dht.h +index d197e61..d096b46 100644 +--- a/src/tracker/tracker_dht.h ++++ b/src/tracker/tracker_dht.h +@@ -71,7 +71,7 @@ public: + + bool has_peers() const { return !m_peers.empty(); } + +- void receive_peers(const Object& peer_list); ++ void receive_peers(SimpleString peers); + void receive_success(); + void receive_failed(const char* msg); + void receive_progress(int replied, int contacted); diff --git a/libtorrent-extended/ipv6.patch b/libtorrent-extended/ipv6.patch new file mode 100644 index 0000000..7da4ebd --- /dev/null +++ b/libtorrent-extended/ipv6.patch @@ -0,0 +1,1235 @@ +diff --git a/rak/socket_address.h b/rak/socket_address.h +index 25fdb37..d38533e 100644 +--- a/rak/socket_address.h ++++ b/rak/socket_address.h +@@ -145,7 +145,7 @@ private: + }; + }; + +-// Remeber to set the AF_INET. ++// Remember to set the AF_INET. + + class socket_address_inet { + public: +@@ -184,6 +184,10 @@ public: + + const sockaddr* c_sockaddr() const { return reinterpret_cast(&m_sockaddr); } + const sockaddr_in* c_sockaddr_inet() const { return &m_sockaddr; } ++ ++#ifdef RAK_USE_INET6 ++ socket_address_inet6 to_mapped_address() const; ++#endif + + bool operator == (const socket_address_inet& rhs) const; + bool operator < (const socket_address_inet& rhs) const; +@@ -192,6 +196,52 @@ private: + struct sockaddr_in m_sockaddr; + }; + ++#ifdef RAK_USE_INET6 ++// Remember to set the AF_INET6. ++ ++class socket_address_inet6 { ++public: ++ bool is_any() const { return is_port_any() && is_address_any(); } ++ bool is_valid() const { return !is_port_any() && !is_address_any(); } ++ bool is_port_any() const { return port() == 0; } ++ bool is_address_any() const { return std::memcmp(&m_sockaddr.sin6_addr, &in6addr_any, sizeof(in6_addr)) == 0; } ++ ++ void clear() { std::memset(this, 0, sizeof(socket_address_inet6)); set_family(); } ++ ++ uint16_t port() const { return ntohs(m_sockaddr.sin6_port); } ++ uint16_t port_n() const { return m_sockaddr.sin6_port; } ++ void set_port(uint16_t p) { m_sockaddr.sin6_port = htons(p); } ++ void set_port_n(uint16_t p) { m_sockaddr.sin6_port = p; } ++ ++ in6_addr address() const { return m_sockaddr.sin6_addr; } ++ std::string address_str() const; ++ bool address_c_str(char* buf, socklen_t size) const; ++ ++ void set_address(in6_addr a) { m_sockaddr.sin6_addr = a; } ++ bool set_address_str(const std::string& a) { return set_address_c_str(a.c_str()); } ++ bool set_address_c_str(const char* a); ++ ++ void set_address_any() { set_port(0); set_address(in6addr_any); } ++ ++ sa_family_t family() const { return m_sockaddr.sin6_family; } ++ void set_family() { m_sockaddr.sin6_family = AF_INET6; } ++ ++ sockaddr* c_sockaddr() { return reinterpret_cast(&m_sockaddr); } ++ sockaddr_in6* c_sockaddr_inet6() { return &m_sockaddr; } ++ ++ const sockaddr* c_sockaddr() const { return reinterpret_cast(&m_sockaddr); } ++ const sockaddr_in6* c_sockaddr_inet6() const { return &m_sockaddr; } ++ ++ socket_address normalize_address() const; ++ ++ bool operator == (const socket_address_inet6& rhs) const; ++ bool operator < (const socket_address_inet6& rhs) const; ++ ++private: ++ struct sockaddr_in6 m_sockaddr; ++}; ++#endif ++ + // Unique key for the address, excluding port numbers etc. + class socket_address_key { + public: +@@ -241,8 +291,10 @@ socket_address::is_valid() const { + switch (family()) { + case af_inet: + return sa_inet()->is_valid(); +-// case af_inet6: +-// return sa_inet6().is_valid(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->is_valid(); ++#endif + default: + return false; + } +@@ -253,6 +305,10 @@ socket_address::is_bindable() const { + switch (family()) { + case af_inet: + return !sa_inet()->is_address_any(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return !sa_inet6()->is_address_any(); ++#endif + default: + return false; + } +@@ -263,6 +319,10 @@ socket_address::is_address_any() const { + switch (family()) { + case af_inet: + return sa_inet()->is_address_any(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->is_address_any(); ++#endif + default: + return true; + } +@@ -273,6 +333,10 @@ socket_address::port() const { + switch (family()) { + case af_inet: + return sa_inet()->port(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->port(); ++#endif + default: + return 0; + } +@@ -283,6 +347,10 @@ socket_address::set_port(uint16_t p) { + switch (family()) { + case af_inet: + return sa_inet()->set_port(p); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->set_port(p); ++#endif + default: + break; + } +@@ -293,6 +361,10 @@ socket_address::address_str() const { + switch (family()) { + case af_inet: + return sa_inet()->address_str(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->address_str(); ++#endif + default: + return std::string(); + } +@@ -303,6 +375,10 @@ socket_address::address_c_str(char* buf, socklen_t size) const { + switch (family()) { + case af_inet: + return sa_inet()->address_c_str(buf, size); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->address_c_str(buf, size); ++#endif + default: + return false; + } +@@ -314,6 +390,12 @@ socket_address::set_address_c_str(const char* a) { + sa_inet()->set_family(); + return true; + ++#ifdef RAK_USE_INET6 ++ } else if (sa_inet6()->set_address_c_str(a)) { ++ sa_inet6()->set_family(); ++ return true; ++#endif ++ + } else { + return false; + } +@@ -325,6 +407,10 @@ socket_address::length() const { + switch(family()) { + case af_inet: + return sizeof(sockaddr_in); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sizeof(sockaddr_in6); ++#endif + default: + return 0; + } +@@ -349,8 +435,10 @@ socket_address::operator == (const socket_address& rhs) const { + switch (family()) { + case af_inet: + return *sa_inet() == *rhs.sa_inet(); +-// case af_inet6: +-// return *sa_inet6() == *rhs.sa_inet6(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return *sa_inet6() == *rhs.sa_inet6(); ++#endif + default: + throw std::logic_error("socket_address::operator == (rhs) invalid type comparison."); + } +@@ -364,8 +452,10 @@ socket_address::operator < (const socket_address& rhs) const { + switch (family()) { + case af_inet: + return *sa_inet() < *rhs.sa_inet(); +-// case af_inet6: +-// return *sa_inet6() < *rhs.sa_inet6(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return *sa_inet6() < *rhs.sa_inet6(); ++#endif + default: + throw std::logic_error("socket_address::operator < (rhs) invalid type comparison."); + } +@@ -391,6 +481,23 @@ socket_address_inet::set_address_c_str(const char* a) { + return inet_pton(AF_INET, a, &m_sockaddr.sin_addr); + } + ++#ifdef RAK_USE_INET6 ++inline socket_address_inet6 ++socket_address_inet::to_mapped_address() const { ++ uint32_t addr32[4]; ++ addr32[0] = 0; ++ addr32[1] = 0; ++ addr32[2] = htonl(0xffff); ++ addr32[3] = m_sockaddr.sin_addr.s_addr; ++ ++ socket_address_inet6 sa; ++ sa.clear(); ++ sa.set_address(*reinterpret_cast(addr32)); ++ sa.set_port_n(m_sockaddr.sin_port); ++ return sa; ++} ++#endif ++ + inline bool + socket_address_inet::operator == (const socket_address_inet& rhs) const { + return +@@ -406,6 +513,59 @@ socket_address_inet::operator < (const socket_address_inet& rhs) const { + m_sockaddr.sin_port < rhs.m_sockaddr.sin_port); + } + ++#ifdef RAK_USE_INET6 ++ ++inline std::string ++socket_address_inet6::address_str() const { ++ char buf[INET6_ADDRSTRLEN]; ++ ++ if (!address_c_str(buf, INET6_ADDRSTRLEN)) ++ return std::string(); ++ ++ return std::string(buf); ++} ++ ++inline bool ++socket_address_inet6::address_c_str(char* buf, socklen_t size) const { ++ return inet_ntop(family(), &m_sockaddr.sin6_addr, buf, size); ++} ++ ++inline bool ++socket_address_inet6::set_address_c_str(const char* a) { ++ return inet_pton(AF_INET6, a, &m_sockaddr.sin6_addr); ++} ++ ++inline socket_address ++socket_address_inet6::normalize_address() const { ++ const uint32_t *addr32 = reinterpret_cast(m_sockaddr.sin6_addr.s6_addr); ++ if (addr32[0] == 0 && addr32[1] == 0 && addr32[2] == htonl(0xffff)) { ++ socket_address addr4; ++ addr4.sa_inet()->set_family(); ++ addr4.sa_inet()->set_address_n(addr32[3]); ++ addr4.sa_inet()->set_port_n(m_sockaddr.sin6_port); ++ return addr4; ++ } ++ return *reinterpret_cast(this); ++} ++ ++inline bool ++socket_address_inet6::operator == (const socket_address_inet6& rhs) const { ++ return ++ memcmp(&m_sockaddr.sin6_addr, &rhs.m_sockaddr.sin6_addr, sizeof(in6_addr)) == 0 && ++ m_sockaddr.sin6_port == rhs.m_sockaddr.sin6_port; ++} ++ ++inline bool ++socket_address_inet6::operator < (const socket_address_inet6& rhs) const { ++ int addr_comp = memcmp(&m_sockaddr.sin6_addr, &rhs.m_sockaddr.sin6_addr, sizeof(in6_addr)); ++ return ++ addr_comp < 0 || ++ (addr_comp == 0 || ++ m_sockaddr.sin6_port < rhs.m_sockaddr.sin6_port); ++} ++ ++#endif ++ + } + + #endif +diff --git a/src/dht/dht_node.cc b/src/dht/dht_node.cc +index 9d51a28..e0eb306 100644 +--- a/src/dht/dht_node.cc ++++ b/src/dht/dht_node.cc +@@ -55,8 +55,15 @@ DhtNode::DhtNode(const HashString& id, const rak::socket_address* sa) : + m_recentlyInactive(0), + m_bucket(NULL) { + ++#ifdef RAK_USE_INET6 ++ if (sa->family() != rak::socket_address::af_inet && ++ (sa->family() != rak::socket_address::af_inet6 || ++ !sa->sa_inet6()->is_any())) ++ throw resource_error("Address not af_inet or in6addr_any"); ++#else + if (sa->family() != rak::socket_address::af_inet) + throw resource_error("Address not af_inet"); ++#endif + } + + DhtNode::DhtNode(const SimpleString& id, const Object& cache) : +@@ -85,8 +92,20 @@ DhtNode::store_compact(char* buffer) const { + + Object* + DhtNode::store_cache(Object* container) const { +- container->insert_key("i", m_socketAddress.sa_inet()->address_h()); +- container->insert_key("p", m_socketAddress.sa_inet()->port()); ++#ifdef RAK_USE_INET6 ++ if (m_socketAddress.family() == rak::socket_address::af_inet6) { ++ // Currently, all we support is in6addr_any (checked in the constructor), ++ // which is effectively equivalent to this. Note that we need to specify ++ // int64_t explicitly here because a zero constant is special in C++ and ++ // thus we need an explicit match. ++ container->insert_key("i", int64_t(0)); ++ container->insert_key("p", m_socketAddress.sa_inet6()->port()); ++ } else ++#endif ++ { ++ container->insert_key("i", m_socketAddress.sa_inet()->address_h()); ++ container->insert_key("p", m_socketAddress.sa_inet()->port()); ++ } + container->insert_key("t", m_lastSeen); + return container; + } +diff --git a/src/dht/dht_server.cc b/src/dht/dht_server.cc +index 256b92b..8c8b380 100644 +--- a/src/dht/dht_server.cc ++++ b/src/dht/dht_server.cc +@@ -692,6 +692,16 @@ DhtServer::event_read() { + if (read < 0) + break; + ++#ifdef RAK_USE_INET6 ++ // We can currently only process mapped-IPv4 addresses, not real IPv6. ++ // Translate them to an af_inet socket_address. ++ if (sa.family() == rak::socket_address::af_inet6) ++ sa = sa.sa_inet6()->normalize_address(); ++#endif ++ ++ if (sa.family() != rak::socket_address::af_inet) ++ continue; ++ + total += read; + + // If it's not a valid bencode dictionary at all, it's probably not a DHT +diff --git a/src/download/download_info.h b/src/download/download_info.h +index 68fb178..2e27ba3 100644 +--- a/src/download/download_info.h ++++ b/src/download/download_info.h +@@ -216,6 +216,28 @@ struct SocketAddressCompact { + const char* c_str() const { return reinterpret_cast(this); } + } __attribute__ ((packed)); + ++#ifdef RAK_USE_INET6 ++struct SocketAddressCompact6 { ++ SocketAddressCompact6() {} ++ SocketAddressCompact6(in6_addr a, uint16_t p) : addr(a), port(p) {} ++ SocketAddressCompact6(const rak::socket_address_inet6* sa) : addr(sa->address()), port(sa->port_n()) {} ++ ++ operator rak::socket_address () const { ++ rak::socket_address sa; ++ sa.sa_inet6()->clear(); ++ sa.sa_inet6()->set_port_n(port); ++ sa.sa_inet6()->set_address(addr); ++ ++ return sa; ++ } ++ ++ in6_addr addr; ++ uint16_t port; ++ ++ const char* c_str() const { return reinterpret_cast(this); } ++} __attribute__ ((packed)); ++#endif ++ + } + + #endif +diff --git a/src/net/Makefile.am b/src/net/Makefile.am +index e07f382..3df0f6d 100644 +--- a/src/net/Makefile.am ++++ b/src/net/Makefile.am +@@ -4,6 +4,8 @@ libsub_net_la_SOURCES = \ + address_list.cc \ + address_list.h \ + data_buffer.h \ ++ local_addr.cc \ ++ local_addr.h \ + listen.cc \ + listen.h \ + protocol_buffer.h \ +diff --git a/src/net/Makefile.in b/src/net/Makefile.in +index 1920403..cdbe398 100644 +--- a/src/net/Makefile.in ++++ b/src/net/Makefile.in +@@ -54,9 +54,9 @@ CONFIG_CLEAN_FILES = + CONFIG_CLEAN_VPATH_FILES = + LTLIBRARIES = $(noinst_LTLIBRARIES) + libsub_net_la_LIBADD = +-am_libsub_net_la_OBJECTS = address_list.lo listen.lo socket_base.lo \ +- socket_datagram.lo socket_fd.lo socket_set.lo socket_stream.lo \ +- throttle_internal.lo throttle_list.lo ++am_libsub_net_la_OBJECTS = address_list.lo local_addr.lo listen.lo \ ++ socket_base.lo socket_datagram.lo socket_fd.lo socket_set.lo \ ++ socket_stream.lo throttle_internal.lo throttle_list.lo + libsub_net_la_OBJECTS = $(am_libsub_net_la_OBJECTS) + DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) + depcomp = $(SHELL) $(top_srcdir)/depcomp +@@ -216,6 +216,8 @@ libsub_net_la_SOURCES = \ + address_list.cc \ + address_list.h \ + data_buffer.h \ ++ local_addr.cc \ ++ local_addr.h \ + listen.cc \ + listen.h \ + protocol_buffer.h \ +@@ -290,6 +292,7 @@ distclean-compile: + + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/address_list.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listen.Plo@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/local_addr.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_base.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_datagram.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_fd.Plo@am__quote@ +diff --git a/src/net/address_list.cc b/src/net/address_list.cc +index e5cf3cb..a7dff2c 100644 +--- a/src/net/address_list.cc ++++ b/src/net/address_list.cc +@@ -92,5 +92,16 @@ AddressList::parse_address_bencode(SimpleString s) { + s = SimpleString(s.c_str() + 2 + sizeof(SocketAddressCompact), s.length() - 2 - sizeof(SocketAddressCompact)); + } + } ++#ifdef RAK_USE_INET6 ++void ++AddressList::parse_address_compact_ipv6(const std::string& s) { ++ if (sizeof(const SocketAddressCompact6) != 18) ++ throw internal_error("ConnectionList::AddressList::parse_address_compact_ipv6(...) bad struct size."); ++ ++ std::copy(reinterpret_cast(s.c_str()), ++ reinterpret_cast(s.c_str() + s.size() - s.size() % sizeof(SocketAddressCompact6)), ++ std::back_inserter(*this)); ++} ++#endif + + } +diff --git a/src/net/address_list.h b/src/net/address_list.h +index 10dbac4..3aec366 100644 +--- a/src/net/address_list.h ++++ b/src/net/address_list.h +@@ -51,6 +51,9 @@ public: + // Parse normal or compact list of addresses and add to AddressList + void parse_address_normal(const Object::list_type& b); + void parse_address_compact(SimpleString s); ++#ifdef RAK_USE_INET6 ++ void parse_address_compact_ipv6(const std::string& s); ++#endif + void parse_address_bencode(SimpleString s); + + private: +diff --git a/src/net/local_addr.cc b/src/net/local_addr.cc +new file mode 100644 +index 0000000..fae3f85 +--- /dev/null ++++ b/src/net/local_addr.cc +@@ -0,0 +1,336 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2007, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef __linux__ ++#include ++#include ++#endif ++ ++#include "torrent/exceptions.h" ++#include "socket_fd.h" ++#include "local_addr.h" ++ ++namespace torrent { ++namespace { ++ ++// IPv4 priority, from highest to lowest: ++// ++// 1. Everything else (global address) ++// 2. Private address space (10.0.0.0/8, 172.16.0.0/16, 192.168.0.0/24) ++// 3. Empty/INADDR_ANY (0.0.0.0) ++// 4. Link-local address (169.254.0.0/16) ++// 5. Localhost (127.0.0.0/8) ++int get_priority_ipv4(const in_addr& addr) { ++ if ((addr.s_addr & htonl(0xff000000U)) == htonl(0x7f000000U)) { ++ return 5; ++ } ++ if (addr.s_addr == htonl(0)) { ++ return 4; ++ } ++ if ((addr.s_addr & htonl(0xffff0000U)) == htonl(0xa9fe0000U)) { ++ return 3; ++ } ++ if ((addr.s_addr & htonl(0xff000000U)) == htonl(0x0a000000U) || ++ (addr.s_addr & htonl(0xffff0000U)) == htonl(0xac100000U) || ++ (addr.s_addr & htonl(0xffff0000U)) == htonl(0xc0a80000U)) { ++ return 2; ++ } ++ return 1; ++} ++ ++#ifdef RAK_USE_INET6 ++// IPv6 priority, from highest to lowest: ++// ++// 1. Global address (2000::/16 not in 6to4 or Teredo) ++// 2. 6to4 (2002::/16) ++// 3. Teredo (2001::/32) ++// 4. Empty/INADDR_ANY (::) ++// 5. Everything else (link-local, ULA, etc.) ++int get_priority_ipv6(const in6_addr& addr) { ++ const uint32_t *addr32 = reinterpret_cast(addr.s6_addr); ++ if (addr32[0] == htonl(0) && ++ addr32[1] == htonl(0) && ++ addr32[2] == htonl(0) && ++ addr32[3] == htonl(0)) { ++ return 4; ++ } ++ if (addr32[0] == htonl(0x20010000)) { ++ return 3; ++ } ++ if ((addr32[0] & htonl(0xffff0000)) == htonl(0x20020000)) { ++ return 2; ++ } ++ if ((addr32[0] & htonl(0xe0000000)) == htonl(0x20000000)) { ++ return 1; ++ } ++ return 5; ++} ++#endif ++ ++int get_priority(const rak::socket_address& addr) { ++ switch (addr.family()) { ++ case AF_INET: ++ return get_priority_ipv4(addr.c_sockaddr_inet()->sin_addr); ++#ifdef RAK_USE_INET6 ++ case AF_INET6: ++ return get_priority_ipv6(addr.c_sockaddr_inet6()->sin6_addr); ++#endif ++ default: ++ throw torrent::internal_error("Unknown address family given to compare"); ++ } ++} ++ ++} ++ ++#ifdef __linux__ ++ ++// Linux-specific implementation that understands how to filter away ++// understands how to filter away secondary addresses. ++bool get_local_address(sa_family_t family, rak::socket_address *address) { ++ ifaddrs *ifaddrs; ++ if (getifaddrs(&ifaddrs)) { ++ return false; ++ } ++ ++ rak::socket_address best_addr; ++ switch (family) { ++ case AF_INET: ++ best_addr.sa_inet()->clear(); ++ break; ++#ifdef RAK_USE_INET6 ++ case AF_INET6: ++ best_addr.sa_inet6()->clear(); ++ break; ++#endif ++ default: ++ throw torrent::internal_error("Unknown address family given to get_local_address"); ++ } ++ ++ // The bottom bit of the priority is used to hold if the address is ++ // a secondary address (e.g. with IPv6 privacy extensions) or not; ++ // secondary addresses have lower priority (higher number). ++ int best_addr_pri = get_priority(best_addr) * 2; ++ ++ // Get all the addresses via Linux' netlink interface. ++ int fd = ::socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); ++ if (fd == -1) { ++ return false; ++ } ++ ++ struct sockaddr_nl nladdr; ++ memset(&nladdr, 0, sizeof(nladdr)); ++ nladdr.nl_family = AF_NETLINK; ++ if (::bind(fd, (sockaddr *)&nladdr, sizeof(nladdr))) { ++ ::close(fd); ++ return false; ++ } ++ ++ const int seq_no = 1; ++ struct { ++ nlmsghdr nh; ++ rtgenmsg g; ++ } req; ++ memset(&req, 0, sizeof(req)); ++ ++ req.nh.nlmsg_len = sizeof(req); ++ req.nh.nlmsg_type = RTM_GETADDR; ++ req.nh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; ++ req.nh.nlmsg_pid = getpid(); ++ req.nh.nlmsg_seq = seq_no; ++ req.g.rtgen_family = AF_UNSPEC; ++ ++ int ret; ++ do { ++ ret = ::sendto(fd, &req, sizeof(req), 0, (sockaddr *)&nladdr, sizeof(nladdr)); ++ } while (ret == -1 && errno == EINTR); ++ ++ if (ret == -1) { ++ ::close(fd); ++ return false; ++ } ++ ++ bool done = false; ++ do { ++ char buf[4096]; ++ socklen_t len = sizeof(nladdr); ++ do { ++ ret = ::recvfrom(fd, buf, sizeof(buf), 0, (sockaddr *)&nladdr, &len); ++ } while (ret == -1 && errno == EINTR); ++ ++ if (ret < 0) { ++ ::close(fd); ++ return false; ++ } ++ ++ for (const nlmsghdr *nlmsg = (const nlmsghdr *)buf; ++ NLMSG_OK(nlmsg, ret); ++ nlmsg = NLMSG_NEXT(nlmsg, ret)) { ++ if (nlmsg->nlmsg_seq != seq_no) ++ continue; ++ if (nlmsg->nlmsg_type == NLMSG_DONE) { ++ done = true; ++ break; ++ } ++ if (nlmsg->nlmsg_type == NLMSG_ERROR) { ++ ::close(fd); ++ return false; ++ } ++ if (nlmsg->nlmsg_type != RTM_NEWADDR) ++ continue; ++ ++ const ifaddrmsg *ifa = (const ifaddrmsg *)NLMSG_DATA(nlmsg); ++ ++ if (ifa->ifa_family != family) ++ continue; ++ ++#ifdef IFA_F_OPTIMISTIC ++ if ((ifa->ifa_flags & IFA_F_OPTIMISTIC) != 0) ++ continue; ++#endif ++#ifdef IFA_F_DADFAILED ++ if ((ifa->ifa_flags & IFA_F_DADFAILED) != 0) ++ continue; ++#endif ++#ifdef IFA_F_DEPRECATED ++ if ((ifa->ifa_flags & IFA_F_DEPRECATED) != 0) ++ continue; ++#endif ++#ifdef IFA_F_TENTATIVE ++ if ((ifa->ifa_flags & IFA_F_TENTATIVE) != 0) ++ continue; ++#endif ++ ++ // Since there can be point-to-point links on the machine, we need to keep ++ // track of the addresses we've seen for this interface; if we see both ++ // IFA_LOCAL and IFA_ADDRESS for an interface, keep only the IFA_LOCAL. ++ rak::socket_address this_addr; ++ bool seen_addr = false; ++ int plen = IFA_PAYLOAD(nlmsg); ++ for (const rtattr *rta = IFA_RTA(ifa); ++ RTA_OK(rta, plen); ++ rta = RTA_NEXT(rta, plen)) { ++ if (rta->rta_type != IFA_LOCAL && ++ rta->rta_type != IFA_ADDRESS) { ++ continue; ++ } ++ if (rta->rta_type == IFA_ADDRESS && seen_addr) { ++ continue; ++ } ++ seen_addr = true; ++ switch (ifa->ifa_family) { ++ case AF_INET: ++ this_addr.sa_inet()->clear(); ++ this_addr.sa_inet()->set_address(*(const in_addr *)RTA_DATA(rta)); ++ break; ++#ifdef RAK_USE_INET6 ++ case AF_INET6: ++ this_addr.sa_inet6()->clear(); ++ this_addr.sa_inet6()->set_address(*(const in6_addr *)RTA_DATA(rta)); ++ break; ++#endif ++ } ++ } ++ if (!seen_addr) ++ continue; ++ ++ int this_addr_pri = get_priority(this_addr) * 2; ++ if ((ifa->ifa_flags & IFA_F_SECONDARY) == IFA_F_SECONDARY) { ++ ++this_addr_pri; ++ } ++ ++ if (this_addr_pri < best_addr_pri) { ++ best_addr = this_addr; ++ best_addr_pri = this_addr_pri; ++ } ++ } ++ } while (!done); ++ ++ ::close(fd); ++ if (!best_addr.is_address_any()) { ++ *address = best_addr; ++ return true; ++ } else { ++ return false; ++ } ++} ++ ++#else ++ ++// Generic POSIX variant. ++bool get_local_address(sa_family_t family, rak::socket_address *address) { ++ SocketFd sock; ++ if (!sock.open_datagram()) { ++ return false; ++ } ++ ++ rak::socket_address dummy_dest; ++ dummy_dest.clear(); ++ ++ switch (family) { ++ case rak::socket_address::af_inet: ++ dummy_dest.set_address_c_str("4.0.0.0"); ++ break; ++#ifdef RAK_USE_INET6 ++ case rak::socket_address::af_inet6: ++ dummy_dest.set_address_c_str("2001:700::"); ++ break; ++#endif ++ default: ++ throw internal_error("Unknown address family"); ++ } ++ dummy_dest.set_port(80); ++ ++ if (!sock.connect(dummy_dest)) { ++ sock.close(); ++ return false; ++ } ++ ++ bool ret = sock.getsockname(address); ++ sock.close(); ++ return ret; ++} ++ ++#endif ++ ++} +diff --git a/src/net/local_addr.h b/src/net/local_addr.h +new file mode 100644 +index 0000000..43bc820 +--- /dev/null ++++ b/src/net/local_addr.h +@@ -0,0 +1,64 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2007, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++// A routine to get a local IP address that can be presented to a tracker. ++// (Does not use UPnP etc., so will not understand NAT.) ++// On a machine with multiple network cards, address selection can be a ++// complex process, and in general what's selected is a source/destination ++// address pair. However, this routine will give an approximation that will ++// be good enough for most purposes and users. ++ ++#ifndef LIBTORRENT_NET_LOCAL_ADDR_H ++#define LIBTORRENT_NET_LOCAL_ADDR_H ++ ++#include ++ ++namespace rak { ++ class socket_address; ++} ++ ++namespace torrent { ++ ++// Note: family must currently be rak::af_inet or rak::af_inet6 ++// (rak::af_unspec won't do); anything else will throw an exception. ++// Returns false if no address of the given family could be found, ++// either because there are none, or because something went wrong in ++// the process (e.g., no free file descriptors). ++bool get_local_address(sa_family_t family, rak::socket_address *address); ++ ++} ++ ++#endif /* LIBTORRENT_NET_LOCAL_ADDR_H */ +diff --git a/src/net/socket_datagram.cc b/src/net/socket_datagram.cc +index b983289..69992e3 100644 +--- a/src/net/socket_datagram.cc ++++ b/src/net/socket_datagram.cc +@@ -73,7 +73,13 @@ SocketDatagram::write_datagram(const void* buffer, unsigned int length, rak::soc + int r; + + if (sa != NULL) { +- r = ::sendto(m_fileDesc, buffer, length, 0, sa->sa_inet()->c_sockaddr(), sizeof(rak::socket_address_inet)); ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket && sa->family() == rak::socket_address::pf_inet) { ++ rak::socket_address_inet6 sa_mapped = sa->sa_inet()->to_mapped_address(); ++ r = ::sendto(m_fileDesc, buffer, length, 0, sa_mapped.c_sockaddr(), sizeof(rak::socket_address_inet6)); ++ } else ++#endif ++ r = ::sendto(m_fileDesc, buffer, length, 0, sa->c_sockaddr(), sa->length()); + } else { + r = ::send(m_fileDesc, buffer, length, 0); + } +diff --git a/src/net/socket_fd.cc b/src/net/socket_fd.cc +index c349d21..8576628 100644 +--- a/src/net/socket_fd.cc ++++ b/src/net/socket_fd.cc +@@ -70,6 +70,11 @@ SocketFd::set_priority(priority_type p) { + check_valid(); + int opt = p; + ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket) ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_TCLASS, &opt, sizeof(opt)) == 0; ++ else ++#endif + return setsockopt(m_fd, IPPROTO_IP, IP_TOS, &opt, sizeof(opt)) == 0; + } + +@@ -112,12 +117,36 @@ SocketFd::get_error() const { + + bool + SocketFd::open_stream() { ++#ifdef RAK_USE_INET6 ++ m_fd = socket(rak::socket_address::pf_inet6, SOCK_STREAM, IPPROTO_TCP); ++ if (m_fd == -1) { ++ m_ipv6_socket = false; ++ return (m_fd = socket(rak::socket_address::pf_inet, SOCK_STREAM, IPPROTO_TCP)) != -1; ++ } ++ m_ipv6_socket = true; ++ ++ int zero = 0; ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) != -1; ++#else + return (m_fd = socket(rak::socket_address::pf_inet, SOCK_STREAM, IPPROTO_TCP)) != -1; ++#endif + } + + bool + SocketFd::open_datagram() { ++#ifdef RAK_USE_INET6 ++ m_fd = socket(rak::socket_address::pf_inet6, SOCK_DGRAM, 0); ++ if (m_fd == -1) { ++ m_ipv6_socket = false; ++ return (m_fd = socket(rak::socket_address::pf_inet, SOCK_DGRAM, 0)) != -1; ++ } ++ m_ipv6_socket = true; ++ ++ int zero = 0; ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) != -1; ++#else + return (m_fd = socket(rak::socket_address::pf_inet, SOCK_DGRAM, 0)) != -1; ++#endif + } + + bool +@@ -149,10 +178,34 @@ bool + SocketFd::connect(const rak::socket_address& sa) { + check_valid(); + ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket && sa.family() == rak::socket_address::pf_inet) { ++ rak::socket_address_inet6 sa_mapped = sa.sa_inet()->to_mapped_address(); ++ return !::connect(m_fd, sa_mapped.c_sockaddr(), sizeof(sa_mapped)) || errno == EINPROGRESS; ++ } ++#endif + return !::connect(m_fd, sa.c_sockaddr(), sa.length()) || errno == EINPROGRESS; + } + + bool ++SocketFd::getsockname(rak::socket_address *sa) { ++ check_valid(); ++ ++ socklen_t len = sizeof(rak::socket_address); ++ if (::getsockname(m_fd, sa->c_sockaddr(), &len)) { ++ return false; ++ } ++ ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket && sa->family() == rak::socket_address::af_inet6) { ++ *sa = sa->sa_inet6()->normalize_address(); ++ } ++#endif ++ ++ return true; ++} ++ ++bool + SocketFd::listen(int size) { + check_valid(); + +@@ -164,7 +217,18 @@ SocketFd::accept(rak::socket_address* sa) { + check_valid(); + socklen_t len = sizeof(rak::socket_address); + ++#ifdef RAK_USE_INET6 ++ if (sa == NULL) { ++ return SocketFd(::accept(m_fd, NULL, &len)); ++ } ++ int fd = ::accept(m_fd, sa->c_sockaddr(), &len); ++ if (fd != -1 && m_ipv6_socket && sa->family() == rak::socket_address::af_inet6) { ++ *sa = sa->sa_inet6()->normalize_address(); ++ } ++ return SocketFd(fd); ++#else + return SocketFd(::accept(m_fd, sa != NULL ? sa->c_sockaddr() : NULL, &len)); ++#endif + } + + // unsigned int +diff --git a/src/net/socket_fd.h b/src/net/socket_fd.h +index a208ad6..d39c413 100644 +--- a/src/net/socket_fd.h ++++ b/src/net/socket_fd.h +@@ -77,6 +77,7 @@ public: + bool bind(const rak::socket_address& sa); + bool bind(const rak::socket_address& sa, unsigned int length); + bool connect(const rak::socket_address& sa); ++ bool getsockname(rak::socket_address* sa); + + bool listen(int size); + SocketFd accept(rak::socket_address* sa); +@@ -88,6 +89,9 @@ private: + inline void check_valid() const; + + int m_fd; ++#ifdef RAK_USE_INET6 ++ bool m_ipv6_socket; ++#endif + }; + + } +diff --git a/src/torrent/connection_manager.cc b/src/torrent/connection_manager.cc +index 64c4197..152a0a1 100644 +--- a/src/torrent/connection_manager.cc ++++ b/src/torrent/connection_manager.cc +@@ -78,13 +78,18 @@ ConnectionManager::ConnectionManager() : + m_slotResolver(&resolve_host) { + + m_bindAddress = (new rak::socket_address())->c_sockaddr(); +- rak::socket_address::cast_from(m_bindAddress)->sa_inet()->clear(); +- + m_localAddress = (new rak::socket_address())->c_sockaddr(); +- rak::socket_address::cast_from(m_localAddress)->sa_inet()->clear(); +- + m_proxyAddress = (new rak::socket_address())->c_sockaddr(); ++ ++#ifdef RAK_USE_INET6 ++ rak::socket_address::cast_from(m_bindAddress)->sa_inet6()->clear(); ++ rak::socket_address::cast_from(m_localAddress)->sa_inet6()->clear(); ++ rak::socket_address::cast_from(m_proxyAddress)->sa_inet6()->clear(); ++#else ++ rak::socket_address::cast_from(m_bindAddress)->sa_inet()->clear(); ++ rak::socket_address::cast_from(m_localAddress)->sa_inet()->clear(); + rak::socket_address::cast_from(m_proxyAddress)->sa_inet()->clear(); ++#endif + } + + ConnectionManager::~ConnectionManager() { +@@ -123,8 +128,10 @@ void + ConnectionManager::set_bind_address(const sockaddr* sa) { + const rak::socket_address* rsa = rak::socket_address::cast_from(sa); + ++#ifndef RAK_USE_INET6 + if (rsa->family() != rak::socket_address::af_inet) + throw input_error("Tried to set a bind address that is not an af_inet address."); ++#endif + + rak::socket_address::cast_from(m_bindAddress)->copy(*rsa, rsa->length()); + } +@@ -133,8 +140,10 @@ void + ConnectionManager::set_local_address(const sockaddr* sa) { + const rak::socket_address* rsa = rak::socket_address::cast_from(sa); + ++#ifndef RAK_USE_INET6 + if (rsa->family() != rak::socket_address::af_inet) + throw input_error("Tried to set a local address that is not an af_inet address."); ++#endif + + rak::socket_address::cast_from(m_localAddress)->copy(*rsa, rsa->length()); + } +@@ -143,8 +152,10 @@ void + ConnectionManager::set_proxy_address(const sockaddr* sa) { + const rak::socket_address* rsa = rak::socket_address::cast_from(sa); + ++#ifndef RAK_USE_INET6 + if (rsa->family() != rak::socket_address::af_inet) + throw input_error("Tried to set a proxy address that is not an af_inet address."); ++#endif + + rak::socket_address::cast_from(m_proxyAddress)->copy(*rsa, rsa->length()); + } +diff --git a/src/torrent/event.h b/src/torrent/event.h +index 335e6d6..2d6e36c 100644 +--- a/src/torrent/event.h ++++ b/src/torrent/event.h +@@ -57,6 +57,10 @@ public: + + protected: + int m_fileDesc; ++ ++#ifdef RAK_USE_INET6 ++ bool m_ipv6_socket; ++#endif + }; + + } +diff --git a/src/torrent/peer/peer_list.cc b/src/torrent/peer/peer_list.cc +index 187ba5b..cef840b 100644 +--- a/src/torrent/peer/peer_list.cc ++++ b/src/torrent/peer/peer_list.cc +@@ -65,15 +65,23 @@ socket_address_less(const sockaddr* s1, const sockaddr* s2) { + // humans. + return sa1->sa_inet()->address_h() < sa2->sa_inet()->address_h(); + ++#ifdef RAK_USE_INET6 ++ else { ++ const in6_addr addr1 = sa1->sa_inet6()->address(); ++ const in6_addr addr2 = sa2->sa_inet6()->address(); ++ return memcmp(&addr1, &addr2, sizeof(in6_addr)) < 0; ++ } ++#else + else +- // When we implement INET6 handling, embed the ipv4 address in +- // the ipv6 address. + throw internal_error("socket_address_key(...) tried to compare an invalid family type."); ++#endif ++ + } + + inline bool + socket_address_key::is_comparable(const sockaddr* sa) { +- return rak::socket_address::cast_from(sa)->family() == rak::socket_address::af_inet; ++ return rak::socket_address::cast_from(sa)->family() == rak::socket_address::af_inet || ++ rak::socket_address::cast_from(sa)->family() == rak::socket_address::af_inet6; + } + + struct peer_list_equal_port : public std::binary_function { +diff --git a/src/tracker/tracker_http.cc b/src/tracker/tracker_http.cc +index d23cae6..493dd0d 100644 +--- a/src/tracker/tracker_http.cc ++++ b/src/tracker/tracker_http.cc +@@ -43,6 +43,7 @@ + + #include "download/download_info.h" + #include "net/address_list.h" ++#include "net/local_addr.h" + #include "torrent/connection_manager.h" + #include "torrent/exceptions.h" + #include "torrent/http.h" +@@ -114,9 +115,16 @@ TrackerHttp::send_state(int state) { + + const rak::socket_address* localAddress = rak::socket_address::cast_from(manager->connection_manager()->local_address()); + +- if (localAddress->family() == rak::socket_address::af_inet && +- !localAddress->sa_inet()->is_address_any()) ++ if (!localAddress->is_address_any()) + s << "&ip=" << localAddress->address_str(); ++ ++#ifdef RAK_USE_INET6 ++ if (localAddress->is_address_any() || localAddress->family() != rak::socket_address::pf_inet6) { ++ rak::socket_address local_v6; ++ if (get_local_address(rak::socket_address::af_inet6, &local_v6)) ++ s << "&ipv6=" << rak::copy_escape_html(local_v6.address_str()); ++ } ++#endif + + if (info->is_compact()) + s << "&compact=1"; +@@ -220,18 +228,34 @@ TrackerHttp::receive_done() { + + AddressList l; + +- try { +- // Due to some trackers sending the wrong type when no peers are +- // available, don't bork on it. +- if (b.get_key("peers").is_string()) +- l.parse_address_compact(b.get_key_string("peers")); ++ if (!b.has_key("peers") ++#ifdef RAK_USE_INET6 ++ && !b.has_key("peers6") ++#endif ++ ) { ++ return receive_failed("No peers returned"); ++ } ++ ++ if (b.has_key("peers")) { ++ try { ++ // Due to some trackers sending the wrong type when no peers are ++ // available, don't bork on it. ++ if (b.get_key("peers").is_string()) ++ l.parse_address_compact(b.get_key_string("peers")); ++ ++ else if (b.get_key("peers").is_list()) ++ l.parse_address_normal(b.get_key_list("peers")); + +- else if (b.get_key("peers").is_list()) +- l.parse_address_normal(b.get_key_list("peers")); ++ } catch (bencode_error& e) { ++ return receive_failed(e.what()); ++ } ++ } + +- } catch (bencode_error& e) { +- return receive_failed(e.what()); ++#ifdef RAK_USE_INET6 ++ if (b.has_key("peers6")) { ++ l.parse_address_compact_ipv6(b.get_key_string("peers6")); + } ++#endif + + close(); + m_parent->receive_success(this, &l); +diff --git a/src/tracker/tracker_udp.cc b/src/tracker/tracker_udp.cc +index dacf21b..63442a3 100644 +--- a/src/tracker/tracker_udp.cc ++++ b/src/tracker/tracker_udp.cc +@@ -271,11 +271,18 @@ TrackerUdp::prepare_announce_input() { + + const rak::socket_address* localAddress = rak::socket_address::cast_from(manager->connection_manager()->local_address()); + +- // This code assumes we're have a inet address. ++#ifdef RAK_USE_INET6 ++ uint32_t local_addr = 0; ++ if (localAddress->family() == rak::socket_address::af_inet) ++ local_addr = localAddress->sa_inet()->address_n(); ++#else + if (localAddress->family() != rak::socket_address::af_inet) + throw internal_error("TrackerUdp::prepare_announce_input() info->local_address() not of family AF_INET."); ++ ++ uint32_t local_addr = localAddress->sa_inet()->address_n(); ++#endif + +- m_writeBuffer->write_32_n(localAddress->sa_inet()->address_n()); ++ m_writeBuffer->write_32_n(local_addr); + m_writeBuffer->write_32(m_parent->key()); + m_writeBuffer->write_32(m_parent->numwant()); + m_writeBuffer->write_16(manager->connection_manager()->listen_port()); diff --git a/libtorrent-extended/magnet_uri.patch b/libtorrent-extended/magnet_uri.patch new file mode 100644 index 0000000..3d00540 --- /dev/null +++ b/libtorrent-extended/magnet_uri.patch @@ -0,0 +1,1652 @@ +diff --git a/src/download/download_constructor.cc b/src/download/download_constructor.cc +index f37f848..86e5351 100644 +--- a/src/download/download_constructor.cc ++++ b/src/download/download_constructor.cc +@@ -81,7 +81,10 @@ struct download_constructor_encoding_match : + }; + + void +-DownloadConstructor::initialize(const Object& b) { ++DownloadConstructor::initialize(Object& b) { ++ if (!b.has_key_map("info") && b.has_key_string("magnet-uri")) ++ parse_magnet_uri(b, b.get_key_string("magnet-uri")); ++ + if (b.has_key_string("encoding")) + m_defaultEncoding = b.get_key_string("encoding"); + +@@ -136,10 +139,24 @@ DownloadConstructor::parse_info(const Object& b) { + if (b.flags() & Object::flag_unordered) + throw input_error("Download has unordered info dictionary."); + +- uint32_t chunkSize = b.get_key_value("piece length"); ++ uint32_t chunkSize; ++ ++ if (b.has_key_value("meta_download") && b.get_key_value("meta_download")) ++ m_download->info()->set_meta_download(true); ++ ++ if (m_download->info()->is_meta_download()) { ++ if (b.get_key_string("pieces").length() != HashString::size_data) ++ throw input_error("Meta-download has invalid piece data."); ++ ++ chunkSize = 1; ++ parse_single_file(b, chunkSize); ++ ++ } else { ++ chunkSize = b.get_key_value("piece length"); + +- if (chunkSize <= (1 << 10) || chunkSize > (128 << 20)) +- throw input_error("Torrent has an invalid \"piece length\"."); ++ if (chunkSize <= (1 << 10) || chunkSize > (128 << 20)) ++ throw input_error("Torrent has an invalid \"piece length\"."); ++ } + + if (b.has_key("length")) { + parse_single_file(b, chunkSize); +@@ -148,11 +165,11 @@ DownloadConstructor::parse_info(const Object& b) { + parse_multi_files(b.get_key("files"), chunkSize); + fileList->set_root_dir("./" + m_download->info()->name()); + +- } else { ++ } else if (!m_download->info()->is_meta_download()) { + throw input_error("Torrent must have either length or files entry."); + } + +- if (fileList->size_bytes() == 0) ++ if (fileList->size_bytes() == 0 && !m_download->info()->is_meta_download()) + throw input_error("Torrent has zero length."); + + // Set chunksize before adding files to make sure the index range is +@@ -239,7 +256,7 @@ DownloadConstructor::parse_single_file(const Object& b, uint32_t chunkSize) { + throw input_error("Bad torrent file, \"name\" is an invalid path name."); + + FileList* fileList = m_download->main()->file_list(); +- fileList->initialize(b.get_key_value("length"), chunkSize); ++ fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), chunkSize); + fileList->set_multi_file(false); + + std::list pathList; +@@ -343,4 +360,132 @@ DownloadConstructor::choose_path(std::list* pathList) { + return pathList->front(); + } + ++static const char* ++parse_base32_sha1(const char* pos, HashString& hash) { ++ HashString::iterator hashItr = hash.begin(); ++ ++ static const int base_shift = 8+8-5; ++ int shift = base_shift; ++ uint16_t decoded = 0; ++ ++ while (*pos) { ++ char c = *pos++; ++ uint16_t value; ++ ++ if (c >= 'A' && c <= 'Z') ++ value = c - 'A'; ++ else if (c >= 'a' && c <= 'z') ++ value = c - 'a'; ++ else if (c >= '2' && c <= '7') ++ value = 26 + c - '2'; ++ else if (c == '&') ++ break; ++ else ++ return NULL; ++ ++ decoded |= (value << shift); ++ if (shift <= 8) { ++ // Too many characters for a base32 SHA1. ++ if (hashItr == hash.end()) ++ return NULL; ++ ++ *hashItr++ = (decoded >> 8); ++ decoded <<= 8; ++ shift += 3; ++ } else { ++ shift -= 5; ++ } ++ } ++ ++ return hashItr != hash.end() || shift != base_shift ? NULL : pos; ++} ++ ++void ++DownloadConstructor::parse_magnet_uri(Object& b, const std::string& uri) { ++ if (std::strncmp(uri.c_str(), "magnet:?", 8)) ++ throw input_error("Invalid magnet URI."); ++ ++ const char* pos = uri.c_str() + 8; ++ ++ Object trackers(Object::create_list()); ++ HashString hash; ++ bool hashValid = false; ++ ++ while (*pos) { ++ const char* tagStart = pos; ++ while (*pos != '=') ++ if (!*pos++) ++ break; ++ ++ SimpleString tag(tagStart, pos - tagStart); ++ pos++; ++ ++ // hash may be base32 encoded (optional in BEP 0009 and common practice) ++ if (tag == "xt") { ++ if (strncmp(pos, "urn:btih:", 9)) ++ throw input_error("Invalid magnet URI."); ++ ++ pos += 9; ++ ++ const char* nextPos = parse_base32_sha1(pos, hash); ++ if (nextPos != NULL) { ++ pos = nextPos; ++ hashValid = true; ++ continue; ++ } ++ } ++ ++ // everything else, including sometimes the hash, is url encoded. ++ std::string decoded; ++ while (*pos) { ++ char c = *pos++; ++ if (c == '%') { ++ if (sscanf(pos, "%02hhx", &c) != 1) ++ throw input_error("Invalid magnet URI."); ++ ++ pos += 2; ++ ++ } else if (c == '&') { ++ break; ++ } ++ ++ decoded.push_back(c); ++ } ++ ++ if (tag == "xt") { ++ // url-encoded hash as per magnet URN specs ++ if (decoded.length() == hash.size_data) { ++ hash = *HashString::cast_from(decoded); ++ hashValid = true; ++ ++ // hex-encoded hash as per BEP 0009 ++ } else if (decoded.length() == hash.size_data * 2) { ++ std::string::iterator hexItr = decoded.begin(); ++ for (HashString::iterator itr = hash.begin(), last = hash.end(); itr != last; itr++, hexItr += 2) ++ *itr = (rak::hexchar_to_value(*hexItr) << 4) + rak::hexchar_to_value(*(hexItr + 1)); ++ hashValid = true; ++ ++ } else { ++ throw input_error("Invalid magnet URI."); ++ } ++ } else if (tag == "tr") { ++ trackers.insert_back(Object::create_list()).insert_back(decoded); ++ } ++ // could also handle "dn" = display name (torrent name), but we can't really use that ++ } ++ ++ if (!hashValid) ++ throw input_error("Invalid magnet URI."); ++ ++ Object& info = b.insert_key("info", Object::create_map()); ++ info.insert_key("pieces", hash.str()); ++ info.insert_key("name", rak::transform_hex(hash.str()) + ".meta"); ++ info.insert_key("meta_download", (int64_t)1); ++ ++ if (!trackers.as_list().empty()) { ++ b.insert_preserve_copy("announce", trackers.as_list().begin()->as_list().begin()->as_string()); ++ b.insert_preserve_type("announce-list", trackers); ++ } ++} ++ + } +diff --git a/src/download/download_constructor.h b/src/download/download_constructor.h +index 7192f90..8af520f 100644 +--- a/src/download/download_constructor.h ++++ b/src/download/download_constructor.h +@@ -55,7 +55,7 @@ class DownloadConstructor { + public: + DownloadConstructor() : m_download(NULL), m_encodingList(NULL) {} + +- void initialize(const Object& b); ++ void initialize(Object& b); + + void set_download(DownloadWrapper* d) { m_download = d; } + void set_encoding_list(const EncodingList* e) { m_encodingList = e; } +@@ -64,6 +64,7 @@ private: + void parse_name(const Object& b); + void parse_tracker(const Object& b); + void parse_info(const Object& b); ++ void parse_magnet_uri(Object& b, const std::string& uri); + + void add_tracker_group(const Object& b); + void add_tracker_single(const Object& b, int group); +diff --git a/src/download/download_info.h b/src/download/download_info.h +index 0a3c0e8..68fb178 100644 +--- a/src/download/download_info.h ++++ b/src/download/download_info.h +@@ -76,6 +76,7 @@ public: + m_isCompact(true), + m_isAcceptingNewPeers(true), + m_isPrivate(false), ++ m_isMetaDownload(false), + m_pexEnabled(true), + m_pexActive(true), + +@@ -86,7 +87,8 @@ public: + m_uploadedBaseline(0), + m_completedBaseline(0), + m_sizePex(0), +- m_maxSizePex(8) { ++ m_maxSizePex(8), ++ m_metadataSize(0) { + } + + const std::string& name() const { return m_name; } +@@ -116,6 +118,9 @@ public: + bool is_private() const { return m_isPrivate; } + void set_private(bool p) { m_isPrivate = p; if (p) m_pexEnabled = false; } + ++ bool is_meta_download() const { return m_isMetaDownload; } ++ void set_meta_download(bool m) { m_isMetaDownload = m; } ++ + bool is_pex_enabled() const { return m_pexEnabled; } + void set_pex_enabled(bool enabled) { m_pexEnabled = enabled && !m_isPrivate; } + +@@ -134,6 +139,9 @@ public: + uint64_t completed_adjusted() const { return std::max(m_slotStatCompleted() - completed_baseline(), 0); } + void set_completed_baseline(uint64_t b) { m_completedBaseline = b; } + ++ size_t metadata_size() const { return m_metadataSize; } ++ void set_metadata_size(size_t size) { m_metadataSize = size; } ++ + uint32_t size_pex() const { return m_sizePex; } + void set_size_pex(uint32_t b) { m_sizePex = b; } + +@@ -165,6 +173,7 @@ private: + bool m_isCompact; + bool m_isAcceptingNewPeers; + bool m_isPrivate; ++ bool m_isMetaDownload; + bool m_pexEnabled; + bool m_pexActive; + +@@ -176,6 +185,7 @@ private: + uint64_t m_completedBaseline; + uint32_t m_sizePex; + uint32_t m_maxSizePex; ++ size_t m_metadataSize; + + slot_stat_type m_slotStatCompleted; + slot_stat_type m_slotStatLeft; +diff --git a/src/download/download_main.cc b/src/download/download_main.cc +index 1dd5f98..5691021 100644 +--- a/src/download/download_main.cc ++++ b/src/download/download_main.cc +@@ -455,4 +455,19 @@ DownloadMain::do_peer_exchange() { + } + } + ++void ++DownloadMain::set_metadata_size(size_t size) { ++ if (m_info->is_meta_download()) { ++ if (m_fileList.size_bytes() < 2) ++ file_list()->reset_filesize(size); ++ else if (size != m_fileList.size_bytes()) ++ throw communication_error("Peer-supplied metadata size mismatch."); ++ ++ } else if (m_info->metadata_size() && m_info->metadata_size() != size) { ++ throw communication_error("Peer-supplied metadata size mismatch."); ++ } ++ ++ m_info->set_metadata_size(size); ++} ++ + } +diff --git a/src/download/download_main.h b/src/download/download_main.h +index 5d0090b..700f41e 100644 +--- a/src/download/download_main.h ++++ b/src/download/download_main.h +@@ -116,6 +116,8 @@ public: + + bool want_pex_msg() { return m_info->is_pex_active() && m_peerList.available_list()->want_more(); }; + ++ void set_metadata_size(size_t s); ++ + // Carefull with these. + void setup_delegator(); + void setup_tracker(); +diff --git a/src/net/data_buffer.h b/src/net/data_buffer.h +index a26ca36..e3d9e38 100644 +--- a/src/net/data_buffer.h ++++ b/src/net/data_buffer.h +@@ -48,6 +48,7 @@ struct DataBuffer { + DataBuffer(char* data, char* end) : m_data(data), m_end(end), m_owned(true) {} + + DataBuffer clone() const { DataBuffer d = *this; d.m_owned = false; return d; } ++ DataBuffer release() { DataBuffer d = *this; set(NULL, NULL, false); return d; } + + char* data() const { return m_data; } + char* end() const { return m_end; } +@@ -70,7 +71,7 @@ private: + + inline void + DataBuffer::clear() { +- if (!empty()) ++ if (!empty() && m_owned) + delete[] m_data; + + m_data = m_end = NULL; +diff --git a/src/net/socket_base.cc b/src/net/socket_base.cc +index 90457dc..13a9c8b 100644 +--- a/src/net/socket_base.cc ++++ b/src/net/socket_base.cc +@@ -47,7 +47,7 @@ + + namespace torrent { + +-char* SocketBase::m_nullBuffer = new char[1 << 17]; ++char* SocketBase::m_nullBuffer = new char[SocketBase::null_buffer_size]; + + SocketBase::~SocketBase() { + if (get_fd().is_valid()) +diff --git a/src/net/socket_base.h b/src/net/socket_base.h +index 9340a23..0f0f424 100644 +--- a/src/net/socket_base.h ++++ b/src/net/socket_base.h +@@ -68,6 +68,8 @@ protected: + SocketBase(const SocketBase&); + void operator = (const SocketBase&); + ++ static const size_t null_buffer_size = 1 << 17; ++ + static char* m_nullBuffer; + }; + +diff --git a/src/protocol/Makefile.am b/src/protocol/Makefile.am +index 6171d06..18f671d 100644 +--- a/src/protocol/Makefile.am ++++ b/src/protocol/Makefile.am +@@ -17,6 +17,8 @@ libsub_protocol_la_SOURCES = \ + peer_connection_base.h \ + peer_connection_leech.cc \ + peer_connection_leech.h \ ++ peer_connection_metadata.cc \ ++ peer_connection_metadata.h \ + peer_factory.cc \ + peer_factory.h \ + protocol_base.h \ +diff --git a/src/protocol/extensions.cc b/src/protocol/extensions.cc +index 7cbf6e3..3e0cf60 100644 +--- a/src/protocol/extensions.cc ++++ b/src/protocol/extensions.cc +@@ -43,6 +43,8 @@ + + #include "download/available_list.h" + #include "download/download_main.h" ++#include "download/download_manager.h" ++#include "download/download_wrapper.h" + #include "protocol/peer_connection_base.h" + #include "torrent/connection_manager.h" + #include "torrent/object.h" +@@ -58,7 +60,9 @@ namespace torrent { + + enum ext_handshake_keys { + key_e, ++ key_m_utMetadata, + key_m_utPex, ++ key_metadataSize, + key_p, + key_reqq, + key_v, +@@ -70,6 +74,13 @@ enum ext_pex_keys { + key_pex_LAST + }; + ++enum ext_metadata_keys { ++ key_msgType, ++ key_piece, ++ key_totalSize, ++ key_metadata_LAST ++}; ++ + class ExtHandshakeMessage : public StaticMap { + public: + typedef StaticMap base_type; +@@ -82,9 +93,17 @@ public: + typedef StaticMapKeys::mapping_type mapping_type; + }; + ++class ExtMetadataMessage : public StaticMap { ++public: ++ typedef StaticMap base_type; ++ typedef StaticMapKeys::mapping_type mapping_type; ++}; ++ + ExtHandshakeMessage::mapping_type ext_handshake_key_names[ExtHandshakeMessage::length] = { + { key_e, "e" }, ++ { key_m_utMetadata, "m::ut_metadata" }, + { key_m_utPex, "m::ut_pex" }, ++ { key_metadataSize, "metadata_size" }, + { key_p, "p" }, + { key_reqq, "reqq" }, + { key_v, "v" }, +@@ -94,9 +113,16 @@ ExtPEXMessage::mapping_type ext_pex_key_names[ExtPEXMessage::length] = { + { key_pex_added, "added" }, + }; + ++ExtMetadataMessage::mapping_type ext_metadata_key_names[ExtMetadataMessage::length] = { ++ { key_msgType, "msg_type" }, ++ { key_piece, "piece" }, ++ { key_totalSize, "total_size" }, ++}; ++ + ext_handshake_keys message_keys[ProtocolExtension::FIRST_INVALID] = { + key_handshake_LAST, // Handshake, not actually used. + key_m_utPex, ++ key_m_utMetadata, + }; + + template<> +@@ -105,6 +131,9 @@ const ExtHandshakeMessage::key_map_init ExtHandshakeMessage::base_type::keyMap(e + template<> + const ExtPEXMessage::key_map_init ExtPEXMessage::base_type::keyMap(ext_pex_key_names); + ++template<> ++const ExtMetadataMessage::key_map_init ExtMetadataMessage::base_type::keyMap(ext_metadata_key_names); ++ + void + ProtocolExtension::cleanup() { + // if (is_default()) +@@ -160,7 +189,11 @@ ProtocolExtension::generate_handshake_message() { + message[key_v] = SimpleString("libTorrent " VERSION); + message[key_reqq] = 2048; // maximum request queue size + ++ if (!m_download->info()->is_meta_download()) ++ message[key_metadataSize] = m_download->info()->metadata_size(); ++ + message[key_m_utPex] = is_local_enabled(UT_PEX) ? UT_PEX : 0; ++ message[key_m_utMetadata] = UT_METADATA; + + char buffer[1024]; + object_buffer_t result = staticMap_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), message); +@@ -224,7 +257,7 @@ ProtocolExtension::generate_ut_pex_message(const PEXList& added, const PEXList& + + void + ProtocolExtension::read_start(int type, uint32_t length, bool skip) { +- if (is_default() || (type >= FIRST_INVALID) || length > (1 << 14)) ++ if (is_default() || (type >= FIRST_INVALID) || length > (1 << 15)) + throw communication_error("Received invalid extension message."); + + if (m_read != NULL || length < 0) +@@ -244,19 +277,25 @@ ProtocolExtension::read_start(int type, uint32_t length, bool skip) { + m_readPos = m_read = new char[length]; + } + +-void ++bool + ProtocolExtension::read_done() { ++ bool blocked = false; ++ + try { + switch(m_readType) { + case SKIP_EXTENSION: + break; + + case HANDSHAKE: +- parse_handshake(); ++ blocked = parse_handshake(); + break; + + case UT_PEX: +- parse_ut_pex(); ++ blocked = parse_ut_pex(); ++ break; ++ ++ case UT_METADATA: ++ blocked = parse_ut_metadata(); + break; + + default: +@@ -272,6 +311,8 @@ ProtocolExtension::read_done() { + + m_readType = FIRST_INVALID; + m_flags |= flag_received_ext; ++ ++ return !blocked; + } + + // Called whenever peer enables or disables an extension. +@@ -285,7 +326,7 @@ ProtocolExtension::peer_toggle_remote(int type, bool active) { + } + } + +-void ++bool + ProtocolExtension::parse_handshake() { + ExtHandshakeMessage message; + staticMap_read_bencode(m_read, m_readPos, message); +@@ -323,10 +364,15 @@ ProtocolExtension::parse_handshake() { + if (message[key_reqq].is_value()) + m_maxQueueLength = message[key_reqq].as_value(); + ++ if (message[key_metadataSize].is_value()) ++ m_download->set_metadata_size(message[key_metadataSize].as_value()); ++ + m_flags &= ~flag_initial_handshake; ++ ++ return false; + } + +-void ++bool + ProtocolExtension::parse_ut_pex() { + // Ignore message if we're still in the handshake (no connection + // yet), or no peers are present. +@@ -336,11 +382,11 @@ ProtocolExtension::parse_ut_pex() { + + // TODO: Check if pex is enabled? + if (!message[key_pex_added].is_sstring()) +- return; ++ return false; + + SimpleString peers = message[key_pex_added].as_sstring(); + if (peers.empty()) +- return; ++ return false; + + AddressList l; + l.parse_address_compact(peers); +@@ -348,6 +394,82 @@ ProtocolExtension::parse_ut_pex() { + l.erase(std::unique(l.begin(), l.end()), l.end()); + + m_download->peer_list()->insert_available(&l); ++ ++ return false; ++} ++ ++bool ++ProtocolExtension::parse_ut_metadata() { ++ ExtMetadataMessage message; ++ ++ // Piece data comes after bencoded extension message. ++ const char* dataStart = staticMap_read_bencode(m_read, m_readPos, message); ++ ++ switch(message[key_msgType].as_value()) { ++ case 0: ++ // Can't process new request while still having data to send. ++ if (has_pending_message()) ++ return true; ++ ++ send_metadata_piece(message[key_piece].as_value()); ++ break; ++ ++ case 1: ++ if (m_connection == NULL) ++ break; ++ ++ m_connection->receive_metadata_piece(message[key_piece].as_value(), dataStart, m_readPos - dataStart); ++ break; ++ ++ case 2: ++ if (m_connection != NULL) ++ m_connection->receive_metadata_piece(message[key_piece].as_value(), NULL, 0); ++ break; ++ }; ++ ++ return false; ++} ++ ++void ++ProtocolExtension::send_metadata_piece(size_t piece) { ++ // Reject out-of-range piece, or if we don't have the complete metadata yet. ++ size_t metadataSize = m_download->info()->metadata_size(); ++ size_t pieceEnd = (metadataSize + metadata_piece_size - 1) >> metadata_piece_shift; ++ ++ if (m_download->info()->is_meta_download() || piece >= pieceEnd) { ++ // reject: { "msg_type" => 2, "piece" => ... } ++ m_pendingType = UT_METADATA; ++ m_pending = build_bencode(40, "d8:msg_typei2e5:piecei%zuee", piece); ++ return; ++ } ++ ++ // These messages will be rare, so we'll just build the ++ // metadata here instead of caching it uselessly. ++ char* buffer = new char[metadataSize]; ++ object_buffer_t result = object_write_bencode_c(object_write_to_buffer, NULL, object_buffer_t(buffer, buffer + metadataSize), ++ &(*manager->download_manager()->find(m_download->info()))->bencode()->get_key("info")); ++ ++ // data: { "msg_type" => 1, "piece" => ..., "total_size" => ... } followed by piece data (outside of dictionary) ++ size_t length = piece == pieceEnd - 1 ? m_download->info()->metadata_size() % metadata_piece_size : metadata_piece_size; ++ m_pendingType = UT_METADATA; ++ m_pending = build_bencode(length + 128, "d8:msg_typei1e5:piecei%zue10:total_sizei%zuee", piece, metadataSize); ++ ++ memcpy(m_pending.end(), buffer + (piece << metadata_piece_shift), length); ++ m_pending.set(m_pending.data(), m_pending.end() + length, m_pending.owned()); ++ delete [] buffer; ++} ++ ++bool ++ProtocolExtension::request_metadata_piece(const Piece* p) { ++ if (p->offset() % metadata_piece_size) ++ throw internal_error("ProtocolExtension::request_metadata_piece got misaligned piece offset."); ++ ++ if (has_pending_message()) ++ return false; ++ ++ m_pendingType = UT_METADATA; ++ m_pending = build_bencode(40, "d8:msg_typei0e5:piecei%uee", (unsigned)(p->offset() >> metadata_piece_shift)); ++ return true; + } + + } +diff --git a/src/protocol/extensions.h b/src/protocol/extensions.h +index 96ed652..485e7d7 100644 +--- a/src/protocol/extensions.h ++++ b/src/protocol/extensions.h +@@ -60,6 +60,7 @@ public: + typedef enum { + HANDSHAKE = 0, + UT_PEX, ++ UT_METADATA, + + FIRST_INVALID, // first invalid message ID + +@@ -81,6 +82,10 @@ public: + // Number of extensions we support, not counting handshake. + static const int extension_count = FIRST_INVALID - HANDSHAKE - 1; + ++ // Fixed size of a metadata piece (16 KB). ++ static const size_t metadata_piece_shift = 14; ++ static const size_t metadata_piece_size = 1 << metadata_piece_shift; ++ + ProtocolExtension(); + ~ProtocolExtension() { delete [] m_read; } + +@@ -91,6 +96,7 @@ public: + static ProtocolExtension make_default(); + + void set_info(PeerInfo* peerInfo, DownloadMain* download) { m_peerInfo = peerInfo; m_download = download; } ++ void set_connection(PeerConnectionBase* c) { m_connection = c; } + + DataBuffer generate_handshake_message(); + static DataBuffer generate_toggle_message(MessageType t, bool on); +@@ -112,7 +118,7 @@ public: + + // Handle reading extension data from peer. + void read_start(int type, uint32_t length, bool skip); +- void read_done(); ++ bool read_done(); + + char* read_position() { return m_readPos; } + bool read_move(uint32_t v) { m_readPos += v; return (m_readLeft -= v) == 0; } +@@ -132,13 +138,23 @@ public: + void clear_initial_pex() { m_flags &= ~flag_initial_pex; } + void reset() { std::memset(&m_idMap, 0, sizeof(m_idMap)); } + ++ bool request_metadata_piece(const Piece* p); ++ ++ // To handle cases where the extension protocol needs to send a reply. ++ bool has_pending_message() const { return m_pendingType != HANDSHAKE; } ++ MessageType pending_message_type() const { return m_pendingType; } ++ DataBuffer pending_message_data() { return m_pending.release(); } ++ void clear_pending_message() { if (m_pending.empty()) m_pendingType = HANDSHAKE; } ++ + private: +- void parse_handshake(); +- void parse_ut_pex(); ++ bool parse_handshake(); ++ bool parse_ut_pex(); ++ bool parse_ut_metadata(); + + static DataBuffer build_bencode(size_t maxLength, const char* format, ...) ATTRIBUTE_PRINTF(2); + + void peer_toggle_remote(int type, bool active); ++ void send_metadata_piece(size_t piece); + + // Map of IDs peer uses for each extension message type, excluding + // HANDSHAKE. +@@ -149,11 +165,15 @@ private: + int m_flags; + PeerInfo* m_peerInfo; + DownloadMain* m_download; ++ PeerConnectionBase* m_connection; + + uint8_t m_readType; + uint32_t m_readLeft; + char* m_read; + char* m_readPos; ++ ++ MessageType m_pendingType; ++ DataBuffer m_pending; + }; + + inline +@@ -163,10 +183,13 @@ ProtocolExtension::ProtocolExtension() : + m_flags(flag_local_enabled_base | flag_remote_supported_base | flag_initial_handshake), + m_peerInfo(NULL), + m_download(NULL), ++ m_connection(NULL), + m_readType(FIRST_INVALID), +- m_read(NULL) { ++ m_read(NULL), ++ m_pendingType(HANDSHAKE) { + + reset(); ++ set_local_enabled(UT_METADATA); + } + + inline ProtocolExtension +diff --git a/src/protocol/handshake.cc b/src/protocol/handshake.cc +index d863f7b..7fb389b 100644 +--- a/src/protocol/handshake.cc ++++ b/src/protocol/handshake.cc +@@ -723,6 +723,17 @@ restart: + + case READ_MESSAGE: + case POST_HANDSHAKE: ++ // For meta-downloads, we aren't interested in the bitfield or ++ // extension messages here, PCMetadata handles all that. The ++ // bitfield only refers to the single-chunk meta-data, so fake that. ++ if (m_download->info()->is_meta_download()) { ++ m_bitfield.set_size_bits(1); ++ m_bitfield.allocate(); ++ m_bitfield.set(0); ++ read_done(); ++ break; ++ } ++ + fill_read_buffer(5); + + // Received a keep-alive message which means we won't be +@@ -1022,6 +1033,10 @@ Handshake::prepare_peer_info() { + std::memcpy(m_peerInfo->set_options(), m_options, 8); + m_peerInfo->mutable_id().assign((const char*)m_readBuffer.position()); + m_readBuffer.consume(20); ++ ++ // For meta downloads, we require support of the extension protocol. ++ if (m_download->info()->is_meta_download() && !m_peerInfo->supports_extensions()) ++ throw handshake_error(ConnectionManager::handshake_dropped, e_handshake_unwanted_connection); + } + + void +diff --git a/src/protocol/peer_connection_base.cc b/src/protocol/peer_connection_base.cc +index ab043a6..815ea93 100644 +--- a/src/protocol/peer_connection_base.cc ++++ b/src/protocol/peer_connection_base.cc +@@ -93,8 +93,7 @@ PeerConnectionBase::~PeerConnectionBase() { + if (m_extensions != NULL && !m_extensions->is_default()) + delete m_extensions; + +- if (m_extensionMessage.owned()) +- m_extensionMessage.clear(); ++ m_extensionMessage.clear(); + } + + void +@@ -116,6 +115,8 @@ PeerConnectionBase::initialize(DownloadMain* download, PeerInfo* peerInfo, Socke + m_encryption = *encryptionInfo; + m_extensions = extensions; + ++ m_extensions->set_connection(this); ++ + m_peerChunks.set_peer_info(m_peerInfo); + m_peerChunks.bitfield()->swap(*bitfield); + +@@ -581,8 +582,12 @@ PeerConnectionBase::down_extension() { + m_extensions->read_move(bytes); + } + +- if (m_extensions->is_complete()) +- m_extensions->read_done(); ++ // If extension can't be processed yet (due to a pending write), ++ // disable reads until the pending message is completely sent. ++ if (m_extensions->is_complete() && !m_extensions->is_invalid() && !m_extensions->read_done()) { ++ manager->poll()->remove_read(this); ++ return false; ++ } + + return m_extensions->is_complete(); + } +@@ -693,12 +698,15 @@ PeerConnectionBase::up_extension() { + if (m_extensionOffset < m_extensionMessage.length()) + return false; + +- // clear() deletes the buffer, only do that if we made a copy, +- // otherwise the buffer is shared among all connections. +- if (m_extensionMessage.owned()) +- m_extensionMessage.clear(); +- else +- m_extensionMessage.set(NULL, NULL, false); ++ m_extensionMessage.clear(); ++ ++ // If we have an unprocessed message, process it now and enable reads again. ++ if (m_extensions->is_complete() && !m_extensions->is_invalid()) { ++ if (!m_extensions->read_done()) ++ throw internal_error("PeerConnectionBase::up_extension could not process complete extension message."); ++ ++ manager->poll()->insert_read(this); ++ } + + return true; + } +@@ -857,4 +865,16 @@ PeerConnectionBase::send_pex_message() { + return true; + } + ++// Extension protocol needs to send a reply. ++bool ++PeerConnectionBase::send_ext_message() { ++ write_prepare_extension(m_extensions->pending_message_type(), m_extensions->pending_message_data()); ++ m_extensions->clear_pending_message(); ++ return true; ++} ++ ++void ++PeerConnectionBase::receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) { ++} ++ + } +diff --git a/src/protocol/peer_connection_base.h b/src/protocol/peer_connection_base.h +index 2994963..d131341 100644 +--- a/src/protocol/peer_connection_base.h ++++ b/src/protocol/peer_connection_base.h +@@ -140,6 +140,9 @@ public: + void read_insert_poll_safe(); + void write_insert_poll_safe(); + ++ // Communication with the protocol extensions ++ virtual void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length); ++ + protected: + static const uint32_t extension_must_encrypt = ~uint32_t(); + +@@ -179,6 +182,7 @@ protected: + bool try_request_pieces(); + + bool send_pex_message(); ++ bool send_ext_message(); + + DownloadMain* m_download; + +diff --git a/src/protocol/peer_connection_leech.cc b/src/protocol/peer_connection_leech.cc +index a75d333..36c6d7a 100644 +--- a/src/protocol/peer_connection_leech.cc ++++ b/src/protocol/peer_connection_leech.cc +@@ -333,9 +333,13 @@ PeerConnection::read_message() { + m_down->set_state(ProtocolRead::READ_EXTENSION); + } + +- if (down_extension()) +- m_down->set_state(ProtocolRead::IDLE); ++ if (!down_extension()) ++ return false; + ++ if (m_extensions->has_pending_message()) ++ write_insert_poll_safe(); ++ ++ m_down->set_state(ProtocolRead::IDLE); + return true; + + default: +@@ -433,6 +437,9 @@ PeerConnection::event_read() { + if (!down_extension()) + return; + ++ if (m_extensions->has_pending_message()) ++ write_insert_poll_safe(); ++ + m_down->set_state(ProtocolRead::IDLE); + break; + +@@ -546,6 +553,10 @@ PeerConnection::fill_write_buffer() { + send_pex_message()) { + // Don't do anything else if send_pex_message() succeeded. + ++ } else if (m_extensions->has_pending_message() && m_up->can_write_extension() && ++ send_ext_message()) { ++ // Same. ++ + } else if (!m_upChoke.choked() && + !m_peerChunks.upload_queue()->empty() && + m_up->can_write_piece() && +diff --git a/src/protocol/peer_connection_metadata.cc b/src/protocol/peer_connection_metadata.cc +new file mode 100644 +index 0000000..24f13ca +--- /dev/null ++++ b/src/protocol/peer_connection_metadata.cc +@@ -0,0 +1,461 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2007, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++#include "config.h" ++ ++#include ++#include ++ ++#include "data/chunk_list_node.h" ++#include "download/choke_manager.h" ++#include "download/chunk_selector.h" ++#include "download/chunk_statistics.h" ++#include "download/download_info.h" ++#include "download/download_main.h" ++#include "torrent/dht_manager.h" ++#include "torrent/peer/connection_list.h" ++#include "torrent/peer/peer_info.h" ++ ++#include "extensions.h" ++#include "peer_connection_metadata.h" ++ ++namespace torrent { ++ ++PeerConnectionMetadata::~PeerConnectionMetadata() { ++} ++ ++void ++PeerConnectionMetadata::initialize_custom() { ++} ++ ++void ++PeerConnectionMetadata::update_interested() { ++} ++ ++bool ++PeerConnectionMetadata::receive_keepalive() { ++ if (cachedTime - m_timeLastRead > rak::timer::from_seconds(240)) ++ return false; ++ ++ m_tryRequest = true; ++ ++ // There's no point in adding ourselves to the write poll if the ++ // buffer is full, as that will already have been taken care of. ++ if (m_up->get_state() == ProtocolWrite::IDLE && ++ m_up->can_write_keepalive()) { ++ ++ write_insert_poll_safe(); ++ ++ ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); ++ m_up->write_keepalive(); ++ ++ if (is_encrypted()) ++ m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); ++ } ++ ++ return true; ++} ++ ++// We keep the message in the buffer if it is incomplete instead of ++// keeping the state and remembering the read information. This ++// shouldn't happen very often compared to full reads. ++inline bool ++PeerConnectionMetadata::read_message() { ++ ProtocolBuffer<512>* buf = m_down->buffer(); ++ ++ if (buf->remaining() < 4) ++ return false; ++ ++ // Remember the start of the message so we may reset it if we don't ++ // have the whole message. ++ ProtocolBuffer<512>::iterator beginning = buf->position(); ++ ++ uint32_t length = buf->read_32(); ++ ++ if (length == 0) { ++ // Keepalive message. ++ m_down->set_last_command(ProtocolBase::KEEP_ALIVE); ++ ++ return true; ++ ++ } else if (buf->remaining() < 1) { ++ buf->set_position_itr(beginning); ++ return false; ++ ++ } else if (length > (1 << 20)) { ++ throw communication_error("PeerConnection::read_message() got an invalid message length."); ++ } ++ ++ m_down->set_last_command((ProtocolBase::Protocol)buf->peek_8()); ++ ++ // Ignore most messages, they aren't relevant for a metadata download. ++ switch (buf->read_8()) { ++ case ProtocolBase::CHOKE: ++ case ProtocolBase::UNCHOKE: ++ case ProtocolBase::INTERESTED: ++ case ProtocolBase::NOT_INTERESTED: ++ return true; ++ ++ case ProtocolBase::HAVE: ++ if (!m_down->can_read_have_body()) ++ break; ++ ++ buf->read_32(); ++ return true; ++ ++ case ProtocolBase::REQUEST: ++ if (!m_down->can_read_request_body()) ++ break; ++ ++ m_down->read_request(); ++ return true; ++ ++ case ProtocolBase::PIECE: ++ throw communication_error("Received a piece but the connection is strictly for meta data."); ++ ++ case ProtocolBase::CANCEL: ++ if (!m_down->can_read_cancel_body()) ++ break; ++ ++ m_down->read_request(); ++ return true; ++ ++ case ProtocolBase::PORT: ++ if (!m_down->can_read_port_body()) ++ break; ++ ++ manager->dht_manager()->add_node(m_peerInfo->socket_address(), m_down->buffer()->read_16()); ++ return true; ++ ++ case ProtocolBase::EXTENSION_PROTOCOL: ++ if (!m_down->can_read_extension_body()) ++ break; ++ ++ if (m_extensions->is_default()) { ++ m_extensions = new ProtocolExtension(); ++ m_extensions->set_info(m_peerInfo, m_download); ++ } ++ ++ { ++ int extension = m_down->buffer()->read_8(); ++ m_extensions->read_start(extension, length - 2, (extension == ProtocolExtension::UT_PEX) && !m_download->want_pex_msg()); ++ m_down->set_state(ProtocolRead::READ_EXTENSION); ++ } ++ ++ if (!down_extension()) ++ return false; ++ ++ // Drop peer if it disabled the metadata extension. ++ if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) ++ throw close_connection(); ++ ++ m_down->set_state(ProtocolRead::IDLE); ++ m_tryRequest = true; ++ write_insert_poll_safe(); ++ ++ return true; ++ ++ case ProtocolBase::BITFIELD: ++ // Discard the bitfield sent by the peer. ++ m_skipLength = length - 1; ++ m_down->set_state(ProtocolRead::READ_SKIP_PIECE); ++ return false; ++ ++ default: ++ throw communication_error("Received unsupported message type."); ++ } ++ ++ // We were unsuccessfull in reading the message, need more data. ++ buf->set_position_itr(beginning); ++ return false; ++} ++ ++void ++PeerConnectionMetadata::event_read() { ++ m_timeLastRead = cachedTime; ++ ++ // Need to make sure ProtocolBuffer::end() is pointing to the end of ++ // the unread data, and that the unread data starts from the ++ // beginning of the buffer. Or do we use position? Propably best, ++ // therefor ProtocolBuffer::position() points to the beginning of ++ // the unused data. ++ ++ try { ++ ++ // Normal read. ++ // ++ // We rarely will read zero bytes as the read of 64 bytes will ++ // almost always either not fill up or it will require additional ++ // reads. ++ // ++ // Only loop when end hits 64. ++ ++ do { ++ switch (m_down->get_state()) { ++ case ProtocolRead::IDLE: ++ if (m_down->buffer()->size_end() < read_size) { ++ unsigned int length = read_stream_throws(m_down->buffer()->end(), read_size - m_down->buffer()->size_end()); ++ m_down->throttle()->node_used_unthrottled(length); ++ ++ if (is_encrypted()) ++ m_encryption.decrypt(m_down->buffer()->end(), length); ++ ++ m_down->buffer()->move_end(length); ++ } ++ ++ while (read_message()); ++ ++ if (m_down->buffer()->size_end() == read_size) { ++ m_down->buffer()->move_unused(); ++ break; ++ } else { ++ m_down->buffer()->move_unused(); ++ return; ++ } ++ ++ case ProtocolRead::READ_EXTENSION: ++ if (!down_extension()) ++ return; ++ ++ // Drop peer if it disabled the metadata extension. ++ if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) ++ throw close_connection(); ++ ++ m_down->set_state(ProtocolRead::IDLE); ++ m_tryRequest = true; ++ write_insert_poll_safe(); ++ break; ++ ++ // Actually skipping the bitfield. ++ // We never receive normal piece messages anyway. ++ case ProtocolRead::READ_SKIP_PIECE: ++ if (!read_skip_bitfield()) ++ return; ++ ++ m_down->set_state(ProtocolRead::IDLE); ++ break; ++ ++ default: ++ throw internal_error("PeerConnection::event_read() wrong state."); ++ } ++ ++ // Figure out how to get rid of the shouldLoop boolean. ++ } while (true); ++ ++ // Exception handlers: ++ ++ } catch (close_connection& e) { ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (blocked_connection& e) { ++ m_download->info()->signal_network_log().emit("Momentarily blocked read connection."); ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (network_error& e) { ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (storage_error& e) { ++ m_download->info()->signal_storage_error().emit(e.what()); ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (base_error& e) { ++ std::stringstream s; ++ s << "Connection read fd(" << get_fd().get_fd() << ',' << m_down->get_state() << ',' << m_down->last_command() << ") \"" << e.what() << '"'; ++ ++ throw internal_error(s.str()); ++ } ++} ++ ++inline void ++PeerConnectionMetadata::fill_write_buffer() { ++ ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); ++ ++ if (m_tryRequest) ++ m_tryRequest = try_request_metadata_pieces(); ++ ++ if (m_sendPEXMask && m_up->can_write_extension() && ++ send_pex_message()) { ++ // Don't do anything else if send_pex_message() succeeded. ++ ++ } else if (m_extensions->has_pending_message() && m_up->can_write_extension() && ++ send_ext_message()) { ++ // Same. ++ } ++ ++ if (is_encrypted()) ++ m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); ++} ++ ++void ++PeerConnectionMetadata::event_write() { ++ try { ++ ++ do { ++ ++ switch (m_up->get_state()) { ++ case ProtocolWrite::IDLE: ++ ++ fill_write_buffer(); ++ ++ if (m_up->buffer()->remaining() == 0) { ++ manager->poll()->remove_write(this); ++ return; ++ } ++ ++ m_up->set_state(ProtocolWrite::MSG); ++ ++ case ProtocolWrite::MSG: ++ if (!m_up->buffer()->consume(m_up->throttle()->node_used_unthrottled(write_stream_throws(m_up->buffer()->position(), m_up->buffer()->remaining())))) ++ return; ++ ++ m_up->buffer()->reset(); ++ ++ if (m_up->last_command() != ProtocolBase::EXTENSION_PROTOCOL) { ++ m_up->set_state(ProtocolWrite::IDLE); ++ break; ++ } ++ ++ m_up->set_state(ProtocolWrite::WRITE_EXTENSION); ++ ++ case ProtocolWrite::WRITE_EXTENSION: ++ if (!up_extension()) ++ return; ++ ++ m_up->set_state(ProtocolWrite::IDLE); ++ break; ++ ++ default: ++ throw internal_error("PeerConnection::event_write() wrong state."); ++ } ++ ++ } while (true); ++ ++ } catch (close_connection& e) { ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (blocked_connection& e) { ++ m_download->info()->signal_network_log().emit("Momentarily blocked write connection."); ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (network_error& e) { ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (storage_error& e) { ++ m_download->info()->signal_storage_error().emit(e.what()); ++ m_download->connection_list()->erase(this, 0); ++ ++ } catch (base_error& e) { ++ std::stringstream s; ++ s << "Connection write fd(" << get_fd().get_fd() << ',' << m_up->get_state() << ',' << m_up->last_command() << ") \"" << e.what() << '"'; ++ ++ throw internal_error(s.str()); ++ } ++} ++ ++bool ++PeerConnectionMetadata::read_skip_bitfield() { ++ if (m_down->buffer()->remaining()) { ++ uint32_t length = std::min(m_skipLength, (uint32_t)m_down->buffer()->remaining()); ++ m_down->buffer()->consume(length); ++ m_skipLength -= length; ++ } ++ ++ if (m_skipLength) { ++ uint32_t length = std::min(m_skipLength, (uint32_t)null_buffer_size); ++ length = read_stream_throws(m_nullBuffer, length); ++ if (!length) ++ return false; ++ m_skipLength -= length; ++ } ++ ++ return !m_skipLength; ++} ++ ++// Same as the PCB code, but only one at a time and with the extension protocol. ++bool ++PeerConnectionMetadata::try_request_metadata_pieces() { ++ if (m_download->file_list()->chunk_size() == 1 || !m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) ++ return false; ++ ++ if (download_queue()->queued_empty()) ++ m_downStall = 0; ++ ++ uint32_t pipeSize = download_queue()->calculate_pipe_size(m_peerChunks.download_throttle()->rate()->rate()); ++ ++ // Don't start requesting if we can't do it in large enough chunks. ++ if (download_queue()->queued_size() >= (pipeSize + 10) / 2) ++ return false; ++ ++ if (!download_queue()->queued_size() < pipeSize || !m_up->can_write_extension() || ++ m_extensions->has_pending_message()) ++ return false; ++ ++ const Piece* p = download_queue()->delegate(); ++ ++ if (p == NULL) ++ return false; ++ ++ if (!m_download->file_list()->is_valid_piece(*p) || !m_peerChunks.bitfield()->get(p->index())) ++ throw internal_error("PeerConnectionMetadata::try_request_metadata_pieces() tried to use an invalid piece."); ++ ++ return m_extensions->request_metadata_piece(p); ++} ++ ++void ++PeerConnectionMetadata::receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) { ++ if (data == NULL) { ++ // Length is not set in a reject message. ++ length = ProtocolExtension::metadata_piece_size; ++ if ((piece << ProtocolExtension::metadata_piece_shift) + ProtocolExtension::metadata_piece_size >= m_download->file_list()->size_bytes()) ++ length = m_download->file_list()->chunk_size() % ProtocolExtension::metadata_piece_size; ++ m_tryRequest = false; ++ read_cancel_piece(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length)); ++ return; ++ } ++ ++ if (!down_chunk_start(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length))) ++ down_chunk_skip_process(data, length); ++ else ++ down_chunk_process(data, length); ++ ++ if (!m_downloadQueue.transfer()->is_finished()) ++ throw internal_error("PeerConnectionMetadata::receive_metadata_piece did not have complete piece."); ++ ++ m_tryRequest = true; ++ down_chunk_finished(); ++} ++ ++} +diff --git a/src/protocol/peer_connection_metadata.h b/src/protocol/peer_connection_metadata.h +new file mode 100644 +index 0000000..127700a +--- /dev/null ++++ b/src/protocol/peer_connection_metadata.h +@@ -0,0 +1,73 @@ ++// libTorrent - BitTorrent library ++// Copyright (C) 2005-2007, Jari Sundell ++// ++// This program is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This program 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 General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this program; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++// ++// In addition, as a special exception, the copyright holders give ++// permission to link the code of portions of this program with the ++// OpenSSL library under certain conditions as described in each ++// individual source file, and distribute linked combinations ++// including the two. ++// ++// You must obey the GNU General Public License in all respects for ++// all of the code used other than OpenSSL. If you modify file(s) ++// with this exception, you may extend this exception to your version ++// of the file(s), but you are not obligated to do so. If you do not ++// wish to do so, delete this exception statement from your version. ++// If you delete this exception statement from all source files in the ++// program, then also delete it here. ++// ++// Contact: Jari Sundell ++// ++// Skomakerveien 33 ++// 3185 Skoppum, NORWAY ++ ++#ifndef LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H ++#define LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H ++ ++#include "peer_connection_base.h" ++ ++#include "torrent/download.h" ++ ++namespace torrent { ++ ++class PeerConnectionMetadata : public PeerConnectionBase { ++public: ++ ~PeerConnectionMetadata(); ++ ++ virtual void initialize_custom(); ++ virtual void update_interested(); ++ virtual bool receive_keepalive(); ++ ++ virtual void event_read(); ++ virtual void event_write(); ++ ++ virtual void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length); ++ ++private: ++ inline bool read_message(); ++ ++ bool read_skip_bitfield(); ++ ++ bool try_request_metadata_pieces(); ++ ++ inline void fill_write_buffer(); ++ ++ uint32_t m_skipLength; ++}; ++ ++} ++ ++#endif +diff --git a/src/protocol/peer_factory.cc b/src/protocol/peer_factory.cc +index 7ab9fe8..cfe6a1e 100644 +--- a/src/protocol/peer_factory.cc ++++ b/src/protocol/peer_factory.cc +@@ -38,6 +38,7 @@ + + #include "peer_factory.h" + #include "peer_connection_leech.h" ++#include "peer_connection_metadata.h" + + namespace torrent { + +@@ -62,4 +63,11 @@ createPeerConnectionInitialSeed(bool encrypted) { + return pc; + } + ++PeerConnectionBase* ++createPeerConnectionMetadata(bool encrypted) { ++ PeerConnectionBase* pc = new PeerConnectionMetadata; ++ ++ return pc; ++} ++ + } +diff --git a/src/protocol/peer_factory.h b/src/protocol/peer_factory.h +index 363a5c3..f22d76f 100644 +--- a/src/protocol/peer_factory.h ++++ b/src/protocol/peer_factory.h +@@ -44,6 +44,7 @@ class PeerConnectionBase; + PeerConnectionBase* createPeerConnectionDefault(bool encrypted); + PeerConnectionBase* createPeerConnectionSeed(bool encrypted); + PeerConnectionBase* createPeerConnectionInitialSeed(bool encrypted); ++PeerConnectionBase* createPeerConnectionMetadata(bool encrypted); + + } + +diff --git a/src/torrent/data/file_list.cc b/src/torrent/data/file_list.cc +index 2f5d8d2..7208612 100644 +--- a/src/torrent/data/file_list.cc ++++ b/src/torrent/data/file_list.cc +@@ -466,6 +466,18 @@ FileList::open(int flags) { + + m_isOpen = true; + m_frozenRootDir = m_rootDir; ++ ++ // For meta-downloads, if the file exists, we have to assume that ++ // it is either 0 or 1 length or the correct size. If the size ++ // turns out wrong later, a communication_error will be thrown elsewhere ++ // to alert the user in this (unlikely) case. ++ if (size_bytes() < 2) { ++ rak::file_stat stat; ++ ++ // This probably recurses into open() once, but that is harmless. ++ if (stat.update((*begin())->frozen_path()) && stat.size() > 1) ++ return reset_filesize(stat.size()); ++ } + } + + void +@@ -661,4 +673,14 @@ FileList::update_completed() { + } + } + ++void ++FileList::reset_filesize(int64_t size) { ++ close(); ++ m_chunkSize = size; ++ m_torrentSize = size; ++ (*begin())->set_size_bytes(size); ++ (*begin())->set_range(m_chunkSize); ++ open(open_no_create); ++} ++ + } +diff --git a/src/torrent/data/file_list.h b/src/torrent/data/file_list.h +index bcc8939..60d418a 100644 +--- a/src/torrent/data/file_list.h ++++ b/src/torrent/data/file_list.h +@@ -167,6 +167,10 @@ protected: + iterator inc_completed(iterator firstItr, uint32_t index) LIBTORRENT_NO_EXPORT; + void update_completed() LIBTORRENT_NO_EXPORT; + ++ // Used for meta downloads; we only know the ++ // size after the first extension handshake. ++ void reset_filesize(int64_t) LIBTORRENT_NO_EXPORT; ++ + private: + bool open_file(File* node, const Path& lastPath, int flags) LIBTORRENT_NO_EXPORT; + void make_directory(Path::const_iterator pathBegin, Path::const_iterator pathEnd, Path::const_iterator startItr) LIBTORRENT_NO_EXPORT; +diff --git a/src/torrent/download.cc b/src/torrent/download.cc +index d6cc199..49daad9 100644 +--- a/src/torrent/download.cc ++++ b/src/torrent/download.cc +@@ -225,6 +225,11 @@ Download::set_pex_enabled(bool enabled) { + m_ptr->info()->set_pex_enabled(enabled); + } + ++bool ++Download::is_meta_download() const { ++ return m_ptr->info()->is_meta_download(); ++} ++ + const std::string& + Download::name() const { + if (m_ptr == NULL) +@@ -504,6 +509,11 @@ Download::connection_type() const { + + void + Download::set_connection_type(ConnectionType t) { ++ if (m_ptr->info()->is_meta_download()) { ++ m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionMetadata); ++ return; ++ } ++ + switch (t) { + case CONNECTION_LEECH: + m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionDefault); +diff --git a/src/torrent/download.h b/src/torrent/download.h +index 5e9700e..5d16d4e 100644 +--- a/src/torrent/download.h ++++ b/src/torrent/download.h +@@ -100,6 +100,8 @@ public: + bool is_pex_enabled() const; + void set_pex_enabled(bool enabled); + ++ bool is_meta_download() const; ++ + // Returns "" if the object is not valid. + const std::string& name() const; + +@@ -184,6 +186,7 @@ public: + CONNECTION_LEECH, + CONNECTION_SEED, + CONNECTION_INITIAL_SEED, ++ CONNECTION_METADATA, + } ConnectionType; + + ConnectionType connection_type() const; +diff --git a/src/torrent/object_stream.cc b/src/torrent/object_stream.cc +index 73c816b..9d9a962 100644 +--- a/src/torrent/object_stream.cc ++++ b/src/torrent/object_stream.cc +@@ -600,4 +600,11 @@ object_write_to_stream(void* data, object_buffer_t buffer) { + return buffer; + } + ++object_buffer_t ++object_write_to_size(void* data, object_buffer_t buffer) { ++ *reinterpret_cast(data) += std::distance(buffer.first, buffer.second); ++ ++ return buffer; ++} ++ + } +diff --git a/src/torrent/object_stream.h b/src/torrent/object_stream.h +index b399bf7..3de5d82 100644 +--- a/src/torrent/object_stream.h ++++ b/src/torrent/object_stream.h +@@ -91,6 +91,9 @@ object_buffer_t staticMap_write_bencode_c_wrap(object_write_t writeFunc, void* d + object_buffer_t object_write_to_buffer(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; + object_buffer_t object_write_to_sha1(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; + object_buffer_t object_write_to_stream(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; ++ ++// Measures bencode size, 'data' is uint64_t*. ++object_buffer_t object_write_to_size(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; + } + + #endif +diff --git a/src/torrent/torrent.cc b/src/torrent/torrent.cc +index e8ffbac..47027cc 100644 +--- a/src/torrent/torrent.cc ++++ b/src/torrent/torrent.cc +@@ -350,11 +350,22 @@ download_add(Object* object) { + + ctor.initialize(*object); + +- std::string infoHash = object_sha1(&object->get_key("info")); ++ std::string infoHash; ++ if (download->info()->is_meta_download()) ++ infoHash = object->get_key("info").get_key("pieces").as_string(); ++ else ++ infoHash = object_sha1(&object->get_key("info")); + + if (manager->download_manager()->find(infoHash) != manager->download_manager()->end()) + throw input_error("Info hash already used by another torrent."); + ++ if (!download->info()->is_meta_download()) { ++ char buffer[1024]; ++ uint64_t metadata_size = 0; ++ object_write_bencode_c(&object_write_to_size, &metadata_size, object_buffer_t(buffer, buffer + sizeof(buffer)), &object->get_key("info")); ++ download->main()->set_metadata_size(metadata_size); ++ } ++ + download->set_hash_queue(manager->hash_queue()); + download->initialize(infoHash, PEER_NAME + rak::generate_random(20 - std::string(PEER_NAME).size())); + diff --git a/libtorrent-extended/object_sstr.patch b/libtorrent-extended/object_sstr.patch new file mode 100644 index 0000000..f577080 --- /dev/null +++ b/libtorrent-extended/object_sstr.patch @@ -0,0 +1,233 @@ +diff --git a/src/dht/dht_node.cc b/src/dht/dht_node.cc +index 3574807..9d51a28 100644 +--- a/src/dht/dht_node.cc ++++ b/src/dht/dht_node.cc +@@ -59,8 +59,8 @@ DhtNode::DhtNode(const HashString& id, const rak::socket_address* sa) : + throw resource_error("Address not af_inet"); + } + +-DhtNode::DhtNode(const std::string& id, const Object& cache) : +- HashString(*HashString::cast_from(id.c_str())), ++DhtNode::DhtNode(const SimpleString& id, const Object& cache) : ++ HashString(*HashString::cast_from(id)), + m_recentlyActive(false), + m_recentlyInactive(0), + m_bucket(NULL) { +diff --git a/src/dht/dht_node.h b/src/dht/dht_node.h +index 032c5cc..8234add 100644 +--- a/src/dht/dht_node.h ++++ b/src/dht/dht_node.h +@@ -57,7 +57,7 @@ public: + static const unsigned int max_failed_replies = 5; + + DhtNode(const HashString& id, const rak::socket_address* sa); +- DhtNode(const std::string& id, const Object& cache); ++ DhtNode(const SimpleString& id, const Object& cache); + + const HashString& id() const { return *this; } + const rak::socket_address* address() const { return &m_socketAddress; } +diff --git a/src/dht/dht_router.cc b/src/dht/dht_router.cc +index ff38b8c..e9abe5d 100644 +--- a/src/dht/dht_router.cc ++++ b/src/dht/dht_router.cc +@@ -337,7 +337,7 @@ DhtRouter::store_cache(Object* container) const { + Object& nodes = container->insert_key("nodes", Object::create_map()); + for (DhtNodeList::const_accessor itr = m_nodes.begin(); itr != m_nodes.end(); ++itr) { + if (!itr.node()->is_bad()) +- itr.node()->store_cache(&nodes.insert_key(itr.id().str(), Object::create_map())); ++ itr.node()->store_cache(&nodes.insert_key(itr.id().s_str(), Object::create_map())); + } + + // Insert contacts, if we have any. +diff --git a/src/torrent/object.cc b/src/torrent/object.cc +index 3a0bcae..b609f9c 100644 +--- a/src/torrent/object.cc ++++ b/src/torrent/object.cc +@@ -44,47 +44,59 @@ + + namespace torrent { + +-Object& +-Object::get_key(const std::string& k) { +- check_throw(TYPE_MAP); +- map_type::iterator itr = m_map->find(k); ++std::pair ++Object::map_type::insert(const value_type& value) { ++ base_type::iterator itr = lower_bound(value.first); + +- if (itr == m_map->end()) +- throw bencode_error("Object operator [" + k + "] could not find element"); ++ if (itr != end() && !key_comp()(value.first, itr->first)) ++ return std::make_pair(itr, false); + +- return itr->second; +-} ++ // Insert with an allocated copy of the key. ++ itr = base_type::insert(itr, value_type(value.first.copy(), value.second)); + ++ // This means the value was actually already present. ++ if (itr->second.get_string() != NULL) ++ throw internal_error("Object::map_type::insert failed to insert value."); + +-const Object& +-Object::get_key(const std::string& k) const { +- check_throw(TYPE_MAP); +- map_type::const_iterator itr = m_map->find(k); ++ // Make entry own the string and free it when erased. ++ itr->second.set_string(itr->first.c_str()); + +- if (itr == m_map->end()) +- throw bencode_error("Object operator [" + k + "] could not find element"); ++ return std::make_pair(itr, true); ++} + +- return itr->second; ++Object::map_type::base_type::iterator ++Object::map_type::insert(base_type::iterator itr, const value_type& value) { ++ SimpleString copy = value.first.copy(); ++ itr = base_type::insert(itr, value_type(copy, value.second)); ++ ++ // If the entry already owns its string, it wasn't really ++ // inserted and already existed, so discard the copy. ++ if (itr->second.get_string() != NULL) ++ delete [] copy.c_str(); ++ else ++ itr->second.set_string(itr->first.c_str()); ++ ++ return itr; + } + + Object& +-Object::get_key(const char* k) { ++Object::get_key(const key_type& k) { + check_throw(TYPE_MAP); +- map_type::iterator itr = m_map->find(std::string(k)); ++ map_type::iterator itr = m_map->find(k); + + if (itr == m_map->end()) +- throw bencode_error("Object operator [" + std::string(k) + "] could not find element"); ++ throw bencode_error("Object operator [" + k.str() + "] could not find element"); + + return itr->second; + } + + const Object& +-Object::get_key(const char* k) const { ++Object::get_key(const key_type& k) const { + check_throw(TYPE_MAP); +- map_type::iterator itr = m_map->find(std::string(k)); ++ map_type::iterator itr = m_map->find(k); + + if (itr == m_map->end()) +- throw bencode_error("Object operator [" + std::string(k) + "] could not find element"); ++ throw bencode_error("Object operator [" + k.str() + "] could not find element"); + + return itr->second; + } +@@ -143,7 +155,7 @@ Object::merge_copy(const Object& object, uint32_t maxDepth) { + while (srcItr != srcLast) { + destItr = std::find_if(destItr, dest.end(), rak::less_equal(srcItr->first, rak::mem_ref(&map_type::value_type::first))); + +- if (srcItr->first < destItr->first) ++ if (dest.key_comp()(srcItr->first, destItr->first)) + // destItr remains valid and pointing to the next possible + // position. + dest.insert(destItr, *srcItr); +diff --git a/src/torrent/object.h b/src/torrent/object.h +index 6cc4e4a..b7b4e8f 100644 +--- a/src/torrent/object.h ++++ b/src/torrent/object.h +@@ -46,18 +46,52 @@ + + namespace torrent { + +-// TODO: Look into making a custom comp and allocator classes for the +-// map_type which use a const char* for key_type. +-// + // TODO: Use placement new/delete in order to avoid the extra level of + // indirection caused by the union. + + class LIBTORRENT_EXPORT Object { ++ template ++ class string_wrapper : public T { ++ public: ++ string_wrapper() : T(), m_string(NULL) {} ++ string_wrapper(const T& value) : T(value), m_string(NULL) {} ++ string_wrapper(const string_wrapper& other) : T(other), m_string(NULL) {} ++ ++ ~string_wrapper() { delete [] m_string; m_string = NULL; } ++ ++ const char* get_string() const { return m_string; } ++ void set_string(const char* s) { m_string = s; } ++ ++ private: ++ string_wrapper& operator = (const string_wrapper& other); ++ ++ const char* m_string; ++ }; ++ + public: + typedef int64_t value_type; + typedef std::string string_type; + typedef std::list list_type; +- typedef std::map map_type; ++ class map_type : public std::map > { ++ public: ++ typedef std::map > base_type; ++ using base_type::value_type; ++ using base_type::key_type; ++ ++ map_type(const map_type& other) : base_type(other.key_comp()) { insert(other.begin(), other.end()); } ++ map_type() {} ++ ++ std::pair insert(const value_type& value); ++ base_type::iterator insert(base_type::iterator itr, const value_type& value); ++ ++ template ++ void insert(InputIterator begin, InputIterator end); ++ ++ Object& operator[] (key_type key); ++ ++ private: ++ map_type& operator = (const map_type& other); ++ }; + typedef map_type::key_type key_type; + + typedef list_type::iterator list_iterator; +@@ -153,8 +187,6 @@ public: + + Object& get_key(const key_type& k); + const Object& get_key(const key_type& k) const; +- Object& get_key(const char* k); +- const Object& get_key(const char* k) const; + + template value_type& get_key_value(const T& k) { return get_key(k).as_value(); } + template const value_type& get_key_value(const T& k) const { return get_key(k).as_value(); } +@@ -213,6 +245,27 @@ public: + }; + }; + ++// We need to call our own insert function, so ++// we have to define this operator to use that. ++inline Object& ++Object::map_type::operator[] (key_type key) { ++ base_type::iterator itr = lower_bound(key); ++ ++ if (itr == end() || key_comp()(key, itr->first)) ++ itr = insert(itr, value_type(key, mapped_type())); ++ ++ return itr->second; ++} ++ ++template ++inline void ++Object::map_type::insert(InputIterator itr, InputIterator itrEnd) { ++ while (itr != itrEnd) { ++ insert(end(), *itr); ++ ++itr; ++ } ++} ++ + inline + Object::Object(const Object& b) : m_flags(b.type()) { + switch (type()) { diff --git a/rtorrent-extended/PKGBUILD b/rtorrent-extended/PKGBUILD new file mode 100644 index 0000000..532273f --- /dev/null +++ b/rtorrent-extended/PKGBUILD @@ -0,0 +1,122 @@ +# Maintainer: Lucky +# Contributor: Ashren +# Contributor: Daenyth +# Contributor: Jeff Mickey +# Contributor: sh__ + +pkgname=rtorrent-extended +_pkgname=rtorrent +pkgver=0.8.6 +pkgrel=4 +pkgdesc="Ncurses BitTorrent client based on libTorrent with magnet link, IPv6, ipfilter, bad peers, color and trackerinfo patch." +arch=('i686' 'x86_64') +url="http://libtorrent.rakshasa.no" +license=('GPL') +depends=('libtorrent-extended=0.12.6-3' 'curl>=7.14.3' 'xmlrpc-c>=1858') +conflicts=('rtorrent') +provides=('rtorrent') +install=${pkgname}.install +source=(http://libtorrent.rakshasa.no/downloads/${_pkgname}-${pkgver}.tar.gz + # support for magnet links + # http://libtorrent.rakshasa.no/ticket/955 (deleted) + # http://libtorrent.rakshasa.no/ticket/2100 + dht_pex_static_map.patch + magnet_uri.patch + # support for IPv6 + # http://libtorrent.rakshasa.no/ticket/1111 + ipv6.patch + # support for ipfilter.dat + # http://libtorrent.rakshasa.no/ticket/239 + ip_filter_no_boost_fast.patch + # support for bad peers (kick/ban) + # http://ovh.ttdpatch.net/~jdrexler/rt/experimental/bad_peer_handling.diff + # https://calomel.org/rtorrent_mods.html + bad_peer_handling.patch + # support for colors + # http://libtorrent.rakshasa.no/ticket/1382 + canvas_color.patch + # karabaja mod patchset (color, interface) + # http://aur.archlinux.org/packages.php?ID=36604 + karabaja_mod.patch + # more infos at tracker list + trackerinfo.patch + # http://aur.archlinux.org/packages.php?ID=33756 + vi_kb_akston.patch + # http://aur.archlinux.org/packages.php?ID=35642 + vi_kb_tjwoosta.patch) +md5sums=('b804c45c01c40312926bcea6b55bb084' + '795c2818d4d869f90f1b7734f94347fc' + '8dcd2c76ee2ed48e86707a2b94b34c34' + 'cd9f4ce9202f6e03699b962f372a09e0' + '142f39586cd916608a7d569473bce1b0' + 'e698da498f306435fb8f89824c47e0e8' + '8721dea9d4da9a4f11051de7a2490e40' + 'fdcee74cc8dc4abb1a5b627ae00b8345' + '1fc3b40153450f34f8f4c4e1a161b6b8' + '752590f272e48a2828a7e64746778df8' + 'c5620714083fff8d609924c6bdb37fa0') + +build() { + cd "${srcdir}/${_pkgname}-${pkgver}" + + echo -e "\e[1;32m===> \e[0m Do you want magentlink patch? [Y/n]" + echo -e "\e[1;31m===> \e[0m !!! needed by ipv6 patch !!!" + read _answer_ml + if [[ ! ${_answer_ml} = [n,N] ]]; then + patch -p1 < ${srcdir}/dht_pex_static_map.patch + patch -p1 < ${srcdir}/magnet_uri.patch + fi + echo -e "\e[1;32m===> \e[0m Do you want ipv6 patch? [y/N]" + echo -e "\e[1;33m===> \e[0m !!! scgi/xmlrpc-c won't work with this patch !!!" + read _answer_ipv6 + if [[ ${_answer_ipv6} = [y,Y] ]]; then + patch -p1 < ${srcdir}/ipv6.patch + _option_ipv6="--enable-ipv6" + fi + echo -e "\e[1;32m===> \e[0m Do you want ipfilter patch? [Y/n]" + read _answer_ipf + if [[ ! ${_answer_ipf} = [n,N] ]]; then + patch -p1 < ${srcdir}/ip_filter_no_boost_fast.patch + fi + echo -e "\e[1;32m===> \e[0m Do you want bad peer handling patch? [Y/n]" + read _answer_bph + if [[ ! ${_answer_bph} = [n,N] ]]; then + patch -p1 < ${srcdir}/bad_peer_handling.patch + fi + echo -e "\e[1;32m===> \e[0m Do you want color/mod patch? [1/2/n]" + echo -e "\e[1;33m===> \e[0m 1) karabaja mod (default)" + echo -e "\e[1;33m===> \e[0m 2) canvas color" + read _answer_cc + if [[ ! ${_answer_cc} = [n,N] ]]; then + if [[ ! ${_answer_cc} = [2] ]]; then + patch -p1 < ${srcdir}/karabaja_mod.patch + else + patch -p1 < ${srcdir}/canvas_color.patch + fi + fi + echo -e "\e[1;32m===> \e[0m Do you want tracker info patch? [Y/n]" + read _answer_ti + if [[ ! ${_answer_ti} = [n,N] ]]; then + patch -p1 < ${srcdir}/trackerinfo.patch + fi + echo -e "\e[1;32m===> \e[0m Do you want keybindings patch? [1/2/n]" + echo -e "\e[1;33m===> \e[0m 1) vi_kb_tjwoosta (default)" + echo -e "\e[1;33m===> \e[0m 2) vi_kb_akston" + read _answer_kb + if [[ ! ${_answer_kb} = [n,N] ]]; then + if [[ ! ${_answer_kb} = [2] ]]; then + patch -p1 < ${srcdir}/vi_kb_tjwoosta.patch + else + patch -p1 < ${srcdir}/vi_kb_akston.patch + fi + fi + + sed -i 's/rTorrent \" VERSION/rTorrent-eXtended " VERSION/' src/ui/download_list.cc + + ./autogen.sh + CXXFLAGS="${CXXFLAGS} -fno-strict-aliasing" \ + ./configure --prefix=/usr --disable-debug ${_option_ipv6} --with-xmlrpc-c || return 1 + make || return 1 + make DESTDIR="${pkgdir}" install +} +# vim:set ts=2 sw=2 et: diff --git a/rtorrent-extended/bad_peer_handling.patch b/rtorrent-extended/bad_peer_handling.patch new file mode 100644 index 0000000..293cfad --- /dev/null +++ b/rtorrent-extended/bad_peer_handling.patch @@ -0,0 +1,219 @@ +diff --git a/src/command_download.cc b/src/command_download.cc +index 6043662..9afd9b0 100644 +--- a/src/command_download.cc ++++ b/src/command_download.cc +@@ -51,12 +51,14 @@ + #include + #include + #include ++#include + #include + + #include "core/download.h" + #include "core/download_store.h" + #include "core/manager.h" + #include "rpc/command_variable.h" ++#include "rpc/parse.h" + + #include "globals.h" + #include "control.h" +@@ -170,6 +172,120 @@ apply_d_delete_tied(core::Download* download) { + rpc::call_command("d.set_tied_to_file", std::string(), rpc::make_target(download)); + } + ++torrent::Object ++apply_d_snub_leechers(core::Download* download, const torrent::Object& rawArgs) { ++ if (!download->is_open() || download->is_done() || rpc::call_command_value("d.get_ignore_commands", rpc::make_target(download)) != 0) ++ return torrent::Object(); ++ ++ const torrent::Object::list_type& args = rawArgs.as_list(); ++ ++ if (args.size() < 3) ++ throw torrent::input_error("Too few arguments."); ++ ++ torrent::Object::list_type::const_iterator argItr = args.begin(); ++ uint64_t snub_ratio = rpc::convert_to_value(*argItr++); ++ uint64_t unsnub_ratio = rpc::convert_to_value(*argItr++); ++ uint64_t min_transfer = rpc::convert_to_value(*argItr++); ++ ++ for (torrent::ConnectionList::iterator itr = download->download()->connection_list()->begin(); itr != download->download()->connection_list()->end(); ++itr) { ++#if LIBTORRENT_FRIENDS ++ if (((*itr)->bitfield() && (*itr)->bitfield()->is_all_set()) || (*itr)->peer_info()->is_friend()) ++#else ++ if (((*itr)->bitfield() && (*itr)->bitfield()->is_all_set())) ++#endif ++ continue; ++ ++ uint64_t up = (*itr)->up_rate()->total(); ++ uint64_t down = (*itr)->down_rate()->total(); ++ ++ if ((*itr)->is_snubbed()) { ++ if (down * unsnub_ratio >= std::max(up, min_transfer)) ++ (*itr)->set_snubbed(false); ++ ++ } else if (up > min_transfer && down * snub_ratio < up) { ++ (*itr)->set_snubbed(true); ++ } ++ } ++ ++ return torrent::Object(); ++} ++ ++torrent::Object ++apply_d_ban_slow_peers(core::Download* download, const torrent::Object& rawArgs) { ++ if (!download->is_open() || download->is_done() || rpc::call_command_value("d.get_ignore_commands", rpc::make_target(download)) != 0) ++ return torrent::Object(); ++ ++ const torrent::Object::list_type& args = rawArgs.as_list(); ++ ++ if (args.size() < 4) ++ throw torrent::input_error("Too few arguments."); ++ ++ torrent::ConnectionList* clist = download->download()->connection_list(); ++ int extraPeers = clist->size() - clist->min_size(); ++ if (extraPeers <= 0) ++ return torrent::Object(); ++ ++ torrent::Object::list_type::const_iterator argItrStart = args.begin(); ++ int extraSeeds = download->download()->peers_complete() - rpc::convert_to_value(*argItrStart++); ++ uint32_t minRate = rpc::convert_to_value(*argItrStart++); ++ ++ for (torrent::ConnectionList::iterator itr = clist->begin(); extraPeers > 0 && itr != clist->end(); ++itr) { ++#if LIBTORRENT_FRIENDS ++ if ((*itr)->peer_info()->is_friend()) ++ continue; ++#endif ++ ++ bool isSeed = (*itr)->bitfield() && (*itr)->bitfield()->is_all_set(); ++ if (isSeed && extraSeeds <= 0) ++ continue; ++ ++ int64_t down = (*itr)->down_rate()->total(); ++ uint32_t rate = (*itr)->down_rate()->rate(); ++ ++ for (torrent::Object::list_type::const_iterator argItr = argItrStart; argItr != args.end(); ++argItr) { ++ if (rate >= minRate || down >= rpc::convert_to_value(*argItr++)) ++ break; ++ ++ if (cachedTime.seconds() - (*itr)->peer_info()->last_connection() < rpc::convert_to_value(*argItr) * 60) ++ continue; ++ ++ (*itr)->set_banned(); ++ ++ extraSeeds -= isSeed; ++ extraPeers--; ++ break; ++ } ++ } ++ ++ // Need to go by indices because erasing may invalidate iterators. ++ for (size_t pId = 0; pId < clist->size(); ) ++ if ((*(clist->begin() + pId))->is_banned()) ++ download->connection_list()->erase(*(clist->begin() + pId), 0); ++ else ++ pId++; ++ ++ return torrent::Object(); ++} ++ ++void ++apply_d_unban_peers(core::Download* download) { ++ torrent::PeerList* list = download->download()->peer_list(); ++ ++ for (torrent::PeerList::const_iterator itr = list->begin(); itr != list->end(); ++itr) ++ if (itr->second->is_banned()) ++ itr->second->set_unbanned(); ++} ++ ++void ++apply_d_unsnub_peers(core::Download* download) { ++ if (!download->is_open()) ++ return; ++ ++ for (torrent::ConnectionList::iterator itr = download->download()->connection_list()->begin(); itr != download->download()->connection_list()->end(); ++itr) ++ if ((*itr)->is_snubbed()) ++ (*itr)->set_snubbed(false); ++} ++ + void + apply_d_connection_type(core::Download* download, const std::string& name) { + torrent::Download::ConnectionType connType; +@@ -580,6 +696,11 @@ initialize_command_download() { + ADD_CD_LIST("delete_link", rak::bind_ptr_fn(&apply_d_change_link, 1)); + ADD_CD_V_VOID("delete_tied", &apply_d_delete_tied); + ++ ADD_CD_LIST("ban_slow_peers", rak::ptr_fn(&apply_d_ban_slow_peers)); ++ ADD_CD_LIST("snub_leechers", rak::ptr_fn(&apply_d_snub_leechers)); ++ ADD_CD_V_VOID("unban_peers", &apply_d_unban_peers); ++ ADD_CD_V_VOID("unsnub_peers", &apply_d_unsnub_peers); ++ + CMD_FUNC_SINGLE("d.start", "d.set_hashing_failed=0 ;view.set_visible=started"); + CMD_FUNC_SINGLE("d.stop", "view.set_visible=stopped"); + CMD_FUNC_SINGLE("d.try_start", "branch=\"or={d.get_hashing_failed=,d.get_ignore_commands=}\",{},{view.set_visible=started}"); +diff --git a/src/command_events.cc b/src/command_events.cc +index b25dfbc..a0250d9 100644 +--- a/src/command_events.cc ++++ b/src/command_events.cc +@@ -42,6 +42,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + +@@ -276,6 +278,31 @@ apply_close_low_diskspace(int64_t arg) { + control->core()->push_log("Closed torrents due to low diskspace."); + } + ++// Should call the d.* commands via RPC, but there doesn't seem to be a way to ++// pass variable-sized argument lists, so call the functions directly for now. ++torrent::Object apply_d_snub_leechers(core::Download*, const torrent::Object&); ++torrent::Object apply_d_ban_slow_peers(core::Download*, const torrent::Object&); ++ ++torrent::Object ++apply_snub_leechers(const torrent::Object& rawArgs) { ++ for (core::Manager::DListItr ditr = control->core()->download_list()->begin(); ditr != control->core()->download_list()->end(); ditr++) { ++ if ((*ditr)->is_open() && !(*ditr)->is_done() && rpc::call_command_value("d.get_ignore_commands", rpc::make_target(*ditr)) == 0) ++ apply_d_snub_leechers(*ditr, rawArgs); ++ } ++ ++ return torrent::Object(); ++} ++ ++torrent::Object ++apply_ban_slow_peers(const torrent::Object& rawArgs) { ++ for (core::Manager::DListItr ditr = control->core()->download_list()->begin(); ditr != control->core()->download_list()->end(); ditr++) { ++ if ((*ditr)->is_open() && !(*ditr)->is_done() && rpc::call_command_value("d.get_ignore_commands", rpc::make_target(*ditr)) == 0) ++ apply_d_ban_slow_peers(*ditr, rawArgs); ++ } ++ ++ return torrent::Object(); ++} ++ + torrent::Object + apply_download_list(const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); +@@ -369,6 +396,8 @@ initialize_command_events() { + ADD_COMMAND_LIST("on_finished", rak::bind_ptr_fn(&apply_on_state_change, "event.download.finished")); + + ADD_COMMAND_STRING("on_ratio", rak::ptr_fn(&apply_on_ratio)); ++ ADD_COMMAND_LIST("snub_leechers", rak::ptr_fn(&apply_snub_leechers)); ++ ADD_COMMAND_LIST("ban_slow_peers", rak::ptr_fn(&apply_ban_slow_peers)); + + ADD_COMMAND_VOID("start_tied", &apply_start_tied); + ADD_COMMAND_VOID("stop_untied", &apply_stop_untied); +diff --git a/src/command_helpers.h b/src/command_helpers.h +index a807b5f..cda29d8 100644 +--- a/src/command_helpers.h ++++ b/src/command_helpers.h +@@ -54,7 +54,7 @@ namespace rpc { + #define COMMAND_DOWNLOAD_SLOTS_SIZE 150 + #define COMMAND_FILE_SLOTS_SIZE 30 + #define COMMAND_FILE_ITR_SLOTS_SIZE 10 +-#define COMMAND_PEER_SLOTS_SIZE 20 ++#define COMMAND_PEER_SLOTS_SIZE 30 + #define COMMAND_TRACKER_SLOTS_SIZE 15 + #define COMMAND_ANY_SLOTS_SIZE 50 + diff --git a/rtorrent-extended/canvas_color.patch b/rtorrent-extended/canvas_color.patch new file mode 100644 index 0000000..88423b1 --- /dev/null +++ b/rtorrent-extended/canvas_color.patch @@ -0,0 +1,282 @@ +diff --git a/src/command_network.cc b/src/command_network.cc +index 2494988..7b105ca 100644 +--- a/src/command_network.cc ++++ b/src/command_network.cc +@@ -560,4 +560,8 @@ initialize_command_network() { + // Not really network stuff: + ADD_VARIABLE_BOOL ("handshake_log", false); + ADD_VARIABLE_STRING("log.tracker", ""); ++ ADD_COMMAND_VALUE_TRI("done_fg_color", rak::make_mem_fun(control->ui(), &ui::Root::set_done_fg_color), rak::make_mem_fun(control->ui(), &ui::Root::get_done_fg_color)); ++ ADD_COMMAND_VALUE_TRI("done_bg_color", rak::make_mem_fun(control->ui(), &ui::Root::set_done_bg_color), rak::make_mem_fun(control->ui(), &ui::Root::get_done_bg_color)); ++ ADD_COMMAND_VALUE_TRI("active_fg_color", rak::make_mem_fun(control->ui(), &ui::Root::set_active_fg_color), rak::make_mem_fun(control->ui(), &ui::Root::get_active_fg_color)); ++ ADD_COMMAND_VALUE_TRI("active_bg_color", rak::make_mem_fun(control->ui(), &ui::Root::set_active_bg_color), rak::make_mem_fun(control->ui(), &ui::Root::get_active_bg_color)); + } +diff --git a/src/display/canvas.cc b/src/display/canvas.cc +index 4e621df..0c97eea 100644 +--- a/src/display/canvas.cc ++++ b/src/display/canvas.cc +@@ -92,6 +92,10 @@ Canvas::initialize() { + m_isInitialized = true; + + initscr(); ++ start_color(); ++ use_default_colors(); ++ init_pair(2, -1, -1); ++ init_pair(1, -1, -1); + raw(); + noecho(); + nodelay(stdscr, TRUE); +diff --git a/src/display/window_download_list.cc b/src/display/window_download_list.cc +index 71efec0..06e7cd4 100644 +--- a/src/display/window_download_list.cc ++++ b/src/display/window_download_list.cc +@@ -37,6 +37,7 @@ + #include "config.h" + + #include ++#include + + #include "core/download.h" + #include "core/view.h" +@@ -96,12 +97,30 @@ WindowDownloadList::redraw() { + char* position; + char* last = buffer + m_canvas->width() - 2 + 1; + ++ if( pos >= m_canvas->height() ) break; + position = print_download_title(buffer, last, *range.first); +- m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); ++ m_canvas->print(0, pos, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); ++ if( (*range.first)->is_done() ) { ++ if( (*range.first)->download()->up_rate()->rate() != 0 ) { ++ m_canvas->set_attr(0, pos, m_canvas->width()-1, A_BOLD, 2); ++ } else { ++ m_canvas->set_attr(0, pos, m_canvas->width()-1, A_NORMAL, 2); ++ } ++ } else if( (*range.first)->download()->is_active() ) { ++ if( (*range.first)->download()->down_rate()->rate() != 0 ) { ++ m_canvas->set_attr(0, pos, m_canvas->width()-1, A_BOLD, 1); ++ } else { ++ m_canvas->set_attr(0, pos, m_canvas->width()-1, A_NORMAL, 1); ++ } ++ } ++ pos++; + ++ if( pos >= m_canvas->height() ) break; ++ + position = print_download_info(buffer, last, *range.first); + m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); + ++ if( pos >= m_canvas->height() ) break; + position = print_download_status(buffer, last, *range.first); + m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); + +@@ -109,4 +128,40 @@ WindowDownloadList::redraw() { + } + } + ++void ++WindowDownloadList::set_done_fg_color(int64_t color) { ++ short fg, bg; ++ pair_content(2, &fg, &bg); ++ if( color < 0 ) color = -1; ++ color = color % 8; ++ init_pair(2, (short)color, bg); ++} ++ ++void ++WindowDownloadList::set_done_bg_color(int64_t color) { ++ short fg, bg; ++ pair_content(2, &fg, &bg); ++ if( color < 0 ) color = -1; ++ color = color % 8; ++ init_pair(2, fg, (short)color); ++} ++ ++void ++WindowDownloadList::set_active_fg_color(int64_t color) { ++ short fg, bg; ++ pair_content(1, &fg, &bg); ++ if( color < 0 ) color = -1; ++ color = color % 8; ++ init_pair(1, (short)color, bg); ++} ++ ++void ++WindowDownloadList::set_active_bg_color(int64_t color) { ++ short fg, bg; ++ pair_content(1, &fg, &bg); ++ if( color < 0 ) color = -1; ++ color = color % 8; ++ init_pair(1, fg, (short)color); ++} ++ + } +diff --git a/src/display/window_download_list.h b/src/display/window_download_list.h +index 4ce5ea1..313e87b 100644 +--- a/src/display/window_download_list.h ++++ b/src/display/window_download_list.h +@@ -59,6 +59,10 @@ public: + virtual void redraw(); + + void set_view(core::View* l); ++ void set_done_fg_color(int64_t color); ++ void set_done_bg_color(int64_t color); ++ void set_active_fg_color(int64_t color); ++ void set_active_bg_color(int64_t color); + + private: + core::View* m_view; +diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc +index b7d6983..e72bff6 100644 +--- a/src/ui/download_list.cc ++++ b/src/ui/download_list.cc +@@ -137,6 +137,11 @@ DownloadList::unfocus_download(core::Download* d) { + current_view()->next_focus(); + } + ++display::WindowDownloadList* ++DownloadList::current_window_list() { ++ return dynamic_cast(m_uiArray[DISPLAY_DOWNLOAD_LIST])->window(); ++} ++ + void + DownloadList::activate_display(Display displayType) { + if (!is_active()) +diff --git a/src/ui/download_list.h b/src/ui/download_list.h +index dda1b34..11137fa 100644 +--- a/src/ui/download_list.h ++++ b/src/ui/download_list.h +@@ -101,6 +101,7 @@ public: + void activate_display(Display d); + + core::View* current_view(); ++ display::WindowDownloadList* current_window_list(); + void set_current_view(const std::string& name); + + void slot_open_uri(SlotOpenUri s) { m_slotOpenUri = s; } +diff --git a/src/ui/element_download_list.h b/src/ui/element_download_list.h +index ed5de30..7c0fb9d 100644 +--- a/src/ui/element_download_list.h ++++ b/src/ui/element_download_list.h +@@ -60,6 +60,7 @@ public: + void disable(); + + core::View* view() { return m_view; } ++ WDownloadList* window() { return m_window; } + void set_view(core::View* l); + + void receive_command(const char* cmd); +diff --git a/src/ui/root.cc b/src/ui/root.cc +index b01f4ed..d344718 100644 +--- a/src/ui/root.cc ++++ b/src/ui/root.cc +@@ -44,6 +44,7 @@ + + #include "core/manager.h" + #include "display/frame.h" ++#include "display/window_download_list.h" + #include "display/window_http_queue.h" + #include "display/window_title.h" + #include "display/window_input.h" +@@ -65,7 +66,11 @@ Root::Root() : + m_windowTitle(NULL), + m_windowHttpQueue(NULL), + m_windowInput(NULL), +- m_windowStatusbar(NULL) { ++ m_windowStatusbar(NULL), ++ done_fg_color(-1), ++ done_bg_color(-1), ++ active_fg_color(-1), ++ active_bg_color(-1) { + } + + void +@@ -97,6 +102,10 @@ Root::init(Control* c) { + setup_keys(); + + m_downloadList->activate(rootFrame->frame(1)); ++ m_downloadList->current_window_list()->set_done_fg_color(done_fg_color); ++ m_downloadList->current_window_list()->set_done_bg_color(done_bg_color); ++ m_downloadList->current_window_list()->set_active_fg_color(active_fg_color); ++ m_downloadList->current_window_list()->set_active_bg_color(active_bg_color); + } + + void +@@ -219,6 +228,46 @@ Root::set_up_throttle(unsigned int throttle) { + torrent::set_max_unchoked(maxUnchoked); + } + ++int ++Root::get_done_fg_color() { ++ return done_fg_color; ++} ++ ++void ++Root::set_done_fg_color(int64_t color) { ++ done_fg_color = color; ++} ++ ++int ++Root::get_done_bg_color() { ++ return done_bg_color; ++} ++ ++void ++Root::set_done_bg_color(int64_t color) { ++ done_bg_color = color; ++} ++ ++int ++Root::get_active_fg_color() { ++ return active_fg_color; ++} ++ ++void ++Root::set_active_fg_color(int64_t color) { ++ active_fg_color = color; ++} ++ ++int ++Root::get_active_bg_color() { ++ return active_bg_color; ++} ++ ++void ++Root::set_active_bg_color(int64_t color) { ++ active_bg_color = color; ++} ++ + void + Root::adjust_down_throttle(int throttle) { + set_down_throttle(std::max(torrent::down_throttle_global()->max_rate() / 1024 + throttle, 0)); +diff --git a/src/ui/root.h b/src/ui/root.h +index e9a7907..4eef1df 100644 +--- a/src/ui/root.h ++++ b/src/ui/root.h +@@ -83,6 +83,15 @@ public: + void set_down_throttle_i64(int64_t throttle) { set_down_throttle(throttle >> 10); } + void set_up_throttle_i64(int64_t throttle) { set_up_throttle(throttle >> 10); } + ++ int get_done_fg_color(); ++ void set_done_fg_color(int64_t color); ++ int get_done_bg_color(); ++ void set_done_bg_color(int64_t color); ++ int get_active_fg_color(); ++ void set_active_fg_color(int64_t color); ++ int get_active_bg_color(); ++ void set_active_bg_color(int64_t color); ++ + void adjust_down_throttle(int throttle); + void adjust_up_throttle(int throttle); + +@@ -105,6 +114,10 @@ private: + WStatusbar* m_windowStatusbar; + + input::Bindings m_bindings; ++ int64_t done_fg_color; ++ int64_t done_bg_color; ++ int64_t active_fg_color; ++ int64_t active_bg_color; + }; + + } diff --git a/rtorrent-extended/dht_pex_static_map.patch b/rtorrent-extended/dht_pex_static_map.patch new file mode 100644 index 0000000..e302b4c --- /dev/null +++ b/rtorrent-extended/dht_pex_static_map.patch @@ -0,0 +1,12 @@ +diff --git a/src/core/view.cc b/src/core/view.cc +index 8e2d997..f3b4f0d 100644 +--- a/src/core/view.cc ++++ b/src/core/view.cc +@@ -90,6 +90,7 @@ struct view_downloads_filter : std::unary_function { + case torrent::Object::TYPE_STRING: return !result.as_string().empty(); + case torrent::Object::TYPE_LIST: return !result.as_list().empty(); + case torrent::Object::TYPE_MAP: return !result.as_map().empty(); ++ case torrent::Object::TYPE_SSTRING:return !result.as_sstring().empty(); + } + + // The default filter action is to return true, to not filter diff --git a/rtorrent-extended/ip_filter_no_boost_fast.patch b/rtorrent-extended/ip_filter_no_boost_fast.patch new file mode 100644 index 0000000..d13fb69 --- /dev/null +++ b/rtorrent-extended/ip_filter_no_boost_fast.patch @@ -0,0 +1,1083 @@ +diff --git a/src/command_network.cc b/src/command_network.cc +index 34f75ab..2494988 100644 +--- a/src/command_network.cc ++++ b/src/command_network.cc +@@ -36,6 +36,11 @@ + + #include "config.h" + ++#include ++#include ++#include ++#include ++ + #include + #include + #include +@@ -62,6 +67,10 @@ + #include "control.h" + #include "command_helpers.h" + ++#include "utils/pattern.h" ++#include "core/ip_filter.h" ++ ++ + torrent::Object + apply_throttle(bool up, const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); +@@ -209,6 +218,59 @@ apply_encryption(const torrent::Object& rawArgs) { + } + + torrent::Object ++apply_ip_filter(const torrent::Object& rawArgs) { ++ const torrent::Object::list_type& args = rawArgs.as_list(); ++ ++ std::list files; ++ ++ for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) { ++ std::string file( itr->as_string() ); ++ utils::trim( file ); ++ if( access(file.c_str(),F_OK | R_OK) ) ++ throw torrent::input_error("IpFilter file '" + file + "' does not exist or not readable. Filter could not be loaded"); ++ files.push_back( file ); ++ } ++ ++ std::stringstream logMsg; ++ if( files.empty() ) { ++ logMsg << "IpFilter is empty"; ++ control->core()->push_log( logMsg.str().c_str() ); ++ } ++ else { ++ core::IpFilter* f = new core::IpFilter(); ++ logMsg << "IpFilter is initialized with files: "; ++ int entries = 0; ++ clock_t time_start = clock(); ++ for( std::list::iterator itr = files.begin(); itr != files.end(); itr++) { ++ std::cout << "Loading IP filters from '" << *itr << "'..."; ++ std::cout.flush(); ++ if( itr != files.begin() ) ++ logMsg << ", "; ++ logMsg << *itr; ++ int merges = f->add_from_file( *itr ); ++ if( merges < 0 ) { ++ std::cout << "error" << std::endl; ++ std::cout.flush(); ++ throw torrent::input_error("IpFilter could not load file '" + *itr + "'"); ++ } ++ std::cout << "done. Loaded " << (f->size()-entries) << " ranges. " << merges << " ranges were merged." << std::endl; ++ std::cout.flush(); ++ entries = f->size(); ++ } ++ control->core()->push_log( logMsg.str().c_str() ); ++ std::stringstream logMsg2("IpFilter loaded with "); ++ logMsg2 << f->size() << " ranges total. " << f->get_merges() << " ranges were merged."; ++ control->core()->push_log( logMsg2.str().c_str() ); ++ std::cout << logMsg2.str() << std::endl; ++ std::cout << "IP_Filters loaded in " << (double)(clock()-time_start)/CLOCKS_PER_SEC << " seconds" << std::endl; ++ std::cout.flush(); ++ control->core()->set_ip_filter( f ); ++ } ++ ++ return torrent::Object(); ++} ++ ++torrent::Object + apply_tos(const torrent::Object& rawArg) { + rpc::Command::value_type value; + torrent::ConnectionManager* cm = torrent::connection_manager(); +@@ -492,6 +554,9 @@ initialize_command_network() { + + ADD_VARIABLE_BOOL("peer_exchange", true); + ++ ADD_COMMAND_VOID("reload_ip_filter", rak::make_mem_fun(control->core(), &core::Manager::reload_ip_filter)); ++ ADD_COMMAND_LIST("ip_filter", rak::ptr_fn(&apply_ip_filter)); ++ + // Not really network stuff: + ADD_VARIABLE_BOOL ("handshake_log", false); + ADD_VARIABLE_STRING("log.tracker", ""); +diff --git a/src/core/Makefile.am b/src/core/Makefile.am +index fe07cbf..fb26380 100644 +--- a/src/core/Makefile.am ++++ b/src/core/Makefile.am +@@ -36,6 +36,15 @@ libsub_core_a_SOURCES = \ + view.cc \ + view.h \ + view_manager.cc \ +- view_manager.h ++ view_manager.h \ ++ ip_address.cc \ ++ ip_address.h \ ++ ip_filter.cc \ ++ ip_filter.h \ ++ ip_range.cc \ ++ ip_range.h \ ++ printable.h \ ++ regex_namespace.h \ ++ ip_filter_statics.cc + + INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) +diff --git a/src/core/Makefile.in b/src/core/Makefile.in +index 24b7eb4..32b0f6b 100644 +--- a/src/core/Makefile.in ++++ b/src/core/Makefile.in +@@ -63,7 +63,11 @@ am_libsub_core_a_OBJECTS = curl_get.$(OBJEXT) curl_socket.$(OBJEXT) \ + manager.$(OBJEXT) poll_manager.$(OBJEXT) \ + poll_manager_epoll.$(OBJEXT) poll_manager_kqueue.$(OBJEXT) \ + poll_manager_select.$(OBJEXT) view.$(OBJEXT) \ +- view_manager.$(OBJEXT) ++ view_manager.$(OBJEXT) \ ++ ip_address.$(OBJEXT) \ ++ ip_filter.$(OBJEXT) \ ++ ip_range.$(OBJEXT) \ ++ ip_filter_statics.$(OBJEXT) + libsub_core_a_OBJECTS = $(am_libsub_core_a_OBJECTS) + DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) + depcomp = $(SHELL) $(top_srcdir)/depcomp +@@ -252,7 +256,16 @@ libsub_core_a_SOURCES = \ + view.cc \ + view.h \ + view_manager.cc \ +- view_manager.h ++ view_manager.h \ ++ ip_address.cc \ ++ ip_address.h \ ++ ip_filter.cc \ ++ ip_filter.h \ ++ ip_range.cc \ ++ ip_range.h \ ++ printable.h \ ++ regex_namespace.h \ ++ ip_filter_statics.cc + + INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) + all: all-am +@@ -320,6 +333,9 @@ distclean-compile: + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/poll_manager_select.Po@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/view.Po@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/view_manager.Po@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ip_address.Po@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ip_range.Po@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ip_filter_statics.Po@am__quote@ + + .cc.o: + @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +diff --git a/src/core/ip_address.cc b/src/core/ip_address.cc +new file mode 100644 +index 0000000..a9a9251 +--- /dev/null ++++ b/src/core/ip_address.cc +@@ -0,0 +1,25 @@ ++#include ++#include ++#include ++ ++#include "ip_address.h" ++#include "utils/pattern.h" ++ ++namespace core { ++ ++std::pair IpAddress::to_int( const std::string& address ) { ++ uint32_t a; ++ int r = inet_pton( AF_INET, address.c_str(), &a); ++ if( r ) ++ a = ntohl( a ); ++ return std::pair( (r!=0), a ); ++} ++ ++std::string IpAddress::to_string() const { ++ char buf[128] = ""; ++ uint32_t a = htonl( m_address ); ++ inet_ntop( AF_INET, &a, buf, sizeof(buf) ); ++ return std::string( buf ); ++} ++ ++} +diff --git a/src/core/ip_address.h b/src/core/ip_address.h +new file mode 100644 +index 0000000..a9ad142 +--- /dev/null ++++ b/src/core/ip_address.h +@@ -0,0 +1,65 @@ ++#ifndef IPADDRESS_H ++#define IPADDRESS_H ++ ++#include ++#include ++ ++#include "printable.h" ++#include "utils/pattern.h" ++#include "regex_namespace.h" ++ ++namespace core { ++ ++class IpAddress : public Printable { ++ friend class IpRange; ++ ++ private: // constants ++ static const std::string PATTERN_IP_EXPRESSION; ++ static const std::string PATTERN_IP_BYTES_EXPRESSION; ++ static const regex::Pattern PATTERN_IP_BYTES; ++ ++ static const int GRP_IP_FIRST_BYTE; ++ static const int GRP_IP_BYTES_COUNT; ++ ++ private: // fields ++ uint32_t m_address; ++ ++ private: // static methods ++ ++ private: // dynamic methods ++ IpAddress() : m_address(0) {} ++ ++ void copy( const IpAddress& addr ) { m_address = addr.m_address;} ++ ++ public: // static methods ++ static std::pair to_int( const std::string& strAddress ); ++ static IpAddress* parse( const std::string& strAddress ) { ++ std::pair result = to_int( strAddress ); ++ return ( !result.first ) ? NULL : new IpAddress( result.second ); ++ } ++ ++ public: // dynamic methods ++ IpAddress( uint32_t address ) : m_address(address) {} ++ IpAddress( const IpAddress& addr ) { copy( addr ); } ++ IpAddress& operator= ( const IpAddress& addr ) { copy( addr ); return *this; } ++ ++ operator uint32_t() const { return m_address; } ++ ++ bool operator>= ( const IpAddress& ip ) const { return (m_address >= ip.m_address); } ++ bool operator<= ( const IpAddress& ip ) const { return (m_address <= ip.m_address); } ++ bool operator< ( const IpAddress& ip ) const { return (m_address < ip.m_address); } ++ bool operator> ( const IpAddress& ip ) const { return (m_address > ip.m_address); } ++ bool operator== ( const IpAddress& ip ) const { return (m_address == ip.m_address); } ++ bool operator!= ( const IpAddress& ip ) const { return (m_address != ip.m_address); } ++ ++ bool operator>= ( uint32_t ip ) const { return (m_address >= ip); } ++ bool operator<= ( uint32_t ip ) const { return (m_address <= ip); } ++ bool operator< ( uint32_t ip ) const { return (m_address < ip); } ++ bool operator> ( uint32_t ip ) const { return (m_address > ip); } ++ bool operator== ( uint32_t ip ) const { return (m_address == ip); } ++ bool operator!= ( uint32_t ip ) const { return (m_address != ip); } ++ ++ std::string to_string() const; ++ }; ++} ++#endif +diff --git a/src/core/ip_filter.cc b/src/core/ip_filter.cc +new file mode 100644 +index 0000000..8f46a42 +--- /dev/null ++++ b/src/core/ip_filter.cc +@@ -0,0 +1,165 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ip_filter.h" ++ ++namespace core { ++ ++int IpFilter::merge_and_insert( range_map* rs, IpRange* r ) { ++ if( !r || !r->get_from() ) ++ return 0; ++ ++ std::pair p( *r->get_from(), IpRange::ptr(r) ); ++ std::pair duo = rs->insert( p ); ++ ++ range_itr idx = duo.first; ++ bool wasInserted = duo.second; ++ IpRange* curr = NULL; ++ int mergeCount = 0; ++ ++ if( !wasInserted ) { // exactly the same start address already exists ++ curr = idx->second; ++ if( *curr->get_to() < *r->get_to() ) ++ curr->set_to( r->get_to() ); ++ delete r; ++ r = curr; ++ mergeCount++; ++ } ++ else { ++ if( idx != rs->begin() ) { ++ --idx; ++ curr = idx->second; // previous ++ if( *r->get_from() <= *curr->get_to() ) ++ r = curr; ++ else ++ ++idx; ++ } ++ } ++ ++ if( idx != rs->end() ) ++ ++idx; ++ ++ while( idx != rs->end() ) { ++ curr = idx->second; ++ if( *r->get_to() < *curr->get_from() ) ++ break; ++ ++ std::string d = r->get_description(); ++ d += " / " + curr->get_description(); ++ r->set_description( d ); ++ if( *r->get_to() < *curr->get_to() ) ++ r->set_to( curr->get_to() ); ++ rs->erase( idx++ ); ++ delete curr; ++ mergeCount++; ++ } ++ return mergeCount; ++} ++ ++int IpFilter::add_from_file( const std::string& fileName, range_map* rs, str_list* files ) { ++ FILE *f = fopen(fileName.c_str(),"r"); ++ int mergeCount = 0; ++ if (f==0) return -1; ++ char *line = (char *)malloc(64); ++ size_t sz=64; ++ int charsread = 0; ++ int linesread=0; ++ while( (charsread=getline(&line,&sz,f)) >=0 ) { ++ if( (line[0] == '#' ) || ( charsread <= 1 ) ) ++ continue; ++ ++ IpRange* ir = IpRange::parse( line, charsread ); ++ if( !ir || !ir->get_from() || !ir->get_to() ) ++ continue; ++ ++ mergeCount += merge_and_insert( rs, ir ); ++ } ++ free(line); ++ files->push_back( std::string(fileName) ); ++ fclose(f); ++ m_merges += mergeCount; ++ return mergeCount; ++} ++ ++int IpFilter::add_from_file( const std::string& fileName ) { ++ if( !m_ranges ) ++ m_ranges = new range_map(); ++ if( !m_loadedFiles ) ++ m_loadedFiles = new std::list(); ++ ++ return add_from_file( fileName, m_ranges, m_loadedFiles ); ++} ++ ++int IpFilter::reload() { ++ if( !m_loadedFiles || m_loadedFiles->empty() ) ++ return 0; ++ ++ range_map* rs = new range_map(); ++ str_list* files = new str_list(); ++ int mergeCount = 0; ++ for( str_list::const_iterator it = m_loadedFiles->begin(), end = m_loadedFiles->end(); it != end; it++ ) ++ mergeCount += add_from_file( *it, rs, files ); ++ ++ range_map* rsOld = m_ranges; ++ m_ranges = rs; ++ if( rsOld ) { ++ clear( rsOld ); ++ delete rsOld; ++ } ++ ++ str_list* filesOld = m_loadedFiles; ++ m_loadedFiles = files; ++ if( filesOld ) { ++ clear( filesOld ); ++ delete filesOld; ++ } ++ ++ m_merges = mergeCount; ++ return mergeCount; ++} ++ ++IpRange* IpFilter::find_range( uint32_t ip ) const { ++ if( (ip >= 0) && m_ranges && !m_ranges->empty() ) { ++ range_itr idx = m_ranges->upper_bound( ip ); ++ if( idx != m_ranges->begin() ) ++ --idx; ++ IpRange* curr = idx->second; ++ if( curr->includes( ip ) ) ++ return curr; ++ } ++ return NULL; ++} ++ ++std::string IpFilter::to_string() const { ++ std::stringstream result; ++ if( !m_ranges ) ++ result << "NULL" << std::endl; ++ else { ++ for( range_map::const_iterator it = m_ranges->begin() ; it != m_ranges->end(); it++ ) { ++ const IpAddress a = it->first; ++ IpRange* ir = it->second; ++ result << a << ": " << *ir << std::endl; ++ } ++ } ++ return result.str(); ++} ++ ++void IpFilter::clear( range_map* map ) { ++ if( map ) { ++ for( range_itr i = map->begin(), j = map->end(); i != j; i++ ) ++ delete i->second; ++ map->clear(); ++ } ++} ++ ++void IpFilter::clear( str_list* list ) { ++ if( list ) ++ list->clear(); ++} ++ ++} +diff --git a/src/core/ip_filter.h b/src/core/ip_filter.h +new file mode 100644 +index 0000000..07f2004 +--- /dev/null ++++ b/src/core/ip_filter.h +@@ -0,0 +1,85 @@ ++#ifndef IPFILTER_H ++#define IPFILTER_H ++ ++#include ++#include ++#include ++ ++#include "printable.h" ++#include "ip_address.h" ++#include "ip_range.h" ++ ++namespace core { ++ ++typedef std::map range_map; ++typedef range_map::iterator range_itr; ++typedef std::list str_list; ++ ++class IpFilter : public Printable { ++ private: // fields ++ int m_merges; ++ range_map* m_ranges; ++ str_list* m_loadedFiles; ++ ++ private: // static methods ++ static void clear( range_map* map ); ++ static void clear( str_list* list ); ++ ++ private: // dynamic methods ++ void init_members(void) { // to avoid long constructor lines for every ctor ++ m_ranges = NULL; ++ m_loadedFiles = NULL; ++ m_merges = 0; ++ } ++ int merge_and_insert( range_map* rs, IpRange* r ); ++ int add_from_file( const std::string& fileName, range_map* rs, str_list* files ); ++ ++ public: // static methods ++ ++ public: // dynamic methods ++ IpFilter() { init_members(); } ++ ~IpFilter() { ++ clear(); ++ if( m_ranges ) delete m_ranges; ++ if( m_loadedFiles ) delete m_loadedFiles; ++ m_ranges = NULL; ++ m_loadedFiles = NULL; ++ } ++ IpFilter( std::string* files, int size ) { ++ init_members(); ++ for( int i = 0; i < size; i++, files++ ) ++ add_from_file( *files ); ++ } ++ IpFilter( str_list& files ) { ++ init_members(); ++ for( str_list::const_iterator i = files.begin(), last = files.end(); i != last; i++ ) ++ add_from_file( *i ); ++ } ++ IpFilter( IpFilter& f ) { ++ init_members(); ++ m_ranges = new range_map( *f.m_ranges ); ++ m_loadedFiles = new str_list( *f.m_loadedFiles ); ++ } ++ ++ int reload(); ++ int add_from_file( const std::string& fileName ); ++ int add_from_file( char* fileName ) { std::string s( fileName ); return add_from_file(s); } ++ void clear() { clear( m_ranges ); clear( m_loadedFiles ); } ++ ++ IpRange* find_range( uint32_t ip ) const; ++ ++ bool is_filtered( uint32_t ip ) const { return (find_range( ip ) != NULL); } ++ bool is_filtered( std::string ip ) const { ++ static std::pair ipInt = IpAddress::to_int( ip ); ++ return (!ipInt.first ? false : is_filtered( ipInt.second )); ++ } ++ ++ std::string to_string() const; ++ ++ int size(void) { return ( m_ranges ? m_ranges->size() : 0 ); } ++ int get_merges(void) { return m_merges; } ++ void set_files( str_list& files) { m_loadedFiles = new str_list( files ); } ++}; ++ ++} ++#endif +diff --git a/src/core/ip_filter_statics.cc b/src/core/ip_filter_statics.cc +new file mode 100644 +index 0000000..042377d +--- /dev/null ++++ b/src/core/ip_filter_statics.cc +@@ -0,0 +1,21 @@ ++#include "ip_address.h" ++#include "ip_range.h" ++#include "utils/pattern.h" ++ ++namespace core { ++ ++const std::string IpAddress::PATTERN_IP_EXPRESSION = "(([0-9]{1,3}\\.){3}[0-9]{1,3})"; ++const std::string IpAddress::PATTERN_IP_BYTES_EXPRESSION = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})"; ++const regex::Pattern IpAddress::PATTERN_IP_BYTES = PATTERN_IP_BYTES_EXPRESSION; ++ ++const int IpAddress::GRP_IP_FIRST_BYTE = 1; ++const int IpAddress::GRP_IP_BYTES_COUNT = 4; ++ ++const std::string IpRange::PATTERN_RANGE_EXPRESSION = "[[:space:]]*(.*)[[:space:]]*:[[:space:]]*" + IpAddress::PATTERN_IP_EXPRESSION + "[[:space:]]*-[[:space:]]*" + IpAddress::PATTERN_IP_EXPRESSION + "[[:space:]]*"; ++const regex::Pattern IpRange::PATTERN_RANGE = PATTERN_RANGE_EXPRESSION; ++ ++const int IpRange::GRP_DESCRIPTION = 1; ++const int IpRange::GRP_FIRST_IP = 2; ++const int IpRange::GRP_SECOND_IP = 4; ++ ++} +diff --git a/src/core/ip_range.cc b/src/core/ip_range.cc +new file mode 100644 +index 0000000..3923e53 +--- /dev/null ++++ b/src/core/ip_range.cc +@@ -0,0 +1,112 @@ ++#include ++#include ++ ++#include "ip_range.h" ++#include "utils/pattern.h" ++#include "regex_namespace.h" ++ ++namespace core { ++ ++IpRange* IpRange::parse( const std::string& s ) { ++ regex::Match m = PATTERN_RANGE.match( s ); ++ ++ if( !m.matches() ) { ++ std::cout << "!! range format is invalid: '" << s << "'" << std::endl; ++ return NULL; ++ } ++ ++ std::string description = m.group( GRP_DESCRIPTION ); ++ std::string ip1 = m.group( GRP_FIRST_IP ); ++ std::string ip2 = m.group( GRP_SECOND_IP ); ++ IpAddress* from = IpAddress::parse( ip1 ); ++ IpAddress* to = IpAddress::parse( ip2 ); ++ ++ if( !from ) { ++ std::cout << "!! from is invalid: description='" << description << ", ip1=" << ip1 << ", ip2=" << ip2 << std::endl; ++ return NULL; ++ } ++ if( !to ) { ++ std::cout << "!! to is invalid: description='" << description << ", ip1=" << ip1 << ", ip2=" << ip2 << std::endl; ++ return NULL; ++ } ++ ++// if( !from || !to || (*to < *from) ) ++// return NULL; ++ ++ IpRange* r = new IpRange(); ++ ++ r->m_description = description; ++ r->m_from = from; ++ r->m_to = to; ++ ++ if( to && from && (*to < *from) ) { ++ std::cout << "!! to < from: " << r->to_string() << std::endl; ++ delete r; ++ return NULL; ++ } ++ ++ return r; ++} ++ ++//fast version ++IpRange* IpRange::parse( const char *s, const int size ){ ++ static char description[256]; ++ static char ip1[24], ip2[24]; ++ int pos=0, post=0, enddesc=size-1; ++ while (enddesc>0 && s[enddesc]!=':') enddesc--; //find last ':' in the line ++ while((pos' '){ ++ if (post<23) ip2[post++]=s[pos]; ++ pos++; ++ } ++ ip2[post]=0; ++ } else ip2[0]=0; ++ ++ IpAddress* from = IpAddress::parse(ip1); ++ IpAddress* to = IpAddress::parse(ip2); ++ ++ if( !from ) { ++ std::cout << "!! from is invalid: description='" << description << ", ip1=" << ip1 << ", ip2=" << ip2 << std::endl; ++ return NULL; ++ } ++ if( !to ) { ++ std::cout << "!! to is invalid: description='" << description << ", ip1=" << ip1 << ", ip2=" << ip2 << std::endl; ++ return NULL; ++ } ++ ++ IpRange* r = new IpRange(); ++ r->m_description = description; ++ r->m_from = from; ++ r->m_to = to; ++ ++ if( (*to < *from) ) { ++ std::cout << "!! to < from: " << r->to_string() << std::endl; ++ delete r; ++ return NULL; ++ } ++ ++ return r; ++} ++ ++std::string IpRange::to_string() const { ++ std::stringstream result; ++ result << m_description << ": [" << m_from->to_string() << " - " << m_to->to_string() << ']'; ++ return result.str(); ++} ++ ++} +diff --git a/src/core/ip_range.h b/src/core/ip_range.h +new file mode 100644 +index 0000000..1fb2322 +--- /dev/null ++++ b/src/core/ip_range.h +@@ -0,0 +1,67 @@ ++#ifndef IPRANGE_H ++#define IPRANGE_H ++ ++#include ++ ++#include "printable.h" ++#include "ip_address.h" ++#include "utils/pattern.h" ++#include "regex_namespace.h" ++ ++namespace core { ++ ++class IpRange : public Printable { ++ public: // constants ++ static const std::string PATTERN_RANGE_EXPRESSION; ++ static const regex::Pattern PATTERN_RANGE; ++ ++ static const int GRP_DESCRIPTION; ++ static const int GRP_FIRST_IP; ++ static const int GRP_SECOND_IP; ++ ++ private: // fields ++ std::string m_description; ++ const IpAddress* m_from; ++ const IpAddress* m_to; ++ ++ private: // dynamic methods ++ IpRange() : m_description(), m_from(NULL), m_to(NULL) {} ++ ++ public: // static methods ++ typedef IpRange* ptr; ++ static IpRange* parse( const std::string& s ); ++ static IpRange* parse( const char *s, const int size ); ++ ++ public: // dynamic methods ++ IpRange( IpRange& rng ) { copy(rng); } ++ IpRange& operator= ( IpRange& rng ) { copy(rng); return *this; } ++ ++ void copy( IpRange& rng ) { ++ m_description = rng.m_description; ++ m_from = (!rng.m_from) ? NULL : new IpAddress( *rng.m_from ); ++ m_to = (!rng.m_to) ? NULL : new IpAddress( *rng.m_to ); ++ } ++ ++ const std::string& get_description ( void ) const { return m_description; } ++ const IpAddress* get_from ( void ) const { return m_from; } ++ const IpAddress* get_to ( void ) const { return m_to; } ++ ++ void set_description ( const std::string& description ) { m_description = description; } ++ void set_from ( const IpAddress* from ) { if( m_from ) delete m_from; m_from = new IpAddress( *from ); } ++ void set_to ( const IpAddress* to ) { if( m_to ) delete m_to; m_to = new IpAddress( *to ); } ++ ++ bool includes( const IpAddress& ip ) const { return includes((uint32_t)ip); } ++ bool includes( uint32_t ip ) const { return (*m_from <= ip) && (*m_to >= ip); } ++ ++ ~IpRange() { ++ delete m_from; ++ m_from = NULL; ++ delete m_to; ++ m_to = NULL; ++ } ++ ++ std::string to_string() const; ++}; ++ ++} ++#endif +diff --git a/src/core/manager.cc b/src/core/manager.cc +index 2a422c8..9c1d004 100644 +--- a/src/core/manager.cc ++++ b/src/core/manager.cc +@@ -153,6 +153,24 @@ Manager::handshake_log(const sockaddr* sa, int msg, int err, const torrent::Hash + } + } + ++uint32_t ++Manager::filter_ip(const sockaddr* sa) { ++ IpRange* r = NULL; ++ // if something's wrong with filter or address it's gonna be allowed ++ if( m_ipFilter && sa ) { ++ const rak::socket_address* socketAddress = rak::socket_address::cast_from(sa); ++ if( socketAddress->is_valid() && (socketAddress->family() == rak::socket_address::af_inet) ) ++ r = m_ipFilter->find_range( socketAddress->sa_inet()->address_h() ); ++ if( r ) ++ m_logComplete.push_front("Address '" + socketAddress->address_str() + "' is rejected by IP filter range '" + r->to_string()); ++ else ++ if( rpc::call_command_value("get_handshake_log") ) ++ m_logComplete.push_front("IP Filter allowed connection with '" + socketAddress->address_str() + "'"); ++ } ++ return (r==NULL); ++} ++ ++ + void + Manager::push_log(const char* msg) { + m_logImportant.push_front(msg); +@@ -160,7 +178,8 @@ Manager::push_log(const char* msg) { + } + + Manager::Manager() : +- m_hashingView(NULL) ++ m_hashingView(NULL), ++ m_ipFilter(NULL) + // m_pollManager(NULL) { + { + m_downloadStore = new DownloadStore(); +@@ -181,6 +200,8 @@ Manager::~Manager() { + delete m_downloadStore; + delete m_httpQueue; + delete m_fileStatusCache; ++ ++ set_ip_filter( NULL ); + } + + void +@@ -226,6 +247,7 @@ Manager::initialize_second() { + CurlStack::global_init(); + + torrent::connection_manager()->set_signal_handshake_log(sigc::mem_fun(this, &Manager::handshake_log)); ++ torrent::connection_manager()->set_filter(sigc::mem_fun(this, &Manager::filter_ip)); + } + + void +@@ -585,4 +607,13 @@ Manager::receive_hashing_changed() { + } + } + ++void Manager::reload_ip_filter(void) { ++ if( m_ipFilter ) { ++ push_log("Reloading IP filter"); ++ m_ipFilter->reload(); ++ std::stringstream logMsg("IpFilter reloaded with "); ++ logMsg << m_ipFilter->size() << " ranges total. " << m_ipFilter->get_merges() << " ranges were merged."; ++ push_log( logMsg.str().c_str() ); ++} ++} + } +diff --git a/src/core/manager.h b/src/core/manager.h +index 16902af..ac01981 100644 +--- a/src/core/manager.h ++++ b/src/core/manager.h +@@ -47,6 +47,8 @@ + #include "range_map.h" + #include "log.h" + ++#include "ip_filter.h" ++ + namespace torrent { + class Bencode; + } +@@ -118,6 +120,15 @@ public: + + void handshake_log(const sockaddr* sa, int msg, int err, const torrent::HashString* hash); + ++ uint32_t filter_ip(const sockaddr* sa); ++ ++ void set_ip_filter( IpFilter* ipFilter ) { ++ IpFilter* old = m_ipFilter; ++ m_ipFilter = ipFilter; ++ if( old ) delete old; ++ } ++ void reload_ip_filter(void); ++ + static const int create_start = 0x1; + static const int create_tied = 0x2; + static const int create_quiet = 0x4; +@@ -154,6 +165,8 @@ private: + + Log m_logImportant; + Log m_logComplete; ++ ++ IpFilter* m_ipFilter; + }; + + // Meh, cleanup. +diff --git a/src/core/printable.h b/src/core/printable.h +new file mode 100644 +index 0000000..8520af4 +--- /dev/null ++++ b/src/core/printable.h +@@ -0,0 +1,16 @@ ++#ifndef PRINTABLE_H ++#define PRINTABLE_H ++ ++#include ++ ++class Printable { ++ public: ++ virtual std::string to_string() const = 0; ++}; ++ ++template inline std::basic_ostream<_CharT,_Traits>& ++ operator<<( std::basic_ostream<_CharT,_Traits>& out, const Printable& val) { ++ return out << val.to_string(); ++} ++ ++#endif +diff --git a/src/core/regex_namespace.h b/src/core/regex_namespace.h +new file mode 100644 +index 0000000..6e10b3c +--- /dev/null ++++ b/src/core/regex_namespace.h +@@ -0,0 +1,6 @@ ++#ifndef REGEXNAMESPACE_H ++#define REGEXNAMESPACE_H ++ ++namespace regex = utils; ++ ++#endif +diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am +index 55ea9ff..cdad48a 100644 +--- a/src/utils/Makefile.am ++++ b/src/utils/Makefile.am +@@ -9,6 +9,8 @@ libsub_utils_a_SOURCES = \ + lockfile.cc \ + lockfile.h \ + socket_fd.cc \ +- socket_fd.h ++ socket_fd.h \ ++ pattern.cc \ ++ pattern.h + + INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) +diff --git a/src/utils/Makefile.in b/src/utils/Makefile.in +index a684a9c..5424965 100644 +--- a/src/utils/Makefile.in ++++ b/src/utils/Makefile.in +@@ -58,7 +58,7 @@ libsub_utils_a_AR = $(AR) $(ARFLAGS) + libsub_utils_a_LIBADD = + am_libsub_utils_a_OBJECTS = directory.$(OBJEXT) \ + file_status_cache.$(OBJEXT) lockfile.$(OBJEXT) \ +- socket_fd.$(OBJEXT) ++ socket_fd.$(OBJEXT) pattern.$(OBJEXT) + libsub_utils_a_OBJECTS = $(am_libsub_utils_a_OBJECTS) + DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) + depcomp = $(SHELL) $(top_srcdir)/depcomp +@@ -220,7 +220,9 @@ libsub_utils_a_SOURCES = \ + lockfile.cc \ + lockfile.h \ + socket_fd.cc \ +- socket_fd.h ++ socket_fd.h \ ++ pattern.cc \ ++ pattern.h + + INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) + all: all-am +@@ -275,6 +277,7 @@ distclean-compile: + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_status_cache.Po@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lockfile.Po@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_fd.Po@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pattern.@am__quote@ + + .cc.o: + @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +diff --git a/src/utils/pattern.cc b/src/utils/pattern.cc +new file mode 100644 +index 0000000..c44bb38 +--- /dev/null ++++ b/src/utils/pattern.cc +@@ -0,0 +1,79 @@ ++#include ++#include ++#include ++#include ++ ++#include "pattern.h" ++ ++namespace utils { ++ ++int Pattern::countGroups( const std::string& str ) { ++ int count1 = 0; ++ int count2 = 0; ++ ++ for( size_t index = -1; (index = str.find( '(', index+1 )) != std::string::npos; ) ++ count1++; ++ for( size_t index = -1; (index = str.find( ')', index+1 )) != std::string::npos; ) ++ count2++; ++ ++ return (count1 < count2) ? count1 : count2; ++} ++ ++Pattern::Pattern( const std::string& pattern, Flags flags ) : lastResult(-1), ++ preg(NULL) { ++ int regFlags = REG_EXTENDED | REG_ICASE | REG_NEWLINE; ++ if( !(flags & CASE_SENSITIVE) ) ++ regFlags ^= REG_ICASE; ++ if( (flags & DOT_MATCH_NEWLINE) ) ++ regFlags ^= REG_NEWLINE; ++ ++ preg = new regex_t; ++ numGroups = countGroups( pattern ) + 1; ++ ++ lastResult = regcomp( preg, pattern.c_str(), regFlags ); ++} ++ ++Pattern::~Pattern() { ++ regfree( preg ); ++ delete( preg ); ++} ++ ++std::string Pattern::getLastError() const { ++ char errBuf[1024]; ++ regerror( lastResult, preg, errBuf, sizeof(errBuf) ); ++ return std::string(errBuf); ++} ++ ++Match Pattern::match( const std::string& expression ) const { ++ ++ regmatch_t* pmatch = new regmatch_t[numGroups]; ++ int res = regexec( preg, expression.c_str(), numGroups, pmatch, 0 ); ++ return Match( expression, numGroups, pmatch, res, getLastError() ); ++} ++ ++Match::Match( const std::string& expr, int ngroups, regmatch_t* groups, int result, const std::string& message ) : ++ expression( expr ), ++ nmatch( ngroups ), ++ pmatch( groups ), ++ matchResult( result ), ++ matchMessage( message ) { ++} ++ ++std::string Match::group( int i ) { ++ if( (i >= nmatch) || (pmatch[i].rm_so < 0) ) ++ return ""; ++ ++ return expression.substr( pmatch[i].rm_so, pmatch[i].rm_eo - pmatch[i].rm_so ); ++} ++ ++std::string& trim( std::string& str ) { ++ std::string::iterator it; ++ for( it = str.begin(); (it < str.end()) && ( isspace(*it) || (*it == 0) ) ; it++ ); ++ str.erase( str.begin(), it ); ++ for( it = str.end()-1; (it >= str.begin()) && ( isspace(*it) || (*it == 0) ) ; it-- ); ++ str.erase( ++it, str.end() ); ++ return str; ++} ++ ++} ++ +diff --git a/src/utils/pattern.h b/src/utils/pattern.h +new file mode 100644 +index 0000000..99e95a4 +--- /dev/null ++++ b/src/utils/pattern.h +@@ -0,0 +1,59 @@ ++#ifndef PATTERN_H ++#define PATTERN_H ++ ++#include ++#include ++#include ++#include ++ ++namespace utils { ++ ++class Match { ++ public: ++ Match( const std::string& expr, int ngroups, regmatch_t* pmatch, int matchResult, const std::string& matchMessage ); ++ ~Match() { delete[] pmatch; } ++ std::string group( int i ); ++ bool found() { return (matchResult == 0); } ++ bool matches() { return found(); } ++ std::string& getMatchMessage() { return matchMessage; } ++ ++ private: ++ std::string expression; ++ int nmatch; ++ regmatch_t* pmatch; ++ int matchResult; ++ std::string matchMessage; ++}; ++ ++class Pattern { ++ public: ++ enum Flags { ++ DEFAULT = 0, // REG_EXTENDED | REG_ICASE | REG_NEWLINE ++ CASE_SENSITIVE, ++ DOT_MATCH_NEWLINE ++ }; ++ ++ public: ++ Pattern( const std::string& pattern, Flags f = Pattern::DEFAULT ); ++ ~Pattern(); ++ bool isSuccess() { return (lastResult == 0); } ++ std::string getLastError() const; ++ Match match( const std::string& expression ) const; ++ ++ private: ++ int countGroups( const std::string& str ); ++ ++ private: ++ regex_t* preg; ++ int lastResult; ++ int numGroups; ++}; ++ ++ ++std::string& trim( std::string& str ); ++ ++} ++ ++// end of ifdef PATTERN_H ++#endif ++ diff --git a/rtorrent-extended/ipv6.patch b/rtorrent-extended/ipv6.patch new file mode 100644 index 0000000..de72d94 --- /dev/null +++ b/rtorrent-extended/ipv6.patch @@ -0,0 +1,572 @@ +diff --git a/rak/socket_address.h b/rak/socket_address.h +index 25fdb37..d38533e 100644 +--- a/rak/socket_address.h ++++ b/rak/socket_address.h +@@ -145,7 +145,7 @@ private: + }; + }; + +-// Remeber to set the AF_INET. ++// Remember to set the AF_INET. + + class socket_address_inet { + public: +@@ -184,6 +184,10 @@ public: + + const sockaddr* c_sockaddr() const { return reinterpret_cast(&m_sockaddr); } + const sockaddr_in* c_sockaddr_inet() const { return &m_sockaddr; } ++ ++#ifdef RAK_USE_INET6 ++ socket_address_inet6 to_mapped_address() const; ++#endif + + bool operator == (const socket_address_inet& rhs) const; + bool operator < (const socket_address_inet& rhs) const; +@@ -192,6 +196,52 @@ private: + struct sockaddr_in m_sockaddr; + }; + ++#ifdef RAK_USE_INET6 ++// Remember to set the AF_INET6. ++ ++class socket_address_inet6 { ++public: ++ bool is_any() const { return is_port_any() && is_address_any(); } ++ bool is_valid() const { return !is_port_any() && !is_address_any(); } ++ bool is_port_any() const { return port() == 0; } ++ bool is_address_any() const { return std::memcmp(&m_sockaddr.sin6_addr, &in6addr_any, sizeof(in6_addr)) == 0; } ++ ++ void clear() { std::memset(this, 0, sizeof(socket_address_inet6)); set_family(); } ++ ++ uint16_t port() const { return ntohs(m_sockaddr.sin6_port); } ++ uint16_t port_n() const { return m_sockaddr.sin6_port; } ++ void set_port(uint16_t p) { m_sockaddr.sin6_port = htons(p); } ++ void set_port_n(uint16_t p) { m_sockaddr.sin6_port = p; } ++ ++ in6_addr address() const { return m_sockaddr.sin6_addr; } ++ std::string address_str() const; ++ bool address_c_str(char* buf, socklen_t size) const; ++ ++ void set_address(in6_addr a) { m_sockaddr.sin6_addr = a; } ++ bool set_address_str(const std::string& a) { return set_address_c_str(a.c_str()); } ++ bool set_address_c_str(const char* a); ++ ++ void set_address_any() { set_port(0); set_address(in6addr_any); } ++ ++ sa_family_t family() const { return m_sockaddr.sin6_family; } ++ void set_family() { m_sockaddr.sin6_family = AF_INET6; } ++ ++ sockaddr* c_sockaddr() { return reinterpret_cast(&m_sockaddr); } ++ sockaddr_in6* c_sockaddr_inet6() { return &m_sockaddr; } ++ ++ const sockaddr* c_sockaddr() const { return reinterpret_cast(&m_sockaddr); } ++ const sockaddr_in6* c_sockaddr_inet6() const { return &m_sockaddr; } ++ ++ socket_address normalize_address() const; ++ ++ bool operator == (const socket_address_inet6& rhs) const; ++ bool operator < (const socket_address_inet6& rhs) const; ++ ++private: ++ struct sockaddr_in6 m_sockaddr; ++}; ++#endif ++ + // Unique key for the address, excluding port numbers etc. + class socket_address_key { + public: +@@ -241,8 +291,10 @@ socket_address::is_valid() const { + switch (family()) { + case af_inet: + return sa_inet()->is_valid(); +-// case af_inet6: +-// return sa_inet6().is_valid(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->is_valid(); ++#endif + default: + return false; + } +@@ -253,6 +305,10 @@ socket_address::is_bindable() const { + switch (family()) { + case af_inet: + return !sa_inet()->is_address_any(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return !sa_inet6()->is_address_any(); ++#endif + default: + return false; + } +@@ -263,6 +319,10 @@ socket_address::is_address_any() const { + switch (family()) { + case af_inet: + return sa_inet()->is_address_any(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->is_address_any(); ++#endif + default: + return true; + } +@@ -273,6 +333,10 @@ socket_address::port() const { + switch (family()) { + case af_inet: + return sa_inet()->port(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->port(); ++#endif + default: + return 0; + } +@@ -283,6 +347,10 @@ socket_address::set_port(uint16_t p) { + switch (family()) { + case af_inet: + return sa_inet()->set_port(p); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->set_port(p); ++#endif + default: + break; + } +@@ -293,6 +361,10 @@ socket_address::address_str() const { + switch (family()) { + case af_inet: + return sa_inet()->address_str(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->address_str(); ++#endif + default: + return std::string(); + } +@@ -303,6 +375,10 @@ socket_address::address_c_str(char* buf, socklen_t size) const { + switch (family()) { + case af_inet: + return sa_inet()->address_c_str(buf, size); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sa_inet6()->address_c_str(buf, size); ++#endif + default: + return false; + } +@@ -314,6 +390,12 @@ socket_address::set_address_c_str(const char* a) { + sa_inet()->set_family(); + return true; + ++#ifdef RAK_USE_INET6 ++ } else if (sa_inet6()->set_address_c_str(a)) { ++ sa_inet6()->set_family(); ++ return true; ++#endif ++ + } else { + return false; + } +@@ -325,6 +407,10 @@ socket_address::length() const { + switch(family()) { + case af_inet: + return sizeof(sockaddr_in); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return sizeof(sockaddr_in6); ++#endif + default: + return 0; + } +@@ -349,8 +435,10 @@ socket_address::operator == (const socket_address& rhs) const { + switch (family()) { + case af_inet: + return *sa_inet() == *rhs.sa_inet(); +-// case af_inet6: +-// return *sa_inet6() == *rhs.sa_inet6(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return *sa_inet6() == *rhs.sa_inet6(); ++#endif + default: + throw std::logic_error("socket_address::operator == (rhs) invalid type comparison."); + } +@@ -364,8 +452,10 @@ socket_address::operator < (const socket_address& rhs) const { + switch (family()) { + case af_inet: + return *sa_inet() < *rhs.sa_inet(); +-// case af_inet6: +-// return *sa_inet6() < *rhs.sa_inet6(); ++#ifdef RAK_USE_INET6 ++ case af_inet6: ++ return *sa_inet6() < *rhs.sa_inet6(); ++#endif + default: + throw std::logic_error("socket_address::operator < (rhs) invalid type comparison."); + } +@@ -391,6 +481,23 @@ socket_address_inet::set_address_c_str(const char* a) { + return inet_pton(AF_INET, a, &m_sockaddr.sin_addr); + } + ++#ifdef RAK_USE_INET6 ++inline socket_address_inet6 ++socket_address_inet::to_mapped_address() const { ++ uint32_t addr32[4]; ++ addr32[0] = 0; ++ addr32[1] = 0; ++ addr32[2] = htonl(0xffff); ++ addr32[3] = m_sockaddr.sin_addr.s_addr; ++ ++ socket_address_inet6 sa; ++ sa.clear(); ++ sa.set_address(*reinterpret_cast(addr32)); ++ sa.set_port_n(m_sockaddr.sin_port); ++ return sa; ++} ++#endif ++ + inline bool + socket_address_inet::operator == (const socket_address_inet& rhs) const { + return +@@ -406,6 +513,59 @@ socket_address_inet::operator < (const socket_address_inet& rhs) const { + m_sockaddr.sin_port < rhs.m_sockaddr.sin_port); + } + ++#ifdef RAK_USE_INET6 ++ ++inline std::string ++socket_address_inet6::address_str() const { ++ char buf[INET6_ADDRSTRLEN]; ++ ++ if (!address_c_str(buf, INET6_ADDRSTRLEN)) ++ return std::string(); ++ ++ return std::string(buf); ++} ++ ++inline bool ++socket_address_inet6::address_c_str(char* buf, socklen_t size) const { ++ return inet_ntop(family(), &m_sockaddr.sin6_addr, buf, size); ++} ++ ++inline bool ++socket_address_inet6::set_address_c_str(const char* a) { ++ return inet_pton(AF_INET6, a, &m_sockaddr.sin6_addr); ++} ++ ++inline socket_address ++socket_address_inet6::normalize_address() const { ++ const uint32_t *addr32 = reinterpret_cast(m_sockaddr.sin6_addr.s6_addr); ++ if (addr32[0] == 0 && addr32[1] == 0 && addr32[2] == htonl(0xffff)) { ++ socket_address addr4; ++ addr4.sa_inet()->set_family(); ++ addr4.sa_inet()->set_address_n(addr32[3]); ++ addr4.sa_inet()->set_port_n(m_sockaddr.sin6_port); ++ return addr4; ++ } ++ return *reinterpret_cast(this); ++} ++ ++inline bool ++socket_address_inet6::operator == (const socket_address_inet6& rhs) const { ++ return ++ memcmp(&m_sockaddr.sin6_addr, &rhs.m_sockaddr.sin6_addr, sizeof(in6_addr)) == 0 && ++ m_sockaddr.sin6_port == rhs.m_sockaddr.sin6_port; ++} ++ ++inline bool ++socket_address_inet6::operator < (const socket_address_inet6& rhs) const { ++ int addr_comp = memcmp(&m_sockaddr.sin6_addr, &rhs.m_sockaddr.sin6_addr, sizeof(in6_addr)); ++ return ++ addr_comp < 0 || ++ (addr_comp == 0 || ++ m_sockaddr.sin6_port < rhs.m_sockaddr.sin6_port); ++} ++ ++#endif ++ + } + + #endif +diff --git a/src/command_peer.cc b/src/command_peer.cc +index 9708a8d..0aae8e0 100644 +--- a/src/command_peer.cc ++++ b/src/command_peer.cc +@@ -68,7 +68,13 @@ retrieve_p_id_html(torrent::Peer* peer) { + + torrent::Object + retrieve_p_address(torrent::Peer* peer) { +- return rak::socket_address::cast_from(peer->peer_info()->socket_address())->address_str(); ++ const rak::socket_address *addr = rak::socket_address::cast_from(peer->peer_info()->socket_address()); ++#ifdef RAK_USE_INET6 ++ if (addr->family() == rak::socket_address::af_inet6) ++ return "[" + addr->address_str() + "]"; ++ else ++#endif ++ return addr->address_str(); + } + + torrent::Object +diff --git a/src/core/curl_get.cc b/src/core/curl_get.cc +index f0767b7..4a904e1 100644 +--- a/src/core/curl_get.cc ++++ b/src/core/curl_get.cc +@@ -88,8 +88,20 @@ CurlGet::start() { + curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, (long)1); + curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, (long)1); + curl_easy_setopt(m_handle, CURLOPT_MAXREDIRS, (long)5); ++ ++ // Even when IPv6-enabled, we don't want to use CURL_IPRESOLVE_WHATEVER, ++ // since that will usually prefer connecting over IPv6 to the tracker. ++ // Since it's usually a lot easier to find our global IPv6 address ++ // (if we have one) than our global IPv4 address, we prefer connecting ++ // over IPv4 if we can, so that the tracker will get our IPv4 address ++ // that way. If the resolve fails, CurlStack will call retry_ipv6() ++ // on us and we'll make a second attempt with CURL_IPRESOLVE_V6. + curl_easy_setopt(m_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); ++ + curl_easy_setopt(m_handle, CURLOPT_ENCODING, ""); ++#ifdef RAK_USE_INET6 ++ m_ipv6 = false; ++#endif + + m_stack->add_get(this); + } +@@ -107,6 +119,17 @@ CurlGet::close() { + m_handle = NULL; + } + ++#ifdef RAK_USE_INET6 ++void ++CurlGet::retry_ipv6() { ++ CURL* nhandle = curl_easy_duphandle(m_handle); ++ curl_easy_setopt(nhandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); ++ curl_easy_cleanup(m_handle); ++ m_handle = nhandle; ++ m_ipv6 = true; ++} ++#endif ++ + void + CurlGet::receive_timeout() { + return m_stack->transfer_done(m_handle, "Timed out"); +diff --git a/src/core/curl_get.h b/src/core/curl_get.h +index 1d3a0d5..2b7836b 100644 +--- a/src/core/curl_get.h ++++ b/src/core/curl_get.h +@@ -56,6 +56,10 @@ public: + + void start(); + void close(); ++#ifdef RAK_USE_INET6 ++ bool is_using_ipv6() { return m_ipv6; } ++ void retry_ipv6(); ++#endif + + bool is_busy() const { return m_handle; } + bool is_active() const { return m_active; } +@@ -74,6 +78,9 @@ private: + void receive_timeout(); + + bool m_active; ++#ifdef RAK_USE_INET6 ++ bool m_ipv6; ++#endif + + rak::priority_item m_taskTimeout; + +diff --git a/src/core/curl_stack.cc b/src/core/curl_stack.cc +index 3adfab5..915b11a 100644 +--- a/src/core/curl_stack.cc ++++ b/src/core/curl_stack.cc +@@ -111,6 +111,21 @@ CurlStack::receive_action(CurlSocket* socket, int events) { + if (msg->msg != CURLMSG_DONE) + throw torrent::internal_error("CurlStack::receive_action() msg->msg != CURLMSG_DONE."); + ++#ifdef RAK_USE_INET6 ++ if (msg->data.result == CURLE_COULDNT_RESOLVE_HOST) { ++ iterator itr = std::find_if(begin(), end(), rak::equal(msg->easy_handle, std::mem_fun(&CurlGet::handle))); ++ ++ if (itr == end()) ++ throw torrent::internal_error("Could not find CurlGet when calling CurlStack::receive_action."); ++ ++ if (!(*itr)->is_using_ipv6()) { ++ (*itr)->retry_ipv6(); ++ if (curl_multi_add_handle((CURLM*)m_handle, (*itr)->handle()) > 0) ++ throw torrent::internal_error("Error calling curl_multi_add_handle."); ++ continue; ++ } ++ } else ++#endif + transfer_done(msg->easy_handle, msg->data.result == CURLE_OK ? NULL : curl_easy_strerror(msg->data.result)); + } + +diff --git a/src/display/window_peer_list.cc b/src/display/window_peer_list.cc +index 628b74b..63a22ef 100644 +--- a/src/display/window_peer_list.cc ++++ b/src/display/window_peer_list.cc +@@ -68,7 +68,11 @@ WindowPeerList::redraw() { + int x = 2; + int y = 0; + +- m_canvas->print(x, y, "IP"); x += 16; ++#ifdef RAK_USE_INET6 ++ m_canvas->print(x, y, "IP"); x += 25; ++#else ++ m_canvas->print(x, y, "IP"); x += 16; ++#endif + m_canvas->print(x, y, "UP"); x += 7; + m_canvas->print(x, y, "DOWN"); x += 7; + m_canvas->print(x, y, "PEER"); x += 7; +@@ -99,10 +103,21 @@ WindowPeerList::redraw() { + + x = 0; + ++ std::string ip_address = rak::socket_address::cast_from(p->address())->address_str(); ++#ifdef RAK_USE_INET6 ++ if (ip_address.size() >= 24) { ++ ip_address.replace(ip_address.begin() + 21, ip_address.end(), "..."); ++ } ++#endif ++ + m_canvas->print(x, y, "%c %s", + range.first == *m_focus ? '*' : ' ', +- rak::socket_address::cast_from(p->address())->address_str().c_str()); ++ ip_address.c_str()); ++#ifdef RAK_USE_INET6 ++ x += 27; ++#else + x += 18; ++#endif + + m_canvas->print(x, y, "%.1f", (double)p->up_rate()->rate() / 1024); x += 7; + m_canvas->print(x, y, "%.1f", (double)p->down_rate()->rate() / 1024); x += 7; +diff --git a/src/utils/socket_fd.cc b/src/utils/socket_fd.cc +index 414d4f7..2545304 100644 +--- a/src/utils/socket_fd.cc ++++ b/src/utils/socket_fd.cc +@@ -71,6 +71,11 @@ SocketFd::set_priority(priority_type p) { + check_valid(); + int opt = p; + ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket) ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_TCLASS, &opt, sizeof(opt)) == 0; ++ else ++#endif + return setsockopt(m_fd, IPPROTO_IP, IP_TOS, &opt, sizeof(opt)) == 0; + } + +@@ -130,12 +135,36 @@ SocketFd::get_error() const { + + bool + SocketFd::open_stream() { ++#ifdef RAK_USE_INET6 ++ m_fd = socket(rak::socket_address::pf_inet6, SOCK_STREAM, IPPROTO_TCP); ++ if (m_fd == -1) { ++ m_ipv6_socket = false; ++ return (m_fd = socket(rak::socket_address::pf_inet, SOCK_STREAM, IPPROTO_TCP)) != -1; ++ } ++ m_ipv6_socket = true; ++ ++ int zero = 0; ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) != -1; ++#else + return (m_fd = socket(rak::socket_address::pf_inet, SOCK_STREAM, IPPROTO_TCP)) != -1; ++#endif + } + + bool + SocketFd::open_datagram() { ++#ifdef RAK_USE_INET6 ++ m_fd = socket(rak::socket_address::pf_inet6, SOCK_DGRAM, 0); ++ if (m_fd == -1) { ++ m_ipv6_socket = false; ++ return (m_fd = socket(rak::socket_address::pf_inet, SOCK_DGRAM, 0)) != -1; ++ } ++ m_ipv6_socket = true; ++ ++ int zero = 0; ++ return setsockopt(m_fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) != -1; ++#else + return (m_fd = socket(rak::socket_address::pf_inet, SOCK_DGRAM, 0)) != -1; ++#endif + } + + bool +@@ -167,10 +196,34 @@ bool + SocketFd::connect(const rak::socket_address& sa) { + check_valid(); + ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket && sa.family() == rak::socket_address::pf_inet) { ++ rak::socket_address_inet6 sa_mapped = sa.sa_inet()->to_mapped_address(); ++ return !::connect(m_fd, sa_mapped.c_sockaddr(), sizeof(sa_mapped)) || errno == EINPROGRESS; ++ } ++#endif + return !::connect(m_fd, sa.c_sockaddr(), sa.length()) || errno == EINPROGRESS; + } + + bool ++SocketFd::getsockname(rak::socket_address *sa) { ++ check_valid(); ++ ++ socklen_t len = sizeof(rak::socket_address); ++ if (::getsockname(m_fd, sa->c_sockaddr(), &len)) { ++ return false; ++ } ++ ++#ifdef RAK_USE_INET6 ++ if (m_ipv6_socket && sa->family() == rak::socket_address::af_inet6) { ++ *sa = sa->sa_inet6()->normalize_address(); ++ } ++#endif ++ ++ return true; ++} ++ ++bool + SocketFd::listen(int size) { + check_valid(); + +@@ -182,7 +235,18 @@ SocketFd::accept(rak::socket_address* sa) { + check_valid(); + socklen_t len = sizeof(rak::socket_address); + ++#ifdef RAK_USE_INET6 ++ if (sa == NULL) { ++ return SocketFd(::accept(m_fd, NULL, &len)); ++ } ++ int fd = ::accept(m_fd, sa->c_sockaddr(), &len); ++ if (fd != -1 && m_ipv6_socket && sa->family() == rak::socket_address::af_inet6) { ++ *sa = sa->sa_inet6()->normalize_address(); ++ } ++ return SocketFd(fd); ++#else + return SocketFd(::accept(m_fd, sa != NULL ? sa->c_sockaddr() : NULL, &len)); ++#endif + } + + // unsigned int +diff --git a/src/utils/socket_fd.h b/src/utils/socket_fd.h +index 7898712..35d2eda 100644 +--- a/src/utils/socket_fd.h ++++ b/src/utils/socket_fd.h +@@ -80,6 +80,7 @@ public: + bool bind(const rak::socket_address& sa); + bool bind(const rak::socket_address& sa, unsigned int length); + bool connect(const rak::socket_address& sa); ++ bool getsockname(rak::socket_address* sa); + + bool listen(int size); + SocketFd accept(rak::socket_address* sa); +@@ -91,6 +92,9 @@ private: + inline void check_valid() const; + + int m_fd; ++#ifdef RAK_USE_INET6 ++ bool m_ipv6_socket; ++#endif + }; + + } diff --git a/rtorrent-extended/karabaja_mod.patch b/rtorrent-extended/karabaja_mod.patch new file mode 100644 index 0000000..e309fff --- /dev/null +++ b/rtorrent-extended/karabaja_mod.patch @@ -0,0 +1,438 @@ +diff --git a/src/display/canvas.cc b/src/display/canvas.cc +index 4e621df..35244eb 100644 +--- a/src/display/canvas.cc ++++ b/src/display/canvas.cc +@@ -92,6 +92,14 @@ Canvas::initialize() { + m_isInitialized = true; + + initscr(); ++ ++ //colors ++ start_color(); ++ use_default_colors(); ++ init_pair(1, COLOR_RED, -1); ++ init_pair(2, COLOR_YELLOW, -1); ++ init_pair(3, COLOR_GREEN, -1); ++ + raw(); + noecho(); + nodelay(stdscr, TRUE); +diff --git a/src/display/utils.cc b/src/display/utils.cc +index d2f1af4..400b940 100644 +--- a/src/display/utils.cc ++++ b/src/display/utils.cc +@@ -34,6 +34,9 @@ + // Skomakerveien 33 + // 3185 Skoppum, NORWAY + ++// interface modifications by karabaja4 ++// ++ + #include "config.h" + + #include +@@ -52,6 +55,10 @@ + #include + #include + ++//peers ++#include ++#include ++ + #include "core/download.h" + #include "core/manager.h" + #include "rpc/parse_commands.h" +@@ -96,9 +103,9 @@ print_hhmmss_local(char* first, char* last, time_t t) { + char* + print_ddhhmm(char* first, char* last, time_t t) { + if (t / (24 * 3600) < 100) +- return print_buffer(first, last, "%2id %2i:%02i", (int)t / (24 * 3600), ((int)t / 3600) % 24, ((int)t / 60) % 60); ++ return print_buffer(first, last, "%id:%ih:%im", (int)t / (24 * 3600), ((int)t / 3600) % 24, ((int)t / 60) % 60); + else +- return print_buffer(first, last, "--d --:--"); ++ return print_buffer(first, last, "--d:--h:--m"); + } + + char* +@@ -127,91 +134,121 @@ print_address(char* first, char* last, const sockaddr* sa) { + + char* + print_download_title(char* first, char* last, core::Download* d) { +- return print_buffer(first, last, " %s", d->download()->name().c_str()); ++ ++ first = print_buffer(first, last, " %s", d->download()->name().c_str()); ++ ++ if (first > last) ++ throw torrent::internal_error("print_download_status(...) wrote past end of the buffer."); ++ ++ return first; ++ + } + + char* +-print_download_info(char* first, char* last, core::Download* d) { +- if (!d->download()->is_open()) +- first = print_buffer(first, last, "[CLOSED] "); +- else if (!d->download()->is_active()) +- first = print_buffer(first, last, "[OPEN] "); +- else +- first = print_buffer(first, last, " "); ++print_download_title_extra(char* first, char* last, core::Download* d) { ++ ++ if (d->is_hash_checking()) { ++ first = print_buffer(first, last, " | Checking hash [%2i%%]", ++ (d->download()->chunks_hashed() * 100) / d->download()->file_list()->size_chunks()); ++ } ++ else if (d->tracker_list()->has_active() && d->tracker_list()->focus() < d->tracker_list()->end()) { ++ ++ torrent::TrackerList* tl = d->tracker_list(); ++ char status[128]; ++ ++ (*tl->focus())->get_status(status, sizeof(status)); ++ first = print_buffer(first, last, " | Tracker[%i:%i]: Connecting to %s %s", ++ (*tl->focus())->group(), tl->focus_index(), (*tl->focus())->url().c_str(), status); ++ } ++ else if (!d->message().empty()) { ++ first = print_buffer(first, last, " | %s", d->message().c_str()); ++ } ++ else { ++ *first = '\0'; ++ } ++ ++ if (first > last) ++ throw torrent::internal_error("print_download_status(...) wrote past end of the buffer."); ++ ++ return first; ++ ++} + +- if (d->is_done()) +- first = print_buffer(first, last, "done %10.1f MB", (double)d->download()->file_list()->size_bytes() / (double)(1 << 20)); +- else +- first = print_buffer(first, last, "%6.1f / %6.1f MB", +- (double)d->download()->bytes_done() / (double)(1 << 20), +- (double)d->download()->file_list()->size_bytes() / (double)(1 << 20)); +- +- first = print_buffer(first, last, " Rate: %5.1f / %5.1f KB Uploaded: %7.1f MB", +- (double)d->download()->up_rate()->rate() / (1 << 10), +- (double)d->download()->down_rate()->rate() / (1 << 10), +- (double)d->download()->up_rate()->total() / (1 << 20)); +- +- if (d->download()->is_active() && !d->is_done()) { +- first = print_buffer(first, last, " "); +- first = print_download_percentage_done(first, last, d); +- +- first = print_buffer(first, last, " "); +- first = print_download_time_left(first, last, d); +- } else { +- first = print_buffer(first, last, " "); +- } ++char* ++print_download_info(char* first, char* last, core::Download* d) { ++ ++ if (!d->download()->is_open()) { ++ first = print_buffer(first, last, " CLOSED |"); ++ } ++ else if (!d->download()->is_active()) { ++ first = print_buffer(first, last, " PAUSED |"); ++ } ++ else { ++ first = print_buffer(first, last, " ACTIVE |"); ++ } ++ ++ if (d->is_done()) { ++ first = print_buffer(first, last, " finished %.1f MB [100%%] |", (double)d->download()->file_list()->size_bytes() / (double)(1 << 20)); ++ } ++ else { ++ first = print_buffer(first, last, " %.1f / %.1f MB [%i%%] |", ++ (double)d->download()->bytes_done() / (double)(1 << 20), ++ (double)d->download()->file_list()->size_bytes() / (double)(1 << 20), ++ (int)(((double)d->download()->bytes_done() / (double)d->download()->file_list()->size_bytes()) * 100)); ++ } ++ ++ //speed ++ first = print_buffer(first, last, " Speed: %.1f / %.1f KB", ++ (double)d->download()->down_rate()->rate() / (1 << 10), ++ (double)d->download()->up_rate()->rate() / (1 << 10)); ++ ++ if (d->download()->is_active() && !d->is_done()) { ++ ++ //peers ++ first = print_buffer(first, last, " | Peers: %i(%i)", ++ (int)d->download()->connection_list()->size(), ++ (int)d->download()->peer_list()->available_list_size()); ++ ++ //eta ++ first = print_buffer(first, last, " | ETA: "); ++ first = print_download_time_left(first, last, d); ++ ++ } ++ ++ if (first > last) ++ throw torrent::internal_error("print_download_info(...) wrote past end of the buffer."); ++ ++ return first; ++} + +- first = print_buffer(first, last, " [%c%c R: %4.2f", +- rpc::call_command_string("d.get_tied_to_file", rpc::make_target(d)).empty() ? ' ' : 'T', +- rpc::call_command_value("d.get_ignore_commands", rpc::make_target(d)) == 0 ? ' ' : 'I', +- (double)rpc::call_command_value("d.get_ratio", rpc::make_target(d)) / 1000.0); ++char* ++print_download_info_extra(char* first, char* last, core::Download* d) { ++ ++ first = print_buffer(first, last, "[%c%c R: %4.2f", ++ rpc::call_command_string("d.get_tied_to_file", rpc::make_target(d)).empty() ? ' ' : 'T', ++ rpc::call_command_value("d.get_ignore_commands", rpc::make_target(d)) == 0 ? ' ' : 'I', ++ (double)rpc::call_command_value("d.get_ratio", rpc::make_target(d)) / 1000.0); + +- if (d->priority() != 2) +- first = print_buffer(first, last, " %s", rpc::call_command_string("d.get_priority_str", rpc::make_target(d)).c_str()); ++ if (d->priority() != 2) ++ first = print_buffer(first, last, " %s", rpc::call_command_string("d.get_priority_str", rpc::make_target(d)).c_str()); + +- if (!d->bencode()->get_key("rtorrent").get_key_string("throttle_name").empty()) +- first = print_buffer(first, last , " %s", rpc::call_command_string("d.get_throttle_name", rpc::make_target(d)).c_str()); ++ if (!d->bencode()->get_key("rtorrent").get_key_string("throttle_name").empty()) ++ first = print_buffer(first, last , " %s", rpc::call_command_string("d.get_throttle_name", rpc::make_target(d)).c_str()); + +- first = print_buffer(first, last , "]"); ++ first = print_buffer(first, last , "]"); + +- if (first > last) +- throw torrent::internal_error("print_download_info(...) wrote past end of the buffer."); ++ if (first > last) ++ throw torrent::internal_error("print_download_info(...) wrote past end of the buffer."); + +- return first; ++ return first; + } + + char* + print_download_status(char* first, char* last, core::Download* d) { +- if (d->is_active()) +- ; +- else if (rpc::call_command_value("d.get_hashing", rpc::make_target(d)) != 0) +- first = print_buffer(first, last, "Hashing: "); +- else if (!d->is_active()) +- first = print_buffer(first, last, "Inactive: "); +- +- if (d->is_hash_checking()) { +- first = print_buffer(first, last, "Checking hash [%2i%%]", +- (d->download()->chunks_hashed() * 100) / d->download()->file_list()->size_chunks()); +- +- } else if (d->tracker_list()->has_active() && d->tracker_list()->focus() < d->tracker_list()->end()) { +- torrent::TrackerList* tl = d->tracker_list(); +- char status[128]; +- +- (*tl->focus())->get_status(status, sizeof(status)); +- first = print_buffer(first, last, "Tracker[%i:%i]: Connecting to %s %s", +- (*tl->focus())->group(), tl->focus_index(), (*tl->focus())->url().c_str(), status); +- +- } else if (!d->message().empty()) { +- first = print_buffer(first, last, "%s", d->message().c_str()); +- +- } else { +- *first = '\0'; +- } +- +- if (first > last) +- throw torrent::internal_error("print_download_status(...) wrote past end of the buffer."); + ++ *first = '\0'; + return first; ++ + } + + char* +@@ -219,7 +256,7 @@ print_download_time_left(char* first, char* last, core::Download* d) { + uint32_t rate = d->download()->down_rate()->rate(); + + if (rate < 512) +- return print_buffer(first, last, "--d --:--"); ++ return print_buffer(first, last, "--d:--h:--m"); + + time_t remaining = (d->download()->file_list()->size_bytes() - d->download()->bytes_done()) / (rate & ~(uint32_t)(512 - 1)); + +@@ -230,9 +267,9 @@ char* + print_download_percentage_done(char* first, char* last, core::Download* d) { + if (!d->is_open() || d->is_done()) + //return print_buffer(first, last, "[--%%]"); +- return print_buffer(first, last, " "); ++ return print_buffer(first, last, " "); + else +- return print_buffer(first, last, "[%2u%%]", (d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks()); ++ return print_buffer(first, last, "%u%%", (d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks()); + } + + char* +@@ -255,19 +292,19 @@ print_client_version(char* first, char* last, const torrent::ClientInfo& clientI + + char* + print_status_info(char* first, char* last) { +- if (!torrent::up_throttle_global()->is_throttled()) ++ if (!torrent::down_throttle_global()->is_throttled()) + first = print_buffer(first, last, "[Throttle off"); + else +- first = print_buffer(first, last, "[Throttle %3i", torrent::up_throttle_global()->max_rate() / 1024); ++ first = print_buffer(first, last, "[Throttle %3i", torrent::down_throttle_global()->max_rate() / 1024); + +- if (!torrent::down_throttle_global()->is_throttled()) ++ if (!torrent::up_throttle_global()->is_throttled()) + first = print_buffer(first, last, "/off KB]"); + else +- first = print_buffer(first, last, "/%3i KB]", torrent::down_throttle_global()->max_rate() / 1024); ++ first = print_buffer(first, last, "/%3i KB]", torrent::up_throttle_global()->max_rate() / 1024); + + first = print_buffer(first, last, " [Rate %5.1f/%5.1f KB]", +- (double)torrent::up_rate()->rate() / 1024.0, +- (double)torrent::down_rate()->rate() / 1024.0); ++ (double)torrent::down_rate()->rate() / 1024.0, ++ (double)torrent::up_rate()->rate() / 1024.0); + + first = print_buffer(first, last, " [Port: %i]", (unsigned int)torrent::connection_manager()->listen_port()); + +diff --git a/src/display/utils.h b/src/display/utils.h +index 3d69021..c9145dd 100644 +--- a/src/display/utils.h ++++ b/src/display/utils.h +@@ -66,7 +66,9 @@ char* print_ddhhmm(char* first, char* last, time_t t); + char* print_ddmmyyyy(char* first, char* last, time_t t); + + char* print_download_title(char* first, char* last, core::Download* d); ++char* print_download_title_extra(char* first, char* last, core::Download* d); + char* print_download_info(char* first, char* last, core::Download* d); ++char* print_download_info_extra(char* first, char* last, core::Download* d); + char* print_download_status(char* first, char* last, core::Download* d); + char* print_download_time_left(char* first, char* last, core::Download* d); + char* print_download_percentage_done(char* first, char* last, core::Download* d); +diff --git a/src/display/window_download_list.cc b/src/display/window_download_list.cc +index 71efec0..353f6b5 100644 +--- a/src/display/window_download_list.cc ++++ b/src/display/window_download_list.cc +@@ -34,6 +34,9 @@ + // Skomakerveien 33 + // 3185 Skoppum, NORWAY + ++// interface modifications by karabaja4 ++// ++ + #include "config.h" + + #include +@@ -81,30 +84,90 @@ WindowDownloadList::redraw() { + Range range = rak::advance_bidirectional(m_view->begin_visible(), + m_view->focus() != m_view->end_visible() ? m_view->focus() : m_view->begin_visible(), + m_view->end_visible(), +- m_canvas->height() / 3); ++ (m_canvas->height() - 1) / 3); + + // Make sure we properly fill out the last lines so it looks like + // there are more torrents, yet don't hide it if we got the last one + // in focus. + if (range.second != m_view->end_visible()) +- ++range.second; ++ ++range.second; + +- int pos = 1; ++ int pos = 2; + + while (range.first != range.second) { + char buffer[m_canvas->width() + 1]; + char* position; + char* last = buffer + m_canvas->width() - 2 + 1; +- ++ int title_length; ++ ++ //1 = red ++ //2 = yellow ++ //3 = green ++ ++ //do not print on last lines if cannot show whole torrent ++ if (pos >= (m_canvas->height() - 1)) ++ break; ++ ++ //print title + position = print_download_title(buffer, last, *range.first); +- m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); ++ title_length = strlen(buffer); ++ m_canvas->print(0, pos, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); ++ ++ //title color ++ if ((*range.first)->is_done()) { ++ //finished ++ m_canvas->set_attr(3, pos, (title_length - 1), A_NORMAL, 3); ++ } ++ else { ++ //not finished ++ m_canvas->set_attr(3, pos, (title_length - 1), A_NORMAL, 2); ++ } ++ ++ //print title extra ++ position = print_download_title_extra(buffer, last, *range.first); ++ ++ //do not let title extra get off screen ++ buffer[m_canvas->width() - title_length - 2] = '\0'; + ++ m_canvas->print((title_length + 2), pos++, "%s", buffer); ++ ++ //print info + position = print_download_info(buffer, last, *range.first); +- m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); +- +- position = print_download_status(buffer, last, *range.first); +- m_canvas->print(0, pos++, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); +- ++ m_canvas->print(0, pos, "%c %s", range.first == m_view->focus() ? '*' : ' ', buffer); ++ ++ //info color ++ if (!(*range.first)->download()->is_open()) { ++ //closed ++ m_canvas->set_attr(3, pos, 6, A_NORMAL, 1); ++ } ++ else if (!(*range.first)->download()->is_active()) { ++ //paused ++ m_canvas->set_attr(3, pos, 6, A_NORMAL, 2); ++ } ++ else { ++ //active ++ m_canvas->set_attr(3, pos, 6, A_NORMAL, 3); ++ } ++ ++ if ((*range.first)->is_done()) { ++ //finished ++ m_canvas->set_attr(12, pos, 8, A_NORMAL, 3); ++ } ++ ++ //do not print info extra if it collides with info ++ if ((strlen(buffer) + 2) <= (m_canvas->width() - 16)) { ++ ++ //print info extra ++ position = print_download_info_extra(buffer, last, *range.first); ++ m_canvas->print((m_canvas->width() - 16), pos++, "%s", buffer); ++ ++ } ++ else { ++ pos++; ++ } ++ ++ //skip one line ++ pos++; + ++range.first; + } + } +diff --git a/src/display/window_title.cc b/src/display/window_title.cc +index 300f655..c0e7fe5 100644 +--- a/src/display/window_title.cc ++++ b/src/display/window_title.cc +@@ -48,6 +48,10 @@ WindowTitle::redraw() { + + m_canvas->print(std::max(0, ((int)m_canvas->width() - (int)m_title.size()) / 2 - 4), 0, + "*** %s ***", m_title.c_str()); ++ ++ //set color in title ++ m_canvas->set_attr((((int)m_canvas->width() - (int)m_title.size()) / 2 - 4), 0, 3, A_NORMAL, 2); ++ m_canvas->set_attr( ((((int)m_canvas->width() - (int)m_title.size()) / 2) + (int)m_title.size() + 1), 0, 3, A_NORMAL, 2); + } + + } diff --git a/rtorrent-extended/magnet_uri.patch b/rtorrent-extended/magnet_uri.patch new file mode 100644 index 0000000..32d45f0 --- /dev/null +++ b/rtorrent-extended/magnet_uri.patch @@ -0,0 +1,228 @@ +diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc +index e2d8ee8..49ac3fa 100644 +--- a/src/core/download_factory.cc ++++ b/src/core/download_factory.cc +@@ -69,6 +69,12 @@ is_network_uri(const std::string& uri) { + std::strncmp(uri.c_str(), "ftp://", 6) == 0; + } + ++bool ++is_magnet_uri(const std::string& uri) { ++ return ++ std::strncmp(uri.c_str(), "magnet:?", 8) == 0; ++} ++ + DownloadFactory::DownloadFactory(Manager* m) : + m_manager(m), + m_stream(NULL), +@@ -133,6 +139,13 @@ DownloadFactory::receive_load() { + + m_variables["tied_to_file"] = (int64_t)false; + ++ } else if (is_magnet_uri(m_uri)) { ++ m_stream = new std::stringstream(); ++ *m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e"; ++ ++ m_variables["tied_to_file"] = (int64_t)false; ++ receive_loaded(); ++ + } else { + std::fstream* stream = new std::fstream(rak::path_expand(m_uri).c_str(), std::ios::in | std::ios::binary); + m_stream = stream; +@@ -177,6 +190,16 @@ DownloadFactory::receive_success() { + + torrent::Object* root = download->bencode(); + ++ if (download->download()->is_meta_download()) { ++ torrent::Object& meta = root->insert_key("rtorrent_meta_download", torrent::Object::create_map()); ++ meta.insert_key("start", m_start); ++ meta.insert_key("print_log", m_printLog); ++ ++ torrent::Object::list_type& commands = meta.insert_key("commands", torrent::Object::create_list()).as_list(); ++ for (command_list_type::iterator itr = m_commands.begin(); itr != m_commands.end(); ++itr) ++ commands.push_back(*itr); ++ } ++ + if (!m_session) { + // We only allow session torrents to keep their + // 'rtorrent/libtorrent' sections. The "fast_resume" section +@@ -229,7 +252,7 @@ DownloadFactory::receive_success() { + rpc::call_command("d.set_directory_base", rtorrent->get_key("directory"), rpc::make_target(download)); + + if (!m_session && m_variables["tied_to_file"].as_value()) +- rpc::call_command("d.set_tied_to_file", m_uri, rpc::make_target(download)); ++ rpc::call_command("d.set_tied_to_file", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download)); + + rpc::call_command("d.set_peer_exchange", rpc::call_command_value("get_peer_exchange"), rpc::make_target(download)); + +diff --git a/src/core/download_factory.h b/src/core/download_factory.h +index 045c9dc..3cc9622 100644 +--- a/src/core/download_factory.h ++++ b/src/core/download_factory.h +@@ -112,6 +112,7 @@ private: + }; + + bool is_network_uri(const std::string& uri); ++bool is_magnet_uri(const std::string& uri); + + } + +diff --git a/src/core/download_list.cc b/src/core/download_list.cc +index 551f873..13df725 100644 +--- a/src/core/download_list.cc ++++ b/src/core/download_list.cc +@@ -37,10 +37,12 @@ + #include "config.h" + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -452,6 +454,9 @@ DownloadList::hash_done(Download* download) { + int64_t hashing = rpc::call_command_value("d.get_hashing", rpc::make_target(download)); + rpc::call_command_set_value("d.set_hashing", Download::variable_hashing_stopped, rpc::make_target(download)); + ++ if (download->is_done() && download->download()->is_meta_download()) ++ return process_meta_download(download); ++ + switch (hashing) { + case Download::variable_hashing_initial: + case Download::variable_hashing_rehash: +@@ -543,6 +548,9 @@ void + DownloadList::confirm_finished(Download* download) { + check_contains(download); + ++ if (download->download()->is_meta_download()) ++ return process_meta_download(download); ++ + rpc::call_command("d.set_complete", (int64_t)1, rpc::make_target(download)); + + rpc::call_command("d.set_connection_current", rpc::call_command_void("d.get_connection_seed", rpc::make_target(download)), rpc::make_target(download)); +@@ -576,4 +584,36 @@ DownloadList::confirm_finished(Download* download) { + resume(download, torrent::Download::start_no_create | torrent::Download::start_skip_tracker | torrent::Download::start_keep_baseline); + } + ++void ++DownloadList::process_meta_download(Download* download) { ++ rpc::call_command("d.stop", torrent::Object(), rpc::make_target(download)); ++ rpc::call_command("d.close", torrent::Object(), rpc::make_target(download)); ++ ++ std::string metafile = (*download->file_list()->begin())->frozen_path(); ++ std::fstream file(metafile.c_str(), std::ios::in | std::ios::binary); ++ if (!file.is_open()) { ++ control->core()->push_log("Could not read download metadata."); ++ return; ++ } ++ ++ torrent::Object* bencode = new torrent::Object(torrent::Object::create_map()); ++ file >> bencode->insert_key("info", torrent::Object()); ++ if (file.fail()) { ++ delete bencode; ++ control->core()->push_log("Could not create download, the input is not a valid torrent."); ++ return; ++ } ++ file.close(); ++ ++ // Steal the keys we still need. The old download has no use for them. ++ bencode->insert_key("rtorrent_meta_download", torrent::Object()).swap(download->bencode()->get_key("rtorrent_meta_download")); ++ if (download->bencode()->has_key("announce")) ++ bencode->insert_key("announce", torrent::Object()).swap(download->bencode()->get_key("announce")); ++ if (download->bencode()->has_key("announce-list")) ++ bencode->insert_key("announce-list", torrent::Object()).swap(download->bencode()->get_key("announce-list")); ++ ++ erase_ptr(download); ++ control->core()->try_create_download_from_meta_download(bencode, metafile); ++} ++ + } +diff --git a/src/core/download_list.h b/src/core/download_list.h +index f7828ea..8ecffa0 100644 +--- a/src/core/download_list.h ++++ b/src/core/download_list.h +@@ -161,6 +161,8 @@ private: + + void received_finished(Download* d); + void confirm_finished(Download* d); ++ ++ void process_meta_download(Download* d); + }; + + } +diff --git a/src/core/manager.cc b/src/core/manager.cc +index 62738ca..2a422c8 100644 +--- a/src/core/manager.cc ++++ b/src/core/manager.cc +@@ -39,6 +39,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -52,6 +53,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -395,6 +397,7 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li + if ((flags & create_tied) && + !(flags & create_raw_data) && + !is_network_uri(uri) && ++ !is_magnet_uri(uri) && + !file_status_cache()->insert(uri, 0)) + return; + +@@ -416,6 +419,31 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li + f->commit(); + } + ++void ++Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) { ++ DownloadFactory* f = new DownloadFactory(this); ++ ++ f->variables()["tied_to_file"] = (int64_t)true; ++ f->variables()["tied_file"] = metafile; ++ ++ torrent::Object& meta = bencode->get_key("rtorrent_meta_download"); ++ torrent::Object::list_type& commands = meta.get_key_list("commands"); ++ for (torrent::Object::list_type::const_iterator itr = commands.begin(); itr != commands.end(); ++itr) ++ f->commands().insert(f->commands().end(), itr->as_string()); ++ ++ f->set_start(meta.get_key_value("start")); ++ f->set_print_log(meta.get_key_value("print_log")); ++ f->slot_finished(sigc::bind(sigc::ptr_fun(&rak::call_delete_func), f)); ++ ++ // Bit of a waste to create the bencode repesentation here ++ // only to have the DownloadFactory decode it. ++ std::stringstream s; ++ s.imbue(std::locale::classic()); ++ s << *bencode; ++ f->load_raw_data(s.str()); ++ f->commit(); ++} ++ + utils::Directory + path_expand_transform(std::string path, const utils::directory_entry& entry) { + return path + entry.d_name; +diff --git a/src/core/manager.h b/src/core/manager.h +index 3b23da3..16902af 100644 +--- a/src/core/manager.h ++++ b/src/core/manager.h +@@ -128,6 +128,7 @@ public: + // Temporary, find a better place for this. + void try_create_download(const std::string& uri, int flags, const command_list_type& commands); + void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type()); ++ void try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile); + + private: + typedef RangeMap AddressThrottleMap; diff --git a/rtorrent-extended/rtorrent-extended.install b/rtorrent-extended/rtorrent-extended.install new file mode 100644 index 0000000..811a690 --- /dev/null +++ b/rtorrent-extended/rtorrent-extended.install @@ -0,0 +1,9 @@ +post_install() { + echo "" + echo "rTorrent-eXtended new options in .rtorrent.rc supported" + echo "examples at http://lky.cc/rtorrent-extended/" +} +post_upgrade() { + post_install +} + diff --git a/rtorrent-extended/trackerinfo.patch b/rtorrent-extended/trackerinfo.patch new file mode 100644 index 0000000..63998a5 --- /dev/null +++ b/rtorrent-extended/trackerinfo.patch @@ -0,0 +1,21 @@ +diff --git a/src/display/window_tracker_list.cc b/src/display/window_tracker_list.cc +index 1903d67..2860f12 100644 +--- a/src/display/window_tracker_list.cc ++++ b/src/display/window_tracker_list.cc +@@ -89,13 +89,14 @@ WindowTrackerList::redraw() { + tracker->url().c_str()); + + if (pos < m_canvas->height()) +- m_canvas->print(4, pos++, "Id: %s Focus: %s Enabled: %s Open: %s S/L: %u/%u", ++ m_canvas->print(4, pos++, "Id: %s Focus: %s Enabled: %s Open: %s S/L/D: %u/%u/%u", + rak::copy_escape_html(tracker->tracker_id()).c_str(), + range.first == tl->focus_index() ? "yes" : " no", + tracker->is_usable() ? "yes" : tracker->is_enabled() ? "off" : " no", + tracker->is_busy() ? "yes" : " no", + tracker->scrape_complete(), +- tracker->scrape_incomplete()); ++ tracker->scrape_incomplete(), ++ tracker->scrape_downloaded()); + + // m_canvas->print(4, pos++, "Id: %s Focus: %s Enabled: %s Open: %s Timer: %u/%u", + // rak::copy_escape_html(tracker->tracker_id()).c_str(), diff --git a/rtorrent-extended/vi_kb_akston.patch b/rtorrent-extended/vi_kb_akston.patch new file mode 100644 index 0000000..46ff7ea --- /dev/null +++ b/rtorrent-extended/vi_kb_akston.patch @@ -0,0 +1,176 @@ +diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc +index e72bff6..04b8625 100644 +--- a/src/ui/download_list.cc ++++ b/src/ui/download_list.cc +@@ -346,13 +346,13 @@ DownloadList::setup_keys() { + m_bindings['\x0F'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::receive_view_input), INPUT_CHANGE_DIRECTORY); + m_bindings['X' - '@'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::receive_view_input), INPUT_COMMAND); + +- m_uiArray[DISPLAY_LOG]->bindings()[KEY_LEFT] = ++ m_uiArray[DISPLAY_LOG]->bindings()['h'] = + m_uiArray[DISPLAY_LOG]->bindings()['B' - '@'] = + m_uiArray[DISPLAY_LOG]->bindings()[' '] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD_LIST); + +- m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[KEY_RIGHT] = ++ m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['l'] = + m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['F' - '@'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD); +- m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['l'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_LOG); ++ m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['L'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_LOG); + } + + } +diff --git a/src/ui/element_chunks_seen.cc b/src/ui/element_chunks_seen.cc +index 8ee1dd9..d2636db 100644 +--- a/src/ui/element_chunks_seen.cc ++++ b/src/ui/element_chunks_seen.cc +@@ -52,10 +52,10 @@ ElementChunksSeen::ElementChunksSeen(core::Download* d) : + m_window(NULL), + m_focus(0) { + +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_next); +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_prev); + m_bindings[KEY_NPAGE] = sigc::mem_fun(*this, &ElementChunksSeen::receive_pagenext); + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementChunksSeen::receive_pageprev); + } +diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc +index 1dc9ece..0c789f1 100644 +--- a/src/ui/element_download_list.cc ++++ b/src/ui/element_download_list.cc +@@ -95,8 +95,8 @@ ElementDownloadList::ElementDownloadList() : + m_bindings['8'] = sigc::bind(sigc::mem_fun(*this, &ElementDownloadList::receive_change_view), "seeding"); + m_bindings['9'] = sigc::bind(sigc::mem_fun(*this, &ElementDownloadList::receive_change_view), "active"); + +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_prev); +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_next); + } + + void +diff --git a/src/ui/element_file_list.cc b/src/ui/element_file_list.cc +index 75ecec0..f23ef23 100644 +--- a/src/ui/element_file_list.cc ++++ b/src/ui/element_file_list.cc +@@ -64,8 +64,8 @@ ElementFileList::ElementFileList(core::Download* d) : + m_selected(iterator(d->download()->file_list()->begin())), + m_collapsed(false) { + +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); +- m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_select); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_select); + + m_bindings[' '] = sigc::mem_fun(*this, &ElementFileList::receive_priority); + m_bindings['*'] = sigc::mem_fun(*this, &ElementFileList::receive_change_all); +@@ -73,8 +73,8 @@ ElementFileList::ElementFileList(core::Download* d) : + m_bindings[KEY_NPAGE] = sigc::mem_fun(*this, &ElementFileList::receive_pagenext); + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementFileList::receive_pageprev); + +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_next); +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_prev); + } + + inline ElementText* +diff --git a/src/ui/element_menu.cc b/src/ui/element_menu.cc +index 04130f3..41779eb 100644 +--- a/src/ui/element_menu.cc ++++ b/src/ui/element_menu.cc +@@ -72,11 +72,11 @@ ElementMenu::ElementMenu() : + m_entry(entry_invalid) { + + // Move bindings into a function that defines default bindings. +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); +- m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_select); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_select); + +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_prev); +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_next); + } + + ElementMenu::~ElementMenu() { +diff --git a/src/ui/element_peer_list.cc b/src/ui/element_peer_list.cc +index be0e0b4..e38f3a2 100644 +--- a/src/ui/element_peer_list.cc ++++ b/src/ui/element_peer_list.cc +@@ -73,14 +73,14 @@ ElementPeerList::ElementPeerList(core::Download* d) : + + m_elementInfo->slot_exit(sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_LIST)); + +- m_bindings['k'] = sigc::mem_fun(this, &ElementPeerList::receive_disconnect_peer); ++ m_bindings['K'] = sigc::mem_fun(this, &ElementPeerList::receive_disconnect_peer); + m_bindings['*'] = sigc::mem_fun(this, &ElementPeerList::receive_snub_peer); + m_bindings['B'] = sigc::mem_fun(this, &ElementPeerList::receive_ban_peer); +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); +- m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_INFO); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_INFO); + +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_prev); +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_next); + } + + ElementPeerList::~ElementPeerList() { +diff --git a/src/ui/element_text.cc b/src/ui/element_text.cc +index a7496f2..a564a81 100644 +--- a/src/ui/element_text.cc ++++ b/src/ui/element_text.cc +@@ -54,7 +54,7 @@ ElementText::ElementText(rpc::target_type target) : + m_columnWidth(0) { + + // Move bindings into a function that defines default bindings. +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + + // m_bindings[KEY_UP] = sigc::mem_fun(this, &ElementText::entry_prev); + // m_bindings[KEY_DOWN] = sigc::mem_fun(this, &ElementText::entry_next); +diff --git a/src/ui/element_tracker_list.cc b/src/ui/element_tracker_list.cc +index 7d81b89..8a51e80 100644 +--- a/src/ui/element_tracker_list.cc ++++ b/src/ui/element_tracker_list.cc +@@ -54,13 +54,13 @@ ElementTrackerList::ElementTrackerList(core::Download* d) : + m_window(NULL), + m_focus(0) { + +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + + m_bindings[' '] = sigc::mem_fun(*this, &ElementTrackerList::receive_cycle_group); + m_bindings['*'] = sigc::mem_fun(*this, &ElementTrackerList::receive_disable); + +- m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_next); +- m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_prev); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_next); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_prev); + } + + void +diff --git a/src/ui/element_transfer_list.cc b/src/ui/element_transfer_list.cc +index 50eaec6..83ae26d 100644 +--- a/src/ui/element_transfer_list.cc ++++ b/src/ui/element_transfer_list.cc +@@ -52,10 +52,10 @@ ElementTransferList::ElementTransferList(core::Download* d) : + m_window(NULL), + m_focus(0) { + +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + +- m_bindings[KEY_DOWN] = sigc::mem_fun(*this, &ElementTransferList::receive_next); +- m_bindings[KEY_UP] = sigc::mem_fun(*this, &ElementTransferList::receive_prev); ++ m_bindings['j'] = sigc::mem_fun(*this, &ElementTransferList::receive_next); ++ m_bindings['k'] = sigc::mem_fun(*this, &ElementTransferList::receive_prev); + m_bindings[KEY_NPAGE] = sigc::mem_fun(*this, &ElementTransferList::receive_pagenext); + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementTransferList::receive_pageprev); + } diff --git a/rtorrent-extended/vi_kb_tjwoosta.patch b/rtorrent-extended/vi_kb_tjwoosta.patch new file mode 100644 index 0000000..b7ea5ac --- /dev/null +++ b/rtorrent-extended/vi_kb_tjwoosta.patch @@ -0,0 +1,175 @@ +diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc +index e72bff6..30b5987 100644 +--- a/src/ui/download_list.cc ++++ b/src/ui/download_list.cc +@@ -350,9 +350,16 @@ DownloadList::setup_keys() { + m_uiArray[DISPLAY_LOG]->bindings()['B' - '@'] = + m_uiArray[DISPLAY_LOG]->bindings()[' '] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD_LIST); + ++ m_uiArray[DISPLAY_LOG]->bindings()['h'] = ++ m_uiArray[DISPLAY_LOG]->bindings()['B' - '@'] = ++ m_uiArray[DISPLAY_LOG]->bindings()[' '] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD_LIST); ++ + m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[KEY_RIGHT] = + m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['F' - '@'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD); +- m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['l'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_LOG); ++ ++ m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['l'] = ++ m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['F' - '@'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_DOWNLOAD); ++ m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['L'] = sigc::bind(sigc::mem_fun(*this, &DownloadList::activate_display), DISPLAY_LOG); + } + + } +diff --git a/src/ui/element_chunks_seen.cc b/src/ui/element_chunks_seen.cc +index 8ee1dd9..5336b05 100644 +--- a/src/ui/element_chunks_seen.cc ++++ b/src/ui/element_chunks_seen.cc +@@ -54,8 +54,12 @@ ElementChunksSeen::ElementChunksSeen(core::Download* d) : + + m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_next); + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementChunksSeen::receive_prev); + m_bindings[KEY_NPAGE] = sigc::mem_fun(*this, &ElementChunksSeen::receive_pagenext); + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementChunksSeen::receive_pageprev); + } +diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc +index 1dc9ece..f2763ca 100644 +--- a/src/ui/element_download_list.cc ++++ b/src/ui/element_download_list.cc +@@ -96,7 +96,9 @@ ElementDownloadList::ElementDownloadList() : + m_bindings['9'] = sigc::bind(sigc::mem_fun(*this, &ElementDownloadList::receive_change_view), "active"); + + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_prev); + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementDownloadList::receive_next); + } + + void +diff --git a/src/ui/element_file_list.cc b/src/ui/element_file_list.cc +index 75ecec0..a24f429 100644 +--- a/src/ui/element_file_list.cc ++++ b/src/ui/element_file_list.cc +@@ -65,7 +65,9 @@ ElementFileList::ElementFileList(core::Download* d) : + m_collapsed(false) { + + m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_select); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_select); + + m_bindings[' '] = sigc::mem_fun(*this, &ElementFileList::receive_priority); + m_bindings['*'] = sigc::mem_fun(*this, &ElementFileList::receive_change_all); +@@ -74,7 +76,9 @@ ElementFileList::ElementFileList(core::Download* d) : + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementFileList::receive_pageprev); + + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_next); + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementFileList::receive_prev); + } + + inline ElementText* +diff --git a/src/ui/element_menu.cc b/src/ui/element_menu.cc +index 04130f3..b6b3ee9 100644 +--- a/src/ui/element_menu.cc ++++ b/src/ui/element_menu.cc +@@ -72,11 +72,15 @@ ElementMenu::ElementMenu() : + m_entry(entry_invalid) { + + // Move bindings into a function that defines default bindings. +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_select); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_select); + + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_prev); + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementMenu::entry_next); + } + + ElementMenu::~ElementMenu() { +diff --git a/src/ui/element_peer_list.cc b/src/ui/element_peer_list.cc +index be0e0b4..8b7303e 100644 +--- a/src/ui/element_peer_list.cc ++++ b/src/ui/element_peer_list.cc +@@ -73,14 +73,18 @@ ElementPeerList::ElementPeerList(core::Download* d) : + + m_elementInfo->slot_exit(sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_LIST)); + +- m_bindings['k'] = sigc::mem_fun(this, &ElementPeerList::receive_disconnect_peer); ++ m_bindings['K'] = sigc::mem_fun(this, &ElementPeerList::receive_disconnect_peer); + m_bindings['*'] = sigc::mem_fun(this, &ElementPeerList::receive_snub_peer); + m_bindings['B'] = sigc::mem_fun(this, &ElementPeerList::receive_ban_peer); +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + m_bindings[KEY_RIGHT] = m_bindings['F' - '@'] = sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_INFO); ++ m_bindings['l'] = m_bindings['F' - '@'] = sigc::bind(sigc::mem_fun(this, &ElementPeerList::activate_display), DISPLAY_INFO); + + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_prev); + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(this, &ElementPeerList::receive_next); + } + + ElementPeerList::~ElementPeerList() { +diff --git a/src/ui/element_text.cc b/src/ui/element_text.cc +index a7496f2..4c2e171 100644 +--- a/src/ui/element_text.cc ++++ b/src/ui/element_text.cc +@@ -54,7 +54,9 @@ ElementText::ElementText(rpc::target_type target) : + m_columnWidth(0) { + + // Move bindings into a function that defines default bindings. +- m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + + // m_bindings[KEY_UP] = sigc::mem_fun(this, &ElementText::entry_prev); + // m_bindings[KEY_DOWN] = sigc::mem_fun(this, &ElementText::entry_next); +diff --git a/src/ui/element_tracker_list.cc b/src/ui/element_tracker_list.cc +index 7d81b89..5584653 100644 +--- a/src/ui/element_tracker_list.cc ++++ b/src/ui/element_tracker_list.cc +@@ -55,12 +55,15 @@ ElementTrackerList::ElementTrackerList(core::Download* d) : + m_focus(0) { + + m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + + m_bindings[' '] = sigc::mem_fun(*this, &ElementTrackerList::receive_cycle_group); + m_bindings['*'] = sigc::mem_fun(*this, &ElementTrackerList::receive_disable); + + m_bindings[KEY_DOWN] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_next); ++ m_bindings['j'] = m_bindings['N' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_next); + m_bindings[KEY_UP] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_prev); ++ m_bindings['k'] = m_bindings['P' - '@'] = sigc::mem_fun(*this, &ElementTrackerList::receive_prev); + } + + void +diff --git a/src/ui/element_transfer_list.cc b/src/ui/element_transfer_list.cc +index 50eaec6..f14a2d2 100644 +--- a/src/ui/element_transfer_list.cc ++++ b/src/ui/element_transfer_list.cc +@@ -54,8 +54,12 @@ ElementTransferList::ElementTransferList(core::Download* d) : + + m_bindings[KEY_LEFT] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); + ++ m_bindings['h'] = m_bindings['B' - '@'] = sigc::mem_fun(&m_slotExit, &slot_type::operator()); ++ + m_bindings[KEY_DOWN] = sigc::mem_fun(*this, &ElementTransferList::receive_next); ++ m_bindings['j'] = sigc::mem_fun(*this, &ElementTransferList::receive_next); + m_bindings[KEY_UP] = sigc::mem_fun(*this, &ElementTransferList::receive_prev); ++ m_bindings['k'] = sigc::mem_fun(*this, &ElementTransferList::receive_prev); + m_bindings[KEY_NPAGE] = sigc::mem_fun(*this, &ElementTransferList::receive_pagenext); + m_bindings[KEY_PPAGE] = sigc::mem_fun(*this, &ElementTransferList::receive_pageprev); + } -- cgit v1.2.3-24-g4f1b