initial support for torrent tag store in DHT
This commit is contained in:
parent
1017a716ef
commit
f36688a364
|
@ -277,6 +277,7 @@ if(build_tests)
|
||||||
test_buffer
|
test_buffer
|
||||||
test_storage
|
test_storage
|
||||||
test_torrent
|
test_torrent
|
||||||
|
test_dht
|
||||||
test_transfer
|
test_transfer
|
||||||
test_piece_picker
|
test_piece_picker
|
||||||
test_fast_extension
|
test_fast_extension
|
||||||
|
|
|
@ -49,6 +49,7 @@ namespace libtorrent
|
||||||
bool TORRENT_EXPORT is_space(char c);
|
bool TORRENT_EXPORT is_space(char c);
|
||||||
char TORRENT_EXPORT to_lower(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_begins_no_case(char const* s1, char const* s2);
|
||||||
bool TORRENT_EXPORT string_equal_no_case(char const* s1, char const* s2);
|
bool TORRENT_EXPORT string_equal_no_case(char const* s1, char const* s2);
|
||||||
|
|
||||||
|
|
|
@ -117,8 +117,6 @@ namespace libtorrent { namespace dht
|
||||||
void on_bootstrap(std::vector<std::pair<node_entry, std::string> > const&);
|
void on_bootstrap(std::vector<std::pair<node_entry, std::string> > const&);
|
||||||
void send_packet(libtorrent::entry const& e, udp::endpoint const& addr, int send_flags);
|
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;
|
node_impl m_dht;
|
||||||
libtorrent::aux::session_impl& m_ses;
|
libtorrent::aux::session_impl& m_ses;
|
||||||
rate_limited_udp_socket& m_sock;
|
rate_limited_udp_socket& m_sock;
|
||||||
|
|
|
@ -87,6 +87,48 @@ struct torrent_entry
|
||||||
std::set<peer_entry> peers;
|
std::set<peer_entry> 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<std::string, int> 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<std::string, int> 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)
|
inline bool operator<(peer_entry const& lhs, peer_entry const& rhs)
|
||||||
{
|
{
|
||||||
return lhs.addr.address() == rhs.addr.address()
|
return lhs.addr.address() == rhs.addr.address()
|
||||||
|
@ -119,9 +161,21 @@ private:
|
||||||
std::string m_token;
|
std::string m_token;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct count_peers
|
||||||
|
{
|
||||||
|
int& count;
|
||||||
|
count_peers(int& c): count(c) {}
|
||||||
|
void operator()(std::pair<libtorrent::dht::node_id
|
||||||
|
, libtorrent::dht::torrent_entry> const& t)
|
||||||
|
{
|
||||||
|
count += t.second.peers.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class node_impl : boost::noncopyable
|
class node_impl : boost::noncopyable
|
||||||
{
|
{
|
||||||
typedef std::map<node_id, torrent_entry> table_t;
|
typedef std::map<node_id, torrent_entry> table_t;
|
||||||
|
typedef std::map<std::pair<node_id, sha1_hash>, search_torrent_entry> search_table_t;
|
||||||
public:
|
public:
|
||||||
node_impl(libtorrent::aux::session_impl& ses
|
node_impl(libtorrent::aux::session_impl& ses
|
||||||
, void (*f)(void*, entry const&, udp::endpoint const&, int)
|
, void (*f)(void*, entry const&, udp::endpoint const&, int)
|
||||||
|
@ -138,6 +192,14 @@ public:
|
||||||
void unreachable(udp::endpoint const& ep);
|
void unreachable(udp::endpoint const& ep);
|
||||||
void incoming(msg const& m);
|
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();
|
||||||
void refresh_bucket(int bucket);
|
void refresh_bucket(int bucket);
|
||||||
int bucket_size(int bucket);
|
int bucket_size(int bucket);
|
||||||
|
@ -147,16 +209,12 @@ public:
|
||||||
iterator begin() const { return m_table.begin(); }
|
iterator begin() const { return m_table.begin(); }
|
||||||
iterator end() const { return m_table.end(); }
|
iterator end() const { return m_table.end(); }
|
||||||
|
|
||||||
typedef table_t::iterator data_iterator;
|
|
||||||
|
|
||||||
node_id const& nid() const { return m_id; }
|
node_id const& nid() const { return m_id; }
|
||||||
|
|
||||||
boost::tuple<int, int> size() const{ return m_table.size(); }
|
boost::tuple<int, int> size() const{ return m_table.size(); }
|
||||||
size_type num_global_nodes() const
|
size_type num_global_nodes() const
|
||||||
{ return m_table.num_global_nodes(); }
|
{ 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()); }
|
int data_size() const { return int(m_map.size()); }
|
||||||
|
|
||||||
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
||||||
|
@ -208,11 +266,9 @@ protected:
|
||||||
// is called when a find data request is received. Should
|
// is called when a find data request is received. Should
|
||||||
// return false if the data is not stored on this node. If
|
// return false if the data is not stored on this node. If
|
||||||
// the data is stored, it should be serialized into 'data'.
|
// the data is stored, it should be serialized into 'data'.
|
||||||
bool on_find(sha1_hash const& info_hash, std::vector<tcp::endpoint>& peers) const;
|
bool lookup_peers(sha1_hash const& info_hash, entry& reply) const;
|
||||||
|
bool lookup_torrents(sha1_hash const& target, entry& reply
|
||||||
// this is called when a store request is received. The data
|
, char* tags) const;
|
||||||
// is store-parameters and the data to be stored.
|
|
||||||
void on_announce(msg const& m, msg& reply);
|
|
||||||
|
|
||||||
dht_settings const& m_settings;
|
dht_settings const& m_settings;
|
||||||
|
|
||||||
|
@ -239,6 +295,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
table_t m_map;
|
table_t m_map;
|
||||||
|
search_table_t m_search_map;
|
||||||
|
|
||||||
ptime m_last_tracker_tick;
|
ptime m_last_tracker_tick;
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,20 @@ namespace libtorrent
|
||||||
|
|
||||||
big_number() {}
|
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)
|
explicit big_number(char const* s)
|
||||||
{
|
{
|
||||||
if (s == 0) clear();
|
if (s == 0) clear();
|
||||||
|
|
|
@ -660,6 +660,7 @@ namespace libtorrent
|
||||||
, search_branching(5)
|
, search_branching(5)
|
||||||
, service_port(0)
|
, service_port(0)
|
||||||
, max_fail_count(20)
|
, max_fail_count(20)
|
||||||
|
, max_torrent_search_reply(20)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// the maximum number of peers to send in a
|
// the maximum number of peers to send in a
|
||||||
|
@ -677,6 +678,10 @@ namespace libtorrent
|
||||||
// the maximum number of times a node can fail
|
// the maximum number of times a node can fail
|
||||||
// in a row before it is removed from the table.
|
// in a row before it is removed from the table.
|
||||||
int max_fail_count;
|
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
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,26 @@ namespace libtorrent
|
||||||
return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;
|
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)
|
bool string_begins_no_case(char const* s1, char const* s2)
|
||||||
{
|
{
|
||||||
while (*s1 != 0)
|
while (*s1 != 0)
|
||||||
|
|
|
@ -73,17 +73,6 @@ namespace
|
||||||
{
|
{
|
||||||
const int tick_period = 1; // minutes
|
const int tick_period = 1; // minutes
|
||||||
|
|
||||||
struct count_peers
|
|
||||||
{
|
|
||||||
int& count;
|
|
||||||
count_peers(int& c): count(c) {}
|
|
||||||
void operator()(std::pair<libtorrent::dht::node_id
|
|
||||||
, libtorrent::dht::torrent_entry> const& t)
|
|
||||||
{
|
|
||||||
count += t.second.peers.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class EndpointType>
|
template <class EndpointType>
|
||||||
void read_endpoint_list(libtorrent::entry const* n, std::vector<EndpointType>& epl)
|
void read_endpoint_list(libtorrent::entry const* n, std::vector<EndpointType>& epl)
|
||||||
{
|
{
|
||||||
|
@ -365,11 +354,10 @@ namespace libtorrent { namespace dht
|
||||||
m_dht.print_state(st);
|
m_dht.print_state(st);
|
||||||
|
|
||||||
// count torrents
|
// count torrents
|
||||||
int torrents = std::distance(m_dht.begin_data(), m_dht.end_data());
|
int torrents = m_dht.num_torrents();
|
||||||
|
|
||||||
// count peers
|
// count peers
|
||||||
int peers = 0;
|
int peers = m_dht.num_peers();
|
||||||
std::for_each(m_dht.begin_data(), m_dht.end_data(), count_peers(peers));
|
|
||||||
|
|
||||||
std::ofstream pc("dht_stats.log", first ? std::ios_base::trunc : std::ios_base::app);
|
std::ofstream pc("dht_stats.log", first ? std::ios_base::trunc : std::ios_base::app);
|
||||||
if (first)
|
if (first)
|
||||||
|
|
|
@ -61,6 +61,89 @@ void incoming_error(entry& e, char const* msg);
|
||||||
|
|
||||||
using detail::write_endpoint;
|
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<std::string, int>::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<std::string, int>::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<std::string, int>::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<std::string, int>::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<std::string, int>::const_iterator max = name.begin();
|
||||||
|
for (std::map<std::string, int>::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<std::string, int>::const_iterator i = tags.begin()
|
||||||
|
, end(tags.end()); i != end; ++i)
|
||||||
|
{
|
||||||
|
if (i != tags.begin()) t += " ";
|
||||||
|
t += i->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -96,6 +179,9 @@ void purge_peers(std::set<peer_entry>& peers)
|
||||||
|
|
||||||
void nop() {}
|
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
|
node_impl::node_impl(libtorrent::aux::session_impl& ses
|
||||||
, void (*f)(void*, entry const&, udp::endpoint const&, int)
|
, void (*f)(void*, entry const&, udp::endpoint const&, int)
|
||||||
, dht_settings const& settings
|
, dht_settings const& settings
|
||||||
|
@ -407,7 +493,7 @@ time_duration node_impl::connection_timeout()
|
||||||
m_last_tracker_tick = now;
|
m_last_tracker_tick = now;
|
||||||
|
|
||||||
// look through all peers and see if any have timed out
|
// 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;
|
torrent_entry& t = i->second;
|
||||||
node_id const& key = i->first;
|
node_id const& key = i->first;
|
||||||
|
@ -425,18 +511,6 @@ time_duration node_impl::connection_timeout()
|
||||||
return d;
|
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)
|
void node_impl::status(session_status& s)
|
||||||
{
|
{
|
||||||
mutex_t::scoped_lock l(m_mutex);
|
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<tcp::endpoint>& peers) const
|
bool node_impl::lookup_torrents(sha1_hash const& target
|
||||||
|
, entry& reply, char* tags) const
|
||||||
|
{
|
||||||
|
// if (m_ses.m_alerts.should_post<dht_find_torrents_alert>())
|
||||||
|
// 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<int, search_table_t::const_iterator> sort_item;
|
||||||
|
std::vector<sort_item> 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<dht_get_peers_alert>())
|
if (m_ses.m_alerts.should_post<dht_get_peers_alert>())
|
||||||
m_ses.m_alerts.post_alert(dht_get_peers_alert(info_hash));
|
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<tcp::endpoint>&
|
||||||
torrent_entry const& v = i->second;
|
torrent_entry const& v = i->second;
|
||||||
|
|
||||||
int num = (std::min)((int)v.peers.size(), m_settings.max_peers_reply);
|
int num = (std::min)((int)v.peers.size(), m_settings.max_peers_reply);
|
||||||
peers.clear();
|
int t = 0;
|
||||||
peers.reserve(num);
|
int m = 0;
|
||||||
random_sample_n(boost::make_transform_iterator(v.peers.begin(), &get_endpoint)
|
std::set<peer_entry>::iterator iter = v.peers.begin();
|
||||||
, boost::make_transform_iterator(v.peers.end(), &get_endpoint)
|
entry::list_type& pe = reply["values"].list();
|
||||||
, std::back_inserter(peers), num);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,7 +698,6 @@ void node_impl::incoming_request(msg const& m, entry& e)
|
||||||
entry& reply = e["r"];
|
entry& reply = e["r"];
|
||||||
m_rpc.add_our_id(reply);
|
m_rpc.add_our_id(reply);
|
||||||
|
|
||||||
|
|
||||||
if (strcmp(query, "ping") == 0)
|
if (strcmp(query, "ping") == 0)
|
||||||
{
|
{
|
||||||
// we already have 't' and 'id' in the response
|
// 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);
|
m_table.find_node(info_hash, n, 0);
|
||||||
write_nodes_entry(reply, n);
|
write_nodes_entry(reply, n);
|
||||||
|
|
||||||
peers_t p;
|
lookup_peers(info_hash, reply);
|
||||||
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));
|
|
||||||
}
|
|
||||||
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
#ifdef TORRENT_DHT_VERBOSE_LOGGING
|
||||||
TORRENT_LOG(node) << " values: " << p.size();
|
TORRENT_LOG(node) << " values: " << reply["values"].list().size();
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (strcmp(query, "find_node") == 0)
|
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());
|
sha1_hash target(target_ent->string_ptr());
|
||||||
nodes_t n;
|
nodes_t n;
|
||||||
// always return nodes as well as peers
|
|
||||||
m_table.find_node(target, n, 0);
|
m_table.find_node(target, n, 0);
|
||||||
write_nodes_entry(reply, n);
|
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++);
|
if (i != v.peers.end()) v.peers.erase(i++);
|
||||||
v.peers.insert(i, e);
|
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<dht_announce_torrent_alert>())
|
||||||
|
// 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
|
else
|
||||||
{
|
{
|
||||||
// if we don't recognize the message but there's a
|
// if we don't recognize the message but there's a
|
||||||
|
|
|
@ -35,6 +35,7 @@ test-suite libtorrent :
|
||||||
[ run test_primitives.cpp ]
|
[ run test_primitives.cpp ]
|
||||||
[ run test_ip_filter.cpp ]
|
[ run test_ip_filter.cpp ]
|
||||||
[ run test_hasher.cpp ]
|
[ run test_hasher.cpp ]
|
||||||
|
[ run test_dht.cpp ]
|
||||||
[ run test_storage.cpp ]
|
[ run test_storage.cpp ]
|
||||||
[ run test_upnp.cpp ]
|
[ run test_upnp.cpp ]
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ test_programs = \
|
||||||
test_hasher \
|
test_hasher \
|
||||||
test_http_connection \
|
test_http_connection \
|
||||||
test_ip_filter \
|
test_ip_filter \
|
||||||
|
test_dht \
|
||||||
test_lsd \
|
test_lsd \
|
||||||
test_metadata_extension \
|
test_metadata_extension \
|
||||||
test_natpmp \
|
test_natpmp \
|
||||||
|
@ -40,6 +41,7 @@ libtest_la_SOURCES = main.cpp setup_transfer.cpp
|
||||||
test_auto_unchoke_SOURCES = test_auto_unchoke.cpp
|
test_auto_unchoke_SOURCES = test_auto_unchoke.cpp
|
||||||
test_bandwidth_limiter_SOURCES = test_bandwidth_limiter.cpp
|
test_bandwidth_limiter_SOURCES = test_bandwidth_limiter.cpp
|
||||||
test_bdecode_performance_SOURCES = test_bdecode_performance.cpp
|
test_bdecode_performance_SOURCES = test_bdecode_performance.cpp
|
||||||
|
test_dht_SOURCES = test_dht.cpp
|
||||||
test_bencoding_SOURCES = test_bencoding.cpp
|
test_bencoding_SOURCES = test_bencoding.cpp
|
||||||
test_buffer_SOURCES = test_buffer.cpp
|
test_buffer_SOURCES = test_buffer.cpp
|
||||||
test_fast_extension_SOURCES = test_fast_extension.cpp
|
test_fast_extension_SOURCES = test_fast_extension.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;
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#ifndef TORRENT_DISABLE_DHT
|
#ifndef TORRENT_DISABLE_DHT
|
||||||
#include "libtorrent/kademlia/node_id.hpp"
|
#include "libtorrent/kademlia/node_id.hpp"
|
||||||
#include "libtorrent/kademlia/routing_table.hpp"
|
#include "libtorrent/kademlia/routing_table.hpp"
|
||||||
|
#include "libtorrent/kademlia/node.hpp"
|
||||||
#endif
|
#endif
|
||||||
#include <boost/tuple/tuple.hpp>
|
#include <boost/tuple/tuple.hpp>
|
||||||
#include <boost/tuple/tuple_comparison.hpp>
|
#include <boost/tuple/tuple_comparison.hpp>
|
||||||
|
@ -365,6 +366,49 @@ int test_main()
|
||||||
{
|
{
|
||||||
using namespace libtorrent;
|
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
|
// test snprintf
|
||||||
|
|
||||||
char msg[10];
|
char msg[10];
|
||||||
|
|
Loading…
Reference in New Issue