summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libtorrent-extended/PKGBUILD54
-rw-r--r--libtorrent-extended/bad_peer_handling.patch67
-rw-r--r--libtorrent-extended/dht_pex_static_map.patch2353
-rw-r--r--libtorrent-extended/ipv6.patch1235
-rw-r--r--libtorrent-extended/magnet_uri.patch1652
-rw-r--r--libtorrent-extended/object_sstr.patch233
-rw-r--r--rtorrent-extended/PKGBUILD122
-rw-r--r--rtorrent-extended/bad_peer_handling.patch219
-rw-r--r--rtorrent-extended/canvas_color.patch282
-rw-r--r--rtorrent-extended/dht_pex_static_map.patch12
-rw-r--r--rtorrent-extended/ip_filter_no_boost_fast.patch1083
-rw-r--r--rtorrent-extended/ipv6.patch572
-rw-r--r--rtorrent-extended/karabaja_mod.patch438
-rw-r--r--rtorrent-extended/magnet_uri.patch228
-rw-r--r--rtorrent-extended/rtorrent-extended.install9
-rw-r--r--rtorrent-extended/trackerinfo.patch21
-rw-r--r--rtorrent-extended/vi_kb_akston.patch176
-rw-r--r--rtorrent-extended/vi_kb_tjwoosta.patch175
18 files changed, 8931 insertions, 0 deletions
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 <aur.archlinux.org [at] lucky.take0ver [dot] net>
+# Contributor: Daenyth <Daenyth+Arch [at] gmail [dot] com>
+# Contributor: Jeff Mickey <jeff@archlinux.org>
+# 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 <algorithm>
+-#include <sstream>
++#include <cstdio>
+
+ #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<DhtAnnounce*>(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<uint16_t>(), 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<uint32_t>(), 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 <rak/socket_address.h>
+
+ #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<SocketAddressCompact> 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<BencodeAddress> PeerList;
+
+ PeerList m_peers;
+ std::vector<uint32_t> 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<dht_keys, key_LAST> {
++public:
++ typedef StaticMap<dht_keys, key_LAST> 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 <cstdio>
+ #include <cstring>
+ #include <string.h>
+ #include <rak/functional.h>
+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<const SocketAddressCompact*>(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 <rak/socket_address.h>
+
+ #include <torrent/object.h>
++#include <torrent/simple_string.h>
+
+ namespace torrent {
+
+@@ -49,7 +50,8 @@ class AddressList : public std::list<rak::socket_address> {
+ 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 <limits>
+-#include <sstream>
++#include <stdarg.h>
+
+ #include <cstdio>
+
+@@ -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<ext_handshake_keys, key_handshake_LAST> {
++public:
++ typedef StaticMap<ext_handshake_keys, key_handshake_LAST> base_type;
++ typedef StaticMapKeys::mapping_type mapping_type;
++};
++
++class ExtPEXMessage : public StaticMap<ext_pex_keys, key_pex_LAST> {
++public:
++ typedef StaticMap<ext_pex_keys, key_pex_LAST> 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 <string>
+ #include <iterator>
+ #include <torrent/common.h>
++#include <torrent/simple_string.h>
+
+ 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 <list>
+ #include <torrent/common.h>
+ #include <torrent/exceptions.h>
++#include <torrent/simple_string.h>
+
+ 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 <iterator>
+ #include <iostream>
++#include <sstream>
+ #include <rak/functional.h>
+
+ #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<typename tmpl_key_type, size_t tmpl_length>
++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<typename tmpl_key_type, size_t tmpl_length>
++inline const char*
++staticMap_read_bencode(const char* buffer, const char* bufferEnd, StaticMap<tmpl_key_type, tmpl_length>& 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<typename tmpl_key_type, size_t tmpl_length>
++inline object_buffer_t
++staticMap_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const StaticMap<tmpl_key_type, tmpl_length>& 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 <jaris@ifi.uio.no>
++//
++// 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 <cstring>
++#include <memory>
++#include <string>
++#include <torrent/common.h>
++
++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<size_t>(-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 <jaris@ifi.uio.no>
++//
++// 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 <jaris@ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_STATIC_MAP_H
++#define LIBTORRENT_STATIC_MAP_H
++
++#include <vector>
++#include <torrent/common.h>
++#include <torrent/object.h>
++#include <torrent/simple_string.h>
++
++// 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<StaticMapKeys> {
++public:
++ typedef std::vector<StaticMapKeys> 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<typename tmpl_key_type, size_t tmpl_length>
++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<const sockaddr*>(&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<sockaddr*>(&m_sockaddr); }
++ sockaddr_in6* c_sockaddr_inet6() { return &m_sockaddr; }
++
++ const sockaddr* c_sockaddr() const { return reinterpret_cast<const sockaddr*>(&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<in6_addr *>(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<const uint32_t *>(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<const socket_address*>(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<const char*>(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<const char*>(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<const SocketAddressCompact6*>(s.c_str()),
++ reinterpret_cast<const SocketAddressCompact6*>(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 <jaris@ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <stdio.h>
++#include <ifaddrs.h>
++#include <rak/socket_address.h>
++#include <sys/types.h>
++#include <errno.h>
++
++#ifdef __linux__
++#include <linux/netlink.h>
++#include <linux/rtnetlink.h>
++#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<const uint32_t *>(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 <jaris@ifi.uio.no>
++//
++// 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 <unistd.h>
++
++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<PeerList::reference, uint16_t, bool> {
+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<Path> pathList;
+@@ -343,4 +360,132 @@ DownloadConstructor::choose_path(std::list<Path>* 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<int64_t>(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<ext_handshake_keys, key_handshake_LAST> {
+ public:
+ typedef StaticMap<ext_handshake_keys, key_handshake_LAST> base_type;
+@@ -82,9 +93,17 @@ public:
+ typedef StaticMapKeys::mapping_type mapping_type;
+ };
+
++class ExtMetadataMessage : public StaticMap<ext_metadata_keys, key_metadata_LAST> {
++public:
++ typedef StaticMap<ext_metadata_keys, key_metadata_LAST> 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<type>::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<type>::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<type>::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 <jaris@ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <cstring>
++#include <sstream>
++
++#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 <jaris@ifi.uio.no>
++//
++// 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<uint64_t*>(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<std::string>(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::base_type::iterator, bool>
++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<typename T>
++ 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<Object> list_type;
+- typedef std::map<std::string, Object> map_type;
++ class map_type : public std::map<SimpleString, string_wrapper<Object> > {
++ public:
++ typedef std::map<SimpleString, string_wrapper<Object> > 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<base_type::iterator, bool> insert(const value_type& value);
++ base_type::iterator insert(base_type::iterator itr, const value_type& value);
++
++ template<typename InputIterator>
++ 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 <typename T> value_type& get_key_value(const T& k) { return get_key(k).as_value(); }
+ template <typename T> 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<typename InputIterator>
++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 <aur.archlinux.org [at] lucky.take0ver [dot] net>
+# Contributor: Ashren <edgar [at] archlinux [dot] us>
+# Contributor: Daenyth <Daenyth [at] gmail [dot] com>
+# Contributor: Jeff Mickey <jeff@archlinux.org>
+# 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 <torrent/data/file.h>
+ #include <torrent/data/file_list.h>
+ #include <torrent/peer/connection_list.h>
++#include <torrent/peer/peer_info.h>
+ #include <torrent/peer/peer_list.h>
+
+ #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<uint64_t>(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 <rak/path.h>
+ #include <rak/string_manip.h>
+ #include <sigc++/adaptors/bind.h>
++#include <torrent/peer/connection_list.h>
++#include <torrent/peer/peer_info.h>
+ #include <torrent/rate.h>
+ #include <torrent/hash_string.h>
+
+@@ -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 <rak/algorithm.h>
++#include <torrent/rate.h>
+
+ #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<ElementDownloadList*>(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<int>(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<Download*, bool> {
+ 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 <string>
++#include <sstream>
++#include <list>
++#include <unistd.h>
++
+ #include <functional>
+ #include <cstdio>
+ #include <rak/address_info.h>
+@@ -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<std::string> 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<std::string>::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 <cstdlib>
++#include <string>
++#include <arpa/inet.h>
++
++#include "ip_address.h"
++#include "utils/pattern.h"
++
++namespace core {
++
++std::pair<bool,uint32_t> 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<bool,uint32_t>( (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 <inttypes.h>
++#include <string>
++
++#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<bool,uint32_t> to_int( const std::string& strAddress );
++ static IpAddress* parse( const std::string& strAddress ) {
++ std::pair<bool,uint32_t> 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 <sstream>
++#include <string>
++#include <map>
++#include <list>
++#include <fstream>
++#include <stdio.h>
++#include <stdlib.h>
++
++#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<const IpAddress,IpRange::ptr> p( *r->get_from(), IpRange::ptr(r) );
++ std::pair<range_itr,bool> 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<std::string>();
++
++ 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 <string>
++#include <map>
++#include <list>
++
++#include "printable.h"
++#include "ip_address.h"
++#include "ip_range.h"
++
++namespace core {
++
++typedef std::map<const IpAddress,IpRange::ptr> range_map;
++typedef range_map::iterator range_itr;
++typedef std::list<std::string> 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<bool,uint32_t> 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 <sstream>
++#include <string>
++
++#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<enddesc) && (unsigned char)s[pos]<=' ') pos++; // strip from start
++ while ((pos<enddesc)){
++ if (post<255) description[post++]=s[pos];
++ pos++;
++ }
++ description[post]=0;
++ if (s[pos]==':') pos++;
++ post=0;
++ while ((pos<size) && s[pos]!='-'){
++ if (post<23) ip1[post++]=s[pos];
++ pos++;
++ }
++ ip1[post]=0;
++ if (s[pos]=='-'){
++ pos++;
++ post=0;
++ while ((pos<size) && s[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 <string>
++
++#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 <iostream>
++
++class Printable {
++ public:
++ virtual std::string to_string() const = 0;
++};
++
++template<typename _CharT,class _Traits> 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 <string>
++#include <sys/types.h>
++#include <ctype.h>
++#include <regex.h>
++
++#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 <string>
++#include <sys/types.h>
++#include <ctype.h>
++#include <regex.h>
++
++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<const sockaddr*>(&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<sockaddr*>(&m_sockaddr); }
++ sockaddr_in6* c_sockaddr_inet6() { return &m_sockaddr; }
++
++ const sockaddr* c_sockaddr() const { return reinterpret_cast<const sockaddr*>(&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<in6_addr *>(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<const uint32_t *>(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<const socket_address*>(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
++// <karabaja4@archlinux.us>
++
+ #include "config.h"
+
+ #include <cstring>
+@@ -52,6 +55,10 @@
+ #include <torrent/data/file_list.h>
+ #include <torrent/peer/client_info.h>
+
++//peers
++#include <torrent/peer/connection_list.h>
++#include <torrent/peer/peer_list.h>
++
+ #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
++// <karabaja4@archlinux.us>
++
+ #include "config.h"
+
+ #include <rak/algorithm.h>
+@@ -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 <algorithm>
++#include <fstream>
+ #include <iostream>
+ #include <sigc++/adaptors/bind.h>
+ #include <rak/functional.h>
+ #include <rak/string_manip.h>
++#include <torrent/data/file.h>
+ #include <torrent/exceptions.h>
+ #include <torrent/download.h>
+ #include <torrent/hash_string.h>
+@@ -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 <cstdio>
+ #include <cstring>
+ #include <fstream>
++#include <sstream>
+ #include <unistd.h>
+ #include <sys/select.h>
+ #include <rak/address_info.h>
+@@ -52,6 +53,7 @@
+ #include <torrent/connection_manager.h>
+ #include <torrent/error.h>
+ #include <torrent/exceptions.h>
++#include <torrent/object_stream.h>
+ #include <torrent/resume.h>
+ #include <torrent/tracker_list.h>
+ #include <torrent/throttle.h>
+@@ -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<core::DownloadFactory>), 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<uint32_t, torrent::ThrottlePair> 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);
+ }