summaryrefslogtreecommitdiffstats
path: root/libtorrent-extended/dht_pex_static_map.patch
diff options
context:
space:
mode:
Diffstat (limited to 'libtorrent-extended/dht_pex_static_map.patch')
-rw-r--r--libtorrent-extended/dht_pex_static_map.patch2353
1 files changed, 2353 insertions, 0 deletions
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);