From d6b1aa4c3602f7dd10e6f7e0098735a0b3702b5c Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Fri, 27 Dec 2013 04:28:25 +0000 Subject: [PATCH] DHT refactoring and support for storing arbitrary data with put --- ChangeLog | 1 + Jamfile | 18 +- include/libtorrent/Makefile.am | 6 +- include/libtorrent/entry.hpp | 1 + include/libtorrent/kademlia/find_data.hpp | 54 +- include/libtorrent/kademlia/get_item.hpp | 82 ++ include/libtorrent/kademlia/get_peers.hpp | 108 +++ include/libtorrent/kademlia/item.hpp | 122 +++ include/libtorrent/kademlia/node.hpp | 7 +- include/libtorrent/kademlia/refresh.hpp | 6 +- .../kademlia/traversal_algorithm.hpp | 5 +- src/Makefile.am | 3 + src/kademlia/find_data.cpp | 237 +----- src/kademlia/get_item.cpp | 231 ++++++ src/kademlia/get_peers.cpp | 313 ++++++++ src/kademlia/item.cpp | 177 +++++ src/kademlia/node.cpp | 59 +- src/kademlia/refresh.cpp | 4 +- src/kademlia/traversal_algorithm.cpp | 13 + test/Jamfile | 2 +- test/test.hpp | 2 +- test/test_dht.cpp | 699 ++++++++++++++++-- 22 files changed, 1756 insertions(+), 394 deletions(-) create mode 100644 include/libtorrent/kademlia/get_item.hpp create mode 100644 include/libtorrent/kademlia/get_peers.hpp create mode 100644 include/libtorrent/kademlia/item.hpp create mode 100644 src/kademlia/get_item.cpp create mode 100644 src/kademlia/get_peers.cpp create mode 100644 src/kademlia/item.cpp diff --git a/ChangeLog b/ChangeLog index 7b1f599a3..e10dce742 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * DHT refactoring and support for storing arbitrary data with put * support building on android * improved support for web seeds that don't support keep-alive * improve DHT routing table to return better nodes (lower RTT and closer to target) diff --git a/Jamfile b/Jamfile index dfeff220b..cb8e976fe 100755 --- a/Jamfile +++ b/Jamfile @@ -118,10 +118,21 @@ rule linking ( properties * ) if on in $(properties) && ( gcc in $(properties) - || darwin in $(properties) ) + || darwin in $(properties) + || clang in $(properties) ) { result += -fprofile-arcs -ftest-coverage - -lgcov NDEBUG ; + NDEBUG ; + + if gcc in $(properties) + || darwin in $(properties) + { + result += -lgcov ; + } + else + { + result += --coverage ; + } } # clock_gettime on linux requires librt @@ -579,6 +590,9 @@ KADEMLIA_SOURCES = routing_table traversal_algorithm logging + get_peers + item + get_item ; ED25519_SOURCES = diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index 601529d04..d8c19b6aa 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -147,4 +147,8 @@ nobase_include_HEADERS = \ kademlia/refresh.hpp \ kademlia/routing_table.hpp \ kademlia/rpc_manager.hpp \ - kademlia/traversal_algorithm.hpp + kademlia/traversal_algorithm.hpp \ + kademlia/item.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp + diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp index 5b31da3d7..7136f8bf2 100644 --- a/include/libtorrent/entry.hpp +++ b/include/libtorrent/entry.hpp @@ -137,6 +137,7 @@ namespace libtorrent // hidden bool operator==(entry const& e) const; + bool operator!=(entry const& e) const { return !(*this == e); } // copies the structure of the right hand side into this // entry. diff --git a/include/libtorrent/kademlia/find_data.hpp b/include/libtorrent/kademlia/find_data.hpp index e53241cda..7c03842e8 100644 --- a/include/libtorrent/kademlia/find_data.hpp +++ b/include/libtorrent/kademlia/find_data.hpp @@ -57,22 +57,15 @@ class node_impl; // -------- find data ----------- -//TODO: 3 rename this class to get_peers, since that's what it does -// find_data is an unnecessarily generic name struct find_data : traversal_algorithm { -public: - typedef boost::function const&)> data_callback; - typedef boost::function > const&, bool)> nodes_callback; + typedef boost::function > const&)> nodes_callback; - void got_peers(std::vector const& peers); void got_write_token(node_id const& n, std::string const& write_token) { m_write_tokens[n] = write_token; } find_data(node_impl& node, node_id target - , data_callback const& dcallback - , nodes_callback const& ncallback - , bool noseeds); + , nodes_callback const& ncallback); virtual void start(); @@ -83,40 +76,12 @@ public: protected: virtual void done(); - observer_ptr new_observer(void* ptr, udp::endpoint const& ep + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep , node_id const& id); - virtual bool invoke(observer_ptr o); - data_callback m_data_callback; nodes_callback m_nodes_callback; std::map m_write_tokens; - node_id const m_target; - bool m_done:1; - bool m_got_peers:1; - bool m_noseeds:1; -}; - -struct obfuscated_get_peers : find_data -{ - typedef find_data::nodes_callback done_callback; - - obfuscated_get_peers(node_impl& node, node_id target - , data_callback const& dcallback - , nodes_callback const& ncallback - , bool noseeds); - - virtual char const* name() const; - -protected: - - observer_ptr new_observer(void* ptr, udp::endpoint const& ep - , node_id const& id); - virtual bool invoke(observer_ptr o); - virtual void done(); -private: - // when set to false, we no longer obfuscate - // the target hash, and send regular get_peers - bool m_obfuscated; + bool m_done; }; struct find_data_observer : traversal_observer @@ -126,17 +91,8 @@ struct find_data_observer : traversal_observer , udp::endpoint const& ep, node_id const& id) : traversal_observer(algorithm, ep, id) {} - void reply(msg const&); -}; -struct obfuscated_find_data_observer : traversal_observer -{ - obfuscated_find_data_observer( - boost::intrusive_ptr const& algorithm - , udp::endpoint const& ep, node_id const& id) - : traversal_observer(algorithm, ep, id) - {} - void reply(msg const&); + virtual void reply(msg const&); }; } } // namespace libtorrent::dht diff --git a/include/libtorrent/kademlia/get_item.hpp b/include/libtorrent/kademlia/get_item.hpp new file mode 100644 index 000000000..a455129bf --- /dev/null +++ b/include/libtorrent/kademlia/get_item.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2013, Steven Siloti +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 LIBTORRENT_GET_ITEM_HPP +#define LIBTORRENT_GET_ITEM_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +class get_item : public find_data +{ +public: + typedef boost::function data_callback; + + void got_data(lazy_entry const* v, + char const* pk, + boost::uint64_t seq, + char const* sig); + + get_item(node_impl& node, node_id target, data_callback const& dcallback); + + virtual char const* name() const; + +protected: + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, node_id const& id); + virtual bool invoke(observer_ptr o); + virtual void done(); + + void put(std::vector > const& v); + + data_callback m_data_callback; + item m_data; +}; + +class get_item_observer : public find_data_observer +{ +public: + get_item_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(algorithm, ep, id) + {} + + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_ITEM_HPP diff --git a/include/libtorrent/kademlia/get_peers.hpp b/include/libtorrent/kademlia/get_peers.hpp new file mode 100644 index 000000000..af1a0a90d --- /dev/null +++ b/include/libtorrent/kademlia/get_peers.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2006-2013, Arvid Norberg & Daniel Wallin +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 LIBTORRENT_GET_PEERS_HPP +#define LIBTORRENT_GET_PEERS_HPP + +#include + +namespace libtorrent { namespace dht +{ + +struct get_peers : find_data +{ + typedef boost::function const&)> data_callback; + + void got_peers(std::vector const& peers); + + get_peers(node_impl& node, node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + virtual char const* name() const; + +protected: + virtual bool invoke(observer_ptr o); + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, node_id const& id); + + data_callback m_data_callback; + bool m_noseeds; +}; + +struct obfuscated_get_peers : get_peers +{ + typedef get_peers::nodes_callback done_callback; + + obfuscated_get_peers(node_impl& node, node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + virtual char const* name() const; + +protected: + + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, + node_id const& id); + virtual bool invoke(observer_ptr o); + virtual void done(); +private: + // when set to false, we no longer obfuscate + // the target hash, and send regular get_peers + bool m_obfuscated; +}; + +struct get_peers_observer : find_data_observer +{ + get_peers_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(algorithm, ep, id) + {} + + virtual void reply(msg const&); +}; + +struct obfuscated_get_peers_observer : traversal_observer +{ + obfuscated_get_peers_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(algorithm, ep, id) + {} + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_PEERS_HPP diff --git a/include/libtorrent/kademlia/item.hpp b/include/libtorrent/kademlia/item.hpp new file mode 100644 index 000000000..ff3d6b93f --- /dev/null +++ b/include/libtorrent/kademlia/item.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2013, Steven Siloti +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 LIBTORRENT_ITEM_HPP +#define LIBTORRENT_ITEM_HPP + +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +bool TORRENT_EXTRA_EXPORT verify_mutable_item(std::pair v, + boost::uint64_t seq, + char const* pk, + char const* sig); + +void TORRENT_EXTRA_EXPORT sign_mutable_item(std::pair v, + boost::uint64_t seq, + char const* pk, + char const* sk, + char* sig); + +sha1_hash TORRENT_EXTRA_EXPORT mutable_item_cas(std::pair v, boost::uint64_t seq); + +struct TORRENT_EXTRA_EXPORT invalid_item : std::exception +{ + virtual const char* what() const throw() { return "invalid DHT item"; } +}; + +enum +{ + item_pk_len = 32, + item_sk_len = 64, + item_sig_len = 64 +}; + +struct lazy_item; + +class TORRENT_EXTRA_EXPORT item +{ +public: + item() : m_mutable(false) {} + item(entry const& v) { assign(v); } + item(entry const& v, boost::uint64_t seq, char const* pk, char const* sk); + item(lazy_entry const* v) { assign(v); } + item(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig); + item(lazy_item const&); + + void assign(entry const& v) { assign(v, 0, NULL, NULL); } + void assign(entry const& v, boost::uint64_t seq, char const* pk, char const* sk); + void assign(lazy_entry const* v) { assign(v, 0, NULL, NULL); } + bool assign(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig); + + void clear() { m_value = entry(); } + bool empty() const { return m_value.type() == entry::undefined_t; } + + bool is_mutable() const { return m_mutable; } + + sha1_hash cas(); + + entry const& value() const { return m_value; } + char const* pk() { TORRENT_ASSERT(m_mutable); return m_pk; } + char const* sig() { TORRENT_ASSERT(m_mutable); return m_sig; } + boost::uint64_t seq() { TORRENT_ASSERT(m_mutable); return m_seq; } + +private: + entry m_value; + char m_pk[item_pk_len]; + char m_sig[item_sig_len]; + boost::uint64_t m_seq; + bool m_mutable; +}; + +struct lazy_item +{ + lazy_item(lazy_entry const* v) : value(v), pk(NULL), sig(NULL), seq(0) {} + lazy_item(lazy_entry const* v, char const* pk, char const* sig, boost::uint64_t seq); + + bool is_mutable() const { return pk && sig; } + + lazy_entry const* value; + char const* pk; + char const* sig; + boost::uint64_t const seq; +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_ITEM_HPP diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index bdbcfbcc0..dce559a96 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -132,11 +133,11 @@ struct dht_immutable_item int size; }; -struct ed25519_public_key { char bytes[32]; }; +struct ed25519_public_key { char bytes[item_pk_len]; }; struct dht_mutable_item : dht_immutable_item { - char sig[64]; + char sig[item_sig_len]; boost::uint64_t seq; ed25519_public_key key; }; @@ -232,6 +233,8 @@ public: void announce(sha1_hash const& info_hash, int listen_port, bool seed , boost::function const&)> f); + void get_item(sha1_hash const& target, boost::function f); + bool verify_token(std::string const& token, char const* info_hash , udp::endpoint const& addr); diff --git a/include/libtorrent/kademlia/refresh.hpp b/include/libtorrent/kademlia/refresh.hpp index a97f9371b..97712fd2f 100644 --- a/include/libtorrent/kademlia/refresh.hpp +++ b/include/libtorrent/kademlia/refresh.hpp @@ -35,7 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include +#include namespace libtorrent { namespace dht { @@ -43,10 +43,10 @@ namespace libtorrent { namespace dht class routing_table; class rpc_manager; -class refresh : public find_data +class refresh : public get_peers { public: - typedef find_data::nodes_callback done_callback; + typedef get_peers::nodes_callback done_callback; refresh(node_impl& node, node_id target , done_callback const& callback); diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp index 35831ea7e..1719a4562 100644 --- a/include/libtorrent/kademlia/traversal_algorithm.hpp +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -67,6 +67,7 @@ struct traversal_algorithm : boost::noncopyable void failed(observer_ptr o, int flags = 0); virtual ~traversal_algorithm(); void status(dht_lookup& l); + void abort(); void* allocate_observer(); void free_observer(void* ptr); @@ -112,7 +113,7 @@ protected: int m_ref_count; node_impl& m_node; - node_id m_target; + node_id const m_target; std::vector m_results; int m_invoke_count; int m_branch_factor; @@ -130,7 +131,7 @@ struct traversal_observer : observer {} // parses out "nodes" and keeps traversing - void reply(msg const&); + virtual void reply(msg const&); }; } } // namespace libtorrent::dht diff --git a/src/Makefile.am b/src/Makefile.am index b12c877cc..ffde2a557 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,9 @@ KADEMLIA_SOURCES = \ kademlia/rpc_manager.cpp \ kademlia/logging.cpp \ kademlia/traversal_algorithm.cpp \ + kademlia/get_peers.cpp \ + kademlia/get_item.cpp \ + kademlia/item.cpp \ ../ed25519/src/add_scalar.c \ ../ed25519/src/fe.c \ ../ed25519/src/ge.c \ diff --git a/src/kademlia/find_data.cpp b/src/kademlia/find_data.cpp index c84a33ddb..7676120cf 100644 --- a/src/kademlia/find_data.cpp +++ b/src/kademlia/find_data.cpp @@ -80,77 +80,7 @@ void find_data_observer::reply(msg const& m) node_id(id->string_ptr()), token->string_value()); } - // look for peers - lazy_entry const* n = r->dict_find_list("values"); - if (n) - { - std::vector peer_list; - if (n->list_size() == 1 && n->list_at(0)->type() == lazy_entry::string_t) - { - // assume it's mainline format - char const* peers = n->list_at(0)->string_ptr(); - char const* end = peers + n->list_at(0)->string_length(); - -#ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) - << "[" << m_algorithm.get() << "] PEERS" - << " invoke-count: " << m_algorithm->invoke_count() - << " branch-factor: " << m_algorithm->branch_factor() - << " addr: " << m.addr - << " id: " << node_id(id->string_ptr()) - << " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr())) - << " p: " << ((end - peers) / 6); -#endif - while (end - peers >= 6) - peer_list.push_back(read_v4_endpoint(peers)); - } - else - { - // assume it's uTorrent/libtorrent format - read_endpoint_list(n, peer_list); -#ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) - << "[" << m_algorithm.get() << "] PEERS" - << " invoke-count: " << m_algorithm->invoke_count() - << " branch-factor: " << m_algorithm->branch_factor() - << " addr: " << m.addr - << " id: " << node_id(id->string_ptr()) - << " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr())) - << " p: " << n->list_size(); -#endif - } - static_cast(m_algorithm.get())->got_peers(peer_list); - } - traversal_observer::reply(m); - - done(); -} - -void obfuscated_find_data_observer::reply(msg const& m) -{ - lazy_entry const* r = m.message.dict_find_dict("r"); - if (!r) - { -#ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) << "[" << m_algorithm.get() - << "] missing response dict"; -#endif - return; - } - - lazy_entry const* id = r->dict_find_string("id"); - if (!id || id->string_length() != 20) - { -#ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) << "[" << m_algorithm.get() - << "] invalid id in response"; -#endif - return; - } - - traversal_observer::reply(m); - done(); } @@ -163,16 +93,10 @@ void add_entry_fun(void* userdata, node_entry const& e) find_data::find_data( node_impl& node , node_id target - , data_callback const& dcallback - , nodes_callback const& ncallback - , bool noseeds) + , nodes_callback const& ncallback) : traversal_algorithm(node, target) - , m_data_callback(dcallback) , m_nodes_callback(ncallback) - , m_target(target) , m_done(false) - , m_got_peers(false) - , m_noseeds(noseeds) { } @@ -196,32 +120,7 @@ observer_ptr find_data::new_observer(void* ptr return o; } -char const* find_data::name() const { return "get_peers"; } - -bool find_data::invoke(observer_ptr o) -{ - if (m_done) - { - m_invoke_count = -1; - return false; - } - - entry e; - e["y"] = "q"; - entry& a = e["a"]; - - e["q"] = "get_peers"; - a["info_hash"] = m_target.to_string(); - if (m_noseeds) a["noseed"] = 1; - - return m_node.m_rpc.invoke(e, o->target_ep(), o); -} - -void find_data::got_peers(std::vector const& peers) -{ - if (!peers.empty()) m_got_peers = true; - if (m_data_callback) m_data_callback(peers); -} +char const* find_data::name() const { return "find_data"; } void find_data::done() { @@ -230,7 +129,7 @@ void find_data::done() m_done = true; #ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) << "[" << this << "] get_peers DONE"; + TORRENT_LOG(traversal) << "[" << this << "] find_data DONE"; #endif std::vector > results; @@ -246,138 +145,10 @@ void find_data::done() results.push_back(std::make_pair(node_entry(o->id(), o->target_ep()), j->second)); --num_results; } - if (m_nodes_callback) m_nodes_callback(results, m_got_peers); + if (m_nodes_callback) m_nodes_callback(results); traversal_algorithm::done(); } -obfuscated_get_peers::obfuscated_get_peers( - node_impl& node - , node_id info_hash - , data_callback const& dcallback - , nodes_callback const& ncallback - , bool noseeds) - : find_data(node, info_hash, dcallback, ncallback, noseeds) - , m_obfuscated(true) -{ -} - -char const* obfuscated_get_peers::name() const -{ return !m_obfuscated ? find_data::name() : "get_peers [obfuscated]"; } - -observer_ptr obfuscated_get_peers::new_observer(void* ptr - , udp::endpoint const& ep, node_id const& id) -{ - if (m_obfuscated) - { - observer_ptr o(new (ptr) obfuscated_find_data_observer(this, ep, id)); -#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS - o->m_in_constructor = false; -#endif - return o; - } - else - { - observer_ptr o(new (ptr) find_data_observer(this, ep, id)); -#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS - o->m_in_constructor = false; -#endif - return o; - } -} - -bool obfuscated_get_peers::invoke(observer_ptr o) -{ - if (!m_obfuscated) return find_data::invoke(o); - - node_id id = o->id(); - int shared_prefix = 160 - distance_exp(id, m_target); - - // when we get close to the target zone in the DHT - // start using the correct info-hash, in order to - // start receiving peers - if (shared_prefix > m_node.m_table.depth() - 10) - { - m_obfuscated = false; - // clear the queried bits on all successful nodes in - // our node-list for this traversal algorithm, to - // allow the get_peers traversal to regress in case - // nodes further down end up being dead - for (std::vector::iterator i = m_results.begin() - , end(m_results.end()); i != end; ++i) - { - observer* o = i->get(); - // don't re-request from nodes that didn't respond - if (o->flags & observer::flag_failed) continue; - // don't interrupt with queries that are already in-flight - if ((o->flags & observer::flag_alive) == 0) continue; - o->flags &= ~(observer::flag_queried | observer::flag_alive); - } - return find_data::invoke(o); - } - - entry e; - e["y"] = "q"; - e["q"] = "find_node"; - entry& a = e["a"]; - - // This logic will obfuscate the target info-hash - // we're looking up, in order to preserve more privacy - // on the DHT. This is done by only including enough - // bits in the info-hash for the node we're querying to - // give a good answer, but not more. - - // now, obfuscate the bits past shared_prefix + 5 - node_id obfuscated_target = generate_random_id(); - obfuscated_target >>= shared_prefix + 3; - obfuscated_target^= m_target; - a["target"] = obfuscated_target.to_string(); - - return m_node.m_rpc.invoke(e, o->target_ep(), o); -} - -void obfuscated_get_peers::done() -{ - if (!m_obfuscated) return find_data::done(); - - // oops, we failed to switch over to the non-obfuscated - // mode early enough. do it now - - boost::intrusive_ptr ta(new find_data(m_node, m_target - , m_data_callback - , m_nodes_callback - , m_noseeds)); - - // don't call these when the obfuscated_get_peers - // is done, we're passing them on to be called when - // ta completes. - m_data_callback.clear(); - m_nodes_callback.clear(); - -#ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(traversal) << " [" << this << "]" - << " obfuscated get_peers phase 1 done, spawning get_peers [" << ta.get() << "]"; -#endif - - int num_added = 0; - for (std::vector::iterator i = m_results.begin() - , end(m_results.end()); i != end && num_added < 16; ++i) - { - observer_ptr o = *i; - - // only add nodes whose node ID we know and that - // we know are alive - if (o->flags & observer::flag_no_id) continue; - if ((o->flags & observer::flag_alive) == 0) continue; - - ta->add_entry(o->id(), o->target_ep(), observer::flag_initial); - ++num_added; - } - - ta->start(); - - find_data::done(); -} - } } // namespace libtorrent::dht diff --git a/src/kademlia/get_item.cpp b/src/kademlia/get_item.cpp new file mode 100644 index 000000000..fd2184f13 --- /dev/null +++ b/src/kademlia/get_item.cpp @@ -0,0 +1,231 @@ +/* + +Copyright (c) 2013, Steven Siloti +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 +#include +#include + +#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && !TORRENT_NO_ASSERTS +#include +#endif + +namespace libtorrent { namespace dht +{ + +void get_item::got_data(lazy_entry const* v, + char const* pk, + boost::uint64_t seq, + char const* sig) +{ + if (pk && sig) + { + if (hasher(pk, item_pk_len).final() != m_target) + return; + + if (m_data.empty() || m_data.seq() < seq) + { + if (!m_data.assign(v, seq, pk, sig)) + return; + } + } + else if (m_data.empty()) + { + std::pair buf = v->data_section(); + if (hasher(buf.first, buf.second).final() != m_target) + return; + + m_data.assign(v); + bool put_requested = m_data_callback(m_data); + if (put_requested) + { +#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && !TORRENT_NO_ASSERTS + std::vector buffer; + bencode(std::back_inserter(buffer), m_data.value()); + TORRENT_ASSERT(m_target == hasher(&buffer[0], buffer.size()).final()); +#endif + m_nodes_callback = boost::bind(&get_item::put, this, _1); + } + else + { + // There can only be one true immutable item with a given id + // Now that we've got it and the user doesn't want to do a put + // there's no point in continuing to query other nodes + abort(); + } + } +} + +get_item::get_item( + node_impl& node + , node_id target + , data_callback const& dcallback) + : find_data(node, target, nodes_callback()) + , m_data_callback(dcallback) +{ +} + +char const* get_item::name() const { return "get"; } + +observer_ptr get_item::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) get_item_observer(this, ep, id)); +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + return o; +} + +bool get_item::invoke(observer_ptr o) +{ + if (m_done) + { + m_invoke_count = -1; + return false; + } + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get"; + a["target"] = m_target.to_string(); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +void get_item::done() +{ + if (m_data.is_mutable() || m_data.empty()) + { + bool put_requested = m_data_callback(m_data); + if (put_requested) + { +#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && !TORRENT_NO_ASSERTS + if (m_data.is_mutable()) + { + TORRENT_ASSERT(m_target == hasher(m_data.pk(), item_pk_len).final()); + } + else + { + std::vector buffer; + bencode(std::back_inserter(buffer), m_data.value()); + TORRENT_ASSERT(m_target == hasher(&buffer[0], buffer.size()).final()); + } +#endif + m_nodes_callback = boost::bind(&get_item::put, this, _1); + } + } + find_data::done(); +} + +void get_item::put(std::vector > const& v) +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "sending put [ v: " << m_data.value() + << " seq: " << (m_data.is_mutable() ? m_data.seq() : -1) + << " nodes: " << v.size() << " ]" ; +#endif + + // create a dummy traversal_algorithm + boost::intrusive_ptr algo( + new traversal_algorithm(m_node, (node_id::min)())); + + // store on the first k nodes + for (std::vector >::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << " put-distance: " << (160 - distance_exp(m_target, i->first.id)); +#endif + + void* ptr = m_node.m_rpc.allocate_observer(); + if (ptr == 0) return; + observer_ptr o(new (ptr) announce_observer(algo, i->first.ep(), i->first.id)); +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + entry e; + e["y"] = "q"; + e["q"] = "put"; + entry& a = e["a"]; + a["v"] = m_data.value(); + a["token"] = i->second; + if (m_data.is_mutable()) + { + a["k"] = std::string(m_data.pk(), item_pk_len); + a["seq"] = m_data.seq(); + a["sig"] = std::string(m_data.sig(), item_sig_len); + } + m_node.m_rpc.invoke(e, i->first.ep(), o); + } +} + +void get_item_observer::reply(msg const& m) +{ + char const* pk = NULL; + char const* sig = NULL; + boost::uint64_t seq = 0; + + lazy_entry const* r = m.message.dict_find_dict("r"); + if (!r) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "[" << m_algorithm.get() << "] missing response dict"; +#endif + return; + } + + lazy_entry const* k = r->dict_find_string("k"); + if (k && k->string_length() == item_pk_len) + pk = k->string_ptr(); + + lazy_entry const* s = r->dict_find_string("sig"); + if (s && s->string_length() == item_sig_len) + sig = s->string_ptr(); + + lazy_entry const* q = r->dict_find_int("seq"); + if (q) + seq = q->int_value(); + else if (pk && sig) + return; + + lazy_entry const* v = r->dict_find("v"); + if (v) + { + static_cast(m_algorithm.get())->got_data(v, pk, seq, sig); + } + + find_data_observer::reply(m); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/get_peers.cpp b/src/kademlia/get_peers.cpp new file mode 100644 index 000000000..b4c275322 --- /dev/null +++ b/src/kademlia/get_peers.cpp @@ -0,0 +1,313 @@ +/* + +Copyright (c) 2006-2013, Arvid Norberg & Daniel Wallin +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 +#include +#include + +namespace libtorrent { namespace dht +{ + +using detail::read_endpoint_list; +using detail::read_v4_endpoint; +#if TORRENT_USE_IPV6 +using detail::read_v6_endpoint; +#endif + +void get_peers_observer::reply(msg const& m) +{ + lazy_entry const* r = m.message.dict_find_dict("r"); + if (!r) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "[" << m_algorithm.get() << "] missing response dict"; +#endif + return; + } + + // look for peers + lazy_entry const* n = r->dict_find_list("values"); + if (n) + { + std::vector peer_list; + if (n->list_size() == 1 && n->list_at(0)->type() == lazy_entry::string_t) + { + // assume it's mainline format + char const* peers = n->list_at(0)->string_ptr(); + char const* end = peers + n->list_at(0)->string_length(); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + lazy_entry const* id = r->dict_find_string("id"); + if (id && id->string_length() == 20) + { + TORRENT_LOG(traversal) + << "[" << m_algorithm.get() << "] PEERS" + << " invoke-count: " << m_algorithm->invoke_count() + << " branch-factor: " << m_algorithm->branch_factor() + << " addr: " << m.addr + << " id: " << node_id(id->string_ptr()) + << " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr())) + << " p: " << ((end - peers) / 6); + } +#endif + while (end - peers >= 6) + peer_list.push_back(read_v4_endpoint(peers)); + } + else + { + // assume it's uTorrent/libtorrent format + read_endpoint_list(n, peer_list); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + lazy_entry const* id = r->dict_find_string("id"); + if (id && id->string_length() == 20) + { + TORRENT_LOG(traversal) + << "[" << m_algorithm.get() << "] PEERS" + << " invoke-count: " << m_algorithm->invoke_count() + << " branch-factor: " << m_algorithm->branch_factor() + << " addr: " << m.addr + << " id: " << node_id(id->string_ptr()) + << " distance: " << distance_exp(m_algorithm->target(), node_id(id->string_ptr())) + << " p: " << n->list_size(); + } +#endif + } + static_cast(m_algorithm.get())->got_peers(peer_list); + } + + find_data_observer::reply(m); +} + +void get_peers::got_peers(std::vector const& peers) +{ + if (m_data_callback) m_data_callback(peers); +} + +get_peers::get_peers( + node_impl& node + , node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : find_data(node, target, ncallback) + , m_data_callback(dcallback) + , m_noseeds(noseeds) +{ +} + +char const* get_peers::name() const { return "get_peers"; } + +bool get_peers::invoke(observer_ptr o) +{ + if (m_done) + { + m_invoke_count = -1; + return false; + } + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get_peers"; + a["info_hash"] = m_target.to_string(); + if (m_noseeds) a["noseed"] = 1; + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +observer_ptr get_peers::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + return o; +} + +obfuscated_get_peers::obfuscated_get_peers( + node_impl& node + , node_id info_hash + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : get_peers(node, info_hash, dcallback, ncallback, noseeds) + , m_obfuscated(true) +{ +} + +char const* obfuscated_get_peers::name() const +{ return !m_obfuscated ? get_peers::name() : "get_peers [obfuscated]"; } + +observer_ptr obfuscated_get_peers::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + if (m_obfuscated) + { + observer_ptr o(new (ptr) obfuscated_get_peers_observer(this, ep, id)); +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + return o; + } + else + { + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + return o; + } +} + +bool obfuscated_get_peers::invoke(observer_ptr o) +{ + if (!m_obfuscated) return get_peers::invoke(o); + + node_id id = o->id(); + int shared_prefix = 160 - distance_exp(id, m_target); + + // when we get close to the target zone in the DHT + // start using the correct info-hash, in order to + // start receiving peers + if (shared_prefix > m_node.m_table.depth() - 10) + { + m_obfuscated = false; + // clear the queried bits on all successful nodes in + // our node-list for this traversal algorithm, to + // allow the get_peers traversal to regress in case + // nodes further down end up being dead + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + observer* o = i->get(); + // don't re-request from nodes that didn't respond + if (o->flags & observer::flag_failed) continue; + // don't interrupt with queries that are already in-flight + if ((o->flags & observer::flag_alive) == 0) continue; + o->flags &= ~(observer::flag_queried | observer::flag_alive); + } + return get_peers::invoke(o); + } + + entry e; + e["y"] = "q"; + e["q"] = "find_node"; + entry& a = e["a"]; + + // This logic will obfuscate the target info-hash + // we're looking up, in order to preserve more privacy + // on the DHT. This is done by only including enough + // bits in the info-hash for the node we're querying to + // give a good answer, but not more. + + // now, obfuscate the bits past shared_prefix + 5 + node_id obfuscated_target = generate_random_id(); + obfuscated_target >>= shared_prefix + 3; + obfuscated_target^= m_target; + a["target"] = obfuscated_target.to_string(); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +void obfuscated_get_peers::done() +{ + if (!m_obfuscated) return get_peers::done(); + + // oops, we failed to switch over to the non-obfuscated + // mode early enough. do it now + + boost::intrusive_ptr ta(new get_peers(m_node, m_target + , m_data_callback + , m_nodes_callback + , m_noseeds)); + + // don't call these when the obfuscated_get_peers + // is done, we're passing them on to be called when + // ta completes. + m_data_callback.clear(); + m_nodes_callback.clear(); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << " [" << this << "]" + << " obfuscated get_peers phase 1 done, spawning get_peers [" << ta.get() << "]"; +#endif + + int num_added = 0; + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end && num_added < 16; ++i) + { + observer_ptr o = *i; + + // only add nodes whose node ID we know and that + // we know are alive + if (o->flags & observer::flag_no_id) continue; + if ((o->flags & observer::flag_alive) == 0) continue; + + ta->add_entry(o->id(), o->target_ep(), observer::flag_initial); + ++num_added; + } + + ta->start(); + + get_peers::done(); +} + +void obfuscated_get_peers_observer::reply(msg const& m) +{ + lazy_entry const* r = m.message.dict_find_dict("r"); + if (!r) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "[" << m_algorithm.get() + << "] missing response dict"; +#endif + return; + } + + lazy_entry const* id = r->dict_find_string("id"); + if (!id || id->string_length() != 20) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "[" << m_algorithm.get() + << "] invalid id in response"; +#endif + return; + } + + traversal_observer::reply(m); + + done(); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/item.cpp b/src/kademlia/item.cpp new file mode 100644 index 000000000..c3f88bb33 --- /dev/null +++ b/src/kademlia/item.cpp @@ -0,0 +1,177 @@ +/* + +Copyright (c) 2013, Steven Siloti +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 +#include +#include + +#include "ed25519.h" + +namespace libtorrent { namespace dht +{ + +namespace +{ + enum { canonical_length = 1100 }; + int canonical_string(std::pair v, boost::uint64_t seq, char out[canonical_length]) + { + int len = snprintf(out, canonical_length, "3:seqi%" PRId64 "e1:v", seq); + memcpy(out + len, v.first, v.second); + len += v.second; + TORRENT_ASSERT(len <= canonical_length); + return len; + } +} + +bool verify_mutable_item(std::pair v, + boost::uint64_t seq, + char const* pk, + char const* sig) +{ +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(v.first, v.second); + VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); + VALGRIND_CHECK_MEM_IS_DEFINED(sig, item_sig_len); +#endif + + char str[canonical_length]; + int len = canonical_string(v, seq, str); + + return ed25519_verify((unsigned char const*)sig, + (unsigned char const*)str, + len, + (unsigned char const*)pk) == 1; +} + +void sign_mutable_item(std::pair v, + boost::uint64_t seq, + char const* pk, + char const* sk, + char* sig) +{ +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(v.first, v.second); + VALGRIND_CHECK_MEM_IS_DEFINED(sk, item_sk_len); + VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); +#endif + + char str[canonical_length]; + int len = canonical_string(v, seq, str); + + ed25519_sign((unsigned char*)sig, + (unsigned char const*)str, + len, + (unsigned char const*)pk, + (unsigned char const*)sk + ); +} + +sha1_hash mutable_item_cas(std::pair v, boost::uint64_t seq) +{ + char str[canonical_length]; + int len = canonical_string(v, seq, str); + return hasher(str, len).final(); +} + +item::item(entry const& v, boost::uint64_t seq, char const* pk, char const* sk) +{ + assign(v, seq, pk, sk); +} + +item::item(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig) +{ + if (!assign(v, seq, pk, sig)) + throw invalid_item(); +} + +item::item(lazy_item const& i) + : m_seq(i.seq) + , m_mutable(i.is_mutable()) +{ + m_value = *i.value; + // if this is a mutable item lazy_item will have already verified it + memcpy(m_pk, i.pk, item_pk_len); + memcpy(m_sig, i.sig, item_sig_len); +} + +void item::assign(entry const& v, boost::uint64_t seq, char const* pk, char const* sk) +{ + m_value = v; + if (pk && sk) + { + char buffer[1000]; + int bsize = bencode(buffer, v); + TORRENT_ASSERT(bsize <= 1000); + sign_mutable_item(std::make_pair(buffer, bsize), seq, pk, sk, m_sig); + memcpy(m_pk, pk, item_pk_len); + m_seq = seq; + m_mutable = true; + } + else + m_mutable = false; +} + +bool item::assign(lazy_entry const* v, boost::uint64_t seq, char const* pk, char const* sig) +{ + TORRENT_ASSERT(v->data_section().second <= 1000); + if (pk && sig) + { + if (!verify_mutable_item(v->data_section(), seq, pk, sig)) + return false; + memcpy(m_pk, pk, item_pk_len); + memcpy(m_sig, sig, item_sig_len); + m_seq = seq; + m_mutable = true; + } + else + m_mutable = false; + + m_value = *v; + return true; +} + +sha1_hash item::cas() +{ + TORRENT_ASSERT(m_mutable); + char buffer[1000]; + int bsize = bencode(buffer, m_value); + return mutable_item_cas(std::make_pair(buffer, bsize), m_seq); +} + +lazy_item::lazy_item(lazy_entry const* v, char const* pk, char const* sig, boost::uint64_t seq) + : value(v), pk(pk), sig(sig), seq(seq) +{ + if (is_mutable() && !verify_mutable_item(v->data_section(), seq, pk, sig)) + throw invalid_item(); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index 2bd16b119..cd906f1c0 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -48,12 +48,11 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/rpc_manager.hpp" #include "libtorrent/kademlia/routing_table.hpp" #include "libtorrent/kademlia/node.hpp" -#include +#include "libtorrent/kademlia/dht_observer.hpp" #include "libtorrent/kademlia/refresh.hpp" -#include "libtorrent/kademlia/find_data.hpp" - -#include "ed25519.h" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/get_item.hpp" #ifdef TORRENT_USE_VALGRIND #include @@ -370,7 +369,7 @@ void node_impl::announce(sha1_hash const& info_hash, int listen_port, bool seed // search for nodes with ids close to id or with peers // for info-hash id. then send announce_peer to them. - boost::intrusive_ptr ta; + boost::intrusive_ptr ta; if (m_settings.privacy_lookups) { ta.reset(new obfuscated_get_peers(*this, info_hash, f @@ -379,7 +378,7 @@ void node_impl::announce(sha1_hash const& info_hash, int listen_port, bool seed } else { - ta.reset(new find_data(*this, info_hash, f + ta.reset(new get_peers(*this, info_hash, f , boost::bind(&announce_fun, _1, boost::ref(*this) , listen_port, info_hash, seed), seed)); } @@ -387,6 +386,17 @@ void node_impl::announce(sha1_hash const& info_hash, int listen_port, bool seed ta->start(); } +void node_impl::get_item(sha1_hash const& target, boost::function f) +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "starting get for [ " << target << " ]" ; +#endif + + boost::intrusive_ptr ta; + ta.reset(new dht::get_item(*this, target, f)); + ta->start(); +} + void node_impl::tick() { node_id target; @@ -512,9 +522,9 @@ void node_impl::lookup_peers(sha1_hash const& info_hash, int prefix, entry& repl return; } -namespace +namespace detail { - void write_nodes_entry(entry& r, nodes_t const& nodes) + void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes) { bool ipv6_nodes = false; entry& n = r["nodes"]; @@ -550,6 +560,7 @@ namespace } } } +using detail::write_nodes_entry; // verifies that a message has all the required // entries and returns them in ret @@ -890,8 +901,8 @@ void node_impl::incoming_request(msg const& m, entry& e) {"v", lazy_entry::none_t, 0, 0}, {"seq", lazy_entry::int_t, 0, key_desc_t::optional}, // public key - {"k", lazy_entry::string_t, 32, key_desc_t::optional}, - {"sig", lazy_entry::string_t, 64, key_desc_t::optional}, + {"k", lazy_entry::string_t, item_pk_len, key_desc_t::optional}, + {"sig", lazy_entry::string_t, item_sig_len, key_desc_t::optional}, {"cas", lazy_entry::string_t, 20, key_desc_t::optional}, }; @@ -918,7 +929,7 @@ void node_impl::incoming_request(msg const& m, entry& e) if (!mutable_put) target = hasher(buf.first, buf.second).final(); else - target = hasher(msg_keys[3]->string_ptr(), 32).final(); + target = hasher(msg_keys[3]->string_ptr(), item_pk_len).final(); // fprintf(stderr, "%s PUT target: %s\n" // , mutable_put ? "mutable":"immutable" @@ -969,25 +980,16 @@ void node_impl::incoming_request(msg const& m, entry& e) else { // mutable put, we must verify the signature - // generate the message digest by merging the sequence number and the - - char seq[1100]; - int len = snprintf(seq, sizeof(seq), "3:seqi%" PRId64 "e1:v", msg_keys[2]->int_value()); - std::pair buf = msg_keys[1]->data_section(); - memcpy(seq + len, buf.first, buf.second); - len += buf.second; - TORRENT_ASSERT(len <= 1100); #ifdef TORRENT_USE_VALGRIND - VALGRIND_CHECK_MEM_IS_DEFINED(buf.first, buf.second); - VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4]->string_ptr(), 64); - VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[3]->string_ptr(), 32); - VALGRIND_CHECK_MEM_IS_DEFINED(seq, len); + VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4]->string_ptr(), item_sig_len); + VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[3]->string_ptr(), item_pk_len); #endif // msg_keys[4] is the signature, msg_keys[3] is the public key - if (ed25519_verify((unsigned char const*)msg_keys[4]->string_ptr() - , (unsigned char const*)seq, len - , (unsigned char const*)msg_keys[3]->string_ptr()) != 1) + if (!verify_mutable_item(msg_keys[1]->data_section() + , msg_keys[2]->int_value() + , msg_keys[3]->string_ptr() + , msg_keys[4]->string_ptr())) { incoming_error(e, "invalid signature", 206); return; @@ -1035,10 +1037,7 @@ void node_impl::incoming_request(msg const& m, entry& e) // matches the expected value before replacing it if (msg_keys[5]) { - int len = snprintf(seq, sizeof(seq), "3:seqi%" PRId64 "e1:v", item->seq); - memcpy(seq + len, item->value, item->size); - len += item->size; - sha1_hash h = hasher(seq, len).final(); + sha1_hash h = mutable_item_cas(std::make_pair(item->value, item->size), item->seq); if (h != sha1_hash(msg_keys[5]->string_ptr())) { diff --git a/src/kademlia/refresh.cpp b/src/kademlia/refresh.cpp index c63865bc8..31e08ccd8 100644 --- a/src/kademlia/refresh.cpp +++ b/src/kademlia/refresh.cpp @@ -49,7 +49,7 @@ refresh::refresh( node_impl& node , node_id target , done_callback const& callback) - : find_data(node, target, find_data::data_callback(), callback, false) + : get_peers(node, target, get_peers::data_callback(), callback, false) { } @@ -61,7 +61,7 @@ char const* refresh::name() const observer_ptr refresh::new_observer(void* ptr , udp::endpoint const& ep, node_id const& id) { - observer_ptr o(new (ptr) find_data_observer(this, ep, id)); + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); #if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS o->m_in_constructor = false; #endif diff --git a/src/kademlia/traversal_algorithm.cpp b/src/kademlia/traversal_algorithm.cpp index 7337289c6..40e2ae95f 100644 --- a/src/kademlia/traversal_algorithm.cpp +++ b/src/kademlia/traversal_algorithm.cpp @@ -500,5 +500,18 @@ void traversal_observer::reply(msg const& m) } } +void traversal_algorithm::abort() +{ + m_num_target_nodes = 0; + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + observer& o = **i; + if (o.flags & observer::flag_queried) + o.flags |= observer::flag_done; + } + done(); +} + } } // namespace libtorrent::dht diff --git a/test/Jamfile b/test/Jamfile index 22f85c2f2..ca9759950 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -52,7 +52,7 @@ rule link_libtorrent ( properties * ) else { result += - /torrent//torrent/static/static/on ; + /torrent//torrent/static/static/on/on ; } return $(result) ; } diff --git a/test/test.hpp b/test/test.hpp index e4423e1f6..9b874a8e7 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -55,7 +55,7 @@ extern "C" int EXPORT ed25519_create_seed(unsigned char *seed); void EXPORT ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); void EXPORT ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); - int EXPORT ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *private_key); + int EXPORT ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key); void EXPORT ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); void EXPORT ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); } diff --git a/test/test_dht.cpp b/test/test_dht.cpp index 759f7b724..a5a453876 100644 --- a/test/test_dht.cpp +++ b/test/test_dht.cpp @@ -32,6 +32,7 @@ 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" @@ -41,6 +42,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/item.hpp" #include #include "test.hpp" @@ -57,6 +59,8 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; using namespace libtorrent::dht; +void nop() {} + sha1_hash to_hash(char const* s) { sha1_hash ret; @@ -84,13 +88,13 @@ void node_push_back(void* userdata, libtorrent::dht::node_entry const& n) void nop(void* userdata, libtorrent::dht::node_entry const& n) {} -std::list > g_responses; +std::list > g_sent_packets; struct mock_socket : udp_socket_interface { bool send_packet(entry& msg, udp::endpoint const& ep, int flags) { - g_responses.push_back(std::make_pair(ep, msg)); + g_sent_packets.push_back(std::make_pair(ep, msg)); return true; } }; @@ -121,7 +125,23 @@ boost::array generate_key() static const std::string no; -void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep +std::list >::iterator +find_packet(udp::endpoint ep) +{ + return std::find_if(g_sent_packets.begin(), g_sent_packets.end() + , boost::bind(&std::pair::first, _1) == ep); +} + +void lazy_from_entry(entry const& e, lazy_entry& l) +{ + error_code ec; + static char inbuf[1500]; + int len = bencode(inbuf, e); + int ret = lazy_bdecode(inbuf, inbuf + len, l, ec); + TEST_CHECK(ret == 0); +} + +void send_dht_request(node_impl& node, char const* msg, udp::endpoint const& ep , lazy_entry* reply, char const* t = "10", char const* info_hash = 0 , char const* name = 0, std::string const token = std::string(), int port = 0 , char const* target = 0, entry const* value = 0 @@ -171,22 +191,81 @@ void send_dht_msg(node_impl& node, char const* msg, udp::endpoint const& ep node.incoming(m); // by now the node should have invoked the send function and put the - // response in g_responses + // response in g_sent_packets std::list >::iterator i - = std::find_if(g_responses.begin(), g_responses.end() - , boost::bind(&std::pair::first, _1) == ep); - if (i == g_responses.end()) + = find_packet(ep); + if (i == g_sent_packets.end()) { TEST_ERROR("not response from DHT node"); return; } - static char inbuf[1500]; - int len = bencode(inbuf, i->second); - g_responses.erase(i); - int ret = lazy_bdecode(inbuf, inbuf + len, *reply, ec); - TEST_CHECK(ret == 0); + lazy_from_entry(i->second, *reply); + g_sent_packets.erase(i); +} + +namespace libtorrent { namespace dht { namespace detail +{ + // defined in node.cpp + void write_nodes_entry(entry& r, nodes_t const& nodes); +} } } + +void write_peers(entry::dictionary_type& r, std::set const& peers) +{ + entry::list_type& pe = r["values"].list(); + for (std::set::const_iterator it = peers.begin() + ; it != peers.end(); ++it) + { + std::string endpoint(18, '\0'); + std::string::iterator out = endpoint.begin(); + libtorrent::detail::write_endpoint(*it, out); + endpoint.resize(out - endpoint.begin()); + pe.push_back(entry(endpoint)); + } +} + +void send_dht_response(node_impl& node, lazy_entry const& request, udp::endpoint const& ep + , nodes_t const& nodes = nodes_t() + , std::string const token = std::string(), int port = 0 + , std::set const& peers = std::set() + , char const* target = 0, entry const* value = 0 + , std::string const key = std::string(), std::string const sig = std::string() + , int seq = -1, sha1_hash const* nid = NULL) +{ + entry e; + e["y"] = "r"; + e["t"] = request.dict_find_string_value("t"); +// e["ip"] = endpoint_to_bytes(ep); + entry::dictionary_type& r = e["r"].dict(); + if (nid == NULL) r["id"] = generate_next().to_string(); + else r["id"] = nid->to_string(); + if (!token.empty()) r["token"] = token; + if (port) r["p"] = port; + if (!nodes.empty()) dht::detail::write_nodes_entry(e["r"], nodes); + if (!peers.empty()) write_peers(r, peers); + if (value) r["v"] = *value; + if (!sig.empty()) r["sig"] = sig; + if (!key.empty()) r["k"] = key; + if (seq >= 0) r["seq"] = seq; + char msg_buf[1500]; + int size = bencode(msg_buf, e); +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM +// this yields a lot of output. too much +// std::cerr << "sending: " << e << "\n"; +#endif + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(msg_buf, size); +#endif + + lazy_entry decoded; + error_code ec; + lazy_bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) fprintf(stderr, "lazy_bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, ep); + node.incoming(m); } struct announce_item @@ -220,7 +299,7 @@ void announce_immutable_items(node_impl& node, udp::endpoint const* eps { if ((i % items[j].num_peers) == 0) continue; lazy_entry response; - send_dht_msg(node, "get", eps[i], &response, "10", 0 + send_dht_request(node, "get", eps[i], &response, "10", 0 , 0, no, 0, (char const*)&items[j].target[0]); key_desc_t desc[] = @@ -258,7 +337,7 @@ void announce_immutable_items(node_impl& node, udp::endpoint const* eps TEST_EQUAL(addr, eps[i].address()); } - send_dht_msg(node, "put", eps[i], &response, "10", 0 + send_dht_request(node, "put", eps[i], &response, "10", 0 , 0, token, 0, (char const*)&items[j].target[0], &items[j].ent); key_desc_t desc2[] = @@ -287,7 +366,7 @@ void announce_immutable_items(node_impl& node, udp::endpoint const* eps for (int j = 0; j < num_items; ++j) { lazy_entry response; - send_dht_msg(node, "get", eps[j], &response, "10", 0 + send_dht_request(node, "get", eps[j], &response, "10", 0 , 0, no, 0, (char const*)&items[j].target[0]); key_desc_t desc[] = @@ -327,12 +406,37 @@ struct print_alert : alert_dispatcher } }; + int sum_distance_exp(int s, node_entry const& e, node_id const& ref) { return s + distance_exp(e.id, ref); } -// TODO: 3 test find_data, obfuscated_get_peers and bootstrap +std::vector g_got_peers; + +void get_peers_cb(std::vector const& peers) +{ + g_got_peers.insert(g_got_peers.end(), peers.begin(), peers.end()); +} + +std::vector g_got_items; +dht::item g_put_item; +int g_put_count; + +bool get_item_cb(dht::item& i) +{ + if (!i.empty()) + g_got_items.push_back(i); + if (!g_put_item.empty()) + { + i = g_put_item; + g_put_count++; + return true; + } + return false; +} + +// TODO: 3 test obfuscated_get_peers int test_main() { dht_settings sett; @@ -346,13 +450,13 @@ int test_main() // DHT should be running on port 48199 now lazy_entry response; - lazy_entry const* parsed[10]; + lazy_entry const* parsed[11]; char error_string[200]; bool ret; // ====== ping ====== udp::endpoint source(address::from_string("10.0.0.1"), 20); - send_dht_msg(node, "ping", source, &response, "10"); + send_dht_request(node, "ping", source, &response, "10"); dht::key_desc_t pong_desc[] = { {"y", lazy_entry::string_t, 1, 0}, @@ -376,7 +480,7 @@ int test_main() // ====== invalid message ====== - send_dht_msg(node, "find_node", source, &response, "10"); + send_dht_request(node, "find_node", source, &response, "10"); dht::key_desc_t err_desc[] = { {"y", lazy_entry::string_t, 1, 0}, @@ -406,7 +510,7 @@ int test_main() // ====== get_peers ====== - send_dht_msg(node, "get_peers", source, &response, "10", "01010101010101010101"); + send_dht_request(node, "get_peers", source, &response, "10", "01010101010101010101"); dht::key_desc_t peer1_desc[] = { {"y", lazy_entry::string_t, 1, 0}, @@ -433,7 +537,7 @@ int test_main() // ====== announce ====== - send_dht_msg(node, "announce_peer", source, &response, "10", "01010101010101010101", "test", token, 8080); + send_dht_request(node, "announce_peer", source, &response, "10", "01010101010101010101", "test", token, 8080); dht::key_desc_t ann_desc[] = { {"y", lazy_entry::string_t, 1, 0}, @@ -458,7 +562,7 @@ int test_main() for (int i = 0; i < 100; ++i) { source = udp::endpoint(rand_v4(), 6000); - send_dht_msg(node, "get_peers", source, &response, "10", "01010101010101010101"); + send_dht_request(node, "get_peers", source, &response, "10", "01010101010101010101"); ret = dht::verify_message(&response, peer1_desc, parsed, 4, error_string, sizeof(error_string)); if (ret) @@ -473,14 +577,14 @@ int test_main() fprintf(stderr, " invalid get_peers response: %s\n", error_string); } response.clear(); - send_dht_msg(node, "announce_peer", source, &response, "10", "01010101010101010101" + send_dht_request(node, "announce_peer", source, &response, "10", "01010101010101010101" , "test", token, 8080, 0, 0, false, i >= 50); response.clear(); } // ====== get_peers ====== - send_dht_msg(node, "get_peers", source, &response, "10", "01010101010101010101" + send_dht_request(node, "get_peers", source, &response, "10", "01010101010101010101" , 0, no, 0, 0, 0, true); dht::key_desc_t peer2_desc[] = { @@ -524,7 +628,7 @@ int test_main() // http://libtorrent.org/dht_sec.html source = udp::endpoint(address::from_string("124.31.75.21"), 20); node_id nid = to_hash("1712f6c70c5d6a4ec8a88e4c6ab4c28b95eee401"); - send_dht_msg(node, "find_node", source, &response, "10", 0, 0, std::string() + send_dht_request(node, "find_node", source, &response, "10", 0, 0, std::string() , 0, "0101010101010101010101010101010101010101", 0, false, false, std::string(), std::string(), -1, 0, &nid); dht::key_desc_t nodes_desc[] = { @@ -549,7 +653,7 @@ int test_main() // verify that we reject invalid node IDs // this is now an invalid node-id for 'source' nid[0] = 0x18; - send_dht_msg(node, "find_node", source, &response, "10", 0, 0, std::string() + send_dht_request(node, "find_node", source, &response, "10", 0, 0, std::string() , 0, "0101010101010101010101010101010101010101", 0, false, false, std::string(), std::string(), -1, 0, &nid); ret = dht::verify_message(&response, err_desc, parsed, 2, error_string, sizeof(error_string)); @@ -648,18 +752,18 @@ int test_main() fprintf(stderr, "generating ed25519 keys\n"); unsigned char seed[32]; ed25519_create_seed(seed); - unsigned char private_key[64]; - unsigned char public_key[32]; + char private_key[item_sk_len]; + char public_key[item_pk_len]; - ed25519_create_keypair(public_key, private_key, seed); + ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed); fprintf(stderr, "pub: %s priv: %s\n" - , to_hex(std::string((char*)public_key, 32)).c_str() - , to_hex(std::string((char*)private_key, 64)).c_str()); + , to_hex(std::string(public_key, item_pk_len)).c_str() + , to_hex(std::string(private_key, item_sk_len)).c_str()); TEST_CHECK(ret); - send_dht_msg(node, "get", source, &response, "10", 0 - , 0, no, 0, (char*)&hasher((char*)public_key, 32).final()[0] + send_dht_request(node, "get", source, &response, "10", 0 + , 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0] , 0, false, false, std::string(), std::string(), 64); key_desc_t desc[] = @@ -686,22 +790,20 @@ int test_main() TEST_ERROR(error_string); } - unsigned char signature[64]; + char signature[item_sig_len]; char buffer[1200]; int seq = 4; - int pos = snprintf(buffer, sizeof(buffer), "3:seqi%de1:v", seq); - char* ptr = buffer + pos; - pos += bencode(ptr, items[0].ent); - ed25519_sign(signature, (unsigned char*)buffer, pos, public_key, private_key); - TEST_EQUAL(ed25519_verify(signature, (unsigned char*)buffer, pos, public_key), 1); + std::pair itemv(buffer, bencode(buffer, items[0].ent)); + sign_mutable_item(itemv, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), true); #ifdef TORRENT_USE_VALGRIND - VALGRIND_CHECK_MEM_IS_DEFINED(signature, 64); + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); #endif - send_dht_msg(node, "put", source, &response, "10", 0 + send_dht_request(node, "put", source, &response, "10", 0 , 0, token, 0, 0, &items[0].ent, false, false - , std::string((char*)public_key, 32) - , std::string((char*)signature, 64), seq); + , std::string(public_key, item_pk_len) + , std::string(signature, item_sig_len), seq); key_desc_t desc2[] = { @@ -722,8 +824,8 @@ int test_main() TEST_ERROR(error_string); } - send_dht_msg(node, "get", source, &response, "10", 0 - , 0, no, 0, (char*)&hasher((char*)public_key, 32).final()[0] + send_dht_request(node, "get", source, &response, "10", 0 + , 0, no, 0, (char*)&hasher(public_key, item_pk_len).final()[0] , 0, false, false, std::string(), std::string(), 64); key_desc_t desc3[] = @@ -759,23 +861,21 @@ int test_main() // also test that invalid signatures fail! - pos = snprintf(buffer, sizeof(buffer), "3:seqi%de1:v", seq); - ptr = buffer + pos; - pos += bencode(ptr, items[0].ent); - ed25519_sign(signature, (unsigned char*)buffer, pos, public_key, private_key); - TEST_EQUAL(ed25519_verify(signature, (unsigned char*)buffer, pos, public_key), 1); + itemv.second = bencode(buffer, items[0].ent); + sign_mutable_item(itemv, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), 1); #ifdef TORRENT_USE_VALGRIND - VALGRIND_CHECK_MEM_IS_DEFINED(signature, 64); + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); #endif // break the signature signature[2] ^= 0xaa; - TEST_CHECK(ed25519_verify(signature, (unsigned char*)buffer, pos, public_key) != 1); + TEST_CHECK(verify_mutable_item(itemv, seq, public_key, signature) != 1); - send_dht_msg(node, "put", source, &response, "10", 0 + send_dht_request(node, "put", source, &response, "10", 0 , 0, token, 0, 0, &items[0].ent, false, false - , std::string((char*)public_key, 32) - , std::string((char*)signature, 64), seq); + , std::string(public_key, item_pk_len) + , std::string(signature, item_sig_len), seq); key_desc_t desc_error[] = { @@ -801,23 +901,21 @@ int test_main() // === test CAS put === // this is the hash that we expect to be there - sha1_hash cas = hasher(buffer, pos).final(); + sha1_hash cas = mutable_item_cas(itemv, seq); // increment sequence number ++seq; - pos = snprintf(buffer, sizeof(buffer), "3:seqi%de1:v", seq); - ptr = buffer + pos; // put item 1 - pos += bencode(ptr, items[1].ent); - ed25519_sign(signature, (unsigned char*)buffer, pos, public_key, private_key); - TEST_EQUAL(ed25519_verify(signature, (unsigned char*)buffer, pos, public_key), 1); + itemv.second = bencode(buffer, items[1].ent); + sign_mutable_item(itemv, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, seq, public_key, signature), 1); #ifdef TORRENT_USE_VALGRIND - VALGRIND_CHECK_MEM_IS_DEFINED(signature, 64); + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); #endif - send_dht_msg(node, "put", source, &response, "10", 0 + send_dht_request(node, "put", source, &response, "10", 0 , 0, token, 0, 0, &items[1].ent, false, false - , std::string((char*)public_key, 32) - , std::string((char*)signature, 64), seq + , std::string(public_key, item_pk_len) + , std::string(signature, item_sig_len), seq , (char const*)&cas[0]); ret = verify_message(&response, desc2, parsed, 1, error_string, sizeof(error_string)); @@ -837,10 +935,10 @@ int test_main() // put the same message again. This should fail because the // CAS hash is outdated, it's not the hash of the value that's // stored anymore - send_dht_msg(node, "put", source, &response, "10", 0 + send_dht_request(node, "put", source, &response, "10", 0 , 0, token, 0, 0, &items[1].ent, false, false - , std::string((char*)public_key, 32) - , std::string((char*)signature, 64), seq + , std::string(public_key, item_pk_len) + , std::string(signature, item_sig_len), seq , (char const*)&cas[0]); ret = verify_message(&response, desc_error, parsed, 2, error_string, sizeof(error_string)); @@ -1231,6 +1329,471 @@ int test_main() , rs[i], to_hex(id.to_string()).c_str()); } } + + // test traversal algorithms + + dht::key_desc_t ping_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 4, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, key_desc_t::last_child}, + }; + + dht::key_desc_t find_node_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 9, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, 0}, + {"target", lazy_entry::string_t, 20, key_desc_t::last_child}, + }; + + dht::key_desc_t get_peers_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 9, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, 0}, + {"info_hash", lazy_entry::string_t, 20, key_desc_t::last_child}, + }; + + dht::key_desc_t get_item_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 3, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, 0}, + {"target", lazy_entry::string_t, 20, key_desc_t::last_child}, + }; + + // bootstrap + + do + { + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + std::vector nodesv; + nodesv.push_back(initial_node); + node.bootstrap(nodesv, boost::bind(&nop)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, find_node_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "find_node"); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "find_node") break; + } + else + { + fprintf(stderr, " invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + udp::endpoint found_node(address_v4::from_string("5.5.5.5"), 2235); + nodes_t nodes; + nodes.push_back(found_node); + g_sent_packets.clear(); + send_dht_response(node, response, initial_node, nodes); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, found_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, find_node_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "find_node"); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "find_node") break; + } + else + { + fprintf(stderr, " invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + send_dht_response(node, response, found_node); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(node.num_global_nodes(), 3); + } while (false); + + // get_peers + + do + { + dht::node_id target = to_hash("1234876923549721020394873245098347598635"); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + node.announce(target, 1234, false, get_peers_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, get_peers_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "get_peers"); + TEST_EQUAL(parsed[5]->string_value(), target.to_string()); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "get_peers") break; + } + else + { + fprintf(stderr, " invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + std::set peers[2]; + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.1"), 4111)); + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.2"), 4112)); + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.3"), 4113)); + + udp::endpoint next_node(address_v4::from_string("5.5.5.5"), 2235); + nodes_t nodes; + nodes.push_back(next_node); + + g_sent_packets.clear(); + send_dht_response(node, response, initial_node, nodes, "10", 1234, peers[0]); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, next_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, get_peers_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "get_peers"); + TEST_EQUAL(parsed[5]->string_value(), target.to_string()); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "get_peers") break; + } + else + { + fprintf(stderr, " invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.4"), 4114)); + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.5"), 4115)); + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.6"), 4116)); + + g_sent_packets.clear(); + send_dht_response(node, response, next_node, nodes_t(), "11", 1234, peers[1]); + + TEST_CHECK(g_sent_packets.empty()); + + for (int i = 0; i < 2; ++i) + for (std::set::iterator peer = peers[i].begin(); peer != peers[i].end(); ++peer) + { + TEST_CHECK(std::find(g_got_peers.begin(), g_got_peers.end(), *peer) != g_got_peers.end()); + } + g_got_peers.clear(); + } while (false); + + // immutable get + + do + { + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + node.get_item(items[0].target, get_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, get_item_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "get"); + TEST_EQUAL(parsed[5]->string_value(), items[0].target.to_string()); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "get") break; + } + else + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + send_dht_response(node, response, initial_node, nodes_t(), "10", 1234, std::set() + , NULL, &items[0].ent); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(g_got_items.size(), 1); + if (g_got_items.empty()) break; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + g_got_items.clear(); + + } while (false); + + // mutable get + + do + { + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + sha1_hash target = hasher(public_key, item_pk_len).final(); + node.get_item(target, get_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(&response, get_item_desc, parsed, 6, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "get"); + TEST_EQUAL(parsed[5]->string_value(), target.to_string()); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "get") break; + } + else + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + + itemv.second = bencode(buffer, items[0].ent); + sign_mutable_item(itemv, seq, public_key, private_key, signature); + send_dht_response(node, response, initial_node, nodes_t(), "10", 1234, std::set() + , NULL, &items[0].ent, std::string(public_key, item_pk_len), std::string(signature, item_sig_len), seq); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(g_got_items.size(), 1); + if (g_got_items.empty()) break; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + TEST_CHECK(memcmp(g_got_items.front().pk(), public_key, item_pk_len) == 0); + TEST_CHECK(memcmp(g_got_items.front().sig(), signature, item_sig_len) == 0); + TEST_EQUAL(g_got_items.front().seq(), seq); + g_got_items.clear(); + + } while (false); + + dht::key_desc_t put_immutable_item_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 3, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, 0}, + {"token", lazy_entry::string_t, 2, 0}, + {"v", lazy_entry::none_t, 0, key_desc_t::last_child}, + }; + + dht::key_desc_t put_mutable_item_desc[] = { + {"y", lazy_entry::string_t, 1, 0}, + {"t", lazy_entry::string_t, 2, 0}, + {"q", lazy_entry::string_t, 3, 0}, + {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, + {"id", lazy_entry::string_t, 20, 0}, + {"cas", lazy_entry::string_t, 20, key_desc_t::optional}, + {"k", lazy_entry::string_t, item_pk_len, 0}, + {"seq", lazy_entry::int_t, 0, 0}, + {"sig", lazy_entry::string_t, item_sig_len, 0}, + {"token", lazy_entry::string_t, 2, 0}, + {"v", lazy_entry::none_t, 0, key_desc_t::last_child}, + }; + + // immutable put + + do + { + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + enum { num_test_nodes = 2 }; + node_entry nodes[num_test_nodes] = + { node_entry(generate_next(), udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) + , node_entry(generate_next(), udp::endpoint(address_v4::from_string("5.5.5.5"), 1235)) }; + + for (int i = 0; i < num_test_nodes; ++i) + node.m_table.add_node(nodes[i]); + + g_put_item.assign(items[0].ent); + node.get_item(items[0].target, get_item_cb); + + TEST_EQUAL(g_sent_packets.size(), num_test_nodes); + if (g_sent_packets.size() != num_test_nodes) break; + + for (int i = 0; i < num_test_nodes; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(&response, get_item_desc, parsed, 6, error_string, sizeof(error_string)); + if (!ret) + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + send_dht_response(node, response, nodes[i].ep(), nodes_t(), t, 1234, + std::set(), 0, 0, std::string(), std::string(), -1, &nodes[i].id); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_put_count, 1); + TEST_EQUAL(g_sent_packets.size(), num_test_nodes); + if (g_sent_packets.size() != num_test_nodes) break; + + itemv.second = bencode(buffer, items[0].ent); + + for (int i = 0; i < num_test_nodes; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(&response, put_immutable_item_desc, parsed, 7, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "put"); + std::pair v = parsed[6]->data_section(); + TEST_EQUAL(v.second, itemv.second); + TEST_CHECK(memcmp(v.first, itemv.first, itemv.second) == 0); + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + TEST_EQUAL(parsed[5]->string_value(), t); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "put") continue; + } + else + { + fprintf(stderr, " invalid immutable put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + } + + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + + } while (false); + + // mutable put + + do + { + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + enum { num_test_nodes = 2 }; + node_entry nodes[num_test_nodes] = + { node_entry(generate_next(), udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) + , node_entry(generate_next(), udp::endpoint(address_v4::from_string("5.5.5.5"), 1235)) }; + + for (int i = 0; i < num_test_nodes; ++i) + node.m_table.add_node(nodes[i]); + + sha1_hash target = hasher(public_key, item_pk_len).final(); + g_put_item.assign(items[0].ent, seq, public_key, private_key); + std::string sig(g_put_item.sig(), item_sig_len); + node.get_item(target, get_item_cb); + + TEST_EQUAL(g_sent_packets.size(), num_test_nodes); + if (g_sent_packets.size() != num_test_nodes) break; + + for (int i = 0; i < num_test_nodes; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(&response, get_item_desc, parsed, 6, error_string, sizeof(error_string)); + if (!ret) + { + fprintf(stderr, " invalid mutable put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + send_dht_response(node, response, nodes[i].ep(), nodes_t(), t, 1234, + std::set(), 0, 0, std::string(), std::string(), -1, &nodes[i].id); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_put_count, 1); + TEST_EQUAL(g_sent_packets.size(), num_test_nodes); + if (g_sent_packets.size() != num_test_nodes) break; + + itemv.second = bencode(buffer, items[0].ent); + + for (int i = 0; i < num_test_nodes; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(&response, put_mutable_item_desc, parsed, 11, error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[0]->string_value(), "q"); + TEST_EQUAL(parsed[2]->string_value(), "put"); + TEST_EQUAL(parsed[6]->string_value(), std::string(public_key, item_pk_len)); + TEST_EQUAL(parsed[7]->int_value(), seq); + TEST_EQUAL(parsed[8]->string_value(), sig); + std::pair v = parsed[10]->data_section(); + TEST_EQUAL(v.second, itemv.second); + TEST_CHECK(memcmp(v.first, itemv.first, itemv.second) == 0); + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + TEST_EQUAL(parsed[9]->string_value(), t); + if (parsed[0]->string_value() != "q" || parsed[2]->string_value() != "put") continue; + } + else + { + fprintf(stderr, " invalid put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + } + + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + + } while (false); + return 0; }