Merge pull request #123 from aldenml/dht-storage

Implementing dht storage interface.
This commit is contained in:
Arvid Norberg 2015-09-16 20:27:34 -04:00
commit 1fd35c01ad
12 changed files with 927 additions and 389 deletions

2
.gitignore vendored
View File

@ -15,3 +15,5 @@ Makefile
build-aux
test_tmp_*
.DS_Store
.idea

View File

@ -117,6 +117,7 @@ set(sources
# -- kademlia --
set(kademlia_sources
dht_storage
dos_blocker
dht_tracker
node

View File

@ -701,6 +701,7 @@ SOURCES =
;
KADEMLIA_SOURCES =
dht_storage
dht_tracker
node
node_entry

View File

@ -179,6 +179,7 @@ nobase_include_HEADERS = \
extensions/ut_metadata.hpp \
extensions/ut_pex.hpp \
\
kademlia/dht_storage.hpp \
kademlia/dht_tracker.hpp \
kademlia/dht_observer.hpp \
kademlia/direct_request.hpp \

View File

@ -0,0 +1,198 @@
/*
Copyright (c) 2012-2015, Arvid Norberg, Alden Torres
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DHT_STORAGE_HPP
#define DHT_STORAGE_HPP
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <libtorrent/config.hpp>
#include <libtorrent/time.hpp>
#include <libtorrent/socket.hpp>
#include <libtorrent/sha1_hash.hpp>
#include <libtorrent/address.hpp>
#include <libtorrent/session_settings.hpp>
#include <libtorrent/performance_counters.hpp>
#include <libtorrent/kademlia/item.hpp>
namespace libtorrent {
namespace dht
{
// The DHT storage interface is a pure virtual class that can
// be implemented to customize how the data for the DHT is stored.
//
// The default storage implementation uses three maps in RAM to save
// the peers, mutable and immutable items and it's designed to
// provide a fast and fully compliant behavior of the BEPs.
//
// libtorrent comes with one built-in storage implementation:
// ``dht_default_storage`` (private non-accessible class). Its
// constructor function is called dht_default_storage_constructor().
//
struct TORRENT_EXPORT dht_storage_interface
{
#ifndef TORRENT_NO_DEPRECATE
// This function returns the number of torrents tracked by
// the DHT at the moment. It's used to fill session_status.
// It's deprecated.
//
virtual size_t num_torrents() const = 0;
// This function returns the sum of all of peers per torrent
// tracker byt the DHT at the moment.
// It's deprecated.
//
virtual size_t num_peers() const = 0;
#endif
// This function retrieve the peers tracked by the DHT
// corresponding to the given info_hash. You can specify if
// you want only seeds and/or you are scraping the data.
//
// For future implementers:
// If the torrent tracked contains a name, such a name
// must be stored as a string in peers["n"]
//
// If the scrape parameter is true, you should fill these keys:
// peers["BFpe"] - with the standard bit representation of a
// 256 bloom filter containing the downloaders
// peers["BFsd"] - with the standard bit representation of a
// 256 bloom filter containing the seeders
//
// If the scrape parameter is false, you should fill the
// key peers["values"] with a list containing a subset of
// peers tracked by the given info_hash. Such a list should
// consider the value of dht_settings::max_peers_reply.
// If noseed is true only peers marked as no seed should be included.
//
// returns true if an entry with the info_hash is found and
// the data is returned inside the (entry) out parameter peers.
//
virtual bool get_peers(sha1_hash const& info_hash
, bool noseed, bool scrape
, entry& peers) const = 0;
// This function is named announce_peer for consistency with the
// upper layers, but has nothing to do with networking. Its only
// responsibility is store the peer in such a way that it's returned
// in the entry with the lookup_peers.
//
// The ``name`` parameter is the name of the torrent if provided in
// the announce_peer DHT message. The length of this value should
// have a maximum length in the final storage. The default
// implementation truncate the value for a maximum of 50 characters.
//
virtual void announce_peer(sha1_hash const& info_hash
, tcp::endpoint const& endp
, std::string const& name, bool seed) = 0;
// This function retrieves the immutable item given its target hash.
//
// For future implementers:
// The value should be returned as an entry in the key item["v"].
//
// returns true if the item is found and the data is returned
// inside the (entry) out parameter item.
//
virtual bool get_immutable_item(sha1_hash const& target
, entry& item) const = 0;
// Store the item's data. This layer is only for storage.
// The authentication of the item is performed by the upper layer.
//
// For implementers:
// This data can be stored only if the target is not already
// present. The implementation should consider the value of
// dht_settings::max_dht_items.
//
virtual void put_immutable_item(sha1_hash const& target
, char const* buf, int size
, address const& addr) = 0;
// This function retrieves the sequence number of a mutable item.
//
// returns true if the item is found and the data is returned
// inside the out parameter seq.
//
virtual bool get_mutable_item_seq(sha1_hash const& target
, boost::int64_t& seq) const = 0;
// This function retrieves the mutable stored in the DHT.
//
// For implementers:
// The item sequence should be stored in the key item["seq"].
// if force_fill is true or (0 <= seq and seq < item["seq"])
// the following keys should be filled
// item["v"] - with the value no encoded.
// item["sig"] - with a string representation of the signature.
// item["k"] - with a string represnetation of the public key.
//
// returns true if the item is found and the data is returned
// inside the (entry) out parameter item.
//
virtual bool get_mutable_item(sha1_hash const& target
, boost::int64_t seq, bool force_fill
, entry& item) const = 0;
// Store the item's data. This layer is only for storage.
// The authentication of the item is performed by the upper layer.
//
// For implementers:
// The sequence number should be checked if the item is already
// present. The implementation should consider the value of
// dht_settings::max_dht_items.
//
virtual void put_mutable_item(sha1_hash const& target
, char const* buf, int size
, char const* sig
, boost::int64_t seq
, char const* pk
, char const* salt, int salt_size
, address const& addr) = 0;
// This function is called periodically (non-constant frequency).
//
// For implementers:
// Use this functions for expire peers or items or any other
// storage cleanup.
//
virtual void tick() = 0;
virtual ~dht_storage_interface() {}
};
TORRENT_EXPORT dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id
, dht_settings const& settings
, counters& counters);
} } // namespace libtorrent::dht
#endif //DHT_STORAGE_HPP

View File

@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <set>
#include <libtorrent/config.hpp>
#include <libtorrent/kademlia/dht_storage.hpp>
#include <libtorrent/kademlia/routing_table.hpp>
#include <libtorrent/kademlia/rpc_manager.hpp>
#include <libtorrent/kademlia/node_id.hpp>
@ -102,65 +103,6 @@ void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes);
void incoming_error(entry& e, char const* msg, int error_code = 203);
// this is the entry for every peer
// the timestamp is there to make it possible
// to remove stale peers
struct peer_entry
{
time_point added;
tcp::endpoint addr;
bool seed;
};
// this is a group. It contains a set of group members
struct torrent_entry
{
std::string name;
std::set<peer_entry> peers;
};
struct dht_immutable_item
{
dht_immutable_item() : value(0), num_announcers(0), size(0) {}
// malloced space for the actual value
char* value;
// this counts the number of IPs we have seen
// announcing this item, this is used to determine
// popularity if we reach the limit of items to store
bloom_filter<128> ips;
// the last time we heard about this
time_point last_seen;
// number of IPs in the bloom filter
int num_announcers;
// size of malloced space pointed to by value
int size;
};
struct ed25519_public_key { char bytes[item_pk_len]; };
struct dht_mutable_item : dht_immutable_item
{
char sig[item_sig_len];
boost::uint64_t seq;
ed25519_public_key key;
char* salt;
int salt_size;
};
// internal
inline bool operator<(ed25519_public_key const& lhs, ed25519_public_key const& rhs)
{
return memcmp(lhs.bytes, rhs.bytes, sizeof(lhs.bytes)) < 0;
}
// internal
inline bool operator<(peer_entry const& lhs, peer_entry const& rhs)
{
return lhs.addr.address() == rhs.addr.address()
? lhs.addr.port() < rhs.addr.port()
: lhs.addr.address() < rhs.addr.address();
}
struct null_type {};
class announce_observer : public observer
@ -174,17 +116,6 @@ public:
void reply(msg const&) { flags |= flag_done; }
};
struct count_peers
{
int* count;
count_peers(int* c): count(c) {}
void operator()(std::pair<libtorrent::dht::node_id
, libtorrent::dht::torrent_entry> const& t)
{
*count += t.second.peers.size();
}
};
struct udp_socket_interface
{
virtual bool has_quota() = 0;
@ -195,16 +126,12 @@ protected:
class TORRENT_EXTRA_EXPORT node : boost::noncopyable
{
typedef std::map<node_id, torrent_entry> table_t;
typedef std::map<node_id, dht_immutable_item> dht_immutable_table_t;
typedef std::map<node_id, dht_mutable_item> dht_mutable_table_t;
public:
node(udp_socket_interface* sock
, libtorrent::dht_settings const& settings, node_id nid
, dht_observer* observer, counters& cnt);
virtual ~node() {}
virtual ~node();
void tick();
void bootstrap(std::vector<udp::endpoint> const& nodes
@ -214,13 +141,10 @@ public:
void unreachable(udp::endpoint const& ep);
void incoming(msg const& m);
int num_torrents() const { return m_map.size(); }
int num_peers() const
{
int ret = 0;
std::for_each(m_map.begin(), m_map.end(), count_peers(&ret));
return ret;
}
#ifndef TORRENT_NO_DEPRECATE
int num_torrents() const { return m_storage->num_torrents(); }
int num_peers() const { return m_storage->num_peers(); }
#endif
int bucket_size(int bucket);
@ -230,7 +154,9 @@ public:
boost::int64_t num_global_nodes() const
{ return m_table.num_global_nodes(); }
int data_size() const { return int(m_map.size()); }
#ifndef TORRENT_NO_DEPRECATE
int data_size() const { return int(m_storage->num_torrents()); }
#endif
#if defined TORRENT_DEBUG
void print_state(std::ostream& os) const
@ -326,10 +252,6 @@ public:
private:
dht_observer* m_observer;
table_t m_map;
dht_immutable_table_t m_immutable_table;
dht_mutable_table_t m_mutable_table;
time_point m_last_tracker_tick;
// the last time we issued a bootstrap or a refresh on our own ID, to expand
@ -341,10 +263,10 @@ private:
udp_socket_interface* m_sock;
counters& m_counters;
};
boost::scoped_ptr<dht_storage_interface> m_storage;
};
} } // namespace libtorrent::dht
#endif // NODE_HPP

View File

@ -4,6 +4,7 @@ lib_LTLIBRARIES = libtorrent-rasterbar.la
if ENABLE_DHT
KADEMLIA_SOURCES = \
kademlia/dht_storage.cpp \
kademlia/dht_tracker.cpp \
kademlia/find_data.cpp \
kademlia/node.cpp \

View File

@ -0,0 +1,527 @@
/*
Copyright (c) 2012-2015, Arvid Norberg, Alden Torres
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "libtorrent/kademlia/dht_storage.hpp"
#include <utility>
#include <boost/bind.hpp>
#include <boost/function/function1.hpp>
#include <boost/tuple/tuple.hpp>
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <libtorrent/socket_io.hpp>
#include <libtorrent/aux_/time.hpp>
#include <libtorrent/config.hpp>
#include <libtorrent/time.hpp>
#include <libtorrent/socket.hpp>
#include <libtorrent/sha1_hash.hpp>
#include <libtorrent/bloom_filter.hpp>
#include <libtorrent/address.hpp>
#include <libtorrent/session_settings.hpp>
#include <libtorrent/performance_counters.hpp>
#include <libtorrent/random.hpp>
#include <libtorrent/kademlia/item.hpp>
#include <libtorrent/kademlia/node_id.hpp>
namespace libtorrent {
namespace dht {
namespace
{
using detail::write_endpoint;
// this is the entry for every peer
// the timestamp is there to make it possible
// to remove stale peers
struct peer_entry
{
time_point added;
tcp::endpoint addr;
bool seed;
};
// internal
bool operator<(peer_entry const& lhs, peer_entry const& rhs)
{
return lhs.addr.address() == rhs.addr.address()
? lhs.addr.port() < rhs.addr.port()
: lhs.addr.address() < rhs.addr.address();
}
// this is a group. It contains a set of group members
struct torrent_entry
{
std::string name;
std::set<peer_entry> peers;
};
#ifndef TORRENT_NO_DEPRECATE
struct count_peers
{
int* count;
count_peers(int* c): count(c) {}
void operator()(std::pair<libtorrent::sha1_hash
, torrent_entry> const& t)
{
*count += t.second.peers.size();
}
};
#endif
// TODO: 2 make this configurable in dht_settings
enum { announce_interval = 30 };
void purge_peers(std::set<peer_entry>& peers)
{
for (std::set<peer_entry>::iterator i = peers.begin()
, end(peers.end()); i != end;)
{
// the peer has timed out
if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now())
peers.erase(i++);
else
++i;
}
}
struct dht_immutable_item
{
dht_immutable_item() : value(0), num_announcers(0), size(0) {}
// malloced space for the actual value
char* value;
// this counts the number of IPs we have seen
// announcing this item, this is used to determine
// popularity if we reach the limit of items to store
bloom_filter<128> ips;
// the last time we heard about this
time_point last_seen;
// number of IPs in the bloom filter
int num_announcers;
// size of malloced space pointed to by value
int size;
};
struct ed25519_public_key { char bytes[item_pk_len]; };
struct dht_mutable_item : dht_immutable_item
{
char sig[item_sig_len];
boost::int64_t seq;
ed25519_public_key key;
char* salt;
int salt_size;
};
void touch_item(dht_immutable_item* f, address const& address)
{
f->last_seen = aux::time_now();
// maybe increase num_announcers if we haven't seen this IP before
sha1_hash iphash;
hash_address(address, iphash);
if (!f->ips.find(iphash))
{
f->ips.set(iphash);
++f->num_announcers;
}
}
// return true of the first argument is a better canidate for removal, i.e.
// less important to keep
struct immutable_item_comparator
{
immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {}
immutable_item_comparator(immutable_item_comparator const& c)
: m_our_id(c.m_our_id) {}
bool operator() (std::pair<node_id, dht_immutable_item> const& lhs
, std::pair<node_id, dht_immutable_item> const& rhs) const
{
int l_distance = distance_exp(lhs.first, m_our_id);
int r_distance = distance_exp(rhs.first, m_our_id);
// this is a score taking the popularity (number of announcers) and the
// fit, in terms of distance from ideal storing node, into account.
// each additional 5 announcers is worth one extra bit in the distance.
// that is, an item with 10 announcers is allowed to be twice as far
// from another item with 5 announcers, from our node ID. Twice as far
// because it gets one more bit.
return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance;
}
private:
// explicitly disallow assignment, to silence msvc warning
immutable_item_comparator& operator=(immutable_item_comparator const&);
node_id const& m_our_id;
};
class dht_default_storage TORRENT_FINAL : public dht_storage_interface, boost::noncopyable
{
typedef std::map<node_id, torrent_entry> table_t;
typedef std::map<node_id, dht_immutable_item> dht_immutable_table_t;
typedef std::map<node_id, dht_mutable_item> dht_mutable_table_t;
public:
dht_default_storage(sha1_hash const& id, dht_settings const& settings
, counters& cnt)
: m_id(id)
, m_settings(settings)
, m_counters(cnt)
{
}
~dht_default_storage() {}
#ifndef TORRENT_NO_DEPRECATE
size_t num_torrents() const TORRENT_OVERRIDE { return m_map.size(); }
size_t num_peers() const TORRENT_OVERRIDE
{
int ret = 0;
std::for_each(m_map.begin(), m_map.end(), count_peers(&ret));
return ret;
}
#endif
bool get_peers(sha1_hash const& info_hash
, bool noseed, bool scrape
, entry& peers) const TORRENT_OVERRIDE
{
table_t::const_iterator i = m_map.lower_bound(info_hash);
if (i == m_map.end()) return false;
if (i->first != info_hash) return false;
torrent_entry const& v = i->second;
if (!v.name.empty()) peers["n"] = v.name;
if (scrape)
{
bloom_filter<256> downloaders;
bloom_filter<256> seeds;
for (std::set<peer_entry>::const_iterator peer_it = v.peers.begin()
, end(v.peers.end()); peer_it != end; ++peer_it)
{
sha1_hash iphash;
hash_address(peer_it->addr.address(), iphash);
if (peer_it->seed) seeds.set(iphash);
else downloaders.set(iphash);
}
peers["BFpe"] = downloaders.to_string();
peers["BFsd"] = seeds.to_string();
}
else
{
int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply);
std::set<peer_entry>::const_iterator iter = v.peers.begin();
entry::list_type& pe = peers["values"].list();
std::string endpoint;
for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t)
{
if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue;
if (noseed && iter->seed) continue;
endpoint.resize(18);
std::string::iterator out = endpoint.begin();
write_endpoint(iter->addr, out);
endpoint.resize(out - endpoint.begin());
pe.push_back(entry(endpoint));
++m;
}
}
return true;
}
void announce_peer(sha1_hash const& info_hash
, tcp::endpoint const& endp
, std::string const& name, bool seed) TORRENT_OVERRIDE
{
table_t::iterator ti = m_map.find(info_hash);
torrent_entry* v;
if (ti == m_map.end())
{
// we don't have this torrent, add it
// do we need to remove another one first?
if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents)
{
// we need to remove some. Remove the ones with the
// fewest peers
int num_peers = m_map.begin()->second.peers.size();
table_t::iterator candidate = m_map.begin();
for (table_t::iterator i = m_map.begin()
, end(m_map.end()); i != end; ++i)
{
if (int(i->second.peers.size()) > num_peers) continue;
if (i->first == info_hash) continue;
num_peers = i->second.peers.size();
candidate = i;
}
m_map.erase(candidate);
m_counters.inc_stats_counter(counters::dht_torrents, -1);
}
m_counters.inc_stats_counter(counters::dht_torrents);
v = &m_map[info_hash];
}
else
{
v = &ti->second;
}
// the peer announces a torrent name, and we don't have a name
// for this torrent. Store it.
if (!name.empty() && v->name.empty())
{
std::string tname = name;
if (tname.size() > 50) tname.resize(50);
v->name = tname;
}
peer_entry peer;
peer.addr = endp;
peer.added = aux::time_now();
peer.seed = seed;
std::set<peer_entry>::iterator i = v->peers.find(peer);
if (i != v->peers.end()) v->peers.erase(i++);
v->peers.insert(i, peer);
}
bool get_immutable_item(sha1_hash const& target
, entry& item) const TORRENT_OVERRIDE
{
dht_immutable_table_t::const_iterator i = m_immutable_table.find(target);
if (i == m_immutable_table.end()) return false;
item["v"] = bdecode(i->second.value, i->second.value + i->second.size);
return true;
}
void put_immutable_item(sha1_hash const& target
, char const* buf, int size
, address const& addr) TORRENT_OVERRIDE
{
dht_immutable_table_t::iterator i = m_immutable_table.find(target);
if (i == m_immutable_table.end())
{
// make sure we don't add too many items
if (int(m_immutable_table.size()) >= m_settings.max_dht_items)
{
// delete the least important one (i.e. the one
// the fewest peers are announcing, and farthest
// from our node ID)
dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin()
, m_immutable_table.end()
, immutable_item_comparator(m_id));
TORRENT_ASSERT(j != m_immutable_table.end());
free(j->second.value);
m_immutable_table.erase(j);
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
}
dht_immutable_item to_add;
to_add.value = static_cast<char*>(malloc(size));
to_add.size = size;
memcpy(to_add.value, buf, size);
boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert(
std::make_pair(target, to_add));
m_counters.inc_stats_counter(counters::dht_immutable_data);
}
// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size()));
touch_item(&i->second, addr);
}
bool get_mutable_item_seq(sha1_hash const& target
, boost::int64_t& seq) const TORRENT_OVERRIDE
{
dht_mutable_table_t::const_iterator i = m_mutable_table.find(target);
if (i == m_mutable_table.end()) return false;
seq = i->second.seq;
return true;
}
bool get_mutable_item(sha1_hash const& target
, boost::int64_t seq, bool force_fill
, entry& item) const TORRENT_OVERRIDE
{
dht_mutable_table_t::const_iterator i = m_mutable_table.find(target);
if (i == m_mutable_table.end()) return false;
dht_mutable_item const& f = i->second;
item["seq"] = f.seq;
if (force_fill || (0 <= seq && seq < f.seq))
{
item["v"] = bdecode(f.value, f.value + f.size);
item["sig"] = std::string(f.sig, f.sig + sizeof(f.sig));
item["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes));
}
return true;
}
void put_mutable_item(sha1_hash const& target
, char const* buf, int size
, char const* sig
, boost::int64_t seq
, char const* pk
, char const* salt, int salt_size
, address const& addr) TORRENT_OVERRIDE
{
dht_mutable_table_t::iterator i = m_mutable_table.find(target);
if (i == m_mutable_table.end())
{
// this is the case where we don't have an item in this slot
// make sure we don't add too many items
if (int(m_mutable_table.size()) >= m_settings.max_dht_items)
{
// delete the least important one (i.e. the one
// the fewest peers are announcing)
dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin()
, m_mutable_table.end()
, boost::bind(&dht_immutable_item::num_announcers
, boost::bind(&dht_mutable_table_t::value_type::second, _1)));
TORRENT_ASSERT(j != m_mutable_table.end());
free(j->second.value);
free(j->second.salt);
m_mutable_table.erase(j);
m_counters.inc_stats_counter(counters::dht_mutable_data, -1);
}
dht_mutable_item to_add;
to_add.value = static_cast<char*>(malloc(size));
to_add.size = size;
to_add.seq = seq;
to_add.salt = NULL;
to_add.salt_size = 0;
if (salt_size > 0)
{
to_add.salt = static_cast<char*>(malloc(salt_size));
to_add.salt_size = salt_size;
memcpy(to_add.salt, salt, salt_size);
}
memcpy(to_add.sig, sig, sizeof(to_add.sig));
memcpy(to_add.value, buf, size);
memcpy(&to_add.key, pk, sizeof(to_add.key));
boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert(
std::make_pair(target, to_add));
m_counters.inc_stats_counter(counters::dht_mutable_data);
// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size()));
}
else
{
// this is the case where we already
dht_mutable_item* item = &i->second;
if (item->seq < seq)
{
if (item->size != size)
{
free(item->value);
item->value = static_cast<char*>(malloc(size));
item->size = size;
}
item->seq = seq;
memcpy(item->sig, sig, sizeof(item->sig));
memcpy(item->value, buf, size);
}
}
touch_item(&i->second, addr);
}
void tick() TORRENT_OVERRIDE
{
time_point now(aux::time_now());
for (dht_immutable_table_t::iterator i = m_immutable_table.begin();
i != m_immutable_table.end();)
{
if (i->second.last_seen + minutes(60) > now)
{
++i;
continue;
}
free(i->second.value);
m_immutable_table.erase(i++);
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
}
// look through all peers and see if any have timed out
for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;)
{
torrent_entry& t = i->second;
node_id const& key = i->first;
++i;
purge_peers(t.peers);
if (!t.peers.empty()) continue;
// if there are no more peers, remove the entry altogether
table_t::iterator it = m_map.find(key);
if (it != m_map.end())
{
m_map.erase(it);
m_counters.inc_stats_counter(counters::dht_torrents, -1);
}
}
}
private:
sha1_hash m_id;
dht_settings const& m_settings;
counters& m_counters;
table_t m_map;
dht_immutable_table_t m_immutable_table;
dht_mutable_table_t m_mutable_table;
};
}
dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id
, dht_settings const& settings
, counters& counters)
{
return new dht_default_storage(id, settings, counters);
}
} } // namespace libtorrent::dht

View File

@ -69,25 +69,8 @@ namespace libtorrent { namespace dht
using detail::write_endpoint;
// TODO: 2 make this configurable in dht_settings
enum { announce_interval = 30 };
namespace {
// remove peers that have timed out
void purge_peers(std::set<peer_entry>& peers)
{
for (std::set<peer_entry>::iterator i = peers.begin()
, end(peers.end()); i != end;)
{
// the peer has timed out
if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now())
peers.erase(i++);
else
++i;
}
}
void nop() {}
node_id calculate_node_id(node_id const& nid, dht_observer* observer)
@ -115,9 +98,16 @@ node::node(udp_socket_interface* sock
, m_last_self_refresh(min_time())
, m_sock(sock)
, m_counters(cnt)
, m_storage(dht_default_storage_constructor(m_id, m_settings, m_counters))
{
m_secret[0] = random();
m_secret[1] = random();
TORRENT_ASSERT(m_storage.get() != NULL);
}
node::~node()
{
}
bool node::verify_token(std::string const& token, char const* info_hash
@ -590,38 +580,7 @@ time_duration node::connection_timeout()
if (now - minutes(2) < m_last_tracker_tick) return d;
m_last_tracker_tick = now;
for (dht_immutable_table_t::iterator i = m_immutable_table.begin();
i != m_immutable_table.end();)
{
if (i->second.last_seen + minutes(60) > now)
{
++i;
continue;
}
free(i->second.value);
m_immutable_table.erase(i++);
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
}
// look through all peers and see if any have timed out
for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;)
{
torrent_entry& t = i->second;
node_id const& key = i->first;
++i;
purge_peers(t.peers);
// if there are no more peers, remove the entry altogether
if (t.peers.empty())
{
table_t::iterator it = m_map.find(key);
if (it != m_map.end())
{
m_map.erase(it);
m_counters.inc_stats_counter(counters::dht_torrents, -1);
}
}
}
m_storage->tick();
return d;
}
@ -649,7 +608,7 @@ void node::status(session_status& s)
mutex_t::scoped_lock l(m_mutex);
m_table.status(s);
s.dht_torrents = int(m_map.size());
s.dht_torrents = int(m_storage->num_torrents());
s.active_requests.clear();
s.dht_total_allocations = m_rpc.num_allocated_observers();
for (std::set<traversal_algorithm*>::iterator i = m_running_requests.begin()
@ -668,52 +627,7 @@ void node::lookup_peers(sha1_hash const& info_hash, entry& reply
if (m_observer)
m_observer->get_peers(info_hash);
table_t::const_iterator i = m_map.lower_bound(info_hash);
if (i == m_map.end()) return;
if (i->first != info_hash) return;
torrent_entry const& v = i->second;
if (!v.name.empty()) reply["n"] = v.name;
if (scrape)
{
bloom_filter<256> downloaders;
bloom_filter<256> seeds;
for (std::set<peer_entry>::const_iterator peer_it = v.peers.begin()
, end(v.peers.end()); peer_it != end; ++peer_it)
{
sha1_hash iphash;
hash_address(peer_it->addr.address(), iphash);
if (peer_it->seed) seeds.set(iphash);
else downloaders.set(iphash);
}
reply["BFpe"] = downloaders.to_string();
reply["BFsd"] = seeds.to_string();
}
else
{
int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply);
std::set<peer_entry>::const_iterator iter = v.peers.begin();
entry::list_type& pe = reply["values"].list();
std::string endpoint;
for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t)
{
if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue;
if (noseed && iter->seed) continue;
endpoint.resize(18);
std::string::iterator out = endpoint.begin();
write_endpoint(iter->addr, out);
endpoint.resize(out - endpoint.begin());
pe.push_back(entry(endpoint));
++m;
}
}
return;
m_storage->get_peers(info_hash, noseed, scrape, reply);
}
void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes)
@ -833,37 +747,6 @@ void incoming_error(entry& e, char const* msg, int error_code)
l.push_back(entry(msg));
}
// return true of the first argument is a better canidate for removal, i.e.
// less important to keep
struct immutable_item_comparator
{
immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {}
immutable_item_comparator(immutable_item_comparator const& c)
: m_our_id(c.m_our_id) {}
bool operator() (std::pair<node_id, dht_immutable_item> const& lhs
, std::pair<node_id, dht_immutable_item> const& rhs) const
{
int l_distance = distance_exp(lhs.first, m_our_id);
int r_distance = distance_exp(rhs.first, m_our_id);
// this is a score taking the popularity (number of announcers) and the
// fit, in terms of distance from ideal storing node, into account.
// each additional 5 announcers is worth one extra bit in the distance.
// that is, an item with 10 announcers is allowed to be twice as far
// from another item with 5 announcers, from our node ID. Twice as far
// because it gets one more bit.
return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance;
}
private:
// explicitly disallow assignment, to silence msvc warning
immutable_item_comparator& operator=(immutable_item_comparator const&);
node_id const& m_our_id;
};
// build response
void node::incoming_request(msg const& m, entry& e)
{
@ -1039,53 +922,11 @@ void node::incoming_request(msg const& m, entry& e)
// the table get a chance to add it.
m_table.node_seen(id, m.addr, 0xffff);
table_t::iterator ti = m_map.find(info_hash);
torrent_entry* v;
if (ti == m_map.end())
{
// we don't have this torrent, add it
// do we need to remove another one first?
if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents)
{
// we need to remove some. Remove the ones with the
// fewest peers
int num_peers = m_map.begin()->second.peers.size();
table_t::iterator candidate = m_map.begin();
for (table_t::iterator i = m_map.begin()
, end(m_map.end()); i != end; ++i)
{
if (int(i->second.peers.size()) > num_peers) continue;
if (i->first == info_hash) continue;
num_peers = i->second.peers.size();
candidate = i;
}
m_map.erase(candidate);
m_counters.inc_stats_counter(counters::dht_torrents, -1);
}
m_counters.inc_stats_counter(counters::dht_torrents);
v = &m_map[info_hash];
}
else
{
v = &ti->second;
}
tcp::endpoint addr = tcp::endpoint(m.addr.address(), port);
std::string name = msg_keys[3] ? msg_keys[3].string_value() : std::string();
bool seed = msg_keys[4] && msg_keys[4].int_value();
// the peer announces a torrent name, and we don't have a name
// for this torrent. Store it.
if (msg_keys[3] && v->name.empty())
{
std::string name = msg_keys[3].string_value();
if (name.size() > 50) name.resize(50);
v->name = name;
}
peer_entry peer;
peer.addr = tcp::endpoint(m.addr.address(), port);
peer.added = aux::time_now();
peer.seed = msg_keys[4] && msg_keys[4].int_value();
std::set<peer_entry>::iterator i = v->peers.find(peer);
if (i != v->peers.end()) v->peers.erase(i++);
v->peers.insert(i, peer);
m_storage->announce_peer(info_hash, addr, name, seed);
}
else if (query_len == 3 && memcmp(query, "put", 3) == 0)
{
@ -1166,41 +1007,9 @@ void node::incoming_request(msg const& m, entry& e)
return;
}
dht_immutable_item* f = 0;
if (!mutable_put)
{
dht_immutable_table_t::iterator i = m_immutable_table.find(target);
if (i == m_immutable_table.end())
{
// make sure we don't add too many items
if (int(m_immutable_table.size()) >= m_settings.max_dht_items)
{
// delete the least important one (i.e. the one
// the fewest peers are announcing, and farthest
// from our node ID)
dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin()
, m_immutable_table.end()
, immutable_item_comparator(m_id));
TORRENT_ASSERT(j != m_immutable_table.end());
free(j->second.value);
m_immutable_table.erase(j);
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
}
dht_immutable_item to_add;
to_add.value = static_cast<char*>(malloc(buf.second));
to_add.size = buf.second;
memcpy(to_add.value, buf.first, buf.second);
boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert(
std::make_pair(target, to_add));
m_counters.inc_stats_counter(counters::dht_immutable_data);
}
// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size()));
f = &i->second;
m_storage->put_immutable_item(target, buf.first, buf.second, m.addr.address());
}
else
{
@ -1210,111 +1019,65 @@ void node::incoming_request(msg const& m, entry& e)
VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4].string_ptr(), item_sig_len);
VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len);
#endif
boost::int64_t seq = msg_keys[2].int_value();
if (seq < 0)
{
m_counters.inc_stats_counter(counters::dht_invalid_put);
incoming_error(e, "invalid (negative) sequence number");
return;
}
// msg_keys[4] is the signature, msg_keys[3] is the public key
if (!verify_mutable_item(buf, salt
, msg_keys[2].int_value(), pk, sig))
, seq, pk, sig))
{
m_counters.inc_stats_counter(counters::dht_invalid_put);
incoming_error(e, "invalid signature", 206);
return;
}
dht_mutable_table_t::iterator i = m_mutable_table.find(target);
if (i == m_mutable_table.end())
{
// this is the case where we don't have an item in this slot
// make sure we don't add too many items
if (int(m_mutable_table.size()) >= m_settings.max_dht_items)
{
// delete the least important one (i.e. the one
// the fewest peers are announcing)
dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin()
, m_mutable_table.end()
, boost::bind(&dht_immutable_item::num_announcers
, boost::bind(&dht_mutable_table_t::value_type::second, _1)));
TORRENT_ASSERT(j != m_mutable_table.end());
free(j->second.value);
free(j->second.salt);
m_mutable_table.erase(j);
m_counters.inc_stats_counter(counters::dht_mutable_data, -1);
}
dht_mutable_item to_add;
to_add.value = static_cast<char*>(malloc(buf.second));
to_add.size = buf.second;
to_add.seq = msg_keys[2].int_value();
to_add.salt = NULL;
to_add.salt_size = 0;
if (salt.second > 0)
{
to_add.salt = static_cast<char*>(malloc(salt.second));
to_add.salt_size = salt.second;
memcpy(to_add.salt, salt.first, salt.second);
}
memcpy(to_add.sig, sig, sizeof(to_add.sig));
TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4].string_length());
memcpy(to_add.value, buf.first, buf.second);
memcpy(&to_add.key, pk, sizeof(to_add.key));
boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert(
std::make_pair(target, to_add));
m_counters.inc_stats_counter(counters::dht_mutable_data);
TORRENT_ASSERT(item_sig_len == msg_keys[4].string_length());
// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size()));
boost::int64_t item_seq;
if (!m_storage->get_mutable_item_seq(target, item_seq))
{
m_storage->put_mutable_item(target
, buf.first, buf.second
, sig, seq, pk
, salt.first, salt.second
, m.addr.address());
}
else
{
// this is the case where we already
dht_mutable_item* item = &i->second;
// this is the "cas" field in the put message
// if it was specified, we MUST make sure the current sequence
// number matches the expected value before replacing it
// this is critical for avoiding race conditions when multiple
// writers are accessing the same slot
if (msg_keys[5] && item->seq != msg_keys[5].int_value())
if (msg_keys[5] && item_seq != msg_keys[5].int_value())
{
m_counters.inc_stats_counter(counters::dht_invalid_put);
incoming_error(e, "CAS mismatch", 301);
return;
}
if (item->seq > boost::uint64_t(msg_keys[2].int_value()))
if (item_seq > seq)
{
m_counters.inc_stats_counter(counters::dht_invalid_put);
incoming_error(e, "old sequence number", 302);
return;
}
if (item->seq < boost::uint64_t(msg_keys[2].int_value()))
{
if (item->size != buf.second)
{
free(item->value);
item->value = static_cast<char*>(malloc(buf.second));
item->size = buf.second;
}
item->seq = msg_keys[2].int_value();
memcpy(item->sig, msg_keys[4].string_ptr(), sizeof(item->sig));
TORRENT_ASSERT(sizeof(item->sig) == msg_keys[4].string_length());
memcpy(item->value, buf.first, buf.second);
}
m_storage->put_mutable_item(target
, buf.first, buf.second
, sig, seq, pk
, salt.first, salt.second
, m.addr.address());
}
f = &i->second;
}
m_table.node_seen(id, m.addr, 0xffff);
f->last_seen = aux::time_now();
// maybe increase num_announcers if we haven't seen this IP before
sha1_hash iphash;
hash_address(m.addr.address(), iphash);
if (!f->ips.find(iphash))
{
f->ips.set(iphash);
++f->num_announcers;
}
}
else if (query_len == 3 && memcmp(query, "get", 3) == 0)
{
@ -1349,32 +1112,20 @@ void node::incoming_request(msg const& m, entry& e)
m_table.find_node(target, n, 0);
write_nodes_entry(reply, n);
dht_immutable_table_t::iterator imutable_it = m_immutable_table.end();
// if the get has a sequence number it must be for a mutable item
// so don't bother searching the immutable table
if (!msg_keys[0])
imutable_it = m_immutable_table.find(target);
if (imutable_it != m_immutable_table.end())
{
dht_immutable_item const& f = imutable_it->second;
reply["v"] = bdecode(f.value, f.value + f.size);
if (!m_storage->get_immutable_item(target, reply)) // ok, check for a mutable one
{
m_storage->get_mutable_item(target, 0, true, reply);
}
}
else
{
dht_mutable_table_t::iterator mutable_it = m_mutable_table.find(target);
if (mutable_it != m_mutable_table.end())
{
dht_mutable_item const& f = mutable_it->second;
reply["seq"] = f.seq;
if (!msg_keys[0] || boost::uint64_t(msg_keys[0].int_value()) < f.seq)
{
reply["v"] = bdecode(f.value, f.value + f.size);
reply["sig"] = std::string(f.sig, f.sig + sizeof(f.sig));
reply["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes));
}
}
m_storage->get_mutable_item(target
, msg_keys[0].int_value(), false
, reply);
}
}
else
@ -1401,7 +1152,4 @@ void node::incoming_request(msg const& m, entry& e)
return;
}
}
} } // namespace libtorrent::dht

View File

@ -139,6 +139,7 @@ test-suite libtorrent :
test_xml.cpp
test_ip_filter.cpp
test_hasher.cpp
test_dht_storage.cpp
test_dht.cpp
test_block_cache.cpp
test_peer_classes.cpp

View File

@ -149,6 +149,7 @@ test_primitives_SOURCES = \
test_xml.cpp \
test_ip_filter.cpp \
test_hasher.cpp \
test_dht_storage.cpp \
test_dht.cpp \
test_block_cache.cpp \
test_peer_classes.cpp \

135
test/test_dht_storage.cpp Normal file
View File

@ -0,0 +1,135 @@
/*
Copyright (c) 2015, Alden Torres
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_DISABLE_DHT
#include "libtorrent/config.hpp"
#include "libtorrent/session.hpp"
#include "libtorrent/kademlia/node.hpp" // for verify_message
#include "libtorrent/bencode.hpp"
#include "libtorrent/socket_io.hpp" // for hash_address
#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6
#include "libtorrent/performance_counters.hpp" // for counters
#include "libtorrent/random.hpp"
#include "libtorrent/ed25519.hpp"
#include "libtorrent/kademlia/dht_storage.hpp"
#include "libtorrent/kademlia/node_id.hpp"
#include "libtorrent/kademlia/routing_table.hpp"
#include "libtorrent/kademlia/item.hpp"
#include "libtorrent/kademlia/dht_observer.hpp"
#include "libtorrent/ed25519.hpp"
#include <numeric>
#include "test.hpp"
using namespace libtorrent;
using namespace libtorrent::dht;
namespace
{
dht_settings test_settings() {
dht_settings sett;
sett.max_torrents = 2;
sett.max_dht_items = 2;
return sett;
}
static sha1_hash to_hash(char const *s) {
sha1_hash ret;
from_hex(s, 40, (char *) &ret[0]);
return ret;
}
}
TORRENT_TEST(dht_storage)
{
dht_settings sett = test_settings();
counters cnt;
dht_storage_interface* s = dht_default_storage_constructor(node_id(0), sett, cnt);
TEST_CHECK(s != NULL);
sha1_hash n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401");
sha1_hash n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402");
sha1_hash n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403");
sha1_hash n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404");
entry peers;
s->get_peers(n1, false, false, peers);
TEST_CHECK(peers["n"].string().empty())
TEST_CHECK(peers["values"].list().empty());
tcp::endpoint p1 = tcp::endpoint(address::from_string("124.31.75.21"), 1);
tcp::endpoint p2 = tcp::endpoint(address::from_string("124.31.75.22"), 1);
tcp::endpoint p3 = tcp::endpoint(address::from_string("124.31.75.23"), 1);
tcp::endpoint p4 = tcp::endpoint(address::from_string("124.31.75.24"), 1);
s->announce_peer(n1, p1, "torrent_name", false);
s->get_peers(n1, false, false, peers);
TEST_EQUAL(peers["n"].string(), "torrent_name")
TEST_EQUAL(peers["values"].list().size(), 1)
s->announce_peer(n2, p2, "torrent_name1", false);
s->announce_peer(n2, p3, "torrent_name1", false);
s->announce_peer(n3, p4, "torrent_name2", false);
bool r = s->get_peers(n1, false, false, peers);
TEST_CHECK(!r);
entry item;
r = s->get_immutable_item(n4, item);
TEST_CHECK(!r);
s->put_immutable_item(n4, "123", 3, address::from_string("124.31.75.21"));
r = s->get_immutable_item(n4, item);
TEST_CHECK(r);
s->put_immutable_item(n1, "123", 3, address::from_string("124.31.75.21"));
s->put_immutable_item(n2, "123", 3, address::from_string("124.31.75.21"));
s->put_immutable_item(n3, "123", 3, address::from_string("124.31.75.21"));
r = s->get_immutable_item(n1, item);
TEST_CHECK(!r);
r = s->get_mutable_item(n4, 0, false, item);
TEST_CHECK(!r);
char public_key[item_pk_len];
char signature[item_sig_len];
s->put_mutable_item(n4, "123", 3, signature, 1, public_key, "salt", 4, address::from_string("124.31.75.21"));
r = s->get_mutable_item(n4, 0, false, item);
TEST_CHECK(r);
delete s;
}
#endif