From e2d682275af50d405e834b95f93bf9dd84cfb5b7 Mon Sep 17 00:00:00 2001 From: Alden Torres Date: Tue, 8 Sep 2015 16:12:54 -0400 Subject: [PATCH] Implementing and using new dht storage interface --- .gitignore | 2 + CMakeLists.txt | 1 + Jamfile | 1 + include/libtorrent/Makefile.am | 1 + include/libtorrent/kademlia/dht_storage.hpp | 198 ++++++++ include/libtorrent/kademlia/node.hpp | 100 +--- src/Makefile.am | 1 + src/kademlia/dht_storage.cpp | 527 ++++++++++++++++++++ src/kademlia/node.cpp | 348 ++----------- test/Jamfile | 1 + test/Makefile.am | 1 + test/test_dht_storage.cpp | 135 +++++ 12 files changed, 927 insertions(+), 389 deletions(-) create mode 100644 include/libtorrent/kademlia/dht_storage.hpp create mode 100644 src/kademlia/dht_storage.cpp create mode 100644 test/test_dht_storage.cpp diff --git a/.gitignore b/.gitignore index 47cd39d56..b7956a516 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ Makefile build-aux test_tmp_* +.DS_Store +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index ff0e20512..f86e0d8bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,7 @@ set(sources # -- kademlia -- set(kademlia_sources + dht_storage dos_blocker dht_tracker node diff --git a/Jamfile b/Jamfile index d57b678e3..40ac999ac 100644 --- a/Jamfile +++ b/Jamfile @@ -701,6 +701,7 @@ SOURCES = ; KADEMLIA_SOURCES = + dht_storage dht_tracker node node_entry diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index 43dc7f7f3..917ad744d 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -179,6 +179,7 @@ nobase_include_HEADERS = \ extensions/ut_metadata.hpp \ extensions/ut_pex.hpp \ \ + kademlia/dht_storage.hpp \ kademlia/dht_tracker.hpp \ kademlia/dht_observer.hpp \ kademlia/direct_request.hpp \ diff --git a/include/libtorrent/kademlia/dht_storage.hpp b/include/libtorrent/kademlia/dht_storage.hpp new file mode 100644 index 000000000..d0504680a --- /dev/null +++ b/include/libtorrent/kademlia/dht_storage.hpp @@ -0,0 +1,198 @@ +/* + +Copyright (c) 2012-2015, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef DHT_STORAGE_HPP +#define DHT_STORAGE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { +namespace dht +{ + // The DHT storage interface is a pure virtual class that can + // be implemented to customize how the data for the DHT is stored. + // + // The default storage implementation uses three maps in RAM to save + // the peers, mutable and immutable items and it's designed to + // provide a fast and fully compliant behavior of the BEPs. + // + // libtorrent comes with one built-in storage implementation: + // ``dht_default_storage`` (private non-accessible class). Its + // constructor function is called dht_default_storage_constructor(). + // + struct TORRENT_EXPORT dht_storage_interface + { +#ifndef TORRENT_NO_DEPRECATE + // This function returns the number of torrents tracked by + // the DHT at the moment. It's used to fill session_status. + // It's deprecated. + // + virtual size_t num_torrents() const = 0; + + // This function returns the sum of all of peers per torrent + // tracker byt the DHT at the moment. + // It's deprecated. + // + virtual size_t num_peers() const = 0; +#endif + + // This function retrieve the peers tracked by the DHT + // corresponding to the given info_hash. You can specify if + // you want only seeds and/or you are scraping the data. + // + // For future implementers: + // If the torrent tracked contains a name, such a name + // must be stored as a string in peers["n"] + // + // If the scrape parameter is true, you should fill these keys: + // peers["BFpe"] - with the standard bit representation of a + // 256 bloom filter containing the downloaders + // peers["BFsd"] - with the standard bit representation of a + // 256 bloom filter containing the seeders + // + // If the scrape parameter is false, you should fill the + // key peers["values"] with a list containing a subset of + // peers tracked by the given info_hash. Such a list should + // consider the value of dht_settings::max_peers_reply. + // If noseed is true only peers marked as no seed should be included. + // + // returns true if an entry with the info_hash is found and + // the data is returned inside the (entry) out parameter peers. + // + virtual bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape + , entry& peers) const = 0; + // This function is named announce_peer for consistency with the + // upper layers, but has nothing to do with networking. Its only + // responsibility is store the peer in such a way that it's returned + // in the entry with the lookup_peers. + // + // The ``name`` parameter is the name of the torrent if provided in + // the announce_peer DHT message. The length of this value should + // have a maximum length in the final storage. The default + // implementation truncate the value for a maximum of 50 characters. + // + virtual void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , std::string const& name, bool seed) = 0; + + // This function retrieves the immutable item given its target hash. + // + // For future implementers: + // The value should be returned as an entry in the key item["v"]. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + // + virtual bool get_immutable_item(sha1_hash const& target + , entry& item) const = 0; + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // This data can be stored only if the target is not already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + // + virtual void put_immutable_item(sha1_hash const& target + , char const* buf, int size + , address const& addr) = 0; + + // This function retrieves the sequence number of a mutable item. + // + // returns true if the item is found and the data is returned + // inside the out parameter seq. + // + virtual bool get_mutable_item_seq(sha1_hash const& target + , boost::int64_t& seq) const = 0; + // This function retrieves the mutable stored in the DHT. + // + // For implementers: + // The item sequence should be stored in the key item["seq"]. + // if force_fill is true or (0 <= seq and seq < item["seq"]) + // the following keys should be filled + // item["v"] - with the value no encoded. + // item["sig"] - with a string representation of the signature. + // item["k"] - with a string represnetation of the public key. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + // + virtual bool get_mutable_item(sha1_hash const& target + , boost::int64_t seq, bool force_fill + , entry& item) const = 0; + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // The sequence number should be checked if the item is already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + // + virtual void put_mutable_item(sha1_hash const& target + , char const* buf, int size + , char const* sig + , boost::int64_t seq + , char const* pk + , char const* salt, int salt_size + , address const& addr) = 0; + + // This function is called periodically (non-constant frequency). + // + // For implementers: + // Use this functions for expire peers or items or any other + // storage cleanup. + // + virtual void tick() = 0; + + virtual ~dht_storage_interface() {} + }; + + TORRENT_EXPORT dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id + , dht_settings const& settings + , counters& counters); + +} } // namespace libtorrent::dht + +#endif //DHT_STORAGE_HPP diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index 179738a50..4c4da04b4 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include #include @@ -102,65 +103,6 @@ void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes); void incoming_error(entry& e, char const* msg, int error_code = 203); -// this is the entry for every peer -// the timestamp is there to make it possible -// to remove stale peers -struct peer_entry -{ - time_point added; - tcp::endpoint addr; - bool seed; -}; - -// this is a group. It contains a set of group members -struct torrent_entry -{ - std::string name; - std::set peers; -}; - -struct dht_immutable_item -{ - dht_immutable_item() : value(0), num_announcers(0), size(0) {} - // malloced space for the actual value - char* value; - // this counts the number of IPs we have seen - // announcing this item, this is used to determine - // popularity if we reach the limit of items to store - bloom_filter<128> ips; - // the last time we heard about this - time_point last_seen; - // number of IPs in the bloom filter - int num_announcers; - // size of malloced space pointed to by value - int size; -}; - -struct ed25519_public_key { char bytes[item_pk_len]; }; - -struct dht_mutable_item : dht_immutable_item -{ - char sig[item_sig_len]; - boost::uint64_t seq; - ed25519_public_key key; - char* salt; - int salt_size; -}; - -// internal -inline bool operator<(ed25519_public_key const& lhs, ed25519_public_key const& rhs) -{ - return memcmp(lhs.bytes, rhs.bytes, sizeof(lhs.bytes)) < 0; -} - -// internal -inline bool operator<(peer_entry const& lhs, peer_entry const& rhs) -{ - return lhs.addr.address() == rhs.addr.address() - ? lhs.addr.port() < rhs.addr.port() - : lhs.addr.address() < rhs.addr.address(); -} - struct null_type {}; class announce_observer : public observer @@ -174,17 +116,6 @@ public: void reply(msg const&) { flags |= flag_done; } }; -struct count_peers -{ - int* count; - count_peers(int* c): count(c) {} - void operator()(std::pair const& t) - { - *count += t.second.peers.size(); - } -}; - struct udp_socket_interface { virtual bool has_quota() = 0; @@ -195,16 +126,12 @@ protected: class TORRENT_EXTRA_EXPORT node : boost::noncopyable { -typedef std::map table_t; -typedef std::map dht_immutable_table_t; -typedef std::map dht_mutable_table_t; - public: node(udp_socket_interface* sock , libtorrent::dht_settings const& settings, node_id nid , dht_observer* observer, counters& cnt); - virtual ~node() {} + virtual ~node(); void tick(); void bootstrap(std::vector const& nodes @@ -214,13 +141,10 @@ public: void unreachable(udp::endpoint const& ep); void incoming(msg const& m); - int num_torrents() const { return m_map.size(); } - int num_peers() const - { - int ret = 0; - std::for_each(m_map.begin(), m_map.end(), count_peers(&ret)); - return ret; - } +#ifndef TORRENT_NO_DEPRECATE + int num_torrents() const { return m_storage->num_torrents(); } + int num_peers() const { return m_storage->num_peers(); } +#endif int bucket_size(int bucket); @@ -230,7 +154,9 @@ public: boost::int64_t num_global_nodes() const { return m_table.num_global_nodes(); } - int data_size() const { return int(m_map.size()); } +#ifndef TORRENT_NO_DEPRECATE + int data_size() const { return int(m_storage->num_torrents()); } +#endif #if defined TORRENT_DEBUG void print_state(std::ostream& os) const @@ -326,10 +252,6 @@ public: private: dht_observer* m_observer; - table_t m_map; - dht_immutable_table_t m_immutable_table; - dht_mutable_table_t m_mutable_table; - time_point m_last_tracker_tick; // the last time we issued a bootstrap or a refresh on our own ID, to expand @@ -341,10 +263,10 @@ private: udp_socket_interface* m_sock; counters& m_counters; -}; + boost::scoped_ptr m_storage; +}; } } // namespace libtorrent::dht #endif // NODE_HPP - diff --git a/src/Makefile.am b/src/Makefile.am index 8ddfb8d52..fa08c0c20 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,6 +4,7 @@ lib_LTLIBRARIES = libtorrent-rasterbar.la if ENABLE_DHT KADEMLIA_SOURCES = \ + kademlia/dht_storage.cpp \ kademlia/dht_tracker.cpp \ kademlia/find_data.cpp \ kademlia/node.cpp \ diff --git a/src/kademlia/dht_storage.cpp b/src/kademlia/dht_storage.cpp new file mode 100644 index 000000000..5dc8be5a7 --- /dev/null +++ b/src/kademlia/dht_storage.cpp @@ -0,0 +1,527 @@ +/* + +Copyright (c) 2012-2015, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_storage.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace libtorrent { +namespace dht { +namespace +{ + using detail::write_endpoint; + + // this is the entry for every peer + // the timestamp is there to make it possible + // to remove stale peers + struct peer_entry + { + time_point added; + tcp::endpoint addr; + bool seed; + }; + + // internal + bool operator<(peer_entry const& lhs, peer_entry const& rhs) + { + return lhs.addr.address() == rhs.addr.address() + ? lhs.addr.port() < rhs.addr.port() + : lhs.addr.address() < rhs.addr.address(); + } + + // this is a group. It contains a set of group members + struct torrent_entry + { + std::string name; + std::set peers; + }; + +#ifndef TORRENT_NO_DEPRECATE + struct count_peers + { + int* count; + count_peers(int* c): count(c) {} + void operator()(std::pair const& t) + { + *count += t.second.peers.size(); + } + }; +#endif + + // TODO: 2 make this configurable in dht_settings + enum { announce_interval = 30 }; + + void purge_peers(std::set& peers) + { + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end;) + { + // the peer has timed out + if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now()) + peers.erase(i++); + else + ++i; + } + } + + struct dht_immutable_item + { + dht_immutable_item() : value(0), num_announcers(0), size(0) {} + // malloced space for the actual value + char* value; + // this counts the number of IPs we have seen + // announcing this item, this is used to determine + // popularity if we reach the limit of items to store + bloom_filter<128> ips; + // the last time we heard about this + time_point last_seen; + // number of IPs in the bloom filter + int num_announcers; + // size of malloced space pointed to by value + int size; + }; + + struct ed25519_public_key { char bytes[item_pk_len]; }; + + struct dht_mutable_item : dht_immutable_item + { + char sig[item_sig_len]; + boost::int64_t seq; + ed25519_public_key key; + char* salt; + int salt_size; + }; + + void touch_item(dht_immutable_item* f, address const& address) + { + f->last_seen = aux::time_now(); + + // maybe increase num_announcers if we haven't seen this IP before + sha1_hash iphash; + hash_address(address, iphash); + if (!f->ips.find(iphash)) + { + f->ips.set(iphash); + ++f->num_announcers; + } + } + + // return true of the first argument is a better canidate for removal, i.e. + // less important to keep + struct immutable_item_comparator + { + immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {} + immutable_item_comparator(immutable_item_comparator const& c) + : m_our_id(c.m_our_id) {} + + bool operator() (std::pair const& lhs + , std::pair const& rhs) const + { + int l_distance = distance_exp(lhs.first, m_our_id); + int r_distance = distance_exp(rhs.first, m_our_id); + + // this is a score taking the popularity (number of announcers) and the + // fit, in terms of distance from ideal storing node, into account. + // each additional 5 announcers is worth one extra bit in the distance. + // that is, an item with 10 announcers is allowed to be twice as far + // from another item with 5 announcers, from our node ID. Twice as far + // because it gets one more bit. + return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance; + } + + private: + + // explicitly disallow assignment, to silence msvc warning + immutable_item_comparator& operator=(immutable_item_comparator const&); + + node_id const& m_our_id; + }; + + class dht_default_storage TORRENT_FINAL : public dht_storage_interface, boost::noncopyable + { + typedef std::map table_t; + typedef std::map dht_immutable_table_t; + typedef std::map dht_mutable_table_t; + + public: + + dht_default_storage(sha1_hash const& id, dht_settings const& settings + , counters& cnt) + : m_id(id) + , m_settings(settings) + , m_counters(cnt) + { + } + + ~dht_default_storage() {} + +#ifndef TORRENT_NO_DEPRECATE + size_t num_torrents() const TORRENT_OVERRIDE { return m_map.size(); } + size_t num_peers() const TORRENT_OVERRIDE + { + int ret = 0; + std::for_each(m_map.begin(), m_map.end(), count_peers(&ret)); + return ret; + } +#endif + + bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape + , entry& peers) const TORRENT_OVERRIDE + { + table_t::const_iterator i = m_map.lower_bound(info_hash); + if (i == m_map.end()) return false; + if (i->first != info_hash) return false; + + torrent_entry const& v = i->second; + + if (!v.name.empty()) peers["n"] = v.name; + + if (scrape) + { + bloom_filter<256> downloaders; + bloom_filter<256> seeds; + + for (std::set::const_iterator peer_it = v.peers.begin() + , end(v.peers.end()); peer_it != end; ++peer_it) + { + sha1_hash iphash; + hash_address(peer_it->addr.address(), iphash); + if (peer_it->seed) seeds.set(iphash); + else downloaders.set(iphash); + } + + peers["BFpe"] = downloaders.to_string(); + peers["BFsd"] = seeds.to_string(); + } + else + { + int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply); + std::set::const_iterator iter = v.peers.begin(); + entry::list_type& pe = peers["values"].list(); + std::string endpoint; + + for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t) + { + if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue; + if (noseed && iter->seed) continue; + endpoint.resize(18); + std::string::iterator out = endpoint.begin(); + write_endpoint(iter->addr, out); + endpoint.resize(out - endpoint.begin()); + pe.push_back(entry(endpoint)); + + ++m; + } + } + return true; + } + + void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , std::string const& name, bool seed) TORRENT_OVERRIDE + { + table_t::iterator ti = m_map.find(info_hash); + torrent_entry* v; + if (ti == m_map.end()) + { + // we don't have this torrent, add it + // do we need to remove another one first? + if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) + { + // we need to remove some. Remove the ones with the + // fewest peers + int num_peers = m_map.begin()->second.peers.size(); + table_t::iterator candidate = m_map.begin(); + for (table_t::iterator i = m_map.begin() + , end(m_map.end()); i != end; ++i) + { + if (int(i->second.peers.size()) > num_peers) continue; + if (i->first == info_hash) continue; + num_peers = i->second.peers.size(); + candidate = i; + } + m_map.erase(candidate); + m_counters.inc_stats_counter(counters::dht_torrents, -1); + } + m_counters.inc_stats_counter(counters::dht_torrents); + v = &m_map[info_hash]; + } + else + { + v = &ti->second; + } + + // the peer announces a torrent name, and we don't have a name + // for this torrent. Store it. + if (!name.empty() && v->name.empty()) + { + std::string tname = name; + if (tname.size() > 50) tname.resize(50); + v->name = tname; + } + + peer_entry peer; + peer.addr = endp; + peer.added = aux::time_now(); + peer.seed = seed; + std::set::iterator i = v->peers.find(peer); + if (i != v->peers.end()) v->peers.erase(i++); + v->peers.insert(i, peer); + } + + bool get_immutable_item(sha1_hash const& target + , entry& item) const TORRENT_OVERRIDE + { + dht_immutable_table_t::const_iterator i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) return false; + + item["v"] = bdecode(i->second.value, i->second.value + i->second.size); + return true; + } + + void put_immutable_item(sha1_hash const& target + , char const* buf, int size + , address const& addr) TORRENT_OVERRIDE + { + dht_immutable_table_t::iterator i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) + { + // make sure we don't add too many items + if (int(m_immutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing, and farthest + // from our node ID) + dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() + , m_immutable_table.end() + , immutable_item_comparator(m_id)); + + TORRENT_ASSERT(j != m_immutable_table.end()); + free(j->second.value); + m_immutable_table.erase(j); + m_counters.inc_stats_counter(counters::dht_immutable_data, -1); + } + dht_immutable_item to_add; + to_add.value = static_cast(malloc(size)); + to_add.size = size; + memcpy(to_add.value, buf, size); + + boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( + std::make_pair(target, to_add)); + m_counters.inc_stats_counter(counters::dht_immutable_data); + } + +// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); + + touch_item(&i->second, addr); + } + + bool get_mutable_item_seq(sha1_hash const& target + , boost::int64_t& seq) const TORRENT_OVERRIDE + { + dht_mutable_table_t::const_iterator i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) return false; + + seq = i->second.seq; + return true; + } + + bool get_mutable_item(sha1_hash const& target + , boost::int64_t seq, bool force_fill + , entry& item) const TORRENT_OVERRIDE + { + dht_mutable_table_t::const_iterator i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) return false; + + dht_mutable_item const& f = i->second; + item["seq"] = f.seq; + if (force_fill || (0 <= seq && seq < f.seq)) + { + item["v"] = bdecode(f.value, f.value + f.size); + item["sig"] = std::string(f.sig, f.sig + sizeof(f.sig)); + item["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes)); + } + return true; + } + + void put_mutable_item(sha1_hash const& target + , char const* buf, int size + , char const* sig + , boost::int64_t seq + , char const* pk + , char const* salt, int salt_size + , address const& addr) TORRENT_OVERRIDE + { + dht_mutable_table_t::iterator i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) + { + // this is the case where we don't have an item in this slot + // make sure we don't add too many items + if (int(m_mutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing) + dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin() + , m_mutable_table.end() + , boost::bind(&dht_immutable_item::num_announcers + , boost::bind(&dht_mutable_table_t::value_type::second, _1))); + TORRENT_ASSERT(j != m_mutable_table.end()); + free(j->second.value); + free(j->second.salt); + m_mutable_table.erase(j); + m_counters.inc_stats_counter(counters::dht_mutable_data, -1); + } + dht_mutable_item to_add; + to_add.value = static_cast(malloc(size)); + to_add.size = size; + to_add.seq = seq; + to_add.salt = NULL; + to_add.salt_size = 0; + if (salt_size > 0) + { + to_add.salt = static_cast(malloc(salt_size)); + to_add.salt_size = salt_size; + memcpy(to_add.salt, salt, salt_size); + } + memcpy(to_add.sig, sig, sizeof(to_add.sig)); + memcpy(to_add.value, buf, size); + memcpy(&to_add.key, pk, sizeof(to_add.key)); + + boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( + std::make_pair(target, to_add)); + m_counters.inc_stats_counter(counters::dht_mutable_data); + +// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); + } + else + { + // this is the case where we already + dht_mutable_item* item = &i->second; + + if (item->seq < seq) + { + if (item->size != size) + { + free(item->value); + item->value = static_cast(malloc(size)); + item->size = size; + } + item->seq = seq; + memcpy(item->sig, sig, sizeof(item->sig)); + memcpy(item->value, buf, size); + } + } + + touch_item(&i->second, addr); + } + + void tick() TORRENT_OVERRIDE + { + time_point now(aux::time_now()); + + for (dht_immutable_table_t::iterator i = m_immutable_table.begin(); + i != m_immutable_table.end();) + { + if (i->second.last_seen + minutes(60) > now) + { + ++i; + continue; + } + free(i->second.value); + m_immutable_table.erase(i++); + m_counters.inc_stats_counter(counters::dht_immutable_data, -1); + } + + // look through all peers and see if any have timed out + for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;) + { + torrent_entry& t = i->second; + node_id const& key = i->first; + ++i; + purge_peers(t.peers); + + if (!t.peers.empty()) continue; + + // if there are no more peers, remove the entry altogether + table_t::iterator it = m_map.find(key); + if (it != m_map.end()) + { + m_map.erase(it); + m_counters.inc_stats_counter(counters::dht_torrents, -1); + } + } + } + + private: + sha1_hash m_id; + dht_settings const& m_settings; + counters& m_counters; + + table_t m_map; + dht_immutable_table_t m_immutable_table; + dht_mutable_table_t m_mutable_table; + }; +} + +dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id + , dht_settings const& settings + , counters& counters) +{ + return new dht_default_storage(id, settings, counters); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index 151c883bc..ed28d4882 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -69,25 +69,8 @@ namespace libtorrent { namespace dht using detail::write_endpoint; -// TODO: 2 make this configurable in dht_settings -enum { announce_interval = 30 }; - namespace { -// remove peers that have timed out -void purge_peers(std::set& peers) -{ - for (std::set::iterator i = peers.begin() - , end(peers.end()); i != end;) - { - // the peer has timed out - if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now()) - peers.erase(i++); - else - ++i; - } -} - void nop() {} node_id calculate_node_id(node_id const& nid, dht_observer* observer) @@ -115,9 +98,16 @@ node::node(udp_socket_interface* sock , m_last_self_refresh(min_time()) , m_sock(sock) , m_counters(cnt) + , m_storage(dht_default_storage_constructor(m_id, m_settings, m_counters)) { m_secret[0] = random(); m_secret[1] = random(); + + TORRENT_ASSERT(m_storage.get() != NULL); +} + +node::~node() +{ } bool node::verify_token(std::string const& token, char const* info_hash @@ -590,38 +580,7 @@ time_duration node::connection_timeout() if (now - minutes(2) < m_last_tracker_tick) return d; m_last_tracker_tick = now; - for (dht_immutable_table_t::iterator i = m_immutable_table.begin(); - i != m_immutable_table.end();) - { - if (i->second.last_seen + minutes(60) > now) - { - ++i; - continue; - } - free(i->second.value); - m_immutable_table.erase(i++); - m_counters.inc_stats_counter(counters::dht_immutable_data, -1); - } - - // look through all peers and see if any have timed out - for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;) - { - torrent_entry& t = i->second; - node_id const& key = i->first; - ++i; - purge_peers(t.peers); - - // if there are no more peers, remove the entry altogether - if (t.peers.empty()) - { - table_t::iterator it = m_map.find(key); - if (it != m_map.end()) - { - m_map.erase(it); - m_counters.inc_stats_counter(counters::dht_torrents, -1); - } - } - } + m_storage->tick(); return d; } @@ -649,7 +608,7 @@ void node::status(session_status& s) mutex_t::scoped_lock l(m_mutex); m_table.status(s); - s.dht_torrents = int(m_map.size()); + s.dht_torrents = int(m_storage->num_torrents()); s.active_requests.clear(); s.dht_total_allocations = m_rpc.num_allocated_observers(); for (std::set::iterator i = m_running_requests.begin() @@ -668,52 +627,7 @@ void node::lookup_peers(sha1_hash const& info_hash, entry& reply if (m_observer) m_observer->get_peers(info_hash); - table_t::const_iterator i = m_map.lower_bound(info_hash); - if (i == m_map.end()) return; - if (i->first != info_hash) return; - - torrent_entry const& v = i->second; - - if (!v.name.empty()) reply["n"] = v.name; - - if (scrape) - { - bloom_filter<256> downloaders; - bloom_filter<256> seeds; - - for (std::set::const_iterator peer_it = v.peers.begin() - , end(v.peers.end()); peer_it != end; ++peer_it) - { - sha1_hash iphash; - hash_address(peer_it->addr.address(), iphash); - if (peer_it->seed) seeds.set(iphash); - else downloaders.set(iphash); - } - - reply["BFpe"] = downloaders.to_string(); - reply["BFsd"] = seeds.to_string(); - } - else - { - int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply); - std::set::const_iterator iter = v.peers.begin(); - entry::list_type& pe = reply["values"].list(); - std::string endpoint; - - for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t) - { - if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue; - if (noseed && iter->seed) continue; - endpoint.resize(18); - std::string::iterator out = endpoint.begin(); - write_endpoint(iter->addr, out); - endpoint.resize(out - endpoint.begin()); - pe.push_back(entry(endpoint)); - - ++m; - } - } - return; + m_storage->get_peers(info_hash, noseed, scrape, reply); } void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes) @@ -833,37 +747,6 @@ void incoming_error(entry& e, char const* msg, int error_code) l.push_back(entry(msg)); } -// return true of the first argument is a better canidate for removal, i.e. -// less important to keep -struct immutable_item_comparator -{ - immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {} - immutable_item_comparator(immutable_item_comparator const& c) - : m_our_id(c.m_our_id) {} - - bool operator() (std::pair const& lhs - , std::pair const& rhs) const - { - int l_distance = distance_exp(lhs.first, m_our_id); - int r_distance = distance_exp(rhs.first, m_our_id); - - // this is a score taking the popularity (number of announcers) and the - // fit, in terms of distance from ideal storing node, into account. - // each additional 5 announcers is worth one extra bit in the distance. - // that is, an item with 10 announcers is allowed to be twice as far - // from another item with 5 announcers, from our node ID. Twice as far - // because it gets one more bit. - return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance; - } - -private: - - // explicitly disallow assignment, to silence msvc warning - immutable_item_comparator& operator=(immutable_item_comparator const&); - - node_id const& m_our_id; -}; - // build response void node::incoming_request(msg const& m, entry& e) { @@ -1039,53 +922,11 @@ void node::incoming_request(msg const& m, entry& e) // the table get a chance to add it. m_table.node_seen(id, m.addr, 0xffff); - table_t::iterator ti = m_map.find(info_hash); - torrent_entry* v; - if (ti == m_map.end()) - { - // we don't have this torrent, add it - // do we need to remove another one first? - if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) - { - // we need to remove some. Remove the ones with the - // fewest peers - int num_peers = m_map.begin()->second.peers.size(); - table_t::iterator candidate = m_map.begin(); - for (table_t::iterator i = m_map.begin() - , end(m_map.end()); i != end; ++i) - { - if (int(i->second.peers.size()) > num_peers) continue; - if (i->first == info_hash) continue; - num_peers = i->second.peers.size(); - candidate = i; - } - m_map.erase(candidate); - m_counters.inc_stats_counter(counters::dht_torrents, -1); - } - m_counters.inc_stats_counter(counters::dht_torrents); - v = &m_map[info_hash]; - } - else - { - v = &ti->second; - } + tcp::endpoint addr = tcp::endpoint(m.addr.address(), port); + std::string name = msg_keys[3] ? msg_keys[3].string_value() : std::string(); + bool seed = msg_keys[4] && msg_keys[4].int_value(); - // the peer announces a torrent name, and we don't have a name - // for this torrent. Store it. - if (msg_keys[3] && v->name.empty()) - { - std::string name = msg_keys[3].string_value(); - if (name.size() > 50) name.resize(50); - v->name = name; - } - - peer_entry peer; - peer.addr = tcp::endpoint(m.addr.address(), port); - peer.added = aux::time_now(); - peer.seed = msg_keys[4] && msg_keys[4].int_value(); - std::set::iterator i = v->peers.find(peer); - if (i != v->peers.end()) v->peers.erase(i++); - v->peers.insert(i, peer); + m_storage->announce_peer(info_hash, addr, name, seed); } else if (query_len == 3 && memcmp(query, "put", 3) == 0) { @@ -1166,41 +1007,9 @@ void node::incoming_request(msg const& m, entry& e) return; } - dht_immutable_item* f = 0; - if (!mutable_put) { - dht_immutable_table_t::iterator i = m_immutable_table.find(target); - if (i == m_immutable_table.end()) - { - // make sure we don't add too many items - if (int(m_immutable_table.size()) >= m_settings.max_dht_items) - { - // delete the least important one (i.e. the one - // the fewest peers are announcing, and farthest - // from our node ID) - dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() - , m_immutable_table.end() - , immutable_item_comparator(m_id)); - - TORRENT_ASSERT(j != m_immutable_table.end()); - free(j->second.value); - m_immutable_table.erase(j); - m_counters.inc_stats_counter(counters::dht_immutable_data, -1); - } - dht_immutable_item to_add; - to_add.value = static_cast(malloc(buf.second)); - to_add.size = buf.second; - memcpy(to_add.value, buf.first, buf.second); - - boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( - std::make_pair(target, to_add)); - m_counters.inc_stats_counter(counters::dht_immutable_data); - } - -// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); - - f = &i->second; + m_storage->put_immutable_item(target, buf.first, buf.second, m.addr.address()); } else { @@ -1210,111 +1019,65 @@ void node::incoming_request(msg const& m, entry& e) VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4].string_ptr(), item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); #endif + boost::int64_t seq = msg_keys[2].int_value(); + + if (seq < 0) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "invalid (negative) sequence number"); + return; + } + // msg_keys[4] is the signature, msg_keys[3] is the public key if (!verify_mutable_item(buf, salt - , msg_keys[2].int_value(), pk, sig)) + , seq, pk, sig)) { m_counters.inc_stats_counter(counters::dht_invalid_put); incoming_error(e, "invalid signature", 206); return; } - dht_mutable_table_t::iterator i = m_mutable_table.find(target); - if (i == m_mutable_table.end()) - { - // this is the case where we don't have an item in this slot - // make sure we don't add too many items - if (int(m_mutable_table.size()) >= m_settings.max_dht_items) - { - // delete the least important one (i.e. the one - // the fewest peers are announcing) - dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin() - , m_mutable_table.end() - , boost::bind(&dht_immutable_item::num_announcers - , boost::bind(&dht_mutable_table_t::value_type::second, _1))); - TORRENT_ASSERT(j != m_mutable_table.end()); - free(j->second.value); - free(j->second.salt); - m_mutable_table.erase(j); - m_counters.inc_stats_counter(counters::dht_mutable_data, -1); - } - dht_mutable_item to_add; - to_add.value = static_cast(malloc(buf.second)); - to_add.size = buf.second; - to_add.seq = msg_keys[2].int_value(); - to_add.salt = NULL; - to_add.salt_size = 0; - if (salt.second > 0) - { - to_add.salt = static_cast(malloc(salt.second)); - to_add.salt_size = salt.second; - memcpy(to_add.salt, salt.first, salt.second); - } - memcpy(to_add.sig, sig, sizeof(to_add.sig)); - TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4].string_length()); - memcpy(to_add.value, buf.first, buf.second); - memcpy(&to_add.key, pk, sizeof(to_add.key)); - - boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( - std::make_pair(target, to_add)); - m_counters.inc_stats_counter(counters::dht_mutable_data); + TORRENT_ASSERT(item_sig_len == msg_keys[4].string_length()); -// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); + boost::int64_t item_seq; + if (!m_storage->get_mutable_item_seq(target, item_seq)) + { + m_storage->put_mutable_item(target + , buf.first, buf.second + , sig, seq, pk + , salt.first, salt.second + , m.addr.address()); } else { - // this is the case where we already - dht_mutable_item* item = &i->second; - // this is the "cas" field in the put message // if it was specified, we MUST make sure the current sequence // number matches the expected value before replacing it // this is critical for avoiding race conditions when multiple // writers are accessing the same slot - if (msg_keys[5] && item->seq != msg_keys[5].int_value()) + if (msg_keys[5] && item_seq != msg_keys[5].int_value()) { m_counters.inc_stats_counter(counters::dht_invalid_put); incoming_error(e, "CAS mismatch", 301); return; } - if (item->seq > boost::uint64_t(msg_keys[2].int_value())) + if (item_seq > seq) { m_counters.inc_stats_counter(counters::dht_invalid_put); incoming_error(e, "old sequence number", 302); return; } - if (item->seq < boost::uint64_t(msg_keys[2].int_value())) - { - if (item->size != buf.second) - { - free(item->value); - item->value = static_cast(malloc(buf.second)); - item->size = buf.second; - } - item->seq = msg_keys[2].int_value(); - memcpy(item->sig, msg_keys[4].string_ptr(), sizeof(item->sig)); - TORRENT_ASSERT(sizeof(item->sig) == msg_keys[4].string_length()); - memcpy(item->value, buf.first, buf.second); - } + m_storage->put_mutable_item(target + , buf.first, buf.second + , sig, seq, pk + , salt.first, salt.second + , m.addr.address()); } - - f = &i->second; } m_table.node_seen(id, m.addr, 0xffff); - - f->last_seen = aux::time_now(); - - // maybe increase num_announcers if we haven't seen this IP before - sha1_hash iphash; - hash_address(m.addr.address(), iphash); - if (!f->ips.find(iphash)) - { - f->ips.set(iphash); - ++f->num_announcers; - } } else if (query_len == 3 && memcmp(query, "get", 3) == 0) { @@ -1349,32 +1112,20 @@ void node::incoming_request(msg const& m, entry& e) m_table.find_node(target, n, 0); write_nodes_entry(reply, n); - dht_immutable_table_t::iterator imutable_it = m_immutable_table.end(); - // if the get has a sequence number it must be for a mutable item // so don't bother searching the immutable table if (!msg_keys[0]) - imutable_it = m_immutable_table.find(target); - - if (imutable_it != m_immutable_table.end()) { - dht_immutable_item const& f = imutable_it->second; - reply["v"] = bdecode(f.value, f.value + f.size); + if (!m_storage->get_immutable_item(target, reply)) // ok, check for a mutable one + { + m_storage->get_mutable_item(target, 0, true, reply); + } } else { - dht_mutable_table_t::iterator mutable_it = m_mutable_table.find(target); - if (mutable_it != m_mutable_table.end()) - { - dht_mutable_item const& f = mutable_it->second; - reply["seq"] = f.seq; - if (!msg_keys[0] || boost::uint64_t(msg_keys[0].int_value()) < f.seq) - { - reply["v"] = bdecode(f.value, f.value + f.size); - reply["sig"] = std::string(f.sig, f.sig + sizeof(f.sig)); - reply["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes)); - } - } + m_storage->get_mutable_item(target + , msg_keys[0].int_value(), false + , reply); } } else @@ -1401,7 +1152,4 @@ void node::incoming_request(msg const& m, entry& e) return; } } - - } } // namespace libtorrent::dht - diff --git a/test/Jamfile b/test/Jamfile index 653317fd5..121c69e36 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -139,6 +139,7 @@ test-suite libtorrent : test_xml.cpp test_ip_filter.cpp test_hasher.cpp + test_dht_storage.cpp test_dht.cpp test_block_cache.cpp test_peer_classes.cpp diff --git a/test/Makefile.am b/test/Makefile.am index 93ba04263..df909bee0 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -149,6 +149,7 @@ test_primitives_SOURCES = \ test_xml.cpp \ test_ip_filter.cpp \ test_hasher.cpp \ + test_dht_storage.cpp \ test_dht.cpp \ test_block_cache.cpp \ test_peer_classes.cpp \ diff --git a/test/test_dht_storage.cpp b/test/test_dht_storage.cpp new file mode 100644 index 000000000..31633e721 --- /dev/null +++ b/test/test_dht_storage.cpp @@ -0,0 +1,135 @@ +/* + +Copyright (c) 2015, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/kademlia/node.hpp" // for verify_message +#include "libtorrent/bencode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/ed25519.hpp" + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/ed25519.hpp" +#include + +#include "test.hpp" + +using namespace libtorrent; +using namespace libtorrent::dht; + +namespace +{ + dht_settings test_settings() { + dht_settings sett; + sett.max_torrents = 2; + sett.max_dht_items = 2; + return sett; + } + + static sha1_hash to_hash(char const *s) { + sha1_hash ret; + from_hex(s, 40, (char *) &ret[0]); + return ret; + } +} + +TORRENT_TEST(dht_storage) +{ + dht_settings sett = test_settings(); + counters cnt; + dht_storage_interface* s = dht_default_storage_constructor(node_id(0), sett, cnt); + + TEST_CHECK(s != NULL); + + sha1_hash n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); + sha1_hash n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402"); + sha1_hash n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403"); + sha1_hash n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404"); + + entry peers; + s->get_peers(n1, false, false, peers); + + TEST_CHECK(peers["n"].string().empty()) + TEST_CHECK(peers["values"].list().empty()); + + tcp::endpoint p1 = tcp::endpoint(address::from_string("124.31.75.21"), 1); + tcp::endpoint p2 = tcp::endpoint(address::from_string("124.31.75.22"), 1); + tcp::endpoint p3 = tcp::endpoint(address::from_string("124.31.75.23"), 1); + tcp::endpoint p4 = tcp::endpoint(address::from_string("124.31.75.24"), 1); + + s->announce_peer(n1, p1, "torrent_name", false); + s->get_peers(n1, false, false, peers); + TEST_EQUAL(peers["n"].string(), "torrent_name") + TEST_EQUAL(peers["values"].list().size(), 1) + + s->announce_peer(n2, p2, "torrent_name1", false); + s->announce_peer(n2, p3, "torrent_name1", false); + s->announce_peer(n3, p4, "torrent_name2", false); + bool r = s->get_peers(n1, false, false, peers); + TEST_CHECK(!r); + + entry item; + r = s->get_immutable_item(n4, item); + TEST_CHECK(!r); + + s->put_immutable_item(n4, "123", 3, address::from_string("124.31.75.21")); + r = s->get_immutable_item(n4, item); + TEST_CHECK(r); + + s->put_immutable_item(n1, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n2, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n3, "123", 3, address::from_string("124.31.75.21")); + r = s->get_immutable_item(n1, item); + TEST_CHECK(!r); + + r = s->get_mutable_item(n4, 0, false, item); + TEST_CHECK(!r); + + char public_key[item_pk_len]; + char signature[item_sig_len]; + s->put_mutable_item(n4, "123", 3, signature, 1, public_key, "salt", 4, address::from_string("124.31.75.21")); + r = s->get_mutable_item(n4, 0, false, item); + TEST_CHECK(r); + + delete s; +} + +#endif \ No newline at end of file