implemented support for BEP 51 (#1652)

This commit is contained in:
Alden Torres 2017-06-12 05:54:11 -04:00 committed by Arvid Norberg
parent 147d996160
commit 48ef3b6bf7
23 changed files with 605 additions and 3 deletions

View File

@ -151,6 +151,7 @@ set(kademlia_sources
get_peers
get_item
ed25519
sample_infohashes
)
# -- ed25519 --

View File

@ -1,3 +1,4 @@
* implemented support for DHT infohash indexing, BEP51
* removed deprecated support for file_base in file_storage
* added support for running separate DHT nodes on each network interface
* added support for establishing UTP connections on any network interface

View File

@ -693,6 +693,7 @@ KADEMLIA_SOURCES =
get_item
put_data
ed25519
sample_infohashes
;
ED25519_SOURCES =

View File

@ -235,4 +235,5 @@ nobase_include_HEADERS = \
kademlia/ed25519.hpp \
kademlia/item.hpp \
kademlia/get_item.hpp \
kademlia/sample_infohashes.hpp \
kademlia/get_peers.hpp

View File

@ -2590,11 +2590,56 @@ namespace libtorrent {
virtual std::string message() const override;
};
struct TORRENT_EXPORT dht_sample_infohashes_alert final : alert
{
dht_sample_infohashes_alert(aux::stack_allocator& alloc
, udp::endpoint const& endp
, time_duration interval
, int num
, std::vector<sha1_hash> const& samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> const& nodes);
static const int static_category = alert::dht_operation_notification;
TORRENT_DEFINE_ALERT(dht_sample_infohashes_alert, 93)
virtual std::string message() const override;
aux::noexcept_movable<udp::endpoint> endpoint;
time_duration const interval;
// This field indicates how many infohash keys are currently in the node's storage.
// If the value is larger than the number of returned samples it indicates that the
// indexer may obtain additional samples after waiting out the interval.
int const num_infohashes;
int num_samples() const;
std::vector<sha1_hash> samples() const;
// The total number of nodes returned by ``nodes()``.
int num_nodes() const;
// This is the set of more DHT nodes returned by the request.
//
// The information is included so that indexing nodes can perform a keyspace
// traversal with a single RPC per node by adjusting the target value for each RPC.
std::vector<std::pair<sha1_hash, udp::endpoint>> nodes() const;
private:
std::reference_wrapper<aux::stack_allocator> m_alloc;
int const m_num_samples;
aux::allocation_slot m_samples_idx;
int m_v4_num_nodes = 0;
int m_v6_num_nodes = 0;
aux::allocation_slot m_v4_nodes_idx;
aux::allocation_slot m_v6_nodes_idx;
};
#undef TORRENT_DEFINE_ALERT_IMPL
#undef TORRENT_DEFINE_ALERT
#undef TORRENT_DEFINE_ALERT_PRIO
enum { num_alert_types = 93 }; // this enum represents "max_alert_index" + 1
enum { num_alert_types = 94 }; // this enum represents "max_alert_index" + 1
}
#endif

View File

@ -403,6 +403,7 @@ namespace aux {
void dht_announce(sha1_hash const& info_hash, int port = 0, int flags = 0);
void dht_live_nodes(sha1_hash const& nid);
void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target);
void dht_direct_request(udp::endpoint const& ep, entry& e
, void* userdata = nullptr);

View File

@ -100,6 +100,11 @@ namespace libtorrent { namespace dht {
void announce(sha1_hash const& ih, int listen_port, int flags
, std::function<void(std::vector<tcp::endpoint> const&)> f);
void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target
, std::function<void(time_duration
, int, std::vector<sha1_hash>
, std::vector<std::pair<sha1_hash, udp::endpoint>>)> f);
void get_item(sha1_hash const& target
, std::function<void(item const&)> cb);

View File

@ -149,6 +149,11 @@ public:
, std::function<void(item const&, int)> f
, std::function<void(item&)> data_cb);
void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target
, std::function<void(time_duration
, int, std::vector<sha1_hash>
, std::vector<std::pair<sha1_hash, udp::endpoint>>)> f);
bool verify_token(string_view token, sha1_hash const& info_hash
, udp::endpoint const& addr) const;

View File

@ -0,0 +1,79 @@
/*
Copyright (c) 2017, 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 TORRENT_SAMPLE_INFOHASHES_HPP
#define TORRENT_SAMPLE_INFOHASHES_HPP
#include <vector>
#include <libtorrent/kademlia/traversal_algorithm.hpp>
#include <libtorrent/time.hpp>
namespace libtorrent { namespace dht
{
class sample_infohashes final : public traversal_algorithm
{
public:
using data_callback = std::function<void(time_duration
, int, std::vector<sha1_hash>
, std::vector<std::pair<sha1_hash, udp::endpoint>>)>;
sample_infohashes(node& dht_node
, node_id const& target
, data_callback const& dcallback);
virtual char const* name() const override;
void got_samples(time_duration interval
, int num, std::vector<sha1_hash> samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> nodes);
protected:
data_callback m_data_callback;
};
class sample_infohashes_observer final : public traversal_observer
{
public:
sample_infohashes_observer(std::shared_ptr<traversal_algorithm> const& algorithm
, udp::endpoint const& ep, node_id const& id);
virtual void reply(msg const&) override;
};
}} // namespace libtorrent::dht
#endif // TORRENT_SAMPLE_INFOHASHES_HPP

View File

@ -235,12 +235,15 @@ namespace libtorrent {
dht_get_out,
dht_put_in,
dht_put_out,
dht_sample_infohashes_in,
dht_sample_infohashes_out,
dht_invalid_announce,
dht_invalid_get_peers,
dht_invalid_find_node,
dht_invalid_put,
dht_invalid_get,
dht_invalid_sample_infohashes,
// uTP counters.
utp_packet_loss,

View File

@ -452,6 +452,14 @@ namespace libtorrent {
// posted, regardless of the alert mask.
void dht_live_nodes(sha1_hash const& nid);
// Query the DHT node specified by ``ep`` to retrieve a sample of the
// infohashes that the node currently have in their storage.
// The ``target`` is included for iterative lookups so that indexing nodes
// can perform a keyspace traversal with a single RPC per node by adjusting
// the target value for each RPC. It has no effect on the returned sample value.
// The result is posted as a ``dht_sample_infohashes_alert``.
void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target);
// Send an arbitrary DHT request directly to the specified endpoint. This
// function is intended for use by plugins. When a response is received
// or the request times out, a dht_direct_response_alert will be posted

View File

@ -58,7 +58,7 @@ namespace libtorrent {
namespace detail {
template <class Proto>
size_t address_size(Proto p)
std::size_t address_size(Proto p)
{
TORRENT_UNUSED(p);
#if TORRENT_USE_IPV6

View File

@ -22,6 +22,7 @@ KADEMLIA_SOURCES = \
kademlia/get_item.cpp \
kademlia/item.cpp \
kademlia/ed25519.cpp \
kademlia/sample_infohashes.cpp \
../ed25519/src/add_scalar.cpp \
../ed25519/src/fe.cpp \
../ed25519/src/ge.cpp \

View File

@ -2275,4 +2275,62 @@ namespace {
return stats_header;
}
dht_sample_infohashes_alert::dht_sample_infohashes_alert(aux::stack_allocator& alloc
, udp::endpoint const& endp
, time_duration _interval
, int _num
, std::vector<sha1_hash> const& samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> const& nodes)
: endpoint(endp)
, interval(_interval)
, num_infohashes(_num)
, m_alloc(alloc)
, m_num_samples(aux::numeric_cast<int>(samples.size()))
{
m_samples_idx = alloc.allocate(m_num_samples * 20);
char *ptr = alloc.ptr(m_samples_idx);
std::memcpy(ptr, samples.data(), samples.size() * 20);
std::tie(m_v4_num_nodes, m_v4_nodes_idx, m_v6_num_nodes, m_v6_nodes_idx)
= write_nodes(alloc, nodes);
}
std::string dht_sample_infohashes_alert::message() const
{
char msg[200];
std::snprintf(msg, sizeof(msg)
, "incoming dht sample_infohashes reply from: %s, samples %d"
, print_endpoint(endpoint).c_str(), m_num_samples);
return msg;
}
int dht_sample_infohashes_alert::num_samples() const
{
return m_num_samples;
}
std::vector<sha1_hash> dht_sample_infohashes_alert::samples() const
{
aux::vector<sha1_hash> samples;
samples.resize(m_num_samples);
const char *ptr = m_alloc.get().ptr(m_samples_idx);
std::memcpy(samples.data(), ptr, samples.size() * 20);
return samples;
}
int dht_sample_infohashes_alert::num_nodes() const
{
return m_v4_num_nodes + m_v6_num_nodes;
}
std::vector<std::pair<sha1_hash, udp::endpoint>> dht_sample_infohashes_alert::nodes() const
{
return read_nodes(m_alloc.get()
, m_v4_num_nodes, m_v4_nodes_idx
, m_v6_num_nodes, m_v6_nodes_idx);
}
} // namespace libtorrent

View File

@ -322,6 +322,20 @@ namespace libtorrent { namespace dht {
n.second.dht.announce(ih, listen_port, flags, f);
}
void dht_tracker::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target
, std::function<void(time_duration
, int, std::vector<sha1_hash>
, std::vector<std::pair<sha1_hash, udp::endpoint>>)> f)
{
for (auto& n : m_nodes)
{
if (ep.protocol() != (n.first->get_external_address().is_v4() ? udp::v4() : udp::v6()))
continue;
n.second.dht.sample_infohashes(ep, target, f);
break;
}
}
namespace {
struct get_immutable_item_ctx

View File

@ -64,6 +64,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/kademlia/get_item.hpp"
#include "libtorrent/kademlia/msg.hpp"
#include <libtorrent/kademlia/put_data.hpp>
#include <libtorrent/kademlia/sample_infohashes.hpp>
using namespace std::placeholders;
@ -565,6 +566,38 @@ void node::put_item(public_key const& pk, std::string const& salt
ta->start();
}
void node::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target
, std::function<void(time_duration
, int, std::vector<sha1_hash>
, std::vector<std::pair<sha1_hash, udp::endpoint>>)> f)
{
#ifndef TORRENT_DISABLE_LOGGING
if (m_observer != nullptr && m_observer->should_log(dht_logger::node))
{
m_observer->log(dht_logger::node, "starting sample_infohashes for [ node: %s, target: %s ]"
, print_endpoint(ep).c_str(), aux::to_hex(target).c_str());
}
#endif
// not an actual traversal
auto ta = std::make_shared<dht::sample_infohashes>(*this, node_id(), std::move(f));
auto o = m_rpc.allocate_observer<sample_infohashes_observer>(ta, ep, node_id());
if (!o) return;
#if TORRENT_USE_ASSERTS
o->m_in_constructor = false;
#endif
entry e;
e["q"] = "sample_infohashes";
e["a"]["target"] = target;
stats_counters().inc_stats_counter(counters::dht_sample_infohashes_out);
m_rpc.invoke(e, ep, o);
}
struct ping_observer : observer
{
ping_observer(
@ -1104,6 +1137,30 @@ void node::incoming_request(msg const& m, entry& e)
, reply);
}
}
else if (query == "sample_infohashes")
{
key_desc_t const msg_desc[] = {
{"target", bdecode_node::string_t, 20, 0},
{"want", bdecode_node::list_t, 0, key_desc_t::optional},
};
bdecode_node msg_keys[2];
if (!verify_message(arg_ent, msg_desc, msg_keys, error_string))
{
m_counters.inc_stats_counter(counters::dht_invalid_sample_infohashes);
incoming_error(e, error_string);
return;
}
m_counters.inc_stats_counter(counters::dht_sample_infohashes_in);
sha1_hash const target(msg_keys[0].string_ptr());
// TODO: keep the returned value to pass as a limit
// to write_nodes_entries when implemented
m_storage.get_infohashes_sample(reply);
write_nodes_entries(target, msg_keys[1], reply);
}
else
{
// if we don't recognize the message but there's a

View File

@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <libtorrent/kademlia/dht_observer.hpp>
#include <libtorrent/kademlia/direct_request.hpp>
#include <libtorrent/kademlia/get_item.hpp>
#include <libtorrent/kademlia/sample_infohashes.hpp>
#include <libtorrent/socket_io.hpp> // for print_endpoint
#include <libtorrent/hasher.hpp>
@ -143,6 +144,7 @@ using observer_storage = aux::aligned_union<1
, get_item_observer
, get_peers_observer
, obfuscated_get_peers_observer
, sample_infohashes_observer
, null_observer
, traversal_observer>::type;

View File

@ -0,0 +1,146 @@
/*
Copyright (c) 2017, 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/sample_infohashes.hpp>
#include <libtorrent/kademlia/dht_observer.hpp>
#include <libtorrent/kademlia/node.hpp>
#include <libtorrent/kademlia/io.hpp>
#include <libtorrent/performance_counters.hpp>
#include <libtorrent/aux_/numeric_cast.hpp>
#include <libtorrent/socket_io.hpp>
namespace libtorrent { namespace dht
{
sample_infohashes::sample_infohashes(node& dht_node
, node_id const& target
, data_callback const& dcallback)
: traversal_algorithm(dht_node, target)
, m_data_callback(dcallback) {}
char const* sample_infohashes::name() const { return "sample_infohashes"; }
void sample_infohashes::got_samples(time_duration interval
, int num, std::vector<sha1_hash> samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> nodes)
{
if (m_data_callback)
{
m_data_callback(interval, num, std::move(samples), std::move(nodes));
m_data_callback = nullptr;
done();
}
}
sample_infohashes_observer::sample_infohashes_observer(
std::shared_ptr<traversal_algorithm> const& algorithm
, udp::endpoint const& ep, node_id const& id)
: traversal_observer(algorithm, ep, id) {}
void sample_infohashes_observer::reply(msg const& m)
{
bdecode_node r = m.message.dict_find_dict("r");
if (!r)
{
#ifndef TORRENT_DISABLE_LOGGING
get_observer()->log(dht_logger::traversal, "[%u] missing response dict"
, algorithm()->id());
#endif
timeout();
return;
}
// look for nodes
std::vector<std::pair<sha1_hash, udp::endpoint>> nodes;
udp const protocol = algorithm()->get_node().protocol();
int const protocol_size = int(detail::address_size(protocol));
char const* nodes_key = algorithm()->get_node().protocol_nodes_key();
bdecode_node const n = r.dict_find_string(nodes_key);
if (n)
{
char const* ptr = n.string_ptr();
char const* end = ptr + n.string_length();
while (end - ptr >= 20 + protocol_size + 2)
{
node_endpoint nep = read_node_endpoint(protocol, ptr);
nodes.emplace_back(nep.id, nep.ep);
}
}
std::int64_t const interval = r.dict_find_int_value("interval", -1);
if (interval < 0 || interval > 21600) // TODO: put constant in a common place
{
#ifndef TORRENT_DISABLE_LOGGING
get_observer()->log(dht_logger::traversal, "[%u] wrong or missing interval value"
, algorithm()->id());
#endif
timeout();
return;
}
std::int64_t const num = r.dict_find_int_value("num", -1);
if (num < 0 || num > std::numeric_limits<int>::max())
{
#ifndef TORRENT_DISABLE_LOGGING
get_observer()->log(dht_logger::traversal, "[%u] wrong or missing num value"
, algorithm()->id());
#endif
timeout();
return;
}
bdecode_node samples = r.dict_find_string("samples");
if (samples && (samples.string_length() % 20 == 0))
{
std::vector<sha1_hash> v(aux::numeric_cast<std::size_t>(samples.string_length() / 20));
std::memcpy(v.data(), samples.string_ptr(), v.size() * 20);
static_cast<sample_infohashes*>(algorithm())->got_samples(
seconds(interval), int(num), std::move(v), std::move(nodes));
}
else
{
#ifndef TORRENT_DISABLE_LOGGING
get_observer()->log(dht_logger::traversal, "[%u] wrong or missing samples value"
, algorithm()->id());
#endif
timeout();
}
traversal_observer::reply(m);
// this is necessary to play nice with
// observer::abort(), observer::done() and observer::timeout()
flags |= flag_done;
}
}} // namespace libtorrent::dht

View File

@ -623,6 +623,16 @@ namespace {
#endif
}
void session_handle::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target)
{
#ifndef TORRENT_DISABLE_DHT
async_call(&session_impl::dht_sample_infohashes, ep, target);
#else
TORRENT_UNUSED(ep);
TORRENT_UNUSED(target);
#endif
}
void session_handle::dht_direct_request(udp::endpoint const& ep, entry const& e, void* userdata)
{
#ifndef TORRENT_DISABLE_DHT

View File

@ -5954,6 +5954,19 @@ namespace {
m_alerts.emplace_alert<dht_live_nodes_alert>(nid, nodes);
}
void session_impl::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target)
{
if (!m_dht) return;
m_dht->sample_infohashes(ep, target, [this, &ep](time_duration interval
, int num, std::vector<sha1_hash> samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> nodes)
{
if (m_alerts.should_post<dht_sample_infohashes_alert>())
m_alerts.emplace_alert<dht_sample_infohashes_alert>(ep
, interval, num, samples, nodes);
});
}
void session_impl::dht_direct_request(udp::endpoint const& ep, entry& e, void* userdata)
{
if (!m_dht) return;

View File

@ -472,6 +472,8 @@ namespace {
METRIC(dht, dht_get_out)
METRIC(dht, dht_put_in)
METRIC(dht, dht_put_out)
METRIC(dht, dht_sample_infohashes_in)
METRIC(dht, dht_sample_infohashes_out)
// the number of failed incoming DHT requests by kind of request
METRIC(dht, dht_invalid_announce)
@ -479,6 +481,7 @@ namespace {
METRIC(dht, dht_invalid_find_node)
METRIC(dht, dht_invalid_put)
METRIC(dht, dht_invalid_get)
METRIC(dht, dht_invalid_sample_infohashes)
// uTP counters. Each counter represents the number of time each event
// has occurred.

View File

@ -160,10 +160,11 @@ TORRENT_TEST(alerts_types)
TEST_ALERT_TYPE(session_error_alert, 90, 0, alert::error_notification);
TEST_ALERT_TYPE(dht_live_nodes_alert, 91, 0, alert::dht_notification);
TEST_ALERT_TYPE(session_stats_header_alert, 92, 0, alert::stats_notification);
TEST_ALERT_TYPE(dht_sample_infohashes_alert, 93, 0, alert::dht_operation_notification);
#undef TEST_ALERT_TYPE
TEST_EQUAL(num_alert_types, 93);
TEST_EQUAL(num_alert_types, 94);
TEST_EQUAL(num_alert_types, count_alert_types);
}
@ -263,3 +264,60 @@ TORRENT_TEST(session_stats_alert)
TEST_CHECK(v != nullptr);
TEST_CHECK(v->message().find("session stats (") != std::string::npos);
}
TORRENT_TEST(dht_sample_infohashes_alert)
{
alert_manager mgr(1, dht_sample_infohashes_alert::static_category);
TEST_EQUAL(mgr.should_post<dht_sample_infohashes_alert>(), true);
udp::endpoint const endpoint = rand_udp_ep();
time_duration const interval = seconds(10);
int const num = 100;
sha1_hash const h1 = rand_hash();
sha1_hash const h2 = rand_hash();
sha1_hash const h3 = rand_hash();
sha1_hash const h4 = rand_hash();
sha1_hash const h5 = rand_hash();
std::vector<sha1_hash> const v = {h1, h2, h3, h4, h5};
sha1_hash const nh1 = rand_hash();
sha1_hash const nh2 = rand_hash();
sha1_hash const nh3 = rand_hash();
sha1_hash const nh4 = rand_hash();
sha1_hash const nh5 = rand_hash();
udp::endpoint const nep1 = rand_udp_ep(rand_v4);
udp::endpoint const nep2 = rand_udp_ep(rand_v4);
udp::endpoint const nep3 = rand_udp_ep(rand_v4);
#if TORRENT_USE_IPV6
udp::endpoint const nep4 = rand_udp_ep(rand_v6);
udp::endpoint const nep5 = rand_udp_ep(rand_v6);
#else
udp::endpoint const nep4 = rand_udp_ep(rand_v4);
udp::endpoint const nep5 = rand_udp_ep(rand_v4);
#endif
std::vector<std::pair<sha1_hash, udp::endpoint>> nv;
nv.emplace_back(nh1, nep1);
nv.emplace_back(nh2, nep2);
nv.emplace_back(nh3, nep3);
nv.emplace_back(nh4, nep4);
nv.emplace_back(nh5, nep5);
mgr.emplace_alert<dht_sample_infohashes_alert>(endpoint, interval, num, v, nv);
auto const* a = alert_cast<dht_sample_infohashes_alert>(mgr.wait_for_alert(seconds(0)));
TEST_CHECK(a != nullptr);
TEST_EQUAL(a->endpoint, endpoint);
TEST_CHECK(a->interval == interval);
TEST_EQUAL(a->num_infohashes, num);
TEST_EQUAL(a->num_samples(), 5);
TEST_CHECK(a->samples() == v);
TEST_EQUAL(a->num_nodes(), 5);
auto nodes = a->nodes();
std::sort(nv.begin(), nv.end());
std::sort(nodes.begin(), nodes.end());
TEST_CHECK(nv == nodes);
}

View File

@ -246,6 +246,19 @@ struct msg_args
msg_args& peers(std::set<tcp::endpoint> const& p)
{ if (!p.empty()) a.dict()["values"] = write_peers(p); return *this; }
msg_args& interval(time_duration interval)
{ a["interval"] = total_seconds(interval); return *this; }
msg_args& num(int num)
{ a["num"] = num; return *this; }
msg_args& samples(std::vector<sha1_hash> const& samples)
{
a["samples"] = span<char const>(
reinterpret_cast<char const*>(samples.data()), samples.size() * 20);
return *this;
}
entry a;
};
@ -600,6 +613,15 @@ dht::key_desc_t const put_mutable_item_desc[] = {
{"v", bdecode_node::none_t, 0, key_desc_t::last_child},
};
dht::key_desc_t const sample_infohashes_desc[] = {
{"y", bdecode_node::string_t, 1, 0},
{"t", bdecode_node::string_t, 2, 0},
{"q", bdecode_node::string_t, 17, 0},
{"a", bdecode_node::dict_t, 0, key_desc_t::parse_children},
{"id", bdecode_node::string_t, 20, 0},
{"target", bdecode_node::string_t, 20, key_desc_t::last_child},
};
void print_state(std::ostream& os, routing_table const& table)
{
std::vector<char> buf(2048);
@ -3593,6 +3615,74 @@ TORRENT_TEST(dht_state)
TEST_CHECK(s2.nodes.empty());
}
TORRENT_TEST(sample_infohashes)
{
dht_test_setup t(rand_udp_ep());
bdecode_node response;
g_sent_packets.clear();
udp::endpoint initial_node = rand_udp_ep();
t.dht_node.m_table.add_node(node_entry{initial_node});
// nodes
sha1_hash const h1 = rand_hash();
sha1_hash const h2 = rand_hash();
udp::endpoint const ep1 = rand_udp_ep(rand_v4);
udp::endpoint const ep2 = rand_udp_ep(rand_v4);
t.dht_node.sample_infohashes(initial_node, items[0].target,
[h1, ep1, h2, ep2](time_duration interval, int num
, std::vector<sha1_hash> samples
, std::vector<std::pair<sha1_hash, udp::endpoint>> const& nodes)
{
TEST_EQUAL(total_seconds(interval), 10);
TEST_EQUAL(num, 2);
TEST_EQUAL(samples.size(), 1);
TEST_EQUAL(samples[0], to_hash("1000000000000000000000000000000000000001"));
TEST_EQUAL(nodes.size(), 2);
TEST_EQUAL(nodes[0].first, h1);
TEST_EQUAL(nodes[0].second, ep1);
TEST_EQUAL(nodes[1].first, h2);
TEST_EQUAL(nodes[1].second, ep2);
});
TEST_EQUAL(g_sent_packets.size(), 1);
if (g_sent_packets.empty()) return;
TEST_EQUAL(g_sent_packets.front().first, initial_node);
lazy_from_entry(g_sent_packets.front().second, response);
bdecode_node sample_infohashes_keys[6];
bool const ret = verify_message(response
, sample_infohashes_desc, sample_infohashes_keys, t.error_string);
if (ret)
{
TEST_EQUAL(sample_infohashes_keys[0].string_value(), "q");
TEST_EQUAL(sample_infohashes_keys[2].string_value(), "sample_infohashes");
TEST_EQUAL(sample_infohashes_keys[5].string_value(), items[0].target.to_string());
}
else
{
std::printf(" invalid sample_infohashes request: %s\n", print_entry(response).c_str());
TEST_ERROR(t.error_string);
return;
}
std::vector<node_entry> nodes;
nodes.emplace_back(h1, ep1);
nodes.emplace_back(h2, ep2);
g_sent_packets.clear();
send_dht_response(t.dht_node, response, initial_node
, msg_args()
.interval(seconds(10))
.num(2)
.samples({to_hash("1000000000000000000000000000000000000001")})
.nodes(nodes));
TEST_CHECK(g_sent_packets.empty());
}
// TODO: test obfuscated_get_peers
#else