From f36688a3645cb94bae8f1782a2adb6be58a34e1e Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sun, 27 Sep 2009 03:38:41 +0000 Subject: [PATCH] initial support for torrent tag store in DHT --- CMakeLists.txt | 1 + include/libtorrent/escape_string.hpp | 1 + include/libtorrent/kademlia/dht_tracker.hpp | 2 - include/libtorrent/kademlia/node.hpp | 75 ++++- include/libtorrent/peer_id.hpp | 14 + include/libtorrent/session_settings.hpp | 5 + src/escape_string.cpp | 20 ++ src/kademlia/dht_tracker.cpp | 16 +- src/kademlia/node.cpp | 296 +++++++++++++++++--- test/Jamfile | 1 + test/Makefile.am | 2 + test/test_dht.cpp | 88 ++++++ test/test_primitives.cpp | 44 +++ 13 files changed, 502 insertions(+), 63 deletions(-) create mode 100644 test/test_dht.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad50ff465..da2b8ba41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,7 @@ if(build_tests) test_buffer test_storage test_torrent + test_dht test_transfer test_piece_picker test_fast_extension diff --git a/include/libtorrent/escape_string.hpp b/include/libtorrent/escape_string.hpp index f818f3bc8..43d9c2ac5 100644 --- a/include/libtorrent/escape_string.hpp +++ b/include/libtorrent/escape_string.hpp @@ -49,6 +49,7 @@ namespace libtorrent bool TORRENT_EXPORT is_space(char c); char TORRENT_EXPORT to_lower(char c); + int TORRENT_EXPORT split_string(char const** tags, int buf_size, char* in); bool TORRENT_EXPORT string_begins_no_case(char const* s1, char const* s2); bool TORRENT_EXPORT string_equal_no_case(char const* s1, char const* s2); diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp index 106dd1b64..aafb8623b 100644 --- a/include/libtorrent/kademlia/dht_tracker.hpp +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -117,8 +117,6 @@ namespace libtorrent { namespace dht void on_bootstrap(std::vector > const&); void send_packet(libtorrent::entry const& e, udp::endpoint const& addr, int send_flags); - void incoming_error(char const* msg, lazy_entry const& e, udp::endpoint const& ep); - node_impl m_dht; libtorrent::aux::session_impl& m_ses; rate_limited_udp_socket& m_sock; diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index a5a927d3f..aef22f7a7 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -87,6 +87,48 @@ struct torrent_entry std::set peers; }; +// this is the entry for a torrent that has been published +// in the DHT. +struct search_torrent_entry +{ + // the tags of the torrent. The key of + // this entry is the sha-1 hash of one of + // these tags. The counter is the number of + // times a tag has been included in a publish + // call. The counters are periodically + // decremented by a factor, so that the + // popularity ratio between the tags is + // maintained. The decrement is rounded down. + std::map tags; + + // this is the sum of all values in the tags + // map. It is only an optimization to avoid + // recalculating it constantly + int total_tag_points; + + // the name of the torrent + std::map name; + int total_name_points; + + // increase the popularity counters for this torrent + void publish(std::string const& name, char const* in_tags[], int num_tags); + + // return a score of how well this torrent matches + // the given set of tags. Each word in the string + // (separated by a space) is considered a tag. + // tags with 2 letters or fewer are ignored + int match(char const* tags[], int num_tags) const; + + // this is called once every hour, and will + // decrement the popularity counters of the + // tags. Returns true if this entry should + // be deleted + bool tick(); + + void get_name(std::string& t) const; + void get_tags(std::string& t) const; +}; + inline bool operator<(peer_entry const& lhs, peer_entry const& rhs) { return lhs.addr.address() == rhs.addr.address() @@ -119,9 +161,21 @@ private: std::string m_token; }; +struct count_peers +{ + int& count; + count_peers(int& c): count(c) {} + void operator()(std::pair const& t) + { + count += t.second.peers.size(); + } +}; + class node_impl : boost::noncopyable { typedef std::map table_t; +typedef std::map, search_torrent_entry> search_table_t; public: node_impl(libtorrent::aux::session_impl& ses , void (*f)(void*, entry const&, udp::endpoint const&, int) @@ -138,6 +192,14 @@ 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; + } + void refresh(); void refresh_bucket(int bucket); int bucket_size(int bucket); @@ -147,16 +209,12 @@ public: iterator begin() const { return m_table.begin(); } iterator end() const { return m_table.end(); } - typedef table_t::iterator data_iterator; - node_id const& nid() const { return m_id; } boost::tuple size() const{ return m_table.size(); } size_type num_global_nodes() const { return m_table.num_global_nodes(); } - data_iterator begin_data() { return m_map.begin(); } - data_iterator end_data() { return m_map.end(); } int data_size() const { return int(m_map.size()); } #ifdef TORRENT_DHT_VERBOSE_LOGGING @@ -208,11 +266,9 @@ protected: // is called when a find data request is received. Should // return false if the data is not stored on this node. If // the data is stored, it should be serialized into 'data'. - bool on_find(sha1_hash const& info_hash, std::vector& peers) const; - - // this is called when a store request is received. The data - // is store-parameters and the data to be stored. - void on_announce(msg const& m, msg& reply); + bool lookup_peers(sha1_hash const& info_hash, entry& reply) const; + bool lookup_torrents(sha1_hash const& target, entry& reply + , char* tags) const; dht_settings const& m_settings; @@ -239,6 +295,7 @@ public: private: table_t m_map; + search_table_t m_search_map; ptime m_last_tracker_tick; diff --git a/include/libtorrent/peer_id.hpp b/include/libtorrent/peer_id.hpp index f770f2d71..2e70ed702 100644 --- a/include/libtorrent/peer_id.hpp +++ b/include/libtorrent/peer_id.hpp @@ -59,6 +59,20 @@ namespace libtorrent big_number() {} + static big_number max() + { + big_number ret; + memset(ret.m_number, 0xff, size); + return ret; + } + + static big_number min() + { + big_number ret; + memset(ret.m_number, 0, size); + return ret; + } + explicit big_number(char const* s) { if (s == 0) clear(); diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index 5c7fef306..e4b1f26b5 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -660,6 +660,7 @@ namespace libtorrent , search_branching(5) , service_port(0) , max_fail_count(20) + , max_torrent_search_reply(20) {} // the maximum number of peers to send in a @@ -677,6 +678,10 @@ namespace libtorrent // the maximum number of times a node can fail // in a row before it is removed from the table. int max_fail_count; + + // the max number of torrents to return in a + // torrent search query to the DHT + int max_torrent_search_reply; }; #endif diff --git a/src/escape_string.cpp b/src/escape_string.cpp index 8fd2074cb..3afa30860 100644 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -100,6 +100,26 @@ namespace libtorrent return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; } + int split_string(char const** tags, int buf_size, char* in) + { + int ret = 0; + char* i = in; + for (;*i; ++i) + { + if (!is_print(*i) || is_space(*i)) + { + *i = 0; + if (ret == buf_size) return ret; + continue; + } + if (i == in || i[-1] == 0) + { + tags[ret++] = i; + } + } + return ret; + } + bool string_begins_no_case(char const* s1, char const* s2) { while (*s1 != 0) diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp index e9cb42512..9786bb058 100644 --- a/src/kademlia/dht_tracker.cpp +++ b/src/kademlia/dht_tracker.cpp @@ -73,17 +73,6 @@ namespace { const int tick_period = 1; // minutes - struct count_peers - { - int& count; - count_peers(int& c): count(c) {} - void operator()(std::pair const& t) - { - count += t.second.peers.size(); - } - }; - template void read_endpoint_list(libtorrent::entry const* n, std::vector& epl) { @@ -365,11 +354,10 @@ namespace libtorrent { namespace dht m_dht.print_state(st); // count torrents - int torrents = std::distance(m_dht.begin_data(), m_dht.end_data()); + int torrents = m_dht.num_torrents(); // count peers - int peers = 0; - std::for_each(m_dht.begin_data(), m_dht.end_data(), count_peers(peers)); + int peers = m_dht.num_peers(); std::ofstream pc("dht_stats.log", first ? std::ios_base::trunc : std::ios_base::app); if (first) diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index f8f502728..b364caa47 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -61,6 +61,89 @@ void incoming_error(entry& e, char const* msg); using detail::write_endpoint; +int search_torrent_entry::match(char const* in_tags[], int num_tags) const +{ + int ret = 0; + for (int i = 0; i < num_tags; ++i) + { + char const* t = in_tags[i]; + std::map::const_iterator i = tags.find(t); + if (i == tags.end()) continue; + // weigh the score by how popular this tag is in this torrent + ret += 100 * i->second / total_tag_points; + } + return ret; +} + +bool search_torrent_entry::tick() +{ + int sum = 0; + for (std::map::iterator i = tags.begin() + , end(tags.end()); i != end;) + { + i->second = (i->second * 2) / 3; + sum += i->second; + if (i->second > 0) { ++i; continue; } + tags.erase(i++); + } + total_tag_points = sum; + + sum = 0; + for (std::map::iterator i = name.begin() + , end(name.end()); i != end;) + { + i->second = (i->second * 2) / 3; + sum += i->second; + if (i->second > 0) { ++i; continue; } + name.erase(i++); + } + total_name_points = sum; + + return total_tag_points == 0; +} + +void search_torrent_entry::publish(std::string const& torrent_name, char const* in_tags[] + , int num_tags) +{ + for (int i = 0; i < num_tags; ++i) + { + char const* t = in_tags[i]; + std::map::iterator i = tags.find(t); + if (i != tags.end()) + ++i->second; + else + tags[t] = 1; + ++total_tag_points; + // TODO: limit the number of tags + } + + name[torrent_name] += 1; + ++total_name_points; + + // TODO: limit the number of names +} + +void search_torrent_entry::get_name(std::string& t) const +{ + std::map::const_iterator max = name.begin(); + for (std::map::const_iterator i = name.begin() + , end(name.end()); i != end; ++i) + { + if (i->second > max->second) max = i; + } + t = max->first; +} + +void search_torrent_entry::get_tags(std::string& t) const +{ + for (std::map::const_iterator i = tags.begin() + , end(tags.end()); i != end; ++i) + { + if (i != tags.begin()) t += " "; + t += i->first; + } +} + #ifdef _MSC_VER namespace { @@ -96,6 +179,9 @@ void purge_peers(std::set& peers) void nop() {} +// TODO: the session_impl argument could be an alert reference +// instead, and make the dht_tracker less dependent on session_impl +// which would make it simpler to unit test node_impl::node_impl(libtorrent::aux::session_impl& ses , void (*f)(void*, entry const&, udp::endpoint const&, int) , dht_settings const& settings @@ -407,7 +493,7 @@ time_duration node_impl::connection_timeout() m_last_tracker_tick = now; // look through all peers and see if any have timed out - for (data_iterator i = begin_data(), end(end_data()); i != end;) + 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; @@ -425,18 +511,6 @@ time_duration node_impl::connection_timeout() return d; } -void node_impl::on_announce(msg const& m, msg& reply) -{ -} - -namespace -{ - tcp::endpoint get_endpoint(peer_entry const& p) - { - return p.addr; - } -} - void node_impl::status(session_status& s) { mutex_t::scoped_lock l(m_mutex); @@ -453,7 +527,54 @@ void node_impl::status(session_status& s) } } -bool node_impl::on_find(sha1_hash const& info_hash, std::vector& peers) const +bool node_impl::lookup_torrents(sha1_hash const& target + , entry& reply, char* tags) const +{ +// if (m_ses.m_alerts.should_post()) +// m_ses.m_alerts.post_alert(dht_find_torrents_alert(info_hash)); + + search_table_t::const_iterator first, last; + first = m_search_map.lower_bound(std::make_pair(target, sha1_hash::min())); + last = m_search_map.upper_bound(std::make_pair(target, sha1_hash::max())); + + if (first == last) return false; + + std::string tags_copy(tags); + char const* in_tags[20]; + int num_tags = 0; + num_tags = split_string(in_tags, 20, &tags_copy[0]); + + typedef std::pair sort_item; + std::vector result; + for (; first != last; ++first) + { + result.push_back(std::make_pair( + first->second.match(in_tags, num_tags), first)); + } + + std::sort(result.begin(), result.end() + , boost::bind(&sort_item::first, _1) > boost::bind(&sort_item::first, _2)); + int num = (std::min)((int)result.size(), m_settings.max_torrent_search_reply); + + entry::list_type& pe = reply["values"].list(); + for (int i = 0; i < num; ++i) + { + pe.push_back(entry()); + entry::list_type& e = pe.back().list(); + // push name + e.push_back(entry()); + result[i].second->second.get_name(e.back().string()); + // push tags + e.push_back(entry()); + result[i].second->second.get_tags(e.back().string()); + // push info-hash + e.push_back(entry()); + e.back().string() = result[i].second->first.second.to_string(); + } + return true; +} + +bool node_impl::lookup_peers(sha1_hash const& info_hash, entry& reply) const { if (m_ses.m_alerts.should_post()) m_ses.m_alerts.post_alert(dht_get_peers_alert(info_hash)); @@ -464,11 +585,32 @@ bool node_impl::on_find(sha1_hash const& info_hash, std::vector& torrent_entry const& v = i->second; int num = (std::min)((int)v.peers.size(), m_settings.max_peers_reply); - peers.clear(); - peers.reserve(num); - random_sample_n(boost::make_transform_iterator(v.peers.begin(), &get_endpoint) - , boost::make_transform_iterator(v.peers.end(), &get_endpoint) - , std::back_inserter(peers), num); + int t = 0; + int m = 0; + std::set::iterator iter = v.peers.begin(); + entry::list_type& pe = reply["values"].list(); + std::string endpoint; + + while (m < num) + { + if ((std::rand() / (RAND_MAX + 1.f)) * (num - t) >= num - m) + { + ++iter; + ++t; + } + else + { + endpoint.resize(18); + std::string::iterator out = endpoint.begin(); + write_endpoint(iter->addr, out); + endpoint.resize(out - endpoint.begin()); + pe.push_back(entry(endpoint)); + + ++iter; + ++t; + ++m; + } + } return true; } @@ -556,7 +698,6 @@ void node_impl::incoming_request(msg const& m, entry& e) entry& reply = e["r"]; m_rpc.add_our_id(reply); - if (strcmp(query, "ping") == 0) { // we already have 't' and 'id' in the response @@ -579,25 +720,10 @@ void node_impl::incoming_request(msg const& m, entry& e) m_table.find_node(info_hash, n, 0); write_nodes_entry(reply, n); - peers_t p; - on_find(info_hash, p); - if (!p.empty()) - { - entry::list_type& pe = reply["values"].list(); - std::string endpoint; - for (peers_t::const_iterator i = p.begin() - , end(p.end()); i != end; ++i) - { - endpoint.resize(18); - std::string::iterator out = endpoint.begin(); - write_endpoint(*i, out); - endpoint.resize(out - endpoint.begin()); - pe.push_back(entry(endpoint)); - } + lookup_peers(info_hash, reply); #ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(node) << " values: " << p.size(); + TORRENT_LOG(node) << " values: " << reply["values"].list().size(); #endif - } } else if (strcmp(query, "find_node") == 0) { @@ -610,7 +736,6 @@ void node_impl::incoming_request(msg const& m, entry& e) sha1_hash target(target_ent->string_ptr()); nodes_t n; - // always return nodes as well as peers m_table.find_node(target, n, 0); write_nodes_entry(reply, n); } @@ -662,6 +787,101 @@ void node_impl::incoming_request(msg const& m, entry& e) if (i != v.peers.end()) v.peers.erase(i++); v.peers.insert(i, e); } + else if (strcmp(query, "find_torrent") == 0) + { + lazy_entry const* target_ent = arg_ent->dict_find_string("target"); + if (target_ent == 0 || target_ent->string_length() != 20) + { + incoming_error(e, "missing 'target' key"); + return; + } + + lazy_entry const* tags_ent = arg_ent->dict_find_string("tags"); + if (tags_ent == 0) + { + incoming_error(e, "missing 'tags' key"); + return; + } + + reply["token"] = generate_token(m.addr, target_ent->string_ptr()); + + sha1_hash target(target_ent->string_ptr()); + nodes_t n; + // always return nodes as well as torrents + m_table.find_node(target, n, 0); + write_nodes_entry(reply, n); + + lookup_torrents(target, reply, (char*)tags_ent->string_cstr()); + } + else if (strcmp(query, "announce_torrent") == 0) + { + lazy_entry const* target_ent = arg_ent->dict_find_string("target"); + if (target_ent == 0 || target_ent->string_length() != 20) + { + incoming_error(e, "missing 'target' key"); + return; + } + + lazy_entry const* info_hash_ent = arg_ent->dict_find_string("info_hash"); + if (info_hash_ent == 0 || info_hash_ent->string_length() != 20) + { + incoming_error(e, "missing 'target' key"); + return; + } + + lazy_entry const* name_ent = arg_ent->dict_find_string("name"); + if (name_ent == 0) + { + incoming_error(e, "missing 'name' key"); + return; + } + + lazy_entry const* tags_ent = arg_ent->dict_find_string("tags"); + if (tags_ent == 0) + { + incoming_error(e, "missing 'tags' key"); + return; + } + +// if (m_ses.m_alerts.should_post()) +// m_ses.m_alerts.post_alert(dht_announce_torrent_alert( +// m.addr.address(), name, tags, info_hash)); + + lazy_entry const* token = arg_ent->dict_find_string("token"); + if (!token) + { + incoming_error(e, "missing 'token' key in announce"); + return; + } + + if (!verify_token(token->string_value(), target_ent->string_ptr(), m.addr)) + { + incoming_error(e, "invalid token in announce"); + return; + } + + sha1_hash target(target_ent->string_ptr()); + sha1_hash info_hash(info_hash_ent->string_ptr()); + + // the token was correct. That means this + // node is not spoofing its address. So, let + // the table get a chance to add it. + m_table.node_seen(id, m.addr); + + search_table_t::iterator i = m_search_map.find(std::make_pair(target, info_hash)); + if (i == m_search_map.end()) + { + boost::tie(i, boost::tuples::ignore) + = m_search_map.insert(std::make_pair(std::make_pair(target, info_hash) + , search_torrent_entry())); + } + + char const* in_tags[20]; + int num_tags = 0; + num_tags = split_string(in_tags, 20, (char*)tags_ent->string_cstr()); + + i->second.publish(name_ent->string_value(), in_tags, num_tags); + } else { // if we don't recognize the message but there's a diff --git a/test/Jamfile b/test/Jamfile index 637b08595..da3718d0e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -35,6 +35,7 @@ test-suite libtorrent : [ run test_primitives.cpp ] [ run test_ip_filter.cpp ] [ run test_hasher.cpp ] + [ run test_dht.cpp ] [ run test_storage.cpp ] [ run test_upnp.cpp ] diff --git a/test/Makefile.am b/test/Makefile.am index b4308f7a0..8e8aed2ba 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -8,6 +8,7 @@ test_programs = \ test_hasher \ test_http_connection \ test_ip_filter \ + test_dht \ test_lsd \ test_metadata_extension \ test_natpmp \ @@ -40,6 +41,7 @@ libtest_la_SOURCES = main.cpp setup_transfer.cpp test_auto_unchoke_SOURCES = test_auto_unchoke.cpp test_bandwidth_limiter_SOURCES = test_bandwidth_limiter.cpp test_bdecode_performance_SOURCES = test_bdecode_performance.cpp +test_dht_SOURCES = test_dht.cpp test_bencoding_SOURCES = test_bencoding.cpp test_buffer_SOURCES = test_buffer.cpp test_fast_extension_SOURCES = test_fast_extension.cpp diff --git a/test/test_dht.cpp b/test/test_dht.cpp new file mode 100644 index 000000000..7273d0348 --- /dev/null +++ b/test/test_dht.cpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" + +#include "test.hpp" + +int test_main() +{ + using namespace libtorrent; + + int dht_port = 48199; + session ses(fingerprint("LT", 0, 1, 0, 0), std::make_pair(dht_port, 49000)); + + // DHT should be running on port 48199 now + + io_service ios; + error_code ec; + datagram_socket sock(ios); + + sock.open(udp::v4(), ec); + TEST_CHECK(!ec); + if (ec) std::cout << ec.message() << std::endl; + + char const ping_msg[] = "d1:ad2:id20:00000000000000000001e1:q4:ping1:t2:101:y1:qe"; + + // ping + sock.send_to(asio::buffer(ping_msg, sizeof(ping_msg) - 1) + , udp::endpoint(address::from_string("127.0.0.1"), dht_port), 0, ec); + TEST_CHECK(!ec); + if (ec) std::cout << ec.message() << std::endl; + + char inbuf[1600]; + udp::endpoint ep; + int size = sock.receive_from(asio::buffer(inbuf, sizeof(inbuf)), ep, 0, ec); + TEST_CHECK(!ec); + if (ec) std::cout << ec.message() << std::endl; + + lazy_entry pong; + int ret = lazy_bdecode(inbuf, inbuf + size, pong); + TEST_CHECK(ret == 0); + + if (ret != 0) return 1; + + TEST_CHECK(pong.type() == lazy_entry::dict_t); + + if (pong.type() != lazy_entry::dict_t) return 1; + + lazy_entry const* t = pong.dict_find_string("t"); + TEST_CHECK(t); + if (t) TEST_CHECK(t->string_value() == "10"); + + lazy_entry const* y = pong.dict_find_string("y"); + TEST_CHECK(y); + if (y) TEST_CHECK(y->string_value() == "r"); + + return 0; +} + diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index 6770c22a3..cc05db240 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_DISABLE_DHT #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node.hpp" #endif #include #include @@ -365,6 +366,49 @@ int test_main() { using namespace libtorrent; +#ifndef TORRENT_DISABLE_DHT + // test search_torrent_entry + + dht::search_torrent_entry ste1; + dht::search_torrent_entry ste2; + char const* ste1_tags[] = {"tag1", "tag2", "tag3", "tag4"}; + ste1.publish("ste1", ste1_tags, 4); + char const* ste11_tags[] = {"tag2", "tag3"}; + ste1.publish("ste1", ste11_tags, 2); + char const* ste2_tags[] = {"tag1", "tag2", "tag5", "tag6"}; + ste2.publish("ste2", ste2_tags, 4); + char const* ste21_tags[] = {"tag1", "tag5"}; + ste2.publish("ste2", ste21_tags, 2); + + char const* test_tags1[] = {"tag1", "tag2"}; + char const* test_tags2[] = {"tag3", "tag2"}; + int m1 = ste1.match(test_tags1, 2); + int m2 = ste2.match(test_tags1, 2); + TEST_CHECK(m1 == m2); + m1 = ste1.match(test_tags2, 2); + m2 = ste2.match(test_tags2, 2); + TEST_CHECK(m1 > m2); +#endif + + // test split_string + + char const* tags[10]; + char tags_str[] = " this is\ta test\t string\x01to be split and it cannot " + "extend over the limit of elements \t"; + int ret = split_string(tags, 10, tags_str); + + TEST_CHECK(ret == 10); + TEST_CHECK(strcmp(tags[0], "this") == 0); + TEST_CHECK(strcmp(tags[1], "is") == 0); + TEST_CHECK(strcmp(tags[2], "a") == 0); + TEST_CHECK(strcmp(tags[3], "test") == 0); + TEST_CHECK(strcmp(tags[4], "string") == 0); + TEST_CHECK(strcmp(tags[5], "to") == 0); + TEST_CHECK(strcmp(tags[6], "be") == 0); + TEST_CHECK(strcmp(tags[7], "split") == 0); + TEST_CHECK(strcmp(tags[8], "and") == 0); + TEST_CHECK(strcmp(tags[9], "it") == 0); + // test snprintf char msg[10];